Skip to content

Two-phase mode-transition engine: stage → verify → commit, journalled + rollback-able (#1072) - #1087

Merged
aarontrowbridge merged 3 commits into
mainfrom
1072-two-phase-transition-engine
Sep 13, 2026
Merged

aarontrowbridge merged 3 commits into
mainfrom
1072-two-phase-transition-engine

Conversation

@aarontrowbridge

Copy link
Copy Markdown
Member

P4a-2 of the approved fleet rearchitect (spec-20260913-114814 row 2, invariant 1 as amended, §8 F2) — the transition engine grown ON the merged #1070 ModeMachine: stage → verify → commit, with the transient/structural verify-fail split, the journaled commit-pending state, and rollback pinned to the pre-stage snapshot. The 09-13 destructive-switch class is now structurally closed: a switch either completes atomically or leaves a labeled, resumable journal — never torn state.

The engine (states: idle | staged | verified | commit-pending | committed | rolled-back):

  • Stage refuses in-flight/confirmed transitions, snapshots mode-scoped state FIRST (pinning the last human-confirmed config), journals transition-stage, writes NO attach-state field — and refuses posture in mode-scoped fields (ADR-0005 vocabulary law)
  • Verify sync-guards wrong states, arms the deadline timer in the synchronous prefix (clamped to the fix(fleet): the way back from standalone — alias-substituting installer + Fleet — Enroll #1034 60s floor), fires the P4a-3 serialization seam (onVerifyEntry — a throwing drain leaves the transition staged) BEFORE the attach test; transient fail → staged-with-snapshot (re-verify needs NO fresh confirm — the human confirmed the proposal, not the timing); structural fail → automatic rollback (local-only, idempotent — double-rollback is a no-op; hub-side untouched)
  • Commit is the ONLY mode writer (human-confirmed path — asserted by F2 across all 8 scenarios): pre-commit hook (kill = nothing written/journaled) → transition-commit-pending journaled at the point of no return (carrying the FULL proposal + snapshot + cap epoch — enough for a FRESH engine to restore or resume: the constructor scans the journal for an unresolved pending) → mid-commit hook (kill = journaled pending, never torn) → confirmMode → transition-commit
  • Commit-pending renders as a labeled badge, persists in the MODE journal (never the posture field — asserted), bounded by a FakeClock-driven wall-clock cap surfacing a staged ABORT/RESUME choice confirmable locally, plus a local force-resolve verb

Gate evidence (director re-run, from commands): engine + the #1070 surfaces 62/62; typecheck clean pre- and post-merge onto current main; post-merge 78/78 including the parallel skills campaign's suites (no cross-breakage); full suite at the 9-failure environmental baseline (the known family, evidenced in prior PRs). Scope: attach_state.ts +660/−17 (the −17 = stale forward-reference prose; zero behavioral removals) + the adopted 740-line RED test (survived an interrupted first cast, adopted verbatim, never weakened) + the post-merge merge-commit onto main @ 606cc74.

F2 verdict: n_non_atomic_mode_switches == 0 over the full matrix (pre/mid-commit × stop/throw/crash + transient + structural fail) — every scenario ends honest: committed, rolled back, or journaled-pending.

Interface notes for the next slices: P4a-3's queue drains at onVerifyEntry (VerifyContext carries the clamped budget — the P4b program composes it); the journal union is additive (byte-compatible with the human-confirm member); writeLog gains writer "restore" (render-only; existing consumers filter on transport and are unaffected).

Merge is per the standing green directive.

… → commit, kill matrix, F2)

The surviving test from the interrupted cast — 740 lines, never green.
It pins the two-phase engine contract exactly: the pre-stage snapshot
semantics, the transient/structural verify split, the KillHookRegistry
kill matrix (pre-commit / mid-commit × stop/throw/crash), the journaled
commit-pending state + FakeClock cap + local force-resolve, the
verify-entry serialization seam (P4a-3's drain point), and F2's
zero-non-atomic-mode-switches assertion across the full matrix.
Currently red: ModeTransitionEngine does not exist (25/25 failing).
…stage → verify → commit, journalled + rollback-able)

Additive evolution of the #1069/#1070 attach_state surface — the engine
grows ON the ModeMachine, no renames or removals:

- ModeTransitionEngine: stage (pre-stage snapshot of mode-scoped local
  state BEFORE any state writes; staging writes only the journal entry) →
  verify (injectable attach test, budget clamped to the #1034 60s floor,
  deadline armed before the first await so the FakeClock drives it with
  zero wall-clock waits) → commit (the only mode writer, through the
  machine's human-confirm path; the commit-pending journal record is
  written BEFORE any state write so a kill mid-commit leaves the
  transition journaled, never torn).
- Transient verify-fail → staged with the snapshot retained, re-verify
  needs no fresh confirm (the human confirmed the proposal, not the
  timing); structural verify-fail → automatic rollback to the pre-stage
  snapshot (local-only, idempotent, hub-side untouched — invariant 1's
  sole automatic action, restores never advances).
- KillHookRegistry boundaries pre-commit/mid-commit; process-death
  recovery from the MODE journal (a fresh engine resumes a journaled
  commit-pending); the wall-clock cap surfaces the abort/resume choice
  confirmable locally + a local force-resolve verb; commit-pending is a
  mode-JOURNAL state rendered as a labeled badge — never a posture
  value (invariant 7, ADR-0005 vocabulary law).
- The P4a-3 serialization seam: the onVerifyEntry hook fires before the
  attach test, once per verify attempt — the future proposal queue
  drains there. No queue in this slice.
- Machine additions: appendJournal (the engine's journal seam),
  restoreModeScoped (the restore path; refuses posture, keeps the
  legacy dual-write honest, compares the RESOLVED mode so restoration
  materializes nothing); ModeJournalEntry extended to the transition
  vocabulary; writeLog writer union extended with 'restore'.
- Budgets injectable (verify budget, pending cap), clock injectable;
  base defaults ship (60s floor + 5min cap).

mode_transition_engine.test.ts: 25/25 green; the #1069 machine +
classifier suites untouched and green (37); typecheck clean; full suite
at baseline parity (9 known environmental failures, family unchanged).
@aarontrowbridge aarontrowbridge added the hitl Needs human review before merge label Sep 13, 2026
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 8b55c869-a62c-4c0b-b952-25bf63090e0b


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hitl Needs human review before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant