step.26.txt -- b3ubot U5.4a: the foreign-workspace arc -- b3ubot
               drives a COMPLETE UoW in a repo that is NOT itself
               (real git closure in the foreign repo; a [T3:validate]
               verdict from the plane inside the pass -- the
               pre-trinity rehearsal), plus THE ENGINE-STATE
               WORKSPACE GUARD: records stamped with their project,
               cross-workspace invocations refused loudly. The UoW
               row itself entered the ledger through the engine's own
               `ledger add-row` -- the amendment path, dogfooded.

Step:     26
Parent:   end_to_end.md §7 P5 -- U5.4a, ADDED BY AMENDMENT (the (p1)
          re-scope, step.51-of-b3u's header + this repo's memory
          NEXT-chain): everything before step.26 ran on toy/tmp
          workspaces or this repo's own ledger; (p2) is b3ubot
          managing ANOTHER project (the xml repo), so the foreign
          shape needs proving BEFORE U5.5 stakes the trinity gate on
          it. design §2.6 (the workspace manager: "clean tree per
          closed UoW"), §2.9 (deployment), §4 (the state machine
          whose records this step hardens).
UoW:      U5.4a -- fifth P5 UoW (by amendment); U5.5 (the trinity
          gate) is the last.
Date:     2026-07-22
Origin:   User directive 2026-07-22: "go with U5.4a, step file and
          its execution" (the (p1) sequence).

## (0) Status

EXECUTED same-session. The guard: EngineStateRecord gains a
`workspace` stamp (resolved root at start_state; None-tolerant for
pre-step.26 records) and load_state gains `expect_workspace` --
threaded through all five stage entries (draft/ratify/execute/
verify/close), so a stage invoked from the WRONG project refuses
loudly naming both roots and the per-project `$B3UBOT_HOME` remedy.
The proof: a foreign git project (own files, own ledger, own
history) driven through the complete arc with a [T0:py_compile] +
[T3:validate] gate set -- closure landed TWO real commits in the
FOREIGN repo (work + gdiff), tree clean, b3ubot's own tree untouched
byte-for-byte; live twin took its T3 verdict from the launcher-
booted b3u (p5_gates 14/14). The U5.4a ledger row was inserted by
`b3ubot ledger add-row --after U5.4` -- the machinery placed it
correctly past U5.4's note block (rehearsed on a copy first), and
the table round-trips. Suite 632 passed + 25 skipped = 657 collected
(measured after the last edit -- the step.25 rule, held; the
first-draft claim of 658 was a hand-sum caught by the measure,
same-day rule enforcement).

## (1) Ground truth probed at drafting (outputs -> named writers)

    CONFIRMED the M1 arc already closes in tmp GIT workspaces
              (tests/test_m1_arc.py: git-init fixture, MockAdapter,
              5 stages to CLOSED with real commits) -- "foreign" in
              the tmp sense was NOT the gap.
    CONFIRMED the REAL gap: EngineStateRecord carried NO workspace
              field -- records keyed by UoW id alone in one shared
              data home. Project B invoking `verify U1.1` would
              silently read project A's record: the exact (p2)
              multi-project hazard (b3ubot's ledger + the xml
              project + any scratch coexisting under ~/.b3ubot).
    CONFIRMED the five stage entry points each do `record =
              load_state(uow, home)` with a workspace_root/root in
              scope (drafting also owns the start_state call) -- the
              guard threads through exactly five call sites; the
              in-function re-loads (ratify's post-decision reads)
              sit behind the entry check and need nothing.
    CONFIRMED `ledger add-row` takes --phase/--after and the U5.4
              row now carries note lines -- placement after a
              noted row REHEARSED on a copy of end_to_end.md before
              touching the real one (the machinery handled it:
              inserted after the notes, before U5.5).

## (2) What U5.4a delivers

  2.1 **The workspace stamp** (persistence.py): EngineStateRecord.
      workspace (resolved root, stamped at start_state; None for
      pre-step.26 records and workspace-less callers -- back-compat,
      the guard never guesses); from_dict tolerant.
  2.2 **The cross-workspace guard**: load_state(...,
      expect_workspace=) -- a stamped record invoked from a
      different root raises OrchestratorError naming BOTH roots and
      the remedy (run from the recorded workspace; per-project
      $B3UBOT_HOME for same-named UoWs). Threaded through the five
      stage entries; drafting's start_state stamps.
  2.3 **The foreign-arc proof** (tests/test_foreign_workspace.py, 7
      hermetic): the complete arc in a foreign git project with
      T0+T3 gates (replay plane) -- CLOSED, exactly +2 commits in
      the foreign repo, evidence files there, tree clean, b3ubot's
      own porcelain byte-identical before/after; the T3 verdict row
      names the foreign UoW; the guard's refusal (both roots + the
      remedy in the message), the legacy-record pass, and the
      one-home-one-idspace start_state refusal.
  2.4 **The live twin** (tests/test_foreign_workspace_live.py, 1):
      the same arc with the [T3:validate] verdict from the REAL
      launcher-booted b3u -- meta.sgr applied in the foreign repo,
      closure commits landed, verdict row recorded. The
      pre-trinity rehearsal, live.
  2.5 **The amendment, dogfooded**: the U5.4a ledger row inserted
      via `b3ubot ledger add-row --uow U5.4a --phase 5 --after
      U5.4 --status active --step step.26` on the REAL
      end_to_end.md (rehearsed on a copy first); closure will use
      `ledger close` the same way. The §7 P5 phase table row added
      with the amendment provenance named.
  2.6 **p5_gates.sh extended**: live 13 -> 14 legs; hermetic twin
      56+13 -> 63+14; summary U5.1-U5.4a.
  2.7 **Docs**: design §2.8-chain U5.4a note; tests/testing.md
      re-measured same-commit.

EXPLICITLY OUT OF SCOPE (each a named later UoW, never silent):
  - the trinity gate itself (human adapter live leg; Q-25-A
    delivery placement)                                          -> U5.5
  - per-workspace data-home NAMESPACING (state under
    <home>/orchestrator/<workspace-hash>/...) -- the guard turns
    the hazard loud; namespacing is the richer future if (p2)
    ever needs same-named UoWs across projects under ONE home
    (the named remedy today: per-project $B3UBOT_HOME)           -> later
  - stamping legacy records retroactively (they pass unguarded;
    rewriting history the engine did not witness would be a lie) -> never

## (3) Verification gates

    G1  THE GUARD: a stamped record + a wrong-root stage call
        refuses loudly with both roots and the $B3UBOT_HOME remedy
        in the message; an unstamped (legacy) record passes; a
        same-id second-project start_state refuses (one home = one
        id-space) -- hermetic green.
    G2  THE FOREIGN ARC (hermetic): complete arc in a foreign git
        repo, T0+T3 (replay) gates green, CLOSED, +2 commits THERE,
        evidence files THERE, foreign tree clean, b3ubot tree
        untouched byte-for-byte.
    G3  LIVE: the same foreign arc with the T3 verdict from the
        real plane -- p5_gates 14/14 live legs, hermetic twin
        63 passed + 14 SKIPPED, B-4 grep clean.
    G4  THE AMENDMENT: the U5.4a row entered the real ledger via
        the engine's own add-row (placement past U5.4's note block
        correct), and the table suites stay green.
    G5  NO REGRESSION: full suite 632 passed + 25 skipped = 657
        collected, 0 failed -- measured AFTER the last edit (the
        step.25 rule, held).
    G6  Hygiene: porcelain after the WORK commit; testing.md moved
        with the tests; ledger + design current; gdiff via
        ../scripts/step_gdiff.

## (4) LOCKs

    C-26-A  b3ubot porcelain clean after each commit.
    C-26-B  ONE HOME = ONE ID-SPACE: a UoW id names ONE project's
            work within a data home; the guard turns cross-project
            confusion into a loud refusal, and the multi-project
            answer is per-project $B3UBOT_HOME -- never a silent
            best-guess about which project a record "probably"
            belongs to.
    C-26-C  THE GUARD NEVER GUESSES: unstamped records pass (the
            engine refuses to invent provenance it did not record);
            only a POSITIVE mismatch of two known roots refuses.
    C-26-D  FOREIGN MEANS FOREIGN: the proof arc asserts b3ubot's
            own tree is byte-identical before/after -- managing
            another project must leave not one dropping here.
    C-26-E  standing locks inherited: C-25-B/C/D (trail delivery,
            oracles-gate-arcs-assist, exit codes), C-24-B (client
            posture), C-23-B/C, B-1/B-3/B-4.

## (5) Open questions

    Q-26-A  Guard placement (DECIDED: expect_workspace on load_state
            at the FIVE stage entries -- one guard at the resume
            primitive, visible in every stage's own code; a
            decorator/wrapper was REJECTED as hiding a refusal that
            should read inline; guarding advance_state too was
            REJECTED for V0: every advance follows an entry-guarded
            load in real flows, and double-guarding adds noise).
    Q-26-B  Amendment mechanics (DECIDED: the engine's own
            `ledger add-row`, rehearsed on a copy first because
            U5.4's row carries note lines -- the machinery placed
            it correctly, which is itself U2.3-era code earning its
            keep on its own repo; hand-editing the table was
            REJECTED after step.25's Q-25-C burned exactly that
            path).
    Q-26-C  Arc-vehicle for the proof (DECIDED: per-stage calls
            with a hand-placed skeleton + StaticProposalAdapter --
            the T3-bearing gate set needs a skeleton the mock
            drafter does not script; run_uow_arc's draft stage is
            already M1-proven, so composing stages loses nothing
            and arranges everything).

## (6) Acceptance

    - [x] Workspace stamp + guard + five-entry threading (2.1, 2.2,
          G1); legacy pass + start_state refusal covered.
    - [x] Foreign-arc proof, hermetic 7/7 (2.3, G2).
    - [x] Live twin green; p5_gates 14/14; twin 63+14 (2.4, 2.6,
          G3).
    - [x] U5.4a row via the engine's own add-row; table suites
          green (2.5, G4).
    - [x] Full suite 632 passed + 25 skipped = 657 collected, 0
          failed, measured after the last edit (G5).
    - [x] Docs moved in the same commit (2.7, G6).

## (7) Hash backfill

    WORK commit (everything above + this file):   (this commit)
    GDIFF commit (step.26.gdiff.txt):             (HEAD)

End of step.
