Files
jason.woltje 9a9c60d9c0 row 227 R3 (#1728): record the round in BUILD.md, BUILD-LOG and SESSIONS
Appends the R3 section to agents/steelbeak/work/queue-227/BUILD.md: the
R2 red on dummy links, the fixes, how the TMPDIR limit is derived, the
namespace containment and control table, runs, signal probes, the tip
check on 5df0e9d6f and follow-ups. One line each in BUILD-LOG.md and
docs/SESSIONS.md. Earlier entries are unchanged.
2026-11-02 16:34:22 -06:00

57 KiB

Row 227 C2-14 (#1728): Steelbeak rebuild

Assigned by Sage (sage-4166, 2026-10-24), reviewer Dewey. Worktree /home/jwoltje/.t3/worktrees/mosaic-stack/steelbeak-row227, branch steelbeak/row227, base refactor 487500a16f.

Recovered packet and choices

Gizmoduck's packet is preserved, unchanged, under agents/gizmoduck/work/queue-227. Cherry-picks: 2d1efa0de -> 471717c59 and 5583fa775 -> 8039fc1a9. Both clean. Read Choices first: one driver process owns fake TLS Gitea lifetime, the runtime's startup sync can require library-sync-busy retries, and refusal evidence uses a read-only store handle. Human bind remains an explicitly labelled in-process stand-in. The no-launcher stand-in comes out under sage-3608 now that row217 is landed: reuse its scripted citation adapter through the real launcher and fakeHarnesses module loader.

Authority and state

Static edits, local git and own-branch pushes are authorized. NO Node invocation (including node --check), npm, driver run, suites or mutants before Sage's CLEAR. Row257 owns the slot; 266/224/214 precede this row. No code in this packet has been executed by this seat.

Current dependency delta

Canonical queue inspection: 216/217/220/225/226/283/285/286/287 are done. Row288 remains briefed (Bubba); row289 remains briefed. The runtime has no library.write, library.revert or library.file.save implementation yet, so S6-S9 and S10's real write notice remain dependent on288. Reported to Sage in steelbeak-0020 with a recommendation to keep pending steps explicit and never count them as complete acceptance. Independent landed-step rebuild proceeds.

Planned static work

  1. Replace manual bindLaunch S3b with the existing scripted citation adapter behind createLauncher.
  2. Activate supporting mapping/editor, real lookup/citation guard/storage and playground-canon refusal.
  3. Adapt runtime/config/credential fixture to current roles while keeping all state scratch-only.
  4. Add the Console render path for checked replies; write/revert/save/notice follow landed APIs only.
  5. Shorten fixed fixture credentials to under24 characters; ensure output diagnostics never contain one.
  6. Search packages/*/tests and scripts for hash pins on each changed path; record searches and controls.
  7. Prepare mutant and full-script gate plan, then push a plainly UNTESTED packet and manifest for CLEAR.

Runs

None. Static reads and git operations only.

Sage ruling sage-4167

No slot request until row288 lands. Build landed steps now; draft S6-S9 and S10 notice against row288's brief, but no stub, skip or acceptance count. After224 ->284 ->288 lands, rebase and complete S6-S10, then request one slot for one full acceptance run with nothing skipped. Row already in-progress at3984; do not retry the refused queue-start or touch its lock.

Pending step Row288 verb/evidence State
S6 library.doc.write; one seat-authored parented commit, trailer and broker notice drafted, unrun
S7 library.doc.write story refusal library-write-story-path; refs unchanged drafted, unrun
S8 library.revert {project,commit}, A2 sage-3596; editor-authored revert commit drafted, unrun
S9 library.file.save {path,base,content}; editor-authored save commit drafted, unrun
S10 notice actual broker library-write message from S6 through Console HTTP and DOM drafted, unrun

The source contains unconditional implementations of all17 steps and no skip writer. None is counted toward acceptance before the gated full run. S6-S10 signatures and runtime wiring must be reconciled with actual288 landing before requesting that run.

Static rebuild so far

New fixture modules scripts/case2/launch.mjs and console.mjs keep the one-driver lifecycle. The launcher uses existing CLI fixture config, real bootConfig/startBroker/createLauncher and C2-06 citation-adapter via an adapter shell wrapper. Only a fixture grant is seeded, as the brief requests; no executor mint is substituted into row227. Actual launcher bind results are captured for later seat requests; no manual bindLaunch stand-in remains. Secret store files are outside data root, with distinct short fetch/seat values. The real message guard supplies stored checks; refusal/event reads remain read-only. The Console path uses consoleBus and startServer with actual chat rows, plus repository browser helper; only unrelated Board service is a local503.

Checks performed: bash -n scripts/test-case2.sh passed; git diff --check passed. Neither parses or executes JavaScript. NO Node/npm/driver/suites/mutants have run.

Static preparation receipt

  • Preserved Gizmoduck packet byte-for-byte against origin/gizmoduck/row227.
  • bash -n scripts/test-case2.sh: rc0. JavaScript syntax has NOT been checked.
  • git diff --check: rc0 on current edits.
  • static/hash-pin-search.txt records literal-path searches across packages/*/tests and scripts for every changed source/note path. No path-specific hash pins; only the driver self-reference and BUILD-LOG.md's gate-lane copy-exclusion entry. case1-accept.sh references are the positive control.
  • static/source-review.txt records the17 step ids in order and absence of a skip writer. This is source inspection, NOT an execution or an acceptance count.
  • No Node, npm, driver run, suite, browser, launcher or mutant has run. All helper code is UNTESTED.

Pre-CLEAR manifest is preparatory, not a frozen review candidate or a slot request. Static commits and pushes are explicitly authorized by sage-4166. No test or acceptance success is claimed.

Completion after row288 lands

  1. Rebase onto its refactor landing, retaining the two original cherry-pick receipts and packet.
  2. Reconcile224/284 lookup/tool snapshots and288 write/revert/save interfaces against the actual code. Current S8 follows A2's {project,commit}; current S9 follows the brief's {path,base,content}.
  3. Finish/verify S6-S10's real write, story refusal, revert, save and actual Console notice path.
  4. Send one slot request to Sage for the whole script, as sage-4167 requires; wait for CLEAR.
  5. Under CLEAR install pinned local dependencies in both worktree root and packages/webui with npm ci --ignore-scripts --no-audit --no-fund; use a new short mktemp TMPDIR. No canonical npm/linking.
  6. Execute the full script once with all17 steps and zero skips. Any first-run new-test RED is placed/reported; an old-test/product/loosened assertion or subsequent RED goes to Sage first.
  7. Proposed semantic negatives: disable playground-canon check ->S4; serve force-pushed tip ->S5; remove supporting-path enforcement ->S7; omit agent trailer/notice ->S6/S10; wrong editor ->S8/S9; remove stored guard metadata ->S3c/S10. A timeout is never a mutant kill. Restore byte-for-byte.
  8. Run applicable library/bus/harness/CLI/webui suites and every scripts/test-*.sh using bash with timeout -k10; MOSAIC_REQUIRE_EDITOR_BUILD=1 for webui. FED skip needs its dedicated same-gate runner. Redo hash-pin search after reconciliation, inspect token/output and owned-process cleanup receipts.
  9. Freeze final tested manifest and hand off directly to Dewey/Sage. Dewey runs the script; Sage's landing gate must also pass. No live host or model, no role expansion, no merge/deploy authorized.

The sole present boundary is row288's unlanded dependency plus explicit no-execution/no-slot ruling; no Jason decision or secret is requested. Leave row227 in-progress, as instructed.

Mos1961 integration / 2026-10-26 (supersedes stale dependency and execution ownership above)

GO authorizes local integration and test-first preparation, not a run or push. Rebased the 56233337b packet onto refactor09a847edaa25b609bac7728ff83f224708a29399. Rewritten packet commits:6df8b0fd8,2a728da86,5a0c801cb. Append-only BUILD-LOG/SESSIONS conflicts retained both sides in order; acceptance source applied cleanly. No product source changed. No662/657/658 work. 288 and331 product holds are met;331 tracking is not an acceptance prerequisite.

Actual source deltas: broker.mjs requires project on library.file.save; runtime.mjs chooses humanCredential from mapping.editor.user; query.mjs returns [line,text] tuples. Corrected the fixture/requests without removing assertions. S6/S8/S9 now inspect POSTs only after their own operation begins, plus returned path/commit. A third short made-up credential separates her pushes from fetch and seat pushes. No real credential/host/provider access occurs.

S3c adds observed library_get evidence through the real runner tool socket. The local adapter performs the lookup then imports C2-06's original scripted adapter; both fixtures are read-only sandbox inputs. Prepared quote data is not counted as a tool read. The fixture keeps real startBroker, createLauncher, normal thread polling/session process and guard/storage/Console. The only authority stand-in remains the explicitly labelled in-process bindHuman. No live model.

Static comparison to56233337b preserves all17 step IDs, names/order and original assertions; 20000ms polls,100 sync retries and150x40ms Console wait remain unchanged. New assertions name wrong-user pushes, omitted writes and absent managed lookup as intended failures. None has run. Target slot must first expose any RED; no production fix or acceptance success is inferred.

Whole execution files (no file filters): scripts/test-case2.sh, scripts/case2/e2e.mjs, scripts/case2/launch.mjs, scripts/case2/console.mjs, scripts/case2/citation-adapter.mjs. MOS1961-SOURCE-MANIFEST.txt hashes their current bytes; SOURCE-IMPORTS.txt records177 existing repository-local literal JS imports/new-URL dependencies recursively. This is a static closure, not a claim to enumerate runtime dynamic/plugin/browser assets or an independently tested manifest. Only whole targeted acceptance entrypoint is bash scripts/test-case2.sh; it runs node --check and the single driver, all17 steps without skips. No package-wide or full-gate run requested here. Pinned isolated npm ci prerequisites remain at root and packages/webui, no canonical links/npm. Parent retains original nice19/private HOME/config/TMPDIR and outer timeout/owned-cleanup bounds; the script also supplies its own private HOME/config to the driver. No deadlines increased.

Serial order:334,332,then227 only on parent CLEAR. Parent coordinates full gate, independent Codex-only review and landing; historic Dewey routing is superseded for Mos1960/#668. No push, queue override or dispatch. Current source is PREPARED/UNTESTED, with uncommitted preparation on5a0c801cb pending targeted evidence; repository green-before-commit rule remains respected. Checks actually performed: bash -n and git diff --check rc0, Python source comparison rc0. No Node/npm/driver/browser/suite/mutant invocation. Runtime behavior and cleanup remain UNVERIFIED.

First actual whole acceptance / 2026-10-26, serial CLEAR

Root and packages/webui isolated lockfile npm ci --ignore-scripts --no-audit --no-fund both0. Executed exactly nice -n19 timeout -k10 900 bash scripts/test-case2.sh with private HOME/config/ TMPDIR under short /tmp/R2271idb52l1. First whole invocation exit1 in0.49s, beforeS1a: e2e: stopped: credential-format credential-format. First wrapper assertion: FAIL driver ran to the end (exit 1). No acceptance steps passed or skipped. Shell2passed/2failed is wrapper accounting, not17-step acceptance. pins.before and pins.after are byte-identical. Evidence runs/first: command/isolation, git.before, pins.before/after, stdout.raw/stderr.raw, raw-hashes.txt, process-history/nice-violations, cleanup/cleanup.final, source-diagnosis. Whole-script stdout SHA256 efd9e00b7b292f96e62d8672a09ffcfdfe31182bb82b45277b22836e39e61479 281bytes; stderr0bytes SHA256 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. Observer driver snapshots caught empty initial files, not final contents; original script deleted its sandbox. Do not present those snapshots as complete driver stdout/stderr evidence.

Source diagnosis before remedy: credentials.mjs:442-445 minimum16, fixture values13/12/13. Prepared fixture-only replacements18/17/18 chars (<24), corresponding shell output checks changed. No assertions/deadlines relaxed, no production code changed. Static bash/whitespace and length checks0. No acceptance rerun; these replacements remain UNVERIFIED by execution. Current source manifest reflects remediation; immutable first-run pins preserve the failed input bytes.

Additional unresolved environment finding: outer nice19 observed; bash -4 and driver16 in /proc process history despite inherited nice invocation. No scheduling/deadline remediation attempted. Report cause as UNRESOLVED rather than claiming strictnice19. Observed owned survivors0; original sandbox gone; remaining private compile/npm cache removed only after evidence capture. Private root removal verified in cleanup.final.json. Serial slot returned to parent, which owns next run/gate.

Absolute-priority source investigation / 2026-10-26

FirstRED stdout/pins rehashed unchanged: stdout efd9e00b7b292f96e62d8672a09ffcfdfe31182bb82b45277b22836e39e61479; pins.before and pins.after both e50ab2b95f34c454660627d05a6fdae39e75304bd9804ea5ae7f76e49af45773. User accepted firstRED as failed evidence only;18/17/18 fixture remedy stays pending execution. Inspected Sage's repository gate machinery and the explicitly identified prior gate directory. BRIEF-TEMPLATE67-71 documents host ananicy and prescribes child preexec os.setpriority (absolute). Sage gate-priority.mjs sets os.setPriority(0,19), reads os.getPriority(0), refuses unless19 after Node exec. model-join-final-gate.mjs29-36 sets/reads driver and direct-child via native API, but its child is already spawned when setPriority runs. Thus it supplies native readback, not a before-test-child-execution release handshake. full-gate4.sh's relative shell correction is not sufficient proof. renice --priority19 --pid PID is absolute (renice -n19 can become relative under POSIXLY_CORRECT); this command was inspected via --help only, never applied to any PID. No established handshake located in inspected machinery. Asked Sage for exact source location; will not invent a substitute or infer successful strict19 from argv. Fethry retains serial slot until SLOTBACK. No new run, priority edits, deadline changes, or unrelated process access/actions. The host-daemon explanation is documented context, not a proven cause for this particular drift.

Owned absolute-priority prerequisite and corrected run / 2026-10-26

User explicitly authorized bounded native equivalent; no nonexistent-handshake wait. Private gate/target.py sets its OWN priority19 and Python child preexec priority19 before exec; gate/ priority.mjs is an invocation-only NODE_OPTIONS preload, sets/measures its OWN nice19 before modules, writes separate native.jsonl and rejects readback !=19, no stdout protocol change. Provider/credential environment keys removed, private mktemp alphanumeric /tmp/R227XXXXXX, original timeout-k10/900s and all17 assertion/deadline source retained. No external PID priority edits.

Prerequisite runs/priorityprobe exited0, Node and its Node directchild both native19, no measured drift/observed survivors, pins unchanged. This is prerequisite proof only. Corrected whole script runs/corrected1 then STOPPED on priority drift, exit143 in0.288s, BEFORE S1a. Syntax wrapper only printed OK. Preload recorded actual e2e PID2598465 native19 at23:13:16.248Z; later native monitor observed16 for same PID/start115758644. Bash2598452 observed-4. This establishes drift AFTER successful own native set/readback, rather than command-text proof or inherited negative priority alone. It does NOT independently attribute that change to a particular daemon. Monitor terminated ONLY owned gate process group. No source remedy, scheduler policy change or rerun after this new cause. Whole stdout36bytes SHAa695ba02e8fcfe4b103fba373fb7b7eabb3736086eccde37fb3df7797c0a1a27; stderr0bytes SHAe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. Full distinct pre/post source/input pins unchanged; firstRED raw/pins remain untouched. Observed owned survivors0, both probe/corrected private roots removed with cleanup.final receipts. Corrected acceptance remains UNVERIFIED; returned source-measured scheduling boundary to parent.

Mos1971 / static admitted1014 packet

No further workstation execution. Mos1971 confirms workstation ananicy configuration matches the observed drift; no daemon event-log evidence is claimed. Admission moves to dedicated1014 with verified no-renicing-daemon profile/identity; parent holds full gate and fresh review. Static target-packet/LAUNCH.md supplies dependencies, own-lockfile install commands, exact whole launch command, invocation-only preload/native monitor/cleanup protocol and explicit limitations. CURRENT-SOURCE.sha256/current-bytes freeze five execution files+two gate tools, independently of consumed first/priorityprobe/corrected1 evidence, inventoried in CONSUMED-EVIDENCE.json. fixture-length-only.diff was reconstructed against exact first-run source pins and hash-verified: ONLY three fake values and matching leak literals change, no assertion names/meaning/deadlines. Reviewer must inspect that hunk separately. No executable-source changes in this static packet turn.

Static guard limitations are explicit: managed sessionEnv drops NODE_OPTIONS/receipt vars (do not widen product allowlist); target external native monitor must cover owned sandbox descendants. Current monitor's host-Node startup exemption is not proof every Node descendant preloaded. Parent must require required host receipts, native19 of actual owned descendants and no daemon/admission evidence before claiming readiness. Target runner also requires explicit post-evidence removal of its recorded private outer root; original tool records it but does not auto-remove. No admitted run or full-launcher cleanup proof exists. No code/test/assertion/deadline changes or product commit.

R1 review response / APPEND-ONLY CORRECTION (2026-10-27 UTC)

Read complete independent report at parent agents/sage/work/mvp-1027-mos1960/ row227-independent-review-r1.md and verified SHA25630bc5f1fb5cf9047a7e06ffc39d93b937a2874167878dbdb313d45b36c3981f4. Verdict CHANGES, no author approval. Earlier length metadata in this BUILD/logs/messages is wrong: actual first literals14/13/14, current19/18/19, as immutable first/source-diagnosis.json already truthfully says. All current three remain16..23; KEEP their valid pinned bytes. Previous statements 13/12/13->18/17/18 are superseded by this correction only. Old packet39f149fb and every consumed first/probe/corrected1 receipt and pin stay unchanged. Current fake ZAI fixture key is17 chars.

R2/R3 STATIC preparation: gate controller no longer scans global /proc nice/argv. Discover children only via kernel children lists of validated owned PID+start parents, revalidate before admission; observe native nice/argv only after identity validation. All mismatches refuse immediately without Node exemption. Every observed Node requires its PID/start/boot receipt; live missing receipts bound5s, and missing completed receipts also veto completion. Root actual e2e receipt, per-launch real runner/adapter receipt, open driver captures, pins unchanged, fixture close/runtime registries, fresh survivor rows and private cleanup are mandatory even when script exits0. Only the proven owned root private session/group can receive TERM; unknown registry identities fail without signals.

Before-effect route uses existing createLauncher bwrap override (no product seam/ENV_ALLOW change). Owned wrapper sets/reads its OWN19, execs real /usr/bin/bwrap with original namespace/mount args, and adds invocation-only NODE_OPTIONS --import of read-only owned preload within that fixture. It preserves original node runner argv so runnerOf/isRunner semantics stay real. Runner preload and adapter preload emit atomic identity receipts under their existing owned writable run directory and block module evaluation up to5s pending release. Controller matches namespace PID and start, boot, nonce and native host19 only within kernel-established ancestry before writing matching ack. Preload rechecks identity ack and OWN19 before evaluation. Host preload does same before e2e. No sandbox/worker authority expansion, renice daemon/policy change or global/unrelated PID edits. These are unparsed/unrun control sources, NOT proof of prerequisite success or1014 admission.

R4 verified against actual parent pi preflight: nonproxy route requires own provider/proxy key, where isolated base preflight only checked binary. Fixture now explicitly supplies only made-up route-appropriate ZAI key in private f.env after clearing inherited provider/session credentials; real preflight is re-exported unchanged by fakeHarnesses. Fake adapter never calls provider. Added fake key to redaction/data-root and shell-output/history leak guards; no real key/proxy, product bypass, token retouch, acceptance-name/meaning/deadline weakening. This is a joined-source preparation, not a claim the future parent overlay has executed.

Superseding target-packet-r2 includes exact new whole files, separate change diffs and first-guard map/response for fresh independent R2 review. Source controls only; no Node, code parser, npm, tests, process/priority probe, admitted1014 access, commit, push or dispatch in this review response. Full acceptance and full gate remain held. Mos1971 no-renicing-daemon admission still mandatory.

Frozen superseding R2-review source packet (static)

Packet target-packet-r2/PACKET.sha256 SHA256bf1e6ed634cc511133c90fa9d46309c88d56e0dbe41c044c59dd42bc6b96c881; CURRENT-SOURCE.sha256 SHAbc76838e4980e6028d77a0c3069399aa054e7dc0980c813bdb85c97de1b79e70. Nine whole files: scripts/test-case2.sh; scripts/case2/{e2e,launch,console,citation-adapter, fixture-credentials}.mjs; agents/steelbeak/work/queue-227/gate/{target.py,priority.mjs,bwrap.py}. CURRENT-INPUTS.sha256 covers287 relevant source/input files plus explicit parent join reference hashes (not an applied combined freeze). REVIEW-RESPONSE.md and FIRST-GUARDS.md separate metadata, native control, fake provider fixture and future admitted guard sequence. All17 assertion bodies/ names/order/deadlines stay R1-byte-equivalent except fake-key import/secret-list/leak-literal extension; console/citation adapter bytes unchanged. Old packet39f149fb and every consumed evidence file rehashed unchanged. No code parsing/test/priority execution; fresh independent R2 verdict and genuine1014 runner checks remain mandatory. No product commit, push, dispatch, execution or selfapproval.

Independent R2 CHANGES response / static source-control only

Read FULL parent report row227-independent-review-r2.md, verified SHA256 3e919b6343eddccfeb3544384b660dfd86bb6983e4b22da31eefe3fff9a5c5b6. Preserve its CHANGES verdict; no author disposition. Packet bf1e6ed6, packet39f149fb and all consumed raw/pin inventories remain immutable. Original R1 length correction/current19/18/19 values and made-up own ZAI17 fixture remain unchanged. No e2e17 assertion/name/order/poll/retry/deadline change.

F1 source preparation now derives role from kernel executable/argv and innermost namespace PID, revalidated wrapper->actual registry runner->adapter lineage for the exact run, not JSON labels. Trusted host-only runtime record corroborates actual runnerPID/start/outerPID/start/business/run; no JSON record creates ownership. Native receipt includes execArgv/run; conflict/replay/rewritten record refuses, release binds full receipt SHA plus exact latest kernel-derived association. Kernel NODE_OPTIONS and explicit adapter import argv must match the pinned private preload. Host main/syntax argv normalization uses its owned kernel cwd, not producer claims.

Missing-preload barrier uses existing fixture seams: private controller authorization outside worker mounts is required before fixture host.bindLaunch returns a capability. Real launcher already records actual runner identity before bindLaunch, so native attribution does not depend on a capability/role action and there is no added product seam. Own fake adapter explicitly imports the barrier before its first tool call as well as invocation preload; missing actual kernel preload cannot let it act while diagnosed. Each wait remains<=5s within original budgets. No product ENV_ALLOW, policy or public API changes. All control code remains UNPARSED/UNTESTED.

F2 registry publication reconciles bounded fresh validated kernel children traversal after read, then retries only within existing5s window before unknown classification; no nice/argv read, release, ownership admission or signal from JSON. Pending identities must resolve, not vanish. True unrelated/stale/reuse inputs still refuse; only proven private root group may receive TERM.

F3 Inputs consumes caller-supplied admitted profile JSON with independently supplied SHA, pinned input-manifest SHA, joined identity and own executables. Both consumed list and admission hashes are included in pre/post comparisons. Required set seeds actual sandbox RUNNER_FILES, runner.mjs, git-tools.mjs, first-output.mjs, fixture/dynamic/plugin/harness/seat/roles/contracts/skills/unslop inputs and relative import closure, plus own installed dependency trees. Missing coverage refuses; actual before/after bytes compare to admitted expected hashes, never a fresh partial baseline. Actual target joined/P3/dependency identity remains parent's future admission, not isolated evidence.

F4 Evidence raw-captures control/registry bytes including pending/release/conflicting inputs before parse. Best-effort reserved result/cleanup handles and failure journal survive separately guarded parse/pin/observer/removal finalization. Any unknown ownership/required evidence or failure retains private root; failures cannot fall through to success or remove unresolved evidence. Only a fully verified run may remove its recorded own root; a removal error reports truthful retained remainder. Fixture closure publication is atomic. No unknown signals/global cleanup or invented lifecycle proof. Filesystem failures are best-effort receipt errors, explicitly nonacceptance, never GREEN.

Superseding target-packet-r3 will separate exact13-file current overlay and complete isolated reference input map from any future admitted freeze. Fresh independent disposition and1014 validation still required. No Node/parser/npm/tests/bwrap/process/priority probe, scheduler change, commit/push, target access or selfapproval occurred in this response.

Frozen proposed R3 source packet — no approval

Final target-packet-r3/PACKET.sha256 SHA2565a28156c8f4ada12f2c848073e0db8751c7ec3624573bf8a31cab76fd83b2eca; CURRENT-SOURCE.sha256 SHA25656f9e0974b7c59786b580dcf0af9782f1cd0f884601e0593cccf9d72410218fe. 13 whole files: prior6 script/fixture files and gate/{target.py,priority.mjs,bwrap.py,evidence.py, inputs.py,owned.py,association.py}. New packet includes R2 report, separate exact R2->R3 diff, response, first guards, explicit sandbox execution file list and14507-entry ISOLATED-REFERENCE (including workstation installed-dependency bytes, NOT target installation/admission evidence). Future controller requires parent ADMISSION_JSON and independently supplied digest; example is explicitly UNADMITTED and cannot authorize execution. Both consumed list/admission hashes are checked, not substituted with this reference. No exact source frozen here claims future joined P3/P5/target dependencies. E2e/shell/console/fake-key source are byte-identical to R2; old packets and every consumed inventory rehashed unchanged. All source remains unparsed/unrun; final fresh independent disposition and admitted validation are still required. No public/product seam boundary was necessary: existing fixture bwrap and async host.bindLaunch hooks suffice at source-design level.

Independent R3 CHANGES — bounded four-finding source response

Full129-line23936-byte report verified SHAeadec81aa0a278a07270584b603d04ecef7e4752168e95dfa4a35d5493f2aa22. R3/R2/R1 packet and consumed raw/pin inventories independently rehashed unchanged. Central native attribution/startup reconciliation retained. Proposed changes ONLY association.py, inputs.py, evidence.py, target.py and test-case2.sh for the four reported issues; no extra product seam.

R3-1: save digest+binding only after actual launch attribution; unchanged proven records survive observed wrapper exit/zombie without redoing dead startup proof. Final actual bound-run closure, empty runtime entries and fresh nonlive producers/survivors remain mandatory. New/changed/never- attributed or disappearing unknown inputs still refuse, pending input is not silently discarded. R3-2: separate mandatory root/webui installed dependency walks include actual files without the source-only node_modules exclusion; missing/outside/symlinked/empty roots refuse. Complete admitted expected coverage/byte comparisons and dynamic runner/sandbox/list/native pins remain required. R3-3: private controller sets ROW227_CONTROLLER_CLEANUP=1, shell leaves actual SANDBOX removal to controller's existing verified private-root decision. Failures retain actual run/registry/control files. Unset standalone marker preserves shell cleanup; no new signal or outside-root deletion. R3-4: raw opens NONBLOCK/NOFOLLOW, descriptor regular-type/size checks before finite initial-size read. FIFO never read toEOF; durable type/size refusal+root retention. Private categories native/ control1MiB, admission8MiB, frozen manifest128MiB, run logs64MiB preserve legitimate large manifests; finite prefix metadata truthful. No product limits or original assertion/deadline changed.

Superseding target-packet-r4/PACKET.sha256 SHA4eb60915b2144137df9b5a42afd0c5b266718d71de051fb8be53a27430a2cad4; CURRENT-SOURCE.sha256 SHA0cfd47c2073bc18a3ba7bff1ce6fdf39fe33a64b7fca209be71517b9feb89020. 13 actual whole source/tool snapshots, five-file diff, full R3 response/report and exact reached- control requirements. E2e/fake-key bytes and shell PASS/assertion/protocol body identical R3; 19/18/19+ZAI17 and all17 names/assertions/deadlines retained. Isolated reference14507 entries is NOT target admission/joined freeze. No local Node/codeparser/tests/fault/bwrap/process/priority probe, commit/push/selfapproval; runner UNADMITTED, fresh independent disposition still required.

R2: the suite runs standalone (lead round 2, 2026-11-02)

Author: implementation sub-agent of the Mosaic Stack lead (T3 c5c1e8ba), Claude Opus 5.5, working from the lead's brief row227-r2-fix.md and the Opus review row227-opus.md. This is the lead's second round on row 227, not the "R2 CHANGES" packet earlier in this file. Branch t3/row227-r2-fix on candidate 46c07df75, base 09a847eda, not rebased. Code commit 04e1ce24c. Earlier sections are unchanged and are not adopted here; each claim below was checked in this checkout.

R1 confirmed red

bash scripts/test-case2.sh on 46c07df75: exit 1 in 0.294 s. The driver stopped in launchFixture with row227-native-control-required because ROW227_CONTROL_ROOT was unset. 2 passed, 2 failed, stderr empty, no process left under the checkout or TMPDIR. This matches the review's 0.218 s red.

Findings and fixes

  • P1, the suite cannot pass outside the private controller. Removed from scripts/case2/launch.mjs: the ROW227_CONTROL_ROOT check, the control.json nonce read, nativePermit and its call in bindLaunch, the fixture-cleanup.pending and fixture-cleanup.json write in close() (only the controller read it), the owned-bwrap.sh wrapper and priority.mjs in readOnly. createLauncher now uses its default bwrap, and the adapter shell runs the adapter without the --import preload. The owned-runtime-cleanup-incomplete check in close() stays. In scripts/test-case2.sh the ROW227_CONTROLLER_CLEANUP branch is gone, so the shell always removes its sandbox. The suite no longer sets its own process priority; the R1 nice-19 handshake belonged to the controller.
  • P2, product scripts import a seat work packet. Removed the priority.mjs import from scripts/case2/citation-adapter.mjs. git grep -n -e 'agents/' -e ROW227_ -- scripts/case2 scripts/test-case2.sh prints nothing (rc 1). The packet's gate files are untouched.
  • P3, leak-check literals (test-case2.sh:63). scripts/case2/fixture-credentials.mjs holds all four fixture values and exports FIXTURE_CREDENTIALS; e2e.mjs imports the tokens from it instead of keeping its own copies. The new scripts/case2/leak-check.mjs reads the driver's stdout and stderr as bytes and exits 1 if any value appears, 0 if none does, and 2 when it gets no file or cannot read one. It never prints a value. R1's grep -qF counted an unreadable file as clean; this refuses instead.
  • P3, caller environment (test-case2.sh:45). The syntax check, the driver and the leak check run under env -i with PATH, LANG=C.UTF-8, a sandbox HOME with the four XDG dirs under it, TMPDIR=$SANDBOX/t, MOSAIC_CONFIG, GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM=1. Nothing else from the caller reaches the driver or the launcher it starts. Control below.
  • P3, driver tmpdir outside the sandbox. The driver's TMPDIR is $SANDBOX/t, so the Chromium profile and the plug-in tree go when the shell removes the sandbox. Length check below.
  • P3, unbounded cleanup wait (test-case2.sh:18). The driver starts under setsid and leads its own group. stop_driver sends it SIGTERM, waits at most 3 s by the clock (EPOCHREALTIME), then sends SIGKILL to the group and reaps it. It then sends SIGKILL to any process whose cwd is under the sandbox, found with one find /proc/[0-9]*/cwd -lname, because the launcher starts each bwrap session in its own group. It prints a count when it kills any. Then the sandbox is removed.
  • P3, browser flags. packages/webui/tests/browser.mjs is unchanged; see follow-ups.

Step failures past launchFixture

None. With the controller ties removed, the first run (debug run d1, rc 0, 5.5 s) passed all 17 steps. No step needed a fix. No assertion, step name, step list or deadline changed. I stopped on no packages/ defect.

TMPDIR length, browser and sockets

The driver TMPDIR in these runs was 48 bytes (/mnt/storage/scratch/tmp/iyiP1X/tmp.XXXXXXXXXX/t), past the 35-byte line, so I measured the sockets instead. Paths from green run final-g1: broker.sock 72 bytes, bus-host/git.sock 74, bus-host/launch.sock 77, the launch's tools.sock 95, and Chromium's SingletonSocket under $SANDBOX/t 93. All are under the 108-byte sun_path limit, and S3b and S10 pass. The longest, tools.sock, is the caller's TMPDIR plus 64 bytes, so the caller's TMPDIR must stay at or under 43 bytes.

Environment control

The shell ran with CASE2_CALLER_PROBE and GITEA_TOKEN set to dummy values in the caller. A sampler outside the repository (python3 -B) read /proc/<pid>/environ of every process under the run. Final code: shell green, 21 passed. 37 processes recorded, 31 read, 6 exited before the read. Read by name: bash 1, node 3 (driver, runner, citation adapter), git 9, git-remote-http 4, bwrap 1, chromium 13. Only the bash shell itself had either variable. No other process had either. A run on the earlier cleanup code gave the same result over 55 processes.

Signal controls

The landing gate sends SIGTERM to the shell's group and SIGKILL 5 s later (packages/seat/src/landing.mjs:84-88). I sent the same from outside, against the final code:

  • Plain run, SIGTERM mid-run: exit 143 in 256 ms, no SIGKILL needed, sandbox removed, nothing left.
  • Driver held with SIGSTOP first, then SIGTERM: exit 143 in 3105 ms, with note: 2 process(es) under the sandbox outlived the driver; sent SIGKILL, sandbox removed, nothing left.

My first bound counted 30 sleeps of 0.1 s and swept with a subshell per process. Its held-driver control took 4017 ms, about 1 s inside the gate's 5 s. On this host 30 x sleep 0.1 took 3.03 s, and the per-process sweep took 0.75 s over 970 processes against 0.013 s for one find. The final code uses the clock bound and the single find. The -lname patterns matched the sandbox root and a path under it, and did not match a sibling directory with the same prefix.

Mutants

Each is a one-line change under packages/. A mutant ran only when git diff --numstat -- packages/ showed exactly 1 1 <file>. Each ran once with bash scripts/test-case2.sh, then git checkout -- <file>. After each, git status --short showed no tracked change and the process list matched the one taken before. Final code:

# File:line Change Red at (failing steps) Result Time
m1 packages/library/src/mirror.mjs:252 scope === 'unmapped' to scope === 'mutant' S2b: refused project-read-missing, expected project-read-path 19 passed, 2 failed 5.7 s
m2 packages/library/src/guard.mjs:72 if (!reply.cited) to if (reply.cited) S3c missing stored guard check; S4 retry answer; S10 Console dropped stored reply/check 17 passed, 4 failed 5.5 s
m3 packages/library/src/guard.mjs:88 found[i].scope !== 'canon' to === 'mutant' S4: retry answer 19 passed, 2 failed 5.0 s
m4 packages/library/src/sync.mjs:168 accept !== tip to accept === 'mutant' S5b: answered, expected library-accept-stale 19 passed, 2 failed 5.0 s
m5 packages/library/src/write.mjs:164 trailer Mosaic-Agent: to Mosaic-Agent-Mutant: S6 missing agent trailer; S8 library-revert-not-agent; S10 actual write notice is required 17 passed, 4 failed 3.9 s
m6 packages/library/src/guard.mjs:68 if (limits.requireCitation && ...) to if (false && limits.requireCitation && ...) none survived: 21 passed, 0 failed 5.7 s

Each failed count includes the shell's own driver ran to the end check. The driver records a failed step and goes on, so one mutant can fail later steps too. m1 to m5 also went red at the same steps on the earlier cleanup code (4.8 to 6.4 s each).

m6 survives because no step sends a reply without a citation. The fixture sets requireCitation: true (scripts/case2/launch.mjs:64), but every scripted reply carries a citation, and the runner's fixed fallback texts are exempt (guard.mjs:65). The brief named the citation requirement as an example target; the guard is covered by m2 and m3 instead. The row brief's "What ships" does not list a citation-missing case, so I added no step. See follow-ups.

One run was discarded: my first m2 attempt edited line 73, the edit did not match, and the suite ran green on unchanged code. Every later mutant was gated on the numstat check above.

Runs

All foreground, one at a time, timeout -k 10 1800, output under TMPDIR, process lists before and after each.

  • R1 on 46c07df75: red, exit 1, 0.294 s.
  • Final code, bash scripts/test-case2.sh, no extra variables: final-g1 green, 21 passed, 4.96 s; final-g2 green, 21 passed, 5.74 s. During final-g1 the driver's pid, group and session ids were equal (2646097).
  • Earlier cleanup code: two green runs, 6.85 s and 5.80 s.
  • bash -n scripts/test-case2.sh passes. node --check passes on citation-adapter.mjs, e2e.mjs, fixture-credentials.mjs, launch.mjs and leak-check.mjs.
  • No process was left under the checkout or TMPDIR after any run, and no sandbox directory remained.

Tip check

git -C /mnt/storage/src/mosaic-stack worktree add --detach /mnt/storage/scratch/rr227b/tip fddae79ef, node_modules linked the same way (git ls-files -co --exclude-standard | grep node_modules printed nothing), then git -c core.attributesFile=/mnt/storage/scratch/q284gate/union.attributes merge --no-commit --no-ff 04e1ce24c: rc 0, no conflicts; BUILD-LOG.md and docs/SESSIONS.md merged as unions. bash scripts/test-case2.sh there: green, 21 passed, 5.05 s, nothing left. Links removed and the worktree removed with worktree remove --force. This file cannot hold the result of a merge that includes it, so the tip check on the records head goes in the return to the lead. The records commit only appends to this file, BUILD-LOG.md and docs/SESSIONS.md.

Follow-ups

  1. packages/webui/tests/browser.mjs:13-14 passes --headless, --no-first-run, --no-default-browser-check, --disable-dev-shm-usage, --remote-debugging-pipe and --user-data-dir, but not --disable-background-networking or --disable-component-update. The S10 browser may try outbound requests. Left alone per the brief; it belongs to the webui owner.
  2. m6: no step exercises citation-missing. A check where the scripted adapter first sends a plain body and the runner retries would cover it. It changes the step map the review checked, so it waits for the lead's ruling.
  3. TMPDIR: the caller's TMPDIR must stay at or under 43 bytes or tools.sock passes sun_path, and S3b fails closed.
  4. The sandbox sweep finds processes by cwd link only. A process that moved its cwd out of the sandbox and left the driver's group would not be found. None did in these runs.

R3: the sandbox guard, a literal sweep and the TMPDIR limit (lead round 3, 2026-11-02)

Author: implementation sub-agent of the Mosaic Stack lead (T3 c5c1e8ba), Claude Opus 5.5, working from the lead's brief row227-r3-fix.md. This is the lead's third round on row 227, not the R3 packets earlier in this file. Branch t3/row227-r3-fix on bd76d4345 (R2), base 09a847eda, not rebased. Code commit abc66a598. Earlier sections are unchanged. Only scripts/test-case2.sh and scripts/case2/ changed; nothing under packages/ did. All of R2 stays: the 17 steps and their assertions, the env -i allowlist, the driver TMPDIR in the sandbox, setsid, the 3 s bound inside the gate's 5 s grace and the leak check. No step was added (the m6 ruling stands).

R2 confirmed red

I did not run the R2 code with a failing mktemp. Both defects were shown the reviewer's way: the R2 find expression (scripts/test-case2.sh:44 at bd76d4345) run against four dummy symlinks under my TMPDIR, with no signal sent (22:10Z).

  • P1. bash -c 'set -uo pipefail; SANDBOX="$(TMPDIR=/nonexistent/dir mktemp -d)"; echo ...' printed status 1; continued with SANDBOX=[], pattern [/*]: the script goes on with an empty SANDBOX. With SANDBOX empty, the expression selected all four links: /opt/unrelated/work, .../a[bc]/tmp.example/root, .../ab/tmp.example/root and /. Against /proc that is every process this user can signal.
  • P2. With SANDBOX set to .../a[bc]/tmp.example, the expression selected only the link to .../ab/tmp.example/root, another tree, and missed .../a[bc]/tmp.example/root, its own. -lname reads [bc] as a pattern.

Findings and fixes

  • P1, allocation (scripts/test-case2.sh:7-29). The script refuses a TMPDIR that is not absolute before it calls mktemp. A failed mktemp -d stops it at line 19. The result must be absolute and more than /, a directory, not a symlink, owned by this user and empty, dot entries included. SANDBOX is its physical path from cd -P, the form a cwd link reads, and SANDBOX_ID is its stat -c %d:%i; both are readonly from line 29. Each failure calls fatal, which prints one test-case2: line and exits 2. Each check is an explicit || fatal, so nothing relies on set -e. No path is derived, no trap is set and nothing starts before line 29; the traps are at lines 66-68. Two later failures also stop the run before the driver starts at line 106: mkdir -p of home and t (line 81) and the creation of the two driver output files (line 105). By then the trap is set, so cleanup runs the sweep and the removal, and each checks the root again.
  • P1, the sweep and the removal refuse on their own (scripts/case2/sandbox.sh:83-113). sweep_sandbox ROOT ID and remove_sandbox ROOT ID first call sandbox_ok. It requires ROOT to be non-empty, absolute and not /, its own physical path (no symlink, trailing slash, . or ..), a directory this user owns, and its device:inode to equal ID. Otherwise each prints one line (kill sweep refused or removal refused) and returns 1 before it reads a link or removes anything. cleanup turns a refusal into a red result, even after a pass. The removal uses rm -rf --one-file-system. During cleanup a second INT or TERM is ignored, so it cannot cut the sweep short; the gate's SIGKILL after its grace still ends everything.
  • P2, a literal match (scripts/case2/sandbox.sh:52-69). cwd_in ROOT TARGET is a bash case "$TARGET" in "$ROOT" | "$ROOT"/*). Quoted text in a case pattern matches only itself, so *, ? and [ in the root are literal; the only pattern is the /* after it. cwd_links_in reads every target with one GNU find, -maxdepth 0 -printf '%p\t%l\0', and the loop compares each one. A link this user cannot read, or a zombie's, gives an empty target and never matches. A cwd in a deleted directory under the root reads ROOT/x (deleted) and still matches. ROOT (deleted) does not: the sweep runs only after sandbox_ok has shown ROOT exists, so a target with that text is another directory whose name ends in " (deleted)". The sweep makes up to three passes, so a child forked during one pass is caught by the next, and it prints R2's note when it killed anything. One read of 1031 cwd links took 56 ms in sandbox-check.sh. R2's single find took 13 ms; the comparison loop in bash costs the rest.
  • P3, TMPDIR length (scripts/test-case2.sh:71-77). The check runs before anything starts and prints one line with the limit and both lengths. With a 44-byte TMPDIR: test-case2: TMPDIR is too long: the run's tools socket path would be 108 bytes, over the 107-byte limit in packages/seat/src/session.mjs; TMPDIR is 44 bytes, at most 43 fit; nothing was started (one line).

How the TMPDIR limit is derived

packages/seat/src/session.mjs:270-278: newRun puts each launch at <data root>/launches/<business>/r<12 hex digits>, the id from randomBytes(6), and refuses with data root path too long for a socket when Buffer.byteLength of its tools.sock is over 107. The driver's data root is <sandbox>/root/data (fixture() in packages/cli/tests/helpers.mjs, given <sandbox>/root) and its business is acme (scripts/case2/e2e.mjs). socket_shape builds <sandbox>/root/data/launches/acme/r000000000000/tools.sock on the real sandbox root, with a run id of the same length, and byte_length counts bytes under LC_ALL=C. That path is the sandbox plus 49 bytes. mktemp's /tmp.XXXXXXXXXX adds 15 bytes to TMPDIR, so the path is TMPDIR plus 64, and a TMPDIR of at most 43 bytes fits, the figure in R2's follow-up 3. The refusal compares the path the run will build, so it is exact. The "at most" figure is TMPDIR bytes - socket bytes + 107. It assumes TMPDIR is already a physical path with no trailing slash; with a symlink in TMPDIR the decision still uses the real path, but the figure can be off.

Measured in the bracket control below (TMPDIR 37 bytes): broker.sock 78 bytes, bus-host/git.sock 80, bus-host/launch.sock 83, launches/acme/r<id>/tools.sock 101 and Chromium's SingletonSocket under <sandbox>/t 99. tools.sock is the longest, and 101 is what socket_shape gives for that sandbox.

Checks with no signal

scripts/case2/sandbox-check.sh is new. It checks the guard, the refusals and the match against dummy symlinks in a scratch directory. Its kill is a stub that records its arguments, so even a broken guard could not reach a process. It covers roots with [bc], * and ?; an empty, relative, missing or / root; a trailing slash, a . part, a symlink root and a path through a symlink; a wrong device:inode; siblings sharing a prefix; newline and tab in targets; an unreadable link; deleted directories; and link_pid shapes. Two runs at 22:14Z: 40 passed, 0 failed. The landing gate globs only scripts/test-*.sh, so this file is not a gate step.

Containment for the failure controls

Every control that makes allocation, a mkdir or a redirect fail on my code ran inside unshare --user --map-root-user --pid --fork --mount-proc: a fresh PID namespace with its own /proc, and a mount namespace for the tmpfs the full-disk cases use. uid 0 inside maps to my uid. Proof first (22:15Z): /proc in the namespace listed pids 1 to 4 only, and kill -0 on the host sentinel's pid gave "No such process". A wrong sweep in there could see and signal only the namespace's own processes. An in-namespace sentinel and a host sentinel ran through every control. The host sentinel was a subshell (pid 2276) and its sleep 600, both with cwd in my TMPDIR, the parent of every sandbox, so no sweep root could match them.

Control table

Run at 22:18Z under bash -x. Bash at euid 0 ignores PS4 from the environment, so the trace counts are lines starting with + for the command named. Every control exited 2, traced no kill and no setsid, and left both sentinels alive.

Control How Message Trace
mktemp fails TMPDIR names a missing directory mktemp -d failed under TMPDIR=...; nothing was started no trap, no sweep, no rm
relative TMPDIR TMPDIR=rel/dir TMPDIR is not an absolute path: rel/dir; nothing was started no trap
mkdir fails tmpfs, nr_inodes=2 cannot create <sandbox>/home and <sandbox>/t trap set; sweep 1 on the valid root, nothing killed; remove 1; tmpfs left empty
steps.txt fails tmpfs, nr_inodes=4 line 105, then cannot create the driver output files in <sandbox> node --check 1, sweep 1, remove 1
stderr.txt fails tmpfs, nr_inodes=5 the same node --check 1, sweep 1, remove 1
mktemp prints '' a fake mktemp first in PATH mktemp -d gave '', not an absolute path no trap
mktemp prints / fake mktemp -d gave '/', not an absolute path no trap
mktemp prints x fake mktemp -d gave 'x', not an absolute path no trap
mktemp names a populated dir fake ... not a new empty directory of this user no trap; the directory kept its file
mktemp names a symlink to an empty dir fake the same no trap; the link and its target kept

These are the GREEN side of the P1 red above: the same failure now stops the script with nothing started.

Over-limit control (22:19Z, on the host, since mktemp succeeds and the refusal comes before anything starts): TMPDIR <my TMPDIR>/over-limit-1, 44 bytes. Exit 2 in 41 ms with the line quoted under P3. The trace shows no kill, setsid, node or mkdir; the sweep ran once on the valid root and the removal once. The sandbox was removed, and a sentinel in a sibling directory and the host sentinel stayed alive.

Bracket control (22:20Z, on the host, a full run): TMPDIR <my TMPDIR>/a[bc], 37 bytes, sandbox a[bc]/tmp.g1AqgFWaIv. As soon as the sandbox appeared, the control script started seven sleep 600 sentinels, outside the driver's group, so only the sweep could reach them, and removed r3gone after 0.2 s:

Sentinel cwd Result
in-root the sandbox root killed, status 137
in-sub <sandbox>/r3probe killed, 137
in-gone <sandbox>/r3gone, removed after the cd, so it reads " (deleted)" killed, 137
out-ab ab/tmp.g1AqgFWaIv, what [bc] matches as a pattern alive
out-ab-root ab/tmp.g1AqgFWaIv/root alive
out-deleted-name a real directory named tmp.g1AqgFWaIv (deleted) alive
out-prefix tmp.g1AqgFWaIvx alive

The suite exited 0 in 5953 ms, 21 passed, stderr empty, with one note: 3 process(es) under the sandbox outlived the driver; sent SIGKILL. The sandbox was removed; the host sentinel stayed alive. I then killed the four survivors and removed their directories.

Runs

All foreground, one at a time, timeout -k 10 1800, output under TMPDIR (31 bytes, /mnt/storage/scratch/tmp/ plus six characters), process lists before and after each.

Run Exit Time Result Notes stderr Left behind
plain g1 (22:20Z) 0 5635 ms 21 passed 0 empty nothing
plain g2 (22:20Z) 0 5204 ms 21 passed 0 empty nothing
bracket (22:20Z) 0 5953 ms 21 passed 1 (the planted sentinels) empty the four planted survivors, by design
tip (22:28Z) 0 5175 ms 21 passed 0 empty nothing

bash -n passes on scripts/test-case2.sh, scripts/case2/sandbox.sh and scripts/case2/sandbox-check.sh. No .mjs changed, so there was no node --check to run. shellcheck is not installed on this host.

Signal probes

The landing gate starts the suite detached and, on abort, sends SIGTERM to its group and SIGKILL 5 s later (packages/seat/src/landing.mjs). My probe script ran each suite under set -m, so each had its own group with SIGINT at its default, and signalled the group. The suite's SigIgn read 0x4 (SIGQUIT only). In every probe the suite's group and the driver's pid, group and session were distinct, so the driver led its own session.

Probe Signal When Exit Time State at the signal
term-a TERM 1.5 s 143 130 ms 7 step lines; a bwrap session (its own group) and node
term-b TERM 1.5 s 143 130 ms 7 step lines; a bwrap session
term-early TERM 0.3 s 143 434 ms 1 step line; nothing in the sandbox yet
int-a INT 1.5 s 130 130 ms 7 step lines; a bwrap session
int-b INT 1.5 s 130 131 ms 7 step lines; a bwrap session
stopped-term-a TERM, driver SIGSTOPped first once a session ran 143 3088 ms 7 step lines; bwrap gone at 3047 ms
stopped-term-b the same, plus a planted sentinel in the sandbox, own group once a session ran 143 3097 ms the note for 1 process; sentinel status 137; bwrap gone at 3056 ms

Each left no sandbox, no driver and nothing under the old sandbox path, and the probe's own sentinel outside the sandbox stayed alive. Earlier probe runs gave TERM 143 in 1244 ms (twice), INT 130 in 1247 ms and 130 ms, and a held driver 143 in 3072 and 3073 ms. The time to close follows the phase the driver's own SIGTERM cleanup is in, from 130 to 1247 ms, always inside the 3 s bound. A held driver takes the full 3 s, then its group's SIGKILL.

R2's held probe had 2 processes outlive the driver. Here none did unless I planted one: bwrap, in its own group, was gone at 3047 and 3056 ms, just after the 3 s bound ran out and the driver's group got SIGKILL (the probe polls, so these times are upper bounds). bwrap has no die-with-parent option in its argv; the setpriv --pdeathsig KILL in packages/seat/src/sandbox.mjs:44 wraps only the command inside. The broker runs in the driver process (startBroker in scripts/case2/launch.mjs), so the likely cause is that the runner loses its broker and exits, taking bwrap with it. I did not confirm that. The sweep covers either case.

Tip check

git -C /mnt/storage/src/mosaic-stack merge-base --is-ancestor a8e71be03 origin/refactor gave rc 0 after the fetch, so the tip was origin/refactor, 5df0e9d6f (also the fallback the brief named). worktree add --detach /mnt/storage/scratch/rr227d/tip 5df0e9d6f, node_modules linked the same way (git ls-files -co --exclude-standard | grep node_modules printed nothing), then git -c core.attributesFile=/mnt/storage/scratch/q284gate/union.attributes merge --no-commit --no-ff abc66a598: rc 0, no conflicts. Row 227 has not landed, so the merge brings the whole row: 171 paths, all under agents/ apart from BUILD-LOG.md, docs/SESSIONS.md (merged as unions), scripts/test-case2.sh and scripts/case2/. bash scripts/test-case2.sh there: exit 0 in 5175 ms, 21 passed, stderr empty, no note, the process lists before and after matched, and the host sentinel stayed alive. Links and the worktree were removed with worktree remove --force. The records commit only appends to this file, BUILD-LOG.md and docs/SESSIONS.md.

Harness notes

These were faults in my control scripts, not in the code under test. They are recorded so the numbers above can be read correctly.

  • Bash at euid 0 ignores PS4 from the environment, so my first namespace trace counts read 0. I counted default + lines instead and reran the controls.
  • Non-interactive bash starts async jobs with SIGINT ignored, and a script cannot trap a signal it started with ignored. The probes use set -m, which matches the gate's detached spawn.
  • My first two held-driver probes waited for a session with a pattern that stopped matching once I added the group to the listing. They signalled after the suite had ended (exit 0 in 0 ms) and were discarded; the stopped-term rows above are the reruns.
  • The first host sentinel was a sleep 600 started at 22:15:00Z. It was alive at 22:24:01Z and gone at the held-driver probes, at about 22:25Z, when its 600 s ran out. Its cwd was my TMPDIR, which no sweep root can match. A second host sentinel (sleep 3600) covered the tip check.
  • During setup I wrote one stray file, /mnt/storage/scratch/tmp/.r227r3-tmpdir-note, outside my TMPDIR, and removed it at once.

Follow-ups

  1. The suite rebuilds the product's socket path from three facts (launches/<business>/r<12 hex>/tools.sock, the 107-byte limit and the fixture's data root). If session.mjs changes any of them, socket_shape goes stale. An exported helper for the socket path, or the run id length, would let the suite ask the product.
  2. Chromium's SingletonSocket is TMPDIR plus 62, 2 bytes under tools.sock. If the S10 profile path grows, it could become the longest.
  3. Whether the held-driver note appears depends on whether bwrap sessions outlive the driver. R2 saw 2, R3 saw none. The cause above is unconfirmed.
  4. One residual risk stays from R2: a process of this user that chdirs into the sandbox (mode 0700, a random name) would be killed by the sweep. pid reuse between the read and the kill is negligible at these timings.