6 Commits
Author SHA1 Message Date
v-openrig-build ab6b0f95cd fix(testbed): committed real StagingDocker invoker — the L6 tar-hang cause dies [NOT guard-accepted]
Founder-ruled engine-independent leg (2026-08-21 ~18:40Z; containers land 5.3,
this fix lands now). Root cause on record in the scoping report: the L6 runbook's
inline execFile invoker predated the stdinFrom tar-pipe contract (9d2f1f2cc) and
ignored it — an open never-fed pipe stdin left the in-container `tar -xf -`
blocked on read forever (row 42576855, the 08-12 seven-minute hang).

makeRealStagingDocker implements the contract as committed, tested code:
- no stdinFrom → stdin IGNORED (closed), so a stdin-reading child terminates on
  EOF (the hang class dies even on a misused step);
- stdinFrom → two processes, producer piped into consumer with EOF forwarded,
  DUAL-EXIT checked (a failing tar into a succeeding exec no longer masks an
  empty stage), EPIPE on the wiring swallowed and the producer's death reported;
- consumer-exits-early → producer killed and the step FAILS named (this
  invoker's own first cut hung exactly there — the producer's stdout backed up
  against a pipe nobody drained; the RED test caught it at 20s);
- every step bounded by a timeout (default 120s) that kills both and returns a
  NAMED failure, never an unbounded wait.

The runbook now IMPORTS the helper — an invoker inlined in a document drifts
behind the code contract with nothing to fail. 8/8 hermetic (sh stand-ins, no
engine) + 19/19 adjacent container suites; typecheck clean. NO live containment
claim: the real-engine proof (docker vs Apple container exec-stdin semantics)
explicitly waits on the 5.3 engine-substrate ruling, and the helper's docs say so.

Claude-Session: https://claude.ai/code/session_01DWhNDDavuNoFWK8ZrYtSdH
(cherry picked from commit 1f6d82c71df86d8fba149c0cfc99d18bc003baaa)
2026-08-22 10:11:58 -07:00
v-openrig-buildandClaude Fable 5 3dffc83598 fix(testbed): L5 — adopt-by-read identity, current host-add grammar, L3 bind+bearer carried; L6 drives a committed scenario
L1-L4 are green (operator receipt 814cd300); L5 failed on four components,
three of which are runbook-vs-shipped-surface staleness and are fixed here.

(1) IDENTITY — adopt-by-read (PM ruling): the runbook now CAPTURES each
    container's minted selfHostId from its own /healthz (server.ts:589) and
    uses that value verbatim, at every provisioning. It never predicts,
    hardcodes, or derives an id. OPENRIG_SELF_HOST_ID was never a knob:
    entrypoint.sh:8 only echoes it for docker logs and nothing in the daemon
    reads it — the runbook no longer sets it.
(2) HOST-ADD GRAMMAR: "rig host add B --url ..." -> the shipped form
    (host.ts:427-433) with required --id/--transport, --url for http, and a
    bearer POINTER (--bearer-env NAME), never a token value.
(3) BIND + BEARER CARRIED FROM L3 verbatim: L5 needs cross-container HTTP, so
    each daemon binds 0.0.0.0 with OPENRIG_AUTH_BEARER_TOKEN set — satisfying
    assertBindAuthInvariant (auth-bearer-token.ts:240-264), never weakening it.
(4) whoami-without-seat is left as a captured product observation with a note,
    not worked around; the daemon-surface read is the shipped substitute.

L6: the literal scenario-collision.yaml DOES NOT EXIST in the tree (operator
caught it at re-entry). L6 proves the container RUNNER, so it now drives the
committed scenario-02-baton.yaml in both container- and host-mode, with
L6_SCENARIO documented as an override that must also name a COMMITTED file.
Authoring a real cross-host collision scenario is a separate scenario-
authoring item, not an improvisation inside a runbook.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-07 19:22:34 +00:00
v-openrig-buildandClaude Fable 5 8c5cd5b78b fix(testbed): L3.2 — align to the staged payload path + pass rig up an explicit source
L3.1 passed on the published-daemon procedure (operator receipt dfcdd798:
published healthz green, authenticated transport routed 404-not-401,
unauthenticated negative 401, sqlite container-local). L3.2 then RED:
"missing required argument 'source'".

Two causes, both in the runbook, both now fixed:
- STAGED PATH: the build verb preserves each census entry's full
  repo-relative path (build-testbed-image.sh:51-52) and the Dockerfile copies
  that context dir wholesale (Dockerfile:52), so the trio lands in-container
  at /opt/openrig-testbed/stub-assets/docker/testbed/stub-assets/ — not the
  stub-assets root the runbook assumed. The nesting is load-bearing: the same
  relative paths are hashed into the byte-reproducible census receipt
  (build-testbed-image.sh:101), so flattening the staging would change receipt
  bytes r1 verified. The RUNBOOK adapts; the staging stands.
- EXPLICIT SOURCE: rig up takes a required positional (up.ts:77); a bare
  invocation is the exact error the operator hit. Now "rig up rig.yaml".

Added a pre-flight fence: assert rig.yaml at the staged path and fail LOUD
naming it (plus a payload listing) rather than surfacing as a downstream
argument error, so a future staging change is self-diagnosing.

PROVENANCE: this failure was PREDICTED and scoped to this runbook by
dev-driver, endorsed in 51-04-STUB-ASSET-TRIO-REVIEW-VERDICT-review-r1.md
(sha-16 5d4a8bb1d0a1605b, "Honest forward-flag") — the note never reached the
runbook text until now; it is quoted in place so it cannot be lost again.
SIBLING SWEEP (desk ask): L4/L5/L6 reference neither the staged payload, nor
rig up, nor the health probe — both defects were L3-only.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-07 19:22:34 +00:00
v-openrig-buildandClaude Fable 5 6bda50d415 fix(testbed): L3 runbook — the published-daemon procedure (control arm can pass again)
The A/B at pin feeda3a80 was BLOCKED-BEFORE-MEASUREMENT: L3 failed on BOTH
arms (Docker curl 52, Apple curl 56) because the runbook started a
LOOPBACK-BOUND daemon and probed it through a published port — unreachable by
construction, on any runtime. A control that cannot pass cannot judge the
variable arm, so no verdict was produced (correct) and thresholds stayed
unengaged. Operator receipt: Q2-AB-feeda3a80-20260807-host/AB-RESULT.md
sha256 3433e95f427828ce.

Four coupled mechanics, all grounded at source, IDENTICAL on both arms:
(a) explicit bind OPENRIG_HOST=0.0.0.0 (index.ts:148 reads it; unset defaults
    to 127.0.0.1 at :167 — that default is the whole defect);
(b) OPENRIG_AUTH_BEARER_TOKEN set, because assertBindAuthInvariant
    (auth-bearer-token.ts:240-264) REFUSES to start a non-loopback bind
    without it — the procedure SATISFIES the guard, never weakens or
    special-cases it (PM rail: build the missing piece, never soften the gate);
(c) an explicit host port, never 0 — Apple 1.2.0 rejects ephemeral publish
    where Docker accepts it (captured parity difference, not an A/B variable);
(d) two probes, two distinct facts, both recorded.

VERIFIED-AT-SOURCE CORRECTION to the dispatched shape: /healthz is registered
directly on the app (server.ts:574) and is UNAUTHENTICATED — there is no
global auth middleware (the app.use("*") at :441 only injects deps) and the
bearer middleware is mounted inside exactly six route modules. So the health
probe does NOT 401 without a header and must not be scored on one. The bearer
is proven separately on a genuinely guarded surface (/api/transport/*, whose
router guards "*" at transport.ts:9), plus a NEGATIVE CONTROL asserting the
same call without a bearer IS 401 — otherwise the runbook would claim to test
an auth path it never exercised.

Acceptance gate written into the runbook: prove the DOCKER BASELINE GREEN
under this exact text before any A/B rerun is requested.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-07 19:22:34 +00:00
v-openrig-buildandClaude Opus 4.8 fc4bfa4dd6 docs(testbed): 51-04 step-3 L6 container-runner e2e runbook (host-side, real docker)
L6 is the live proof of the step-3 mechanism the VM units injected: the runner's
container-mode drives a REAL scenario through the testbed image BY MANIFEST
IDENTITY (host-side reads -> published container port), stamps the manifest id
onto every ledger row, and carries two riders:
- L6.2 host-mode PARITY: the same scenario in host-mode must match container-mode
  (the additive-opt-in fence, proven behaviorally where the VM units prove it
  structurally; a divergence is a PM 51-02 gate, not a fold).
- L6.3 the L4 fail-closed STEP-3 FORM: a foreign OPENRIG_URL hard-refuses before
  any container is created (the container is not an excuse to weaken the guard).

Wiring composes only already-tested helpers (spawnContainerDaemon, runScenarioFile,
withImageId); the only new-at-runtime piece is the real execFile docker invoker
(the container analogue of runRig). README index + sequencing updated (L6 after
L3); L4's grounding note now names L6.3 as its step-3 form.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-06 05:23:49 +00:00
v-openrig-buildandClaude Opus 4.8 49a8e6b588 docs(testbed): 51-04 step-2 L0-L5 verification runbooks (host-side, evidence-defined)
Step-2 (plan §2): the L1-L4 live legs authored as host-executed, evidence-defined
runbooks (the 51-09 live-legs pattern; the VM seat has no runtime per the locus ruling).
Each leg = exact commands + real-byte capture + PASS/FAIL verdict-on-bytes (never from
memory) + hashed evidence dir (census-receipt discipline):
- L0 resolve inputs (base-image digest + stub-assets.list census — the two host-resolved slots)
- L1 PTY allocation (docker run -t + tmux capture real bytes + resize propagation)
- L2 tmux server lifecycle (detach-survive, multi-pane, exact send-keys/capture round-trip)
- L3 daemon-in-container (container-local sqlite, healthz on published port, rig up stub
  topology settles — where stub-assets.list is grounded/finalized; zero tokens)
- L4 hermetic fail-closed (foreign OPENRIG_URL hard-refused INSIDE the container; grounding
  note flags the packaged-vs-runner-mode form honestly)
- L5 multi-host (N named self-hosts on one docker network via the host registry, plan §3)
  + the 51-09 live-leg RIDER: executes the shipped collision/alignment RUNBOOK.md (LEG A
  cross-host triple, LEG B founder-collision) against the testbed containers in one session.
Fences carried on every leg: no real-HOME mounts, network-only shared surface, scenarios
byte-unchanged, docker-gap-watch routes to PM.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-06 05:23:49 +00:00