feat: SSO accounts sessions and deployed-path reconciliation (M3, #1873) #1918

Merged
jason.woltje merged 6 commits from org-m3-recon into refactor 2026-11-11 04:16:20 +00:00
Owner

Phase M, slice M3 with its reconciliation (#1873). They land together, as the Lead's hold requires.

M3 (three commits):

  • Account sessions are separate from root authority. Only an operator account whose subject the instance names gets rootOwner. Hook owner and the decision, restart and system-type write routes require it. The dev local owner carries it, so the LAN dev Console is unchanged.
  • A stamped copy and a deployed instance enforce their boundaries: a stamped Console needs console.sso, a loadable accounts.json and a .deployed naming the instance.
  • Accounts system units need a dedicated host uid.

Reconciliation (three commits):

  • stack-release.sh rehearse on a stamped copy runs a local HTTPS rehearsal provider. It serves discovery and JWKS for the live provider ids and answers 403 to authorize and token, so no sign-in can complete. The gate requires every provider ready. The live slot is never opened, and the live config and accounts.json are checked unchanged.
  • A user-unit activate, rollback or pm-on refuses accounts-user-unit before anything stops when any accounts sign is present. The way through is root's case2-unit.sh and --units external.
  • case2-unit.sh renders a stamped copy only with --sso and all M3 prerequisites, naming each missing step in run-sheet order. A stamped library unit takes a bare --sso and refuses proxy-preview admission and its proof (Jason's SSO ruling). --check and --check-new let stack-upgrade.sh --next-args migrate installed units without loosening the installed-unit match.
  • The host run sheet gains "Migrating a host to SSO accounts".
  • Rewritten release tests are listed with old and new assertions in the lane report. No release or rollback outcome assertion was dropped.

Lanes: Sol (M3), Opus (reconciliation). Gate on d0cfebe28 (base 82dcba2d4): every cli and webui test file, 275 files, 2727/1. The one red is the known case1-accept P10 timing check, which passes alone on this head and on base (56/0). scripts/test-case2.sh is 13 OK / 8 FAIL with the same lines on base: the existing launcher-reply timeout in the seat fake harness, which this change doesn't touch. Reviews: M3 build (changes 6, fixed), M3 security daybreak-blue (approve), system-types re-check (approve); reconciliation security daybreak-blue (approve, 0), build (changes 4, fixed), focused re-check (changes 1, P3, fixed). Mutation rounds killed 57 mutants.

Lead rulings recorded: a library rollback past this migration restores the old copy's proxy-preview flags. Rollback is root's recovery path, so it stays allowed; the run sheet will say so in a follow-up. A2's agent-level writes require root authority under M3, which matches the accounts design.

Phase M, slice M3 with its reconciliation (#1873). They land together, as the Lead's hold requires. M3 (three commits): - Account sessions are separate from root authority. Only an operator account whose subject the instance names gets `rootOwner`. Hook `owner` and the decision, restart and system-type write routes require it. The dev local owner carries it, so the LAN dev Console is unchanged. - A stamped copy and a deployed instance enforce their boundaries: a stamped Console needs `console.sso`, a loadable `accounts.json` and a `.deployed` naming the instance. - Accounts system units need a dedicated host uid. Reconciliation (three commits): - `stack-release.sh rehearse` on a stamped copy runs a local HTTPS rehearsal provider. It serves discovery and JWKS for the live provider ids and answers 403 to authorize and token, so no sign-in can complete. The gate requires every provider `ready`. The live slot is never opened, and the live config and `accounts.json` are checked unchanged. - A user-unit `activate`, `rollback` or `pm-on` refuses `accounts-user-unit` before anything stops when any accounts sign is present. The way through is root's `case2-unit.sh` and `--units external`. - `case2-unit.sh` renders a stamped copy only with `--sso` and all M3 prerequisites, naming each missing step in run-sheet order. A stamped library unit takes a bare `--sso` and refuses proxy-preview admission and its proof (Jason's SSO ruling). `--check` and `--check-new` let `stack-upgrade.sh --next-args` migrate installed units without loosening the installed-unit match. - The host run sheet gains "Migrating a host to SSO accounts". - Rewritten release tests are listed with old and new assertions in the lane report. No release or rollback outcome assertion was dropped. Lanes: Sol (M3), Opus (reconciliation). Gate on d0cfebe28 (base 82dcba2d4): every cli and webui test file, 275 files, 2727/1. The one red is the known case1-accept P10 timing check, which passes alone on this head and on base (56/0). `scripts/test-case2.sh` is 13 OK / 8 FAIL with the same lines on base: the existing launcher-reply timeout in the seat fake harness, which this change doesn't touch. Reviews: M3 build (changes 6, fixed), M3 security daybreak-blue (approve), system-types re-check (approve); reconciliation security daybreak-blue (approve, 0), build (changes 4, fixed), focused re-check (changes 1, P3, fixed). Mutation rounds killed 57 mutants. Lead rulings recorded: a library rollback past this migration restores the old copy's proxy-preview flags. Rollback is root's recovery path, so it stays allowed; the run sheet will say so in a follow-up. A2's agent-level writes require root authority under M3, which matches the accounts design.
jason.woltje added 6 commits 2026-11-11 04:16:16 +00:00
stack-release refuses a user-unit activate, rollback or pm-on when the
instance shows any accounts sign (stamped target, accounts.json,
console.sso, .deployed), before anything stops. Root renders the system
units with case2-unit.sh, and the op runs with --units external.

case2-unit renders a stamped copy's units only with --sso, console.sso,
an accounts.json that loads and a valid .deployed, and names each
missing step in run-sheet order before any write. Per Jason's ruling of
2026-11-10, a stamped library unit takes --sso and refuses proxy-preview
admission and its proof. The unstamped and dev paths are unchanged.

rehearse runs a stamped copy against rehearsal.invalid with scratch
slot placeholders, a copy of the live accounts.json and the copy's own
.deployed. It refuses sso-not-prepared and accounts-not-initialized
before any rehearsal directory exists, and sso-isolated before the host
starts.

The tests that turned SSO on through the user unit now pin the refusal
and reach the same end state through the system path. The copy tests
start the Console on real scratch prerequisites and pin each refusal.
The run sheet, TOOLS.md and the accounts design record the ruling and
the migration order.
An installed unit must equal the render but for the copy id, so a
re-pin that adds --sso refused render-refused. case2-unit gains --check
(every render check, nothing written) and --check-new (a first install's
checks). stack-upgrade runs both checks before anything stops, and
--next-args reads root's <instance>.args.next: equal before -- but for
--copy, different after it. After the stage it moves the installed unit
files into a new <instance>.replaced.XXXXXX directory and renders a
first install. A failure moves them back unchanged and restarts the old
copy, and confirm swaps the args file. The run sheet gives the commands,
the rollback past the migration included.

A stamped rehearsal now starts an isolated provider on 127.0.0.1 port 0
with a certificate and keys made for the run. It serves discovery and a
JWKS for the live provider ids and answers 403 to authorize and token.
Only the scratch Console trusts its certificate. The gate requires each
provider ready (sso-ready); provider-discovery requires discovery and
JWKS fetches and no code or token request. The provider stops with the
run, and by itself once the run's shell is gone.

The system-path test helper asserts the log's from copy again. The
pinned-copy sign-in requests are bounded, and cleanup waits for each
Console to exit.
The rehearsal provider matches routes on the parsed pathname, so a
query string no longer turns authorize or token into a 404. Authorize
and token answer 403 to any method before the GET check, and only
discovery and the JWKS answer 405 to another method. R20 now sends a
POST to token and a GET to authorize with a query, and expects 403 for
both.

The upstream A2 agent-levels write test stubbed an owner session
without rootOwner. M3 sets the hook's owner flag from rootOwner, as
sso.mjs grants it, so the stub now carries rootOwner: true, as
system-types-http does. No check changes.
jason.woltje merged commit d0cfebe286 into refactor 2026-11-11 04:16:20 +00:00
Sign in to join this conversation.