# FIELD ACCEPTANCE — #213/#159 two-condition floor recipe (todlando)

doyle's dispatch 2026-08-23: the milestone's last owed leg — never-executed entry 16, the one
proof REQ-ADAPTER-FLOOR-VS-STAGED-CORE and REQ-UPDATE-REFUSAL-EXIT-DISTINCT both defer to.
v0.60.0 is Latest (517c9f6f, counter 95). Rig = ISOLATED SPT_HOME + disposable daemon, never the
live fleet. Exits read from FILES, never pipelines. df first, one pool (77 GB at dispatch — no
cold build needed: the rig runs RELEASED binaries).

## Census (measured at 517c9f6f blobs, not the root tree)

- Composite: `spt update --restart` plans Fetch → Adapters → Finish (`plan_update_legs`,
  cli.rs:9236). Adapters leg (:9282) reads `floor_basis(staged_release_metadata_json())`,
  degrades LOUDLY, passes `core_version` to `cmd_adapter_update` — the staged release's
  `product_version` off the VERIFIED signed metadata.
- Core fetch (:10440): gh from `install_repo()` (spt-bs-releases), default tag = Latest,
  verified under `identity/release-keys.json` policy; rollback floor = staged version.
- Adapter sweep (:20982): updates registrations whose manifest `[update]` has
  `avenue = "gh_release"`, `repo = <github repo>`, `asset` (default `adapter.spt`).
  `gh_latest_release_version(repo)` decides freshness vs installed `adapter.version`; the
  downloaded .spt is extracted to a throwaway, `manifest.toml` parsed,
  `version_meets_floor(core_version, floor)` refuses via `RegistryError::CoreFloor` Display
  (:20390); post-swap re-register via `register_with_core` with the SAME threaded version.
  Local-path registrations SKIP loud.
- Refusal class: REFUSED variant (never FAILED), sweep exit 3 refused / 1 failed / 0 applied,
  mixed = 1 (failure masks nothing). Composite worst carries the adapters leg's code.
- `mock-adapter.zip` rides every release: canonical manifest (v1.0.0, floor 0.1.0, no [update]
  table). Its manifest validates role templates — the base for both manufactured packages.

## The one-roll shape (both arms in a single real composite)

Rig home at spt 0.59.0 (released binary), TWO adapters registered from manufactured packages
(mock-adapter manifest + edits), both installed at version 1.0.0, both `[update]` gh_release at
the SAME scratch repo, different `asset` names:

| adapter | installed | served latest | min_spt_core_version | expected |
|---|---|---|---|---|
| floor-admit | 1.0.0 | 1.1.0 (`adapter-admit.spt`) | **0.60.0** (> running 0.59.0, = incoming) | ADMITTED; record registers at 1.1.0 judged vs 0.60.0 |
| floor-refuse | 1.0.0 | 1.1.0 (`adapter-refuse.spt`) | **99.0.0** (> both cores) | REFUSED (not FAILED), CoreFloor reason |

One `spt update --restart`: Fetch stages real v0.60.0 (real signed set, real gh transport) →
Adapters judged vs staged 0.60.0 → admit arm + refuse arm in one sweep → sweep exit 3 (refused
present, no failure — the precedence pin, field-witnessed) → composite proceeds (adapters leg
isolated) → Finish cycles the disposable daemon onto 0.60.0. Captures: full stderr/stdout to
files; `$?` of the composite written to a file by the wrapper's OWN next statement; registry
records read back BEFORE and AFTER (floor-admit at 1.1.0/new basis, floor-refuse UNTOUCHED at
1.0.0 — refusal leaves record and bytes alone); adapter install dirs hashed before/after
(refuse arm byte-untouched).

Post-roll bonus already in the shape: a second `spt update` after Finish = adapters-only run on
a current core (basis degrade arm renders its own diagnostic — captured if it fires, not claimed
as an arm).

## OPEN — doyle's call before anything outward
1. **Scratch repo for the adapter release** (one repo, one release tag v1.1.0, two .spt assets).
   Cutting a release is outward-facing and the repo choice is not mine: spt-bs-releases is the
   PRODUCTION Latest — a scratch release there would poison every fleet node's next fetch, so
   never there. Need: an existing sanctioned scratch repo, or approval to create one
   (suggest BigscreenVR/spt-field-scratch, private), or doyle/deployah cut it.
2. Confirm isolated-home bring-up mechanic: plan is released `spt-x86_64-windows.exe` (v0.59.0
   asset) with SPT_HOME=<rig>, `spt install`-or-equivalent measured at rig time (first measured
   step, reported not assumed — including whether a fresh home's release-keys carry the stable
   anchor by default, which decides whether fetch verifies out of the box).

## Rig discipline
- Isolated SPT_HOME under the project scratchpad? NO — scratchpad targets ate the box once
  (memory: scratchpad-target-trees-eat-the-box is about CARGO target dirs; a small rig HOME is
  fine in scratch, no cargo involved). Keep the rig home small, reap after.
- Disposable daemon: started with rig SPT_HOME, torn down by pid+path match at the end; never
  the resident.
- Identity env scrubbed in every child (OWL_SESSION_ID / SPT_AGENT_ID / SPT_ENDPOINT_ID) except
  where an arm needs a sender.
- Every exit: `> leg.raw 2>&1` then `echo $? > leg.exit` — and the wrapper's last command is
  never the capture (IR-60).

## ✅ ROLLS 1+2 EXECUTED — pre-fix control + arm (ii) banked; arm (i) blocked on ruling

doyle approved the plan; ruled Q1 = create BigscreenVR/spt-field-scratch (private — the fleet's
gh arm is authenticated, so private exercises the real path; internal consistency check rides
free). IR-62 flagged (isolated home ≠ isolated TCP ports); REDESIGN taken per his instruction:
plain `spt update` (no --restart) — no daemon ever starts, and the composite ends on the sweep so
exit 3 cannot be overwritten by a Finish red (`worst = code` is last-nonzero, not max).

Rig facts: scratch repo + release v1.1.0 (two assets) live; fresh SPT_HOME verifies the stable
channel OUT OF THE BOX (real v0.60.0 set fetched+verified, zero key setup — the bring-up finding
doyle asked for, positive). Registrations = pointer-mode at the fieldpkg dirs; the in-place swap
therefore lands THERE (hash the pointed dirs, not rig-home).

**ROLL 1 (released 0.59.0 invoking) = THE PRE-FIX CONTROL, both defects one capture:** core
staged 95 + applied in-cycle (self-update swaps the INVOKING exe — it prints 0.60.0 after), then
both adapters refused against 0.59.0 (floor-admit's floor met by the core the run had just
installed — #159 exactly as filed) rendering FAILED, composite exit 1 (#213 as filed).

**ROLL 2 (the 0.60.0 the roll installed, same command) = ARM (ii) DELIVERED:** floor-refuse
REFUSED carrying the CoreFloor reason (never FAILED), composite exit **3 read from file**, sweep
MIXED (floor-admit applied 1.0.0→1.1.0 + refused) = the precedence pin field-witnessed.
Read-backs: admit manifest 1.1.0 + record re-registered (timestamp bumped); refuse manifest
byte-identical 1.0.0. The FAILED/1-vs-REFUSED/3 pair on the same adapter+floor is the #213
contrast in two captures.

**MEASURED CONTRADICTION reshaping arm (i):** the dispatched condition cannot execute post-fix
code — the composite that must admit RUNS the old image by construction (the fix rides the
incoming). Post-fix (i) = staged-basis half only, needs incoming product_version > 0.60.0:
(a) debug-channel version-bumped local build (DEBUG-ROLLOUT substrate, rig-home pin only, one
pool build) or (b) defer to the v0.61.0 release window. Sent to doyle; HOLDING.

Rig craft: env(1) parses flags only BEFORE assignments — `env SPT_HOME=x -u VAR cmd` executes
'-u' as the command (all-127 rig, nothing ran, no state touched). And a refusal-armed edit
script chained with a bare newline FIRES THE RIG even when the edit refused — the guard must
gate the fire, second instance of the class this session.

## ✅ ROLL 3 EXECUTED (2026-08-23, shape B per doyle's pick) — arm (i) staged-basis half CLOSED

Composite exit **3 from file**; fetch classified "Already up to date (counter 95)" with the stage
INTACT (96/stable/0.60.1 read back after); NO STAGED_CORE degrade line; floor-staged (floor
0.60.1) ADMITTED 1.0.0→1.1.0 + re-registered — satisfiable ONLY on the staged basis; floor-refuse
REFUSED with the discriminator IN the CoreFloor text ("this machine runs spt-core **0.60.1**" —
printed by the 0.60.0 image); admit/refuse dirs byte-identical pre/post, staged dir swapped.
Captures: session scratchpad e82c75fe `r3_*`. Report sent to doyle (QUEUED); acceptance pending.

Two structural facts (source-read at 517c9f6f, then measured), resolutions inside doyle's grounds:
1. **debug-pin can never reach the subject**: pin flips home channel to debug (xtask main.rs:1616);
   verify checks ChannelMismatch BEFORE Rollback (release.rs:552 vs :564); non-Rollback reject =
   GenuineError (cli.rs:10412) → fetch exit 1 → composite aborts pre-Adapters (cli.rs:9295,:9313).
   Used debug KEY overlay + channel=stable signed set instead (all verify arms live).
2. **fetch exit-0 needs the promotion record**: AlreadyApplied arm reads `applied.json`, written
   only at brain promotion — a daemonless rig (IR-62) can never carry it. First fire = measured
   abort (Rollback{96,95}, kept as negative control). Wrote `{"version":95}` — hash-proven true
   (invoking exe == counter-95 asset). The roll's single manufactured record; on the
   classification path, never the floor-basis path.

Build: worktree `.worktrees/roll3` @517c9f6f, version bump 0.60.1, xtask debug-rollout grew
`--product-version`/`--channel` (hardcode was main.rs:1714; patch uncommitted — doyle to rule
land-vs-discard). Pool claimed from the worktree (label roll3-fieldaccept); df 117G at fire.
Owed answer delivered: `worst = code` last-nonzero-overwrite at cli.rs:9302 (source-read),
refusal-can-mask-failure under --restart; register-row candidate, doyle to file.

Scope: Finish/daemon leg not exercised (IR-62, as ruled); v0.61.0 window re-runs arm (i) free.

## ✅ CLOSED (2026-08-23): doyle ACCEPTED all three rolls — milestone's last open claim done.
Rulings executed: cleanup DONE (roll3 worktree 5.07G + rig dirs ~226MB reaped, pools released,
classified teardowns, captures kept); scratch repo BigscreenVR/spt-field-scratch KEPT until the
v0.61.0 window re-runs arm (i) (doyle's standing check), reap after; cli.rs:9302 register row =
doyle's, filed by him. xtask patch LANDED per his ruling: PR #154 merged ff-only, main =
`5c80515f` (tested == merged), --product-version/--channel + pure debug_rollout_meta seam +
2 units [unit->REQ-UPD-6] + runbook flags; lane worktree + pool released and removed.
Forensic coda in doyle's delivery-loss file: the "~04:30 loss" was a never-sent send falsely
testified by my pre-clear commune (off perri's seam ledger), plus his spool audit's QUEUED-only
population blindness — both taken with my capture pair as proof.

## ✅ ARM (i) FULL-CYCLE CLOSED (2026-08-24): roll 4 accepted, scratch repo husked pending operator delete_repo.
Roll 4 in the v0.61.0 window (shape B daemonless, released 0.60.0 invoking): fresh fetch staged
counter 96 + applied v0.61.0 in-cycle; floor-staged (0.60.1, incoming-only floor) ADMITTED
1.0.0→1.1.0 + re-registered; floor-refuse REFUSED (CoreFloor names 0.61.0 off the new image);
composite exit 3 from file, mixed sweep. No manufactured records. Captures: session bfc85fa8
`roll4/cap/*`. Release v1.1.0 + tag stripped (repo husk inert); full repo deletion = operator's
`gh auth refresh -s delete_repo` (both agents' tokens lack the scope), surfaced by doyle.
