# Row 296 / #1802 — R2 scoped acceptance verified; review pending Base: e0c6142f3a411ed70e32b2c50d01930e0b0641c8. Branch: lena/row296-r2. Reviewer: Filbert. Gate owner: Sage. Handoff: c6ab9bdc9 preserved as ad84c1e45, rebased as 4a16ebaa4, including agents/quackerjack/work/queue-296/BUILD.md. Every changed path is in MANIFEST.sha256, except the checksum file itself (self-hash is excluded). No product file or pre-existing test changed. Pre-existing test numstat: empty. BUILD-LOG.md and docs/SESSIONS.md are append-only additions. Binding ruling: sage-3625 replaces the brief's transient-unit start with plain child Consoles and explicit test stand-ins. **This candidate does not prove genuine outside-agent ancestry.** It creates no transient systemd unit and makes no systemd CLI call. R8 remains row 209. ## What is real, and what is a stand-in - Two configs created by bootstrap --instance hub/factory, two marked data roots, real Console cli.mjs serve --sso --send, real broker runtime and launcher, real path-bound human-cli and user-cli wrappers, real access loader, CLI writes/reload, grant filters and revocation. - One existing HTTPS fakeOidc server, two issuer/client identities. Its generated CA is trusted only through NODE_EXTRA_CA_CERTS in this test's child environment, never any trust store. - federation-host.mjs replaces verifyHuman's readProcess argument with actual process fields plus ppid=1, exactly the test pattern Sage named. This stands in for Jason's shell on setup, grant/revoke and both Consoles' transports. The actual Console parent/path check remains. There is no product parameter, environment switch or altered verifier default. - fakeHarnesses replaces only the pi harness adapter() with a scratch shell invoking the existing thread-adapter.mjs. The existing webui channel is re-exported unchanged. No network model runs. - Public HTTPS origins are modelled through HTTP loopback requests with matching Host/Origin; reverse-proxy TLS deployment is outside this scratch proof. ## Acceptance map All checks live in `FED R7 scratch two-instance acceptance` in packages/cli/tests/federation-acceptance.test.mjs. | Requirement | Assertion/evidence | | --- | --- | | Isolated instances | different config paths/data roots/ports; scratch HOME/TMPDIR; two named markers from bootstrap; port 0, forbidden live ports 7330–7332 checked | | Jason is collaborator, not owner | owner melanie; access user jason-test, home factory, fake sub jason-test-sub; hub /api/config user=true | | One project, one granted instance | actual access grant CLI, projects=[shared], instances=[assistant]; user project list contains shared only | | Thread and reply | create thread on shared, POST message, correlated RESULT with in_reply_to=sent.id, from_role=assistant, to_role=user, body contains question | | Other project refused | private project absent from listing; create on private returns 403 access-refused | | Board reads refused | /api/board, /api/bus/inbox and /api/bus/tasks return 403 owner-only | | Factory sees no hub objects | Jason sub refused at factory; factory owner sees factory-only project and no threads; hub cookie gets 401 at factory | | Revoke takes effect | actual access revoke CLI; existing hub session 401 and re-login 403 | | Claim and process ended | live run before revoke; end log exitCode=0; no registered sessions; engine and runner pid/startTime gone; readonly claim history has revoke/access-revoked before the process-end wait, latest op=revoke afterward | | Normal cleanup | EXIT trap terminates only recorded pid/startTime-checked groups; checks no live group member, no recorded TCP/Unix listener, no other scratch Unix listener, removes scratch directory/socket files | | Killed-driver cleanup | --kill-after-start prints both-Consoles-started marker then SIGKILLs test driver; shell performs the same independent cleanup; expected nonzero run with successful cleanup line | ## Verification commands (executed on sage-4035 CLEAR) - scripts/test-federation-acceptance.sh — must exit 0, one acceptance test passes, cleanup line present. - scripts/test-federation-acceptance.sh --kill-after-start — must exit nonzero, print the explicit interruption marker and `R7 cleanup: no child groups, ports or sockets remain; no units created`. - python3 agents/lena/work/queue-296/mutants.py M1 — once-only R5 grant projects -> ['*']; must fail `R7 ungranted project must not appear` and verify cleanup, then restore original source bytes. Python relies on the shell's own run deadline and cleanup; no outer killing timeout. - python3 agents/lena/work/queue-296/mutants.py M2 — once-only R4 claim end omitted; must fail `R7 revoke must end the claim as access-revoked` before shutdown, verify cleanup and restore bytes. - Sage integration gate remains separate: every scripts/test-*.sh and full applicable suites per Sage; not run in this scoped slot. Status: scoped gate passed: plain 1/1 (exit 0), killed-driver expected exit 1 with verified cleanup, M1/M2 drivers exit 0 with named assertion kills and source restoration; normal whole-file invocation exit 0 with one intentional dedicated-script skip. All cancellations 0. Receipts in run-r2/; counts and commands in BUILD.md. The script never silently skips a missing tool/sandbox/provider: prerequisites fail the acceptance. Ordinary CLI suites skip the dedicated test until invoked by its owning script. Static-review follow-up: filbert-296-first-push-static. C1 removes Python's outer timeout so it cannot SIGKILL the shell before EXIT cleanup. N2 moves the named claim-history assertion before the process-end wait. N1's spawn-to-ledger SIGKILL gap is retained and documented in BUILD.md; --kill-after-start exercises the fully recorded groups. N3 remains a cosmetic fixture limitation. N4 indentation corrected. N5 scratch-bootstrap config overwrite is explicitly fixture-only. R2 provenance: source identical to the statically cleared a1321f8f9. Rebase mapping/range-diff and stable patch IDs are in run-r2/. Only append-log conflict context changed. No unexpected first-run failure, no old test changed/run, and no broader suite or shared gate claimed. Manifest coverage is the complete changed-path set over e0c6142f3..candidate, including all text evidence and this description; only MANIFEST.sha256 itself is excluded from its own hash.