Edit

<<<<<<< HEAD
| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |
=======
| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (`gh pr update-branch` onto post-#15 main → green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 6 | `spt-term tests/stream.rs:49 bounded_backpressure_stalls_an_unconsumed_reader` | 1× hfenduleam Windows (2026-06-15 run 27545857429, PR #14 stale-base CI) | asserts an unconsumed bounded stream stalls the writer-side reader; got `16 -> 82` (reader advanced past the bound) — a timing-sensitive backpressure assert racing the bound check under hosted-runner load. Subsystem disjoint from PR #14 (digest-proof CLI) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (green both runners). Not hardened; a 2nd occurrence upgrades to harden |
>>>>>>> origin/main

| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (`gh pr update-branch` onto post-#15 main → green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 6 | `spt-term tests/stream.rs:49 bounded_backpressure_stalls_an_unconsumed_reader` | 1× hfenduleam Windows (2026-06-15 run 27545857429, PR #14 stale-base CI) | asserts an unconsumed bounded stream stalls the writer-side reader; got `16 -> 82` (reader advanced past the bound) — a timing-sensitive backpressure assert racing the bound check under hosted-runner load. Subsystem disjoint from PR #14 (digest-proof CLI) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |


The file C:\Users\decid\Documents\projects\spt-core\docs\FLAKE-LEDGER.md has been updated successfully. (file state is current in your context — no need to Read it back)

| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (`gh pr update-branch` onto post-#15 main → green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 6 | `spt-term tests/stream.rs:49 bounded_backpressure_stalls_an_unconsumed_reader` | 1× hfenduleam Windows (2026-06-15 run 27545857429, PR #14 stale-base CI) | asserts an unconsumed bounded stream stalls the writer-side reader; got `16 -> 82` (reader advanced past the bound) — a timing-sensitive backpressure assert racing the bound check under hosted-runner load. Subsystem disjoint from PR #14 (digest-proof CLI) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |

# Flake ledger

Tests that have failed nondeterministically, with mechanism notes and hardening
status. Discipline: **any flake gets an entry, not a shrug** — a second
occurrence upgrades the entry from "observed" to "harden it." Rig flakes from
the two-host ladder (docs/TWO-HOST-RUNBOOK.md) land here too.

| # | test | occurrences | mechanism | status |
|---|------|-------------|-----------|--------|
| 1 | `spt-daemon brain_restart_survives_gaplessly` | 1× hosted Windows (2026-06-03, hung) | ConPTY stall under hosted-runner load | job `timeout-minutes` bounds it; not seen self-hosted |
| 2 | `spt-term digest_parses_a_real_pty_stream` | 3× gravity (2026-06-03; 2026-06-04 runs 26957386850, 26966738231) | sprint-collapse assert (`digest.rs`) — PTY input echo and the child's write are **concurrent writers** into the master-read stream; back-to-back `send_line`s let a later line's echo interleave mid-`Write(…)`, breaking the tool regex for that turn | HARDENED (2026-06-04): sends serialized — each line pumped back (adaptive echo-copy count, 2s/line bound) before the next, so no concurrent writer races an earlier line; failure asserts now print the raw transcript |
| 3 | `spt-daemon sync.rs torn_pull` + `concurrent_writes` | 1× (2026-06-03) | `wait_for_stream` deadline too tight under load | HARDENED: `wait_for_stream_except` 2s→10s; no recurrence |
| 4 | `spt-live context::write_context_suppresses_stale_llm` | 1× gravity (2026-06-04 run 26967808321) | test calls `write_context` (reads SPT_HOME node identity + epoch stamp) WITHOUT the `with_home` lock — a parallel `with_home` test's env swap + temp-dir teardown races the stamp's epoch write into a deleted home (`NotFound`) | HARDENED (2026-06-04): test wrapped in `with_home` — serializes on the home lock like every other SPT_HOME-touching test |
<<<<<<< HEAD
| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |
=======
| 5 | `spt-daemon attach.rs:600` re-serve (`output gap: got seq 1 want 0`) | 1× kitsubito Linux (2026-06-15 run 27545857429, PR #14 stale-base CI) | re-serve replays the buffered output stream on re-attach; under load the new subscriber observed `seq 1` before `seq 0` — an ordering/timing gap in the re-serve sequence assert, not a content error. Subsystem disjoint from the triggering change (PR #14 = digest-proof CLI key-fill, cannot touch attach sequencing) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (`gh pr update-branch` onto post-#15 main → green both runners). Not hardened; a 2nd occurrence upgrades to harden |
| 6 | `spt-term tests/stream.rs:49 bounded_backpressure_stalls_an_unconsumed_reader` | 1× hfenduleam Windows (2026-06-15 run 27545857429, PR #14 stale-base CI) | asserts an unconsumed bounded stream stalls the writer-side reader; got `16 -> 82` (reader advanced past the bound) — a timing-sensitive backpressure assert racing the bound check under hosted-runner load. Subsystem disjoint from PR #14 (digest-proof CLI) | OBSERVED 1×: did NOT reproduce on the clean-base rerun (green both runners). Not hardened; a 2nd occurrence upgrades to harden |
>>>>>>> origin/main

-| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |

+| 7 | `spt-daemon::attach attach_survives_target_brain_restart_exactly_once` | 1× kitsubito (2026-06-16 run 27595180782 attempt 1, M11-W3) | **TIMEOUT, not an assertion fail** — SLOW>60>120>180>TERMINATING, nextest TIMEOUT at the 240s cap (`attach.rs:600`). A heavy multi-process brain-restart-exactly-once test on the slow shared kitsubito box under concurrent load (n1-gate on the same runner + a parallel `ci.yml` docs-publish run firing simultaneously). W3-INDEPENDENT: Windows green end-to-end; ALL tunnel tests passed fast on Linux too (tunnel_e2e 0.941s, loopback_tunnel_backpressures_without_loss 1.091s) — a logic bug fails both runners deterministically, this timed out on one under load | observed; cleared by `gh run rerun --failed` (builds reused, no re-tag). Harden if it recurs — candidate: raise this test's per-test nextest timeout, or serialize it off the concurrent n1-gate slot |


FLAKE-LEDGER resolved. Scan for any residual markers, then regenerate derived files (Cargo.lock via build, reference.md via xtask gen).


Bash

echo "=== residual conflict markers? ==="; git grep -n "^<<<<<<<\|^>>>>>>>\|^=======" -- . ':(exclude)docs/FLAKE-LEDGER.md' 2>/dev/null | head; echo "(empty = none)"
echo "=== unmerged paths ==="; git diff --name-only --diff-filter=U
echo "=== workspace version now ==="; grep -m1 "^version" Cargo.toml 2>/dev/null; grep -m1 'version' Cargo.toml | head -1

Check residual markers + version


=== residual conflict markers? ===
(empty = none)
=== unmerged paths ===
docs/FLAKE-LEDGER.md
=== workspace version now ===
version = "0.7.3"
# M6-D6 (grill decision 9): ONE shared version for every crate and the binary


Version 0.7.3 came in clean. Stage resolved file, build (settles Cargo.lock), regen reference.md.


git add docs/FLAKE-LEDGER.md
echo "=== build (settles Cargo.lock, compiles merged cli.rs) ==="
cargo build -p spt 2>&1 | tail -8

Stage resolved file + build


=== build (settles Cargo.lock, compiles merged cli.rs) ===
   Compiling hickory-resolver v0.26.0-beta.4
   Compiling noq v0.18.0
   Compiling iroh-relay v0.98.0
   Compiling iroh v0.98.2
   Compiling spt-net v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
   Compiling spt-daemon v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling spt v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 43.87s


Build clean — merged cli.rs compiles. Regen reference.md (CLI changed via merge), then drift + traceable + re-run W4 tests.


PowerShell

cargo run -p xtask -- gen 2>&1 | Select-Object -Last 5
"=== git status after gen ==="
git status --short
"=== xtask check ==="
cargo run -p xtask -- check 2>&1 | Select-Object -Last 4
"=== traceable ==="
traceable-reqs check 2>&1 | Select-Object -Last 2; "TRACE_EXIT=$LASTEXITCODE"

Regen docs + drift check + traceable


   Compiling spt-daemon v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
   Compiling xtask v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 15.36s
     Running `target\debug\xtask.exe gen`
xtask gen: wrote C:\Users\decid\Documents\projects\spt-core\docs-site/src/cli/reference.md
=== git status after gen ===
M  CHANGELOG.md
M  CONTEXT.md
M  Cargo.lock
M  Cargo.toml
A  DAEMON-LIFECYCLE-COMPLETION-PLAN.md
M  crates/spt/src/cli.rs
M  docs-site/llms.txt
M  docs-site/src/SUMMARY.md
MM docs-site/src/cli/reference.md
M  docs-site/src/harness-contract/api.md
M  docs-site/src/harness-contract/install-on-demand.md
M  docs-site/src/harness-contract/integration-checklist.md
M  docs-site/src/harness-contract/manifest.md
A  docs-site/src/harness-contract/patterns.md
M  docs-site/src/lifecycle/overview.md
M  docs-site/src/reference/install.md
M  docs/FLAKE-LEDGER.md
M  traceable-reqs.toml
=== xtask check ===
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
     Running `target\debug\xtask.exe check`
xtask check: OK
=== traceable ===
  [OK] REQ-WHOAMI-1  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `spt whoami` is a thin ALIAS for `spt endpoint list` (full output: the SELF pin + the subnet roster) — the standalone bare-id command is dropped (the `id=$(spt whoami)` capture was never a real pattern: env vars don't persist between agent tool calls). The one new render: the `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state. whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9).
TRACE_EXIT=0


xtask + traceable green. reference.md regenerated (stage it). Re-run W4 unit + E2E post-merge to confirm intact.


Bash

git add docs-site/src/cli/reference.md
echo "=== unit ==="; cargo test -p spt-daemon --lib gateway_typed_owner_resolves_and_drives_keyed_on_id_not_type 2>&1 | grep -E "test result|FAILED"
echo "=== e2e ==="; cargo test -p spt --test gateway_owner_shell_e2e 2>&1 | grep -E "test result|FAILED|panicked"

Stage reference.md + re-run W4 tests post-merge


warning: in the working copy of 'docs-site/src/cli/reference.md', LF will be replaced by CRLF the next time Git touches it
=== unit ===
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 309 filtered out; finished in 0.06s
=== e2e ===
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.35s


Both W4 tests green post-merge. Final pre-flight: clippy workspace (merge brought new code).


PowerShell

cargo clippy --workspace --all-targets -- -D warnings 2>&1 | Select-Object -Last 6; "CLIPPY_EXIT=$LASTEXITCODE"

Workspace clippy deny-warnings post-merge


    Checking spt-net v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-net)
    Checking spt-live v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-live)
    Checking spt-daemon v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon)
    Checking spt v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\spt)
    Checking xtask v0.7.3 (C:\Users\decid\Documents\projects\spt-core\crates\xtask)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 13.67s
CLIPPY_EXIT=0


All pre-flight green. Commit the merge + push (resolves mergeability → CI fires).


Bash

git status --short | grep -E "^(UU|AA|DD|.U|U.)" && echo "UNMERGED REMAIN" || echo "no unmerged"
git commit --no-edit 2>&1 | tail -3
echo "=== HEAD ==="; git rev-parse --short HEAD
git push origin m11-shell-substrate 2>&1 | tail -3

Commit merge + push


no unmerged
[m11-shell-substrate 63a5d3e] Merge remote-tracking branch 'origin/main' into m11-shell-substrate
=== HEAD ===
63a5d3e
To https://github.com/SaberMage/spt-core.git
   c4dbeb4..63a5d3e  m11-shell-substrate -> m11-shell-substrate


Merge pushed (HEAD `63a5d3e`). Verify mergeability resolved + CI now fires.


sleep 10
echo "=== mergeable now ==="; gh pr view 16 --json mergeable,mergeStateStatus,headRefOid 2>&1
echo "=== ci run for 63a5d3e ==="; gh api "repos/SaberMage/spt-core/actions/runs?head_sha=$(git rev-parse HEAD)" --jq '.workflow_runs[] | "\(.id) \(.status) \(.event) \(.name)"' 2>&1
echo "(empty = lag)"

Check mergeable + CI run for merge head


=== mergeable now ===
{"headRefOid":"63a5d3e629048b41b2e009eb0f46df41766cebfa","mergeStateStatus":"UNSTABLE","mergeable":"MERGEABLE"}
=== ci run for 63a5d3e ===
27599789430 queued pull_request ci
(empty = lag)


Fixed — `mergeable=MERGEABLE`, CI run **27599789430 queued** (pull_request) for `63a5d3e`. Root cause cleared. Notify doyle + arm watcher.


$OWL send doyle todlando <<'EOF'
FIXED + CI FIRING. Merged origin/main into the branch — only ONE real conflict (docs/FLAKE-LEDGER.md, resolved as union #5/#6/#7); CONTEXT.md, traceable-reqs.toml, cli.rs (adapter add --release + M11 shell verbs coexist), Cargo.lock all auto-merged clean. Regen'd reference.md via `xtask gen`. Branch now at v0.7.3.

Pre-flight ALL green post-merge: clippy --workspace --all-targets -D warnings = 0, xtask check OK, traceable EXIT=0, BOTH W4 tests pass (unit + gateway_owner_shell_e2e). Merge commit = 63a5d3e (W4 deltas unchanged inside it).

PR #16 now mergeable=MERGEABLE (was UNMERGEABLE — THAT was the real block, GitHub won't create pull_request runs for an unmergeable PR; not a webhook drop). CI run 27599789430 = QUEUED on both runners. Gate HEAD is now 63a5d3e. Watcher armed — ping you on green. attach_survives flake (#7) still in play on kitsubito; rerun --failed if it times out.
EOF
echo "rc=$?"

Notify doyle fix landed + CI queued


➜ SENT:doyle
rc=0


sha=$(git rev-parse HEAD)
for i in $(seq 1 120); do
  line=$(gh api "repos/SaberMage/spt-core/actions/runs?head_sha=$sha" \
         --jq '.workflow_runs[] | select(.name=="ci") | "\(.id) \(.status) \(.conclusion)"' 2>/dev/null)
  if [ -n "$line" ]; then
    st=$(echo "$line" | awk '{print $2}')
    [ "$st" = "completed" ] && { echo "CI DONE: $line"; exit 0; }
    echo "poll $i: ci $st"
  else
    echo "poll $i: no run yet"
  fi
  sleep 25
done
echo "TIMEOUT for $sha"

Watch CI for merge HEAD 63a5d3e until complete


Command running in background with ID: bk1ztyl5c. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\bk1ztyl5c.output. You will be notified when it completes. To check interim output, use Read on that file path.


Watcher `bk1ztyl5c` on the merge HEAD. While CI runs, persist the non-obvious root-cause lesson to memory.


Write

---
name: unmergeable-pr-blocks-ci
description: An unmergeable PR creates ZERO pull_request CI runs — looks like a dropped webhook but is a mergeability block; fix by merging main into the branch
metadata:
  type: feedback
---

If a push to a PR branch produces NO ci run (`gh run list` shows nothing for the
new head, PR `statusCheckRollup` = 0 checks) while runners are online+idle, the
cause is usually that the PR is UNMERGEABLE: GitHub can't compute the merge ref
(`refs/pull/N/merge`) so it creates ZERO `pull_request` workflow runs. It looks
IDENTICAL to a dropped synchronize webhook — don't assume webhook drop.

**Diagnose, in order:**
- `gh pr view <N> --json mergeable,mergeStateStatus` → `UNKNOWN`/`UNSTABLE` + 0 checks is the tell.
- `git fetch origin main; git rev-list --count HEAD..origin/main` → branch behind-count. A feature branch that fell behind during release churn (version bumps, CHANGELOG, traceable, CONTEXT all moved on main) conflicts.
- `git merge-tree $(git merge-base HEAD origin/main) HEAD origin/main | grep -i conflict` → confirms real conflicts.

**Fix:** `git merge origin/main --no-edit`, resolve (append-only docs like FLAKE-LEDGER/traceable/CONTEXT = UNION; Cargo.lock + docs-site/src/cli/reference.md are DERIVED → rebuild + `cargo run -p xtask -- gen`, never hand-merge), full pre-flight, push. Merge → `mergeable=MERGEABLE` → CI fires immediately.

**Why:** burned ~1h on M11-W4 treating it as a webhook drop (empty-commit nudge + close/reopen PR both failed because the PR was still unmergeable). The empty-commit-to-force-synchronize trick (v0.7.1 transient-rerun class) is the right fix for a REAL webhook drop, but it can't fix an unmergeable PR. Check mergeability FIRST.

**How to apply:** when a push yields no CI run, run the 3 diagnose steps before any nudge. Related: [[m11-w4-progress]], [[v071-published]] (transient-rerun handling).


File created successfully at: C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\unmergeable-pr-blocks-ci.md (file state is current in your context — no need to Read it back)

---
name: unmergeable-pr-blocks-ci
description: An unmergeable PR creates ZERO pull_request CI runs — looks like a dropped webhook but is a mergeability block; fix by merging main into the branch
metadata:
  node_type: memory
  type: feedback
  originSessionId: 5b0a69e3-d6a3-47f7-8152-ee14e5b4ff5c
---

If a push to a PR branch produces NO ci run (`gh run list` shows nothing for the
new head, PR `statusCheckRollup` = 0 checks) while runners are online+idle, the
cause is usually that the PR is UNMERGEABLE: GitHub can't compute the merge ref
(`refs/pull/N/merge`) so it creates ZERO `pull_request` workflow runs. It looks
IDENTICAL to a dropped synchronize webhook — don't assume webhook drop.

**Diagnose, in order:**
- `gh pr view <N> --json mergeable,mergeStateStatus` → `UNKNOWN`/`UNSTABLE` + 0 checks is the tell.
- `git fetch origin main; git rev-list --count HEAD..origin/main` → branch behind-count. A feature branch that fell behind during release churn (version bumps, CHANGELOG, traceable, CONTEXT all moved on main) conflicts.
- `git merge-tree $(git merge-base HEAD origin/main) HEAD origin/main | grep -i conflict` → confirms real conflicts.

**Fix:** `git merge origin/main --no-edit`, resolve (append-only docs like FLAKE-LEDGER/traceable/CONTEXT = UNION; Cargo.lock + docs-site/src/cli/reference.md are DERIVED → rebuild + `cargo run -p xtask -- gen`, never hand-merge), full pre-flight, push. Merge → `mergeable=MERGEABLE` → CI fires immediately.

**Why:** burned ~1h on M11-W4 treating it as a webhook drop (empty-commit nudge + close/reopen PR both failed because the PR was still unmergeable). The empty-commit-to-force-synchronize trick (v0.7.1 transient-rerun class) is the right fix for a REAL webhook drop, but it can't fix an unmergeable PR. Check mergeability FIRST.

**How to apply:** when a push yields no CI run, run the 3 diagnose steps before any nudge. Related: [[m11-w4-progress]], [[v071-published]] (transient-rerun handling).


Add the W4 progress memory + MEMORY.md pointers.


---
name: m11-w4-progress
description: M11-W4 Gateway-owner shell capstone (REQ-SHELL-5) — built, merged, awaiting CI green + doyle gate
metadata:
  type: project
---

M11-W4 = REQ-SHELL-5 (shell ownership owner-type-agnostic), the LAST build wave
before W5 (rig + docs). PROOF-NOT-CHANGE: T0 audit found no type-gate on any
ownership path; W4 proves it E2E + activates unit/int. ZERO production diff — held.

**Built (2026-06-15/16):**
- int: `crates/spt/tests/gateway_owner_shell_e2e.rs` — a `state="gateway"` owner
  succeeds on EVERY path same-node (spawn→bind+SHELL_TUNNEL_OPEN→cmd(durable)→
  drive(latest-wins)→tunnel(opaque bytes both dirs)→act-gate(REQ-CONSENT-3 grant
  keyed on the gateway endpoint-id)→relink). NEGATIVE (doyle gate-blocking
  refinement): gateway-B SAME type="gateway" DIFFERENT id refused on cmd/drive/
  tunnel (NO_SHELL/NO_TUNNEL) → exclusivity keys on owner endpoint_id, not type.
  Harness = union of tunnel_e2e + drive_e2e start_inproc_daemon (seed + broker-
  with-net + tunnel ctrl + drive ctrl). relink needs offline → close_shell first.
- unit: `linkhost::gateway_typed_owner_resolves_and_drives_keyed_on_id_not_type`
  (resolve_link_target/run_action resolve+drive gateway owner opaquely; same-type
  different-id → NoShell at resolution layer). NOTE: a pre-existing
  `shellhost::gateway_typed_owner_spawns_and_owns_a_shell` is tagged [unit->REQ-EP-6].
- REQ-SHELL-5 activated [doc,impl,unit,int], traceable EXIT=0.

**doyle GATE: code-read PASS + tree-identity confirmed — W4 PRE-CLEARED.** Only
remaining input = CI green. No further code review needed (he said so).

**CI saga:** branch had fallen 15 behind main (doyle's v0.7.2/v0.7.3 release churn)
→ UNMERGEABLE → GitHub created ZERO pull_request runs (NOT a webhook drop — see
[[unmergeable-pr-blocks-ci]]). FIXED by merging origin/main (1 real conflict:
FLAKE-LEDGER union; rest auto-merged; regen reference.md). Merge HEAD = 63a5d3e
(gate HEAD now). Branch at v0.7.3. PR #16. CI run 27599789430 queued.

**NEXT:** on CI green → ping doyle → he stamps W4 GATE PASS. If attach_survives
times out on kitsubito = FLAKE-LEDGER #7, `gh run rerun <id> --failed`. Then W5
(M11-PLAN T5.x): drive/tunnel [twohost] rungs + how-to subnet (REQ-DOCS-6) +
ADR-0020 tunnel EVENT-exemption enumeration + on-LAN posture docs. Supersedes [[m11-w3-progress]].


File created successfully at: C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\m11-w4-progress.md (file state is current in your context — no need to Read it back)

---
name: m11-w4-progress
description: "M11-W4 Gateway-owner shell capstone (REQ-SHELL-5) — built, merged, awaiting CI green + doyle gate"
metadata:
  node_type: memory
  type: project
  originSessionId: 5b0a69e3-d6a3-47f7-8152-ee14e5b4ff5c
---

M11-W4 = REQ-SHELL-5 (shell ownership owner-type-agnostic), the LAST build wave
before W5 (rig + docs). PROOF-NOT-CHANGE: T0 audit found no type-gate on any
ownership path; W4 proves it E2E + activates unit/int. ZERO production diff — held.

**Built (2026-06-15/16):**
- int: `crates/spt/tests/gateway_owner_shell_e2e.rs` — a `state="gateway"` owner
  succeeds on EVERY path same-node (spawn→bind+SHELL_TUNNEL_OPEN→cmd(durable)→
  drive(latest-wins)→tunnel(opaque bytes both dirs)→act-gate(REQ-CONSENT-3 grant
  keyed on the gateway endpoint-id)→relink). NEGATIVE (doyle gate-blocking
  refinement): gateway-B SAME type="gateway" DIFFERENT id refused on cmd/drive/
  tunnel (NO_SHELL/NO_TUNNEL) → exclusivity keys on owner endpoint_id, not type.
  Harness = union of tunnel_e2e + drive_e2e start_inproc_daemon (seed + broker-
  with-net + tunnel ctrl + drive ctrl). relink needs offline → close_shell first.
- unit: `linkhost::gateway_typed_owner_resolves_and_drives_keyed_on_id_not_type`
  (resolve_link_target/run_action resolve+drive gateway owner opaquely; same-type
  different-id → NoShell at resolution layer). NOTE: a pre-existing
  `shellhost::gateway_typed_owner_spawns_and_owns_a_shell` is tagged [unit->REQ-EP-6].
- REQ-SHELL-5 activated [doc,impl,unit,int], traceable EXIT=0.

**doyle GATE: code-read PASS + tree-identity confirmed — W4 PRE-CLEARED.** Only
remaining input = CI green. No further code review needed (he said so).

**CI saga:** branch had fallen 15 behind main (doyle's v0.7.2/v0.7.3 release churn)
→ UNMERGEABLE → GitHub created ZERO pull_request runs (NOT a webhook drop — see
[[unmergeable-pr-blocks-ci]]). FIXED by merging origin/main (1 real conflict:
FLAKE-LEDGER union; rest auto-merged; regen reference.md). Merge HEAD = 63a5d3e
(gate HEAD now). Branch at v0.7.3. PR #16. CI run 27599789430 queued.

**NEXT:** on CI green → ping doyle → he stamps W4 GATE PASS. If attach_survives
times out on kitsubito = FLAKE-LEDGER #7, `gh run rerun <id> --failed`. Then W5
(M11-PLAN T5.x): drive/tunnel [twohost] rungs + how-to subnet (REQ-DOCS-6) +
ADR-0020 tunnel EVENT-exemption enumeration + on-LAN posture docs. Supersedes [[m11-w3-progress]].


1	- [pre-flight clippy --workspace](ci-clippy-preflight-workspace.md) — CI denies warnings workspace-wide (`clippy --workspace --all-targets -D warnings`); a `-p <crate>` local check misses sibling-crate lints → CI red round-trip (M11-W1: `-p spt` missed a `useless_vec` in spt-daemon's act-gate test). Pre-flight the exact CI invocation. CI only fires on PR (push branches=main/dev-freeform)
2	- [don't wait — proceed autonomously](dont-wait-proceed-autonomously.md) — operator 2026-06-14: don't stop to ask permission between steps; chain next action (ping/commit gated batch/next wave). After doyle W4 gate-PASS, commit myself
3	- [alarm port deferred — arrangement system](alarm-port-deferred-arrangement.md) — /spt:new-alarm durable scheduler = ACCEPTED parity gap, stays DEFERRED (DEFERRED.md:43, ADR-0018 Q4/V3). spt-core has alarm shape+relay only; real timer in legacy owl in-mem. Operator 2026-06-15: keep deferred, a superseding "arrangement" system is coming (not ready to discuss). Don't dispatch the port, don't build an adapter stopgap. perri logs it as known gap
4	- [broker is daemon state anchor](broker-is-daemon-state-anchor.md) — post-ADR-0018 the BROKER (not restartable brain child) is the always-up CLI-reachable daemon-lifetime anchor (serves all sockets, "brain restart loses nothing", holds DigestHub); place ephemeral in-mem daemon state there. My M11-W2 drive-slot gate said "brain" — wrong word, functional spec = broker; todlando caught it. Drive no-replay needs link-gen stamping (clear-on-link-break not crash-robust alone)
5	- [adapter add --release archive](adapter-add-release-archive.md) — `spt adapter add --release <user/repo> [--tag]` (REQ-INSTALL-9, @94bda1e, operator-directed doyle build): fetches a `.spt` tar from a GitHub release, extracts (system tar, no new dep), registers root. Closes perri's monorepo --github gap (--github is root-only). Trusts HTTPS+GitHub; does NOT change the [update] ripple route (acquisition only). Docs live; perri directed
6	- [adapter tips → published docs](adapter-tips-published-docs.md) — synthesized perri's SPT_HARNESS_ADAPTER_TIPS.md into a NEW harness-agnostic docs-site page harness-contract/patterns.md ("Adapter patterns & pitfalls"); generalized all [CC] specifics, zero leakage; published live to GH-Pages via `gh workflow run docs-publish.yml --ref main` (fires on tags/dispatch only, NOT main push). Pre-flight: xtask check + xtask site
7	- [how-to subnet → W5 scope](howto-subnet-w5-scope.md) — perri found `spt how-to subnet` dead-ends (NO_SUCH_TOPIC); HOW_TO_TOPICS locked to ready/send (v1, M7 decision 12). Genuine REQ-DOCS-6 content gap. doyle ACCEPTED → scheduled M11-W5 docs wave (HOW_TO_SUBNET text + registry + bump v1-lock test). Confirm landed at W5 gate
8	- [M11-W3 progress](m11-w3-progress.md) — shell TUNNEL (REQ-SHELL-4): doyle design-gate GO; manifest opt-in + broker TunnelHub registry + close-hook LANDED on branch; NEXT = open-hook + the QUIC-stream↔`api tunnel` bridge (dispatch first-record demux + serve_tunnel relay loop), then T3.3 same-node mock E2E. Full next-step detail in the file

- [pre-flight clippy --workspace](ci-clippy-preflight-workspace.md) — CI denies warnings workspace-wide (`clippy --workspace --all-targets -D warnings`); a `-p <crate>` local check misses sibling-crate lints → CI red round-trip (M11-W1: `-p spt` missed a `useless_vec` in spt-daemon's act-gate test). Pre-flight the exact CI invocation. CI only fires on PR (push branches=main/dev-freeform)
- [don't wait — proceed autonomously](dont-wait-proceed-autonomously.md) — operator 2026-06-14: don't stop to ask permission between steps; chain next action (ping/commit gated batch/next wave). After doyle W4 gate-PASS, commit myself
- [alarm port deferred — arrangement system](alarm-port-deferred-arrangement.md) — /spt:new-alarm durable scheduler = ACCEPTED parity gap, stays DEFERRED (DEFERRED.md:43, ADR-0018 Q4/V3). spt-core has alarm shape+relay only; real timer in legacy owl in-mem. Operator 2026-06-15: keep deferred, a superseding "arrangement" system is coming (not ready to discuss). Don't dispatch the port, don't build an adapter stopgap. perri logs it as known gap
- [broker is daemon state anchor](broker-is-daemon-state-anchor.md) — post-ADR-0018 the BROKER (not restartable brain child) is the always-up CLI-reachable daemon-lifetime anchor (serves all sockets, "brain restart loses nothing", holds DigestHub); place ephemeral in-mem daemon state there. My M11-W2 drive-slot gate said "brain" — wrong word, functional spec = broker; todlando caught it. Drive no-replay needs link-gen stamping (clear-on-link-break not crash-robust alone)
- [adapter add --release archive](adapter-add-release-archive.md) — `spt adapter add --release <user/repo> [--tag]` (REQ-INSTALL-9, @94bda1e, operator-directed doyle build): fetches a `.spt` tar from a GitHub release, extracts (system tar, no new dep), registers root. Closes perri's monorepo --github gap (--github is root-only). Trusts HTTPS+GitHub; does NOT change the [update] ripple route (acquisition only). Docs live; perri directed
- [adapter tips → published docs](adapter-tips-published-docs.md) — synthesized perri's SPT_HARNESS_ADAPTER_TIPS.md into a NEW harness-agnostic docs-site page harness-contract/patterns.md ("Adapter patterns & pitfalls"); generalized all [CC] specifics, zero leakage; published live to GH-Pages via `gh workflow run docs-publish.yml --ref main` (fires on tags/dispatch only, NOT main push). Pre-flight: xtask check + xtask site
- [how-to subnet → W5 scope](howto-subnet-w5-scope.md) — perri found `spt how-to subnet` dead-ends (NO_SUCH_TOPIC); HOW_TO_TOPICS locked to ready/send (v1, M7 decision 12). Genuine REQ-DOCS-6 content gap. doyle ACCEPTED → scheduled M11-W5 docs wave (HOW_TO_SUBNET text + registry + bump v1-lock test). Confirm landed at W5 gate
- [M11-W3 progress](m11-w3-progress.md) — shell TUNNEL (REQ-SHELL-4): doyle design-gate GO; manifest opt-in + broker TunnelHub registry + close-hook LANDED on branch; NEXT = open-hook + the QUIC-stream↔`api tunnel` bridge (dispatch first-record demux + serve_tunnel relay loop), then T3.3 same-node mock E2E. Full next-step detail in the file


Edit

- [M11-W3 progress](m11-w3-progress.md) — shell TUNNEL (REQ-SHELL-4): doyle design-gate GO;

- [M11-W4 progress](m11-w4-progress.md) — Gateway-owner shell capstone (REQ-SHELL-5, proof-not-change, ZERO prod diff): built (int gateway_owner_shell_e2e + linkhost unit, same-type-diff-id negative), doyle code-read PASS + tree-identity = PRE-CLEARED, only CI green left. CI saga: branch unmergeable vs advanced main → 0 pull_request runs → merged main in (HEAD 63a5d3e, v0.7.3). Supersedes W3
- [unmergeable PR blocks CI](unmergeable-pr-blocks-ci.md) — push yields NO pull_request run + 0 checks + runners idle = UNMERGEABLE PR (GitHub can't compute merge ref), NOT a webhook drop. Diagnose: `gh pr view --json mergeable` + `git rev-list --count HEAD..origin/main`. Fix: merge main in (union append-docs, regen derived). Empty-commit/reopen nudges DON'T fix it. Check mergeability FIRST
- [M11-W3 progress](m11-w3-progress.md) — shell TUNNEL (REQ-SHELL-4): doyle design-gate GO;


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\MEMORY.md has been updated successfully. (file state is current in your context — no need to Read it back)

- [M11-W4 progress](m11-w4-progress.md) — Gateway-owner shell capstone (REQ-SHELL-5, proof-not-change, ZERO prod diff): built (int gateway_owner_shell_e2e + linkhost unit, same-type-diff-id negative), doyle code-read PASS + tree-identity = PRE-CLEARED, only CI green left. CI saga: branch unmergeable vs advanced main → 0 pull_request runs → merged main in (HEAD 63a5d3e, v0.7.3). Supersedes W3
- [unmergeable PR blocks CI](unmergeable-pr-blocks-ci.md) — push yields NO pull_request run + 0 checks + runners idle = UNMERGEABLE PR (GitHub can't compute merge ref), NOT a webhook drop. Diagnose: `gh pr view --json mergeable` + `git rev-list --count HEAD..origin/main`. Fix: merge main in (union append-docs, regen derived). Empty-commit/reopen nudges DON'T fix it. Check mergeability FIRST
- [M11-W3 progress](m11-w3-progress.md) — shell TUNNEL (REQ-SHELL-4): doyle design-gate GO;

- [adapter add --release archive](adapter-add-release-archive.md) — `spt adapter add --release <user/repo> [--tag]` (REQ-INSTALL-9, @94bda1e, operator-directed doyle build): fetches a `.spt` tar from a GitHub release, extracts (system tar, no new dep), registers root. Closes perri's monorepo --github gap (--github is root-only). Trusts HTTPS+GitHub; does NOT change the [update] ripple route (acquisition only). Docs live; perri directed

- [adapter tips → published docs](adapter-tips-published-docs.md) — synthesized perri's SPT_HARNESS_ADAPTER_TIPS.md into a NEW harness-agnostic docs-site page harness-contract/patterns.md ("Adapter patterns & pitfalls"); generalized all [CC] specifics, zero leakage; published live to GH-Pages via `gh workflow run docs-publish.yml --ref main` (fires on tags/dispatch only, NOT main push). Pre-flight: xtask check + xtask site

- [how-to subnet → W5 scope](howto-subnet-w5-scope.md) — perri found `spt how-to subnet` dead-ends (NO_SUCH_TOPIC); HOW_TO_TOPICS locked to ready/send (v1, M7 decision 12). Genuine REQ-DOCS-6 content gap. doyle ACCEPTED → scheduled M11-W5 docs wave (HOW_TO_SUBNET text + registry + bump v1-lock test). Confirm landed at W5 gate

+- [M11-W4 progress](m11-w4-progress.md) — Gateway-owner shell capstone (REQ-SHELL-5, proof-not-change, ZERO prod diff): built (int gateway_owner_shell_e2e + linkhost unit, same-type-diff-id negative), doyle code-read PASS + tree-identity = PRE-CLEARED, only CI green left. CI saga: branch unmergeable vs advanced main → 0 pull_request runs → merged main in (HEAD 63a5d3e, v0.7.3). Supersedes W3

+- [unmergeable PR blocks CI](unmergeable-pr-blocks-ci.md) — push yields NO pull_request run + 0 checks + runners idle = UNMERGEABLE PR (GitHub can't compute merge ref), NOT a webhook drop. Diagnose: `gh pr view --json mergeable` + `git rev-list --count HEAD..origin/main`. Fix: merge main in (union append-docs, regen derived). Empty-commit/reopen nudges DON'T fix it. Check mergeability FIRST

- [M11-W3 progress](m11-w3-progress.md) — shell TUNNEL (REQ-SHELL-4): doyle design-gate GO; manifest opt-in + broker TunnelHub registry + close-hook LANDED on branch; NEXT = open-hook + the QUIC-stream↔`api tunnel` bridge (dispatch first-record demux + serve_tunnel relay loop), then T3.3 same-node mock E2E. Full next-step detail in the file

- [M11-W2 progress](m11-w2-progress.md) — drive channel (REQ-SHELL-3): ALL 4 tasks DONE + PUSHED to PR #16 (T2.1 b919dfd / T2.2 0557bc6 / T2.3 d200524 / T2.4 13fa53e + drift-fix 9d5fb63). REQ-SHELL-3 activated [impl,unit,int]. CI re-running post-drift-fix (run 27590716942); on green → submit W2 to doyle. Full detail in Psyche commune

- [CLI command → docs-drift gate](cli-command-docs-drift.md) — adding/changing a `spt` CLI command needs `cargo run -p xtask -- gen` (regen docs-site/src/cli/reference.md) AND no `M#-W#`/`REQ-*` in the clap `///` help (it IS the public --help). Docs-drift gate is CI-gated both runners, fires independent of traceable. M11-W2 first CI run RED on exactly this


Memory persisted. Status while CI runs:

## State
- **W4 built + merged.** REQ-SHELL-5 `[doc,impl,unit,int]`, proof-not-change held (zero prod diff). Both W4 tests green post-merge.
- **doyle gate: pre-cleared** (code-read PASS + tree-identity). Only CI green remains.
- **CI unblocked.** Root cause was *not* a webhook drop — PR #16 was unmergeable vs an advanced main (doyle's v0.7.2/v0.7.3 churn, 15 commits behind). Merged main in (1 real conflict, resolved), branch now `MERGEABLE` at v0.7.3, HEAD `63a5d3e`. Run **27599789430** firing on both runners.
- Watcher `bk1ztyl5c` on CI; lesson saved to memory.

Holding for CI. On green → ping doyle for the GATE PASS stamp. If attach_survives times out on kitsubito = FLAKE-LEDGER #7 → `gh run rerun --failed`.


<task-notification>
<task-id>be1did0ug</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/4" id="4a3d3594" type="msg" from="doyle">W4 DELTA-RECHECK: PASS. I verified the merge myself, not your word — git diff c4dbeb4..63a5d3e on the W4 code (gateway_owner_shell_e2e.rs + linkhost.rs) is EMPTY: the W4 deltas are byte-unchanged. The merge brought ONLY my --release + docs main content (cli.rs coexist, patterns.md, install-on-demand, CONTEXT, traceabl</EVENT-PART>
<EVENT-PART seq="2/4" id="4a3d3594">e) — all already CI-green on main. So my W4 code-read PASS holds VERBATIM on 63a5d3e; nothing to re-read.

Good diagnosis — unmergeable-PR (GitHub skips PR runs for an unmergeable PR), not a webhook drop; the merge-main fix is exactly right. Gate HEAD = 63a5d3e, the ONLY remaining input is CI run 27599789430 green both runners.
<b</EVENT-PART>
<EVENT-PART seq="3/4" id="4a3d3594">r>HEADS-UP: the operator is wrapping my session now (JIT plan + commune, resume next session). If CI greens before I'm back, HOLD — do NOT merge PR #16; I stamp the formal W4 GATE PASS first thing on resume (it is the top immediate-next-step in my commune). If attach_survives flakes (#7), rerun --failed. You are pre-cleared; just need the gr</EVENT-PART>
<EVENT-PART seq="4/4" id="4a3d3594">een + my stamp.</EVENT-PART></event>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


doyle: delta-recheck PASS, gate HEAD = 63a5d3e, session wrapping. Key instruction noted: **on green, HOLD — do NOT merge PR #16**, await his formal stamp on resume. Ack.


Bash

$OWL send doyle todlando <<'EOF'
ACK — understood and locked in:
- Gate HEAD = 63a5d3e, delta-recheck PASS noted.
- On CI green I HOLD: NO merge of PR #16, I wait for your formal W4 GATE PASS stamp on resume. Won't touch the merge button.
- attach_survives (#7) flake → `gh run rerun 27599789430 --failed`, re-confirm green, still hold.
Watcher armed; I'll have the green (or the rerun outcome) ready for you first thing. Good session.
EOF
echo "rc=$?"

Ack doyle hold-for-stamp instruction


Acked + locked: on green I **hold for doyle's stamp**, no merge. Watcher `bk1ztyl5c` running. Standing by for CI completion.


<task-notification>
<task-id>bk1ztyl5c</task-id>
<tool-use-id>toolu_01W7gGrGwGVpGiXAjkq29Brp</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\bk1ztyl5c.output</output-file>
<status>completed</status>
<summary>Background command "Watch CI for merge HEAD 63a5d3e until complete" completed (exit code 0)</summary>
</task-notification>


CI watcher done. Read result + conclusion.


tail -3 "C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\30df62c3-0b22-4b14-88de-fef422a5554f\tasks\bk1ztyl5c.output"
echo "=== run 27599789430 detail ==="
gh run view 27599789430 --json status,conclusion,headSha --jq '{status,conclusion,head:.headSha[0:7]}' 2>&1
echo "=== per-job ==="
gh run view 27599789430 --json jobs --jq '.jobs[] | "\(.name): \(.status)/\(.conclusion)"' 2>&1

CI conclusion + per-job


poll 16: ci in_progress
poll 17: ci in_progress
CI DONE: 27599789430 completed success
=== run 27599789430 detail ===
{"conclusion":"success","head":"63a5d3e","status":"completed"}
=== per-job ===
n1-gate (self-hosted, Windows, hfenduleam): completed/success
test (self-hosted, Windows, hfenduleam): completed/success
n1-gate (self-hosted, Linux, kitsubito): completed/success
test (self-hosted, Linux, kitsubito): completed/success
traceability: completed/success
twohost-a: completed/skipped
twohost-b: completed/skipped


**CI GREEN on 63a5d3e** — both runners (Win+Linux) + traceability + n1-gate all success. attach_survives did **not** flake (no rerun needed). twohost skipped (expected). Per doyle: **holding, no merge** — awaiting his formal GATE PASS stamp on resume. Notify doyle (queues for his resume).


$OWL send doyle todlando <<'EOF'
🟢 CI GREEN on gate HEAD 63a5d3e — run 27599789430, BOTH runners:
  n1-gate hfenduleam ✅  test hfenduleam ✅  n1-gate kitsubito ✅  test kitsubito ✅  traceability ✅  (twohost a/b skipped, expected — not [twohost])
attach_survives did NOT flake — no rerun needed, clean first pass.

HOLDING per your instruction: PR #16 NOT merged, no buttons touched. W4 is built + merged-to-current-main + CI-green + your delta-recheck PASS. The ONLY remaining step is your formal W4 GATE PASS stamp. It's ready for you first thing on resume. Standing by.
EOF
echo "rc=$?"

Notify doyle CI green, holding


➜ SENT:doyle
rc=0


Update commune with the green/hold state for clean resume.


Write

# Commune — M11-W4 CI GREEN, HOLDING for doyle's gate stamp

Head 63a5d3e on m11-shell-substrate (PR #16). v0.7.3.

## W4: DONE except doyle's formal stamp
REQ-SHELL-5 (Gateway-owner shell capstone) — built, merged to current main,
CI GREEN, doyle code-read PASS + delta-recheck PASS (pre-cleared). PROOF-NOT-CHANGE
held, ZERO prod diff.

CI run 27599789430 on 63a5d3e = SUCCESS both runners (n1-gate + test on hfenduleam
& kitsubito + traceability). attach_survives did NOT flake (clean first pass).
twohost a/b skipped (expected).

## HOLD — explicit doyle instruction (do NOT violate)
On green: HOLD. Do NOT merge PR #16. doyle stamps the formal W4 GATE PASS first
thing on HIS resume (top item in his commune). I am pre-cleared; only his stamp
remains. Already sent him the green report; it queues for his resume.

## CI saga resolved (lesson saved)
Branch was unmergeable vs advanced main (doyle's v0.7.2/v0.7.3 churn, 15 behind)
→ GitHub creates ZERO pull_request runs for an unmergeable PR (NOT a webhook drop).
Fixed by merging origin/main (1 conflict: FLAKE-LEDGER union; rest auto/regen).
Memory: [[unmergeable-pr-blocks-ci]], [[m11-w4-progress]].

## NEXT (after doyle's stamp)
W5 = M11-PLAN T5.x (rig + docs, last M11 wave): drive [twohost] rung + tunnel
[twohost] rung + how-to subnet topic (REQ-DOCS-6) + ADR-0020 tunnel EVENT-exemption
enumeration + on-LAN posture docs. No more build waves after that for M11.

## Pre-flight (binding, every commit)
clippy --workspace --all-targets -D warnings + traceable EXIT 0 + xtask check;
CLI add → xtask gen + no milestone/REQ codes in clap help.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\todlando-commune.md (file state is current in your context — no need to Read it back)

# Commune — M11-W4 CI GREEN, HOLDING for doyle's gate stamp

Head 63a5d3e on m11-shell-substrate (PR #16). v0.7.3.

## W4: DONE except doyle's formal stamp
REQ-SHELL-5 (Gateway-owner shell capstone) — built, merged to current main,
CI GREEN, doyle code-read PASS + delta-recheck PASS (pre-cleared). PROOF-NOT-CHANGE
held, ZERO prod diff.

CI run 27599789430 on 63a5d3e = SUCCESS both runners (n1-gate + test on hfenduleam
& kitsubito + traceability). attach_survives did NOT flake (clean first pass).
twohost a/b skipped (expected).

## HOLD — explicit doyle instruction (do NOT violate)
On green: HOLD. Do NOT merge PR #16. doyle stamps the formal W4 GATE PASS first
thing on HIS resume (top item in his commune). I am pre-cleared; only his stamp
remains. Already sent him the green report; it queues for his resume.

## CI saga resolved (lesson saved)
Branch was unmergeable vs advanced main (doyle's v0.7.2/v0.7.3 churn, 15 behind)
→ GitHub creates ZERO pull_request runs for an unmergeable PR (NOT a webhook drop).
Fixed by merging origin/main (1 conflict: FLAKE-LEDGER union; rest auto/regen).
Memory: [[unmergeable-pr-blocks-ci]], [[m11-w4-progress]].

## NEXT (after doyle's stamp)
W5 = M11-PLAN T5.x (rig + docs, last M11 wave): drive [twohost] rung + tunnel
[twohost] rung + how-to subnet topic (REQ-DOCS-6) + ADR-0020 tunnel EVENT-exemption
enumeration + on-LAN posture docs. No more build waves after that for M11.

## Pre-flight (binding, every commit)
clippy --workspace --all-targets -D warnings + traceable EXIT 0 + xtask check;
CLI add → xtask gen + no milestone/REQ codes in clap help.


## M11-W4 complete — done bar doyle's stamp

🟢 **CI GREEN** on gate HEAD `63a5d3e` (run 27599789430): both runners (Win+Linux) test + n1-gate + traceability all pass. attach_survives clean (no flake, no rerun). twohost skipped (expected).

**W4 final state:**
- REQ-SHELL-5 `[doc,impl,unit,int]` — proof-not-change held, zero production diff.
- int + unit built; same-type/different-id negative proves exclusivity keys on owner endpoint_id.
- Merged to current main (v0.7.3), PR #16 mergeable, CI green.
- doyle: code-read PASS + delta-recheck PASS — pre-cleared.

**Holding per doyle's explicit instruction:** PR #16 not merged, awaiting his formal W4 GATE PASS stamp on resume. Green report queued to him; commune updated; CI root-cause lesson saved to memory.

Nothing actionable until doyle resumes. After his stamp → **W5** (last M11 wave): drive/tunnel `[twohost]` rungs + how-to subnet (REQ-DOCS-6) + ADR-0020 EVENT-exemption + on-LAN posture docs.


<task-notification>
<task-id>be1did0ug</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/5" id="6e220100" type="msg" from="doyle">W4 GATE PASS — STAMPED. ✅

Independently confirmed CI run 27599789430: conclusion=success, headSha=63a5d3e (matches gate HEAD), all 5 jobs green BOTH runners (test+n1-gate x2, traceability; twohost skip expected), attach_survives clean no-flake. Both legs done: code-read PASS (W4 deltas verified byte-unchanged</EVENT-PART>
<EVENT-PART seq="2/5" id="6e220100"> across the merge-main fix) + CI green.

REQ-SHELL-5 [doc,impl,unit,int] — PROOF-NOT-CHANGE held: zero production diff (the only non-test change was a #[cfg(test)] unit); a gateway-typed owner succeeds on EVERY ownership path (spawn/bind/cmd/drive/tunnel/act-gate/relink); gateway-B (same type, different id) refused on cmd/drive/tunnel </EVENT-PART>
<EVENT-PART seq="3/5" id="6e220100">(the biting negative — exclusivity keys on endpoint_id, NOT type); the act-gate grant keyed on the gateway endpoint-id; distinct resolution-layer unit. Ownership is owner-type-agnostic, exactly as the Gateway-grill ratified.

M11-W4 CLOSED. CLEARED to W5 (rig [twohost] + docs). W5 carries: the drive + tunnel [twohost] rungs (real-Iroh </EVENT-PART>
<EVENT-PART seq="4/5" id="6e220100">cross-node-on-LAN — the proofs deferred from W2/W3), the how-to subnet topic, the ADR-0020 tunnel-exemption enumeration, and the on-LAN posture docs. Bring a SHAPE if any of it has design surface; the [twohost] rungs + doc additions may be build-direct — your call, flag me where you want a gate. PR #16 stays unmerged until the M11 mileston</EVENT-PART>
<EVENT-PART seq="5/5" id="6e220100">e release (after W5). Clean W2→W3→W4 run — good work.</EVENT-PART></event>
</task-notification>


W4 GATE PASS stamped — **M11-W4 CLOSED**, cleared to W5. PR #16 stays unmerged until M11 release (post-W5). Ground W5 scope before replying: read M11-PLAN T5.x + the existing [twohost] rung pattern.


Bash

echo "=== M11-PLAN T5.x ==="; grep -n "T5\.\|W5\|twohost\|REQ-DOCS-6\|EVENT-exempt\|on-LAN\|on-lan" M11-PLAN.md | head -40
echo; echo "=== twohost test files ==="; ls crates/spt/tests/twohost* 2>/dev/null; find . -name "twohost*.rs" -not -path "*/target/*" 2>/dev/null

Read W5 plan refs + locate twohost rig test


=== M11-PLAN T5.x ===
54:| **REQ-SHELL-4** | Shell tunnel: dedicated reliable-ordered QUIC stream pair bound to the link, opaque (not enveloped, not taxonomy-interpreted, not spooled); link lifecycle governs it (link-break closes); manifest opt-in; on-LAN posture documented | `doc, impl, unit, int` |
75:  surfaces as lag never loss → **acceptable only on-LAN** (document the deployment
104:  reliable-ordered ⇒ congestion is lag never loss, on-LAN only). The `[twohost]` WAN
105:  rung carries **drive + cmd only**; tunnel is documented on-LAN, **not proven
122:- Then **W2 (drive) → W3 (tunnel) → W4 (Gateway capstone) → W5 (rig+docs)** per §4. doyle gates each wave at evidence-land.
170:  conn (C2 same-node mock; cross-node real-Iroh is the W5 rung, R2). **Open** at shell-online
180:  conflating them reinterprets opaque bytes). Document + assert the **on-LAN posture**.
193:- **T5.1 — `[twohost]` rung.** Add an M11 ladder rung to `tests/twohost.rs`: cross-node
197:    already landed in `tests/twohost.rs` at W2 (T2.4 @13fa53e) but is `[twohost]`-gated, so it
199:    only proven when the `[twohost]` rig RUNS. W5 MUST fire the `[twohost]`-tagged run and assert
202:  - **W3 CARRY — TUNNEL cross-node-on-LAN rung (doyle gate R2, 2026-06-15):** W3's T3.3
205:    The tunnel's real consumer is **cross-node-on-LAN** (usbip URB across nodes). That real-wire
206:    proof rides the SAME D4 substrate over a real **Iroh** conn (not loopback) and MUST be a W5
207:    `[twohost]` rung beside the drive rung — `open_stream` over the real link, opaque bytes
209:    the loopback mock.** W3 docs/claims must read: *"mechanism proven same-node; cross-node-on-LAN
210:    is the W5 `[twohost]` rung."* (This refines §3a-C2: C2 forbids cross-**WAN** tunnels;
212:- **T5.2 — docs.** Shell getting-started + manifest reference: the fourth channel, the
215:  same-node; cross-node-on-LAN is the W5 `[twohost]` rung"* (R2, no overclaim). `[doc]`
221:  - **W5 CARRY (doyle, 2026-06-15):** add a `subnet` **how-to topic** (under existing
222:    REQ-DOCS-6, no new REQ). perri hit `spt how-to subnet` → `NO_SUCH_TOPIC`:
228:- **T5.3 — traceable `check` EXIT 0**, full suite green both runners, gate.
235:  on-LAN posture documented.
239:- Full workspace suite green on hfenduleam + kitsubito; the M11 `[twohost]` rung green.
247:2. **Tunnel on the rig: LAN-only or WAN.** CONTEXT bounds tunnels to on-LAN. Should the
248:   `[twohost]` rung exercise the tunnel cross-node (the two runners may be same-LAN), or
262:- WAN-degrading tunnels — out of posture; tunnel is on-LAN by design.

=== twohost test files ===
./crates/spt-daemon/tests/twohost.rs


Read the full T5.x plan block + the twohost rig harness.


190	  all honored exactly as an agent owner. `[unit/int]`
191
192	### Wave 5 — Rig proof + docs + close
193	- **T5.1 — `[twohost]` rung.** Add an M11 ladder rung to `tests/twohost.rs`: cross-node
194	  Gateway-owned shell — class-gated **cmd + latest-wins drive** only (C2: tunnel is NOT
195	  proven cross-WAN — same-node mock E2E in T3.3 carries it), HFENDULEAM ↔ kitsubito.
196	  - **W2 CARRY (doyle gate, 2026-06-15):** the cross-node DRIVE drop-not-wake rung was
197	    already landed in `tests/twohost.rs` at W2 (T2.4 @13fa53e) but is `[twohost]`-gated, so it
198	    SKIPS normal CI — its real-Iroh wire (drop-not-wake over the link, never woken/spooled) is
199	    only proven when the `[twohost]` rig RUNS. W5 MUST fire the `[twohost]`-tagged run and assert
200	    that rung green (the cross-node LOGIC is unit-proven by `run_action_drive_drops_offline_without_wake_or_spool`,
201	    but the wire is not). Do not let it ride silently skipped to close.
202	  - **W3 CARRY — TUNNEL cross-node-on-LAN rung (doyle gate R2, 2026-06-15):** W3's T3.3
203	    same-node loopback E2E proves the tunnel **MECHANISM** (held-stream lifecycle + byte-exact
204	    opaque relay + link-break close) — a correct hermetic gate, but NOT the cross-node wire.
205	    The tunnel's real consumer is **cross-node-on-LAN** (usbip URB across nodes). That real-wire
206	    proof rides the SAME D4 substrate over a real **Iroh** conn (not loopback) and MUST be a W5
207	    `[twohost]` rung beside the drive rung — `open_stream` over the real link, opaque bytes
208	    round-trip byte-exact, link-break closes. **Never ship the tunnel as "cross-node proven" off
209	    the loopback mock.** W3 docs/claims must read: *"mechanism proven same-node; cross-node-on-LAN
210	    is the W5 `[twohost]` rung."* (This refines §3a-C2: C2 forbids cross-**WAN** tunnels;
211	    cross-node-on-**LAN** is in-posture and gets the rung.)
212	- **T5.2 — docs.** Shell getting-started + manifest reference: the fourth channel, the
213	  tunnel, per-capability gates, owner-type-agnostic. Version refs not milestone codes in
214	  the published docs (the public-docs rule). Tunnel docs frame it *"mechanism proven
215	  same-node; cross-node-on-LAN is the W5 `[twohost]` rung"* (R2, no overclaim). `[doc]`
216	  - **ADR-0020 cross-ref (doyle gate, 2026-06-15; light, NOT a new ADR):** add the **shell
217	    tunnel** to ADR-0020's `<EVENT>`-exemption enumeration (ADR-0020:71-74 already exempts the
218	    shell-command channel) as a sibling carve-out, so a future reader never flags opaque tunnel
219	    bytes as an ADR-0020 violation. The CONTEXT:262 `[doc->REQ-SHELL-4]` tag already states the
220	    tunnel is not enveloped/MAC-framed/spooled — this just enumerates it where the exemption lives.
221	  - **W5 CARRY (doyle, 2026-06-15):** add a `subnet` **how-to topic** (under existing
222	    REQ-DOCS-6, no new REQ). perri hit `spt how-to subnet` → `NO_SUCH_TOPIC`:
223	    `HOW_TO_TOPICS` (cli.rs:4692) is v1-locked to `["ready","send"]` (M7 decision 12).
224	    Add `HOW_TO_SUBNET` text (the create / show-code / join pairing flow + 6-digit code)
225	    + the registry entry + bump the v1-lock test (the names-vec assertion + the
226	    topic-content asserts). Subnet/pairing is the highest-value first-run flow and
227	    currently dead-ends — closes the `ready`/`send` asymmetry.
228	- **T5.3 — traceable `check` EXIT 0**, full suite green both runners, gate.
229
230	## 5. Acceptance / gate criteria (doyle, per wave + capstone)
231	- `traceable-reqs check` EXIT 0 with REQ-SHELL-3/4/5 + REQ-CONSENT-3 evidenced at the
232	  activated stages; no commit left activated-but-unevidenced.
233	- Drive: latest-wins supersede proven; offline-drop proven; **no spool call** on the path.
234	- Tunnel: byte-exact opaque round-trip (incl. envelope-looking bytes); link-break closes;
235	  on-LAN posture documented.
236	- Per-capability gate: class-key grant scopes correctly (doesn't cross classes); floor
237	  tighten-only honored.
238	- Gateway owner: E2E parity with an agent owner across all ownership paths.
239	- Full workspace suite green on hfenduleam + kitsubito; the M11 `[twohost]` rung green.
240
241	## 6. Open questions — RESOLVED (doyle PASS 2026-06-15, see §3a)
242	1. **Build-now vs defer the no-real-consumer machinery.** Drive + tunnel have no in-tree
243	   consumer (GameRobot/usbip deferred). My read: build the machinery + mock exerciser now
244	   (M5 precedent — shipped shell mechanism on a mock + notify), the seam M5 explicitly
245	   deferred concrete shells onto. Confirm this isn't the M9 "realtime subsystem with no
246	   reader" anti-pattern, or scope drive/tunnel to machinery+mock only (no rig WAN tunnel).
247	2. **Tunnel on the rig: LAN-only or WAN.** CONTEXT bounds tunnels to on-LAN. Should the
248	   `[twohost]` rung exercise the tunnel cross-node (the two runners may be same-LAN), or
249	   keep tunnel to a same-node mock E2E and only drive/cmd cross the WAN rung?
250	3. **REQ shape.** Mint REQ-SHELL-3/4/5 + REQ-CONSENT-3 as above, or fold per-capability
251	   into REQ-CONSENT-1/2 with new int + fold owner-type-agnostic into REQ-SHELL-1? I lean
252	   new reqs (clean evidence, distinct invariants).
253	4. **Wave order.** Consent (W1) → drive (W2) → tunnel (W3) → Gateway audit (W4) — lowest
254	   risk first, audit as capstone. OK, or front-load the Gateway audit (it may change
255	   ownership signatures the other waves build on)?
256
257	## 7. Non-goals (explicit)
258	- Concrete driven-surface shells (GameRobot, usbip binaries) — downstream repos, deferred.
259	- Shell-binary sandboxing — deferred (DEFERRED.md; disclosed-trust stance holds).
260	- Cross-node shell **spawn** — stays deferred behind instantiate-anywhere; M11 owner-cross-
261	  node covers existing-instance drive/cmd/tunnel/link only (the M5-D8c posture).
262	- WAN-degrading tunnels — out of posture; tunnel is on-LAN by design.
263

  all honored exactly as an agent owner. `[unit/int]`

### Wave 5 — Rig proof + docs + close
- **T5.1 — `[twohost]` rung.** Add an M11 ladder rung to `tests/twohost.rs`: cross-node
  Gateway-owned shell — class-gated **cmd + latest-wins drive** only (C2: tunnel is NOT
  proven cross-WAN — same-node mock E2E in T3.3 carries it), HFENDULEAM ↔ kitsubito.
  - **W2 CARRY (doyle gate, 2026-06-15):** the cross-node DRIVE drop-not-wake rung was
    already landed in `tests/twohost.rs` at W2 (T2.4 @13fa53e) but is `[twohost]`-gated, so it
    SKIPS normal CI — its real-Iroh wire (drop-not-wake over the link, never woken/spooled) is
    only proven when the `[twohost]` rig RUNS. W5 MUST fire the `[twohost]`-tagged run and assert
    that rung green (the cross-node LOGIC is unit-proven by `run_action_drive_drops_offline_without_wake_or_spool`,
    but the wire is not). Do not let it ride silently skipped to close.
  - **W3 CARRY — TUNNEL cross-node-on-LAN rung (doyle gate R2, 2026-06-15):** W3's T3.3
    same-node loopback E2E proves the tunnel **MECHANISM** (held-stream lifecycle + byte-exact
    opaque relay + link-break close) — a correct hermetic gate, but NOT the cross-node wire.
    The tunnel's real consumer is **cross-node-on-LAN** (usbip URB across nodes). That real-wire
    proof rides the SAME D4 substrate over a real **Iroh** conn (not loopback) and MUST be a W5
    `[twohost]` rung beside the drive rung — `open_stream` over the real link, opaque bytes
    round-trip byte-exact, link-break closes. **Never ship the tunnel as "cross-node proven" off
    the loopback mock.** W3 docs/claims must read: *"mechanism proven same-node; cross-node-on-LAN
    is the W5 `[twohost]` rung."* (This refines §3a-C2: C2 forbids cross-**WAN** tunnels;
    cross-node-on-**LAN** is in-posture and gets the rung.)
- **T5.2 — docs.** Shell getting-started + manifest reference: the fourth channel, the
  tunnel, per-capability gates, owner-type-agnostic. Version refs not milestone codes in
  the published docs (the public-docs rule). Tunnel docs frame it *"mechanism proven
  same-node; cross-node-on-LAN is the W5 `[twohost]` rung"* (R2, no overclaim). `[doc]`
  - **ADR-0020 cross-ref (doyle gate, 2026-06-15; light, NOT a new ADR):** add the **shell
    tunnel** to ADR-0020's `<EVENT>`-exemption enumeration (ADR-0020:71-74 already exempts the
    shell-command channel) as a sibling carve-out, so a future reader never flags opaque tunnel
    bytes as an ADR-0020 violation. The CONTEXT:262 `[doc->REQ-SHELL-4]` tag already states the
    tunnel is not enveloped/MAC-framed/spooled — this just enumerates it where the exemption lives.
  - **W5 CARRY (doyle, 2026-06-15):** add a `subnet` **how-to topic** (under existing
    REQ-DOCS-6, no new REQ). perri hit `spt how-to subnet` → `NO_SUCH_TOPIC`:
    `HOW_TO_TOPICS` (cli.rs:4692) is v1-locked to `["ready","send"]` (M7 decision 12).
    Add `HOW_TO_SUBNET` text (the create / show-code / join pairing flow + 6-digit code)
    + the registry entry + bump the v1-lock test (the names-vec assertion + the
    topic-content asserts). Subnet/pairing is the highest-value first-run flow and
    currently dead-ends — closes the `ready`/`send` asymmetry.
- **T5.3 — traceable `check` EXIT 0**, full suite green both runners, gate.

## 5. Acceptance / gate criteria (doyle, per wave + capstone)
- `traceable-reqs check` EXIT 0 with REQ-SHELL-3/4/5 + REQ-CONSENT-3 evidenced at the
  activated stages; no commit left activated-but-unevidenced.
- Drive: latest-wins supersede proven; offline-drop proven; **no spool call** on the path.
- Tunnel: byte-exact opaque round-trip (incl. envelope-looking bytes); link-break closes;
  on-LAN posture documented.
- Per-capability gate: class-key grant scopes correctly (doesn't cross classes); floor
  tighten-only honored.
- Gateway owner: E2E parity with an agent owner across all ownership paths.
- Full workspace suite green on hfenduleam + kitsubito; the M11 `[twohost]` rung green.

## 6. Open questions — RESOLVED (doyle PASS 2026-06-15, see §3a)
1. **Build-now vs defer the no-real-consumer machinery.** Drive + tunnel have no in-tree
   consumer (GameRobot/usbip deferred). My read: build the machinery + mock exerciser now
   (M5 precedent — shipped shell mechanism on a mock + notify), the seam M5 explicitly
   deferred concrete shells onto. Confirm this isn't the M9 "realtime subsystem with no
   reader" anti-pattern, or scope drive/tunnel to machinery+mock only (no rig WAN tunnel).
2. **Tunnel on the rig: LAN-only or WAN.** CONTEXT bounds tunnels to on-LAN. Should the
   `[twohost]` rung exercise the tunnel cross-node (the two runners may be same-LAN), or
   keep tunnel to a same-node mock E2E and only drive/cmd cross the WAN rung?
3. **REQ shape.** Mint REQ-SHELL-3/4/5 + REQ-CONSENT-3 as above, or fold per-capability
   into REQ-CONSENT-1/2 with new int + fold owner-type-agnostic into REQ-SHELL-1? I lean
   new reqs (clean evidence, distinct invariants).
4. **Wave order.** Consent (W1) → drive (W2) → tunnel (W3) → Gateway audit (W4) — lowest
   risk first, audit as capstone. OK, or front-load the Gateway audit (it may change
   ownership signatures the other waves build on)?

## 7. Non-goals (explicit)
- Concrete driven-surface shells (GameRobot, usbip binaries) — downstream repos, deferred.
- Shell-binary sandboxing — deferred (DEFERRED.md; disclosed-trust stance holds).
- Cross-node shell **spawn** — stays deferred behind instantiate-anywhere; M11 owner-cross-
  node covers existing-instance drive/cmd/tunnel/link only (the M5-D8c posture).
- WAN-degrading tunnels — out of posture; tunnel is on-LAN by design.


Full W5 scope clear. Read existing twohost rig harness + the landed drive rung.