docs(ops): runbook for the submodule build & mount gap — Docker creates missing binds as directories - #2485
Conversation
|
Warning Review limit reached
Next review available in: 4 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3e56cf1b9f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…mpose that still fails WS2 item 4. A runbook and not a ratchet: the gap is a property of the CHECKOUT, not of any file, so it cannot appear in a diff — and both relevant CI gates decline to check it. Two classes, verified against origin/main @ 22c78fb. CLASS A — build contexts. 8 services from 7 registered submodules (Archon, cipher, PMOVES.YT, OpenRoom, llama-throughput-lab, transcribe-and-fetch, n8n). Split overlays re-declare the same contexts rather than adding services. Fails loudly at build time. Includes the second n8n path at pmoves/compose/docker-compose.core.yml (../../PMOVES-n8n), which is live via pmoves-integrations-ci.yml:73 and which a single-`../` scan misses. CLASS B — bind-mount sources. 5 mounts from 2 submodules. The dangerous one: Docker does NOT error on a missing bind source, it CREATES it as a directory. A file mount silently becomes a directory mount and the container then fails on its own config, so the error never mentions submodules. WHY CI DOESN'T CATCH IT — corrected. An earlier draft said the workflows check out with `submodules: recursive` and therefore see a populated tree. That is wrong; neither validator checks out submodules at all (validate-dockerfile-paths-ratchet.yml:54, validate-composes-ratchet.yml:41). They pass for two different DELIBERATE reasons: - validate_dockerfile_paths.py skips anything failing _is_in_scope_build_target(), with the reasoning in the source: sibling submodule / vendor / provisions paths are "external repos the ratchet can't statically check". Class A is a known, accepted blind spot. - validate-composes never inspects bind sources; Class B is outside its question. That is a defensible split — a static ratchet genuinely cannot reason about an external repo's contents — but it means the only thing between this and a silent production failure is a human knowing about it. BRING-UP now covers both stopped services, and says why they differ. The recovery stops vector AND edge-functions; an earlier draft only recreated vector, leaving edge-functions down after the "complete" fix. Docker records the mount TYPE at creation, so a FILE mount needs `supa-recreate-svc` while a DIRECTORY mount recovers on a plain `docker start`. Getting that backwards costs a confusing round — edge-functions came straight back while vector refused, from the same fix. Non-Supabase services use `recreate-svc SVC=<name>` (Makefile:553). up-cipher-nobuild DOWNGRADED from "escape hatch" to what it actually is. Its recipe is `--no-build --force-recreate cipher-api`, and cipher-api declares only a build: stanza with no image:, so it reuses an image Compose already built LOCALLY. Its own help text calls it a gitlink-drift workaround. On a node or worktree where that image was never built it cannot start Cipher — so it is not a fresh-checkout answer, and the worktree section no longer offers it as one. Worked example from 2026-08-08 on the 4090: two containers crash-looping since 08-05, supabase-vector at 78 restarts, 28 of 33 running containers from a SECOND clone whose 57 submodules were all unpopulated. Two incident findings kept because both would otherwise be re-derived the hard way: supabase-kong declares two file mounts from the same submodule and was NEVER affected (HostConfig.Binds is null; its config is inlined into the entrypoint — check the container, not the compose file); and `git submodule status` is the fast triage, where `-` is unpopulated (this bug) and `+` is gitlink drift (different problem). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3e56cf1 to
0e6351d
Compare
…, not a worktree escape hatch Caught on review of the companion runbook (#2485). This file listed up-cipher-nobuild as keep-regardless-of-reference-count because it was "the only way cipher-api comes up from a worktree". That reason is wrong. Its recipe is `--no-build --force-recreate cipher-api`, and cipher-api declares only a build: stanza with no image:. So `--no-build` can only reuse an image Compose already built LOCALLY under its default tag — on a cold worktree, or a node that never built it, there is nothing to reuse and it cannot start Cipher. Its own help text was accurate and I read past it: "applies env/port changes when the Pmoves-cipher submodule build is unavailable (gitlink drift)". Gitlink drift means you HAVE the image and the submodule is at the wrong commit — the opposite of a fresh checkout. Verdict is unchanged — still a keeper, still no substitute, still zero references. Only the reason changes, and the wrong reason is left visible rather than quietly swapped, since a keep-this justified by a false premise is exactly what a reviewer needs to be able to re-check. Also adds a `text` language identifier to the singleton block (MD040). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…Makefile edits (#2486) * docs(ops): up-* target inventory — 88 targets grouped, 11 with zero references WS2 item 5. Inventory only: no Makefile is edited by this commit. The operator picks what to retire; consolidation is a separate follow-up. 88 targets — 82 in pmoves/Makefile, 6 across mk/{egress,infra,yt-cookies}.mk. Grouped into 12 families plus 46 singletons, each with a count of how many files outside the makefiles mention it. The count is a conversation starter, not a kill list, and the doc says so up front. A zero means nothing in the repo writes the target down — not that it is dead. Shell history is not in the repo. Two of the eleven zero-reference targets are already known to be load-bearing: up-cipher-nobuild the documented escape hatch for the submodule build gap; the only way cipher-api comes up from a worktree up-tokenism deliberate Known Road from #2326, env fix in #2334, paired with down-tokenism That up-cipher-nobuild scores 0 while being the answer to a live operational problem is the argument for inventory-only in one line. Patterns worth an operator decision, ordered by risk: 1. up-yt-cookies-rebuild (0) vs up-yt-cookies-recreate (1) — the clearest near-duplicate pair in the set. One pair, one decision. 2. The -hardened suffix looks like a convention that only partly took: up-core-hardened 0, up-workers-hardened 0, up-agents-hardened 4, up-yt-hardened 2. One decision covers four targets. Retiring them does not retire hardening — that lives in docker-compose.hardened.yml and the hardening-validation workflow. 3. up-all (21) vs up-all-new (5) — both referenced, and the names give no clue which supersedes which. That ambiguity is the cost. Needs a naming decision more than a delete. 4. up-agents-* (8) splits along two independent axes — image source (published/integrations/standalone) and profile (ui/stack/hardened/auto). Possibly flags on one target rather than seven targets. Biggest surface, most likely to break a habit, so last. Explicitly NOT redundant: up-archon-native vs up-archon-submodule. Archon 0.6.0 is TS/SQLite-native with its own compose, so those are two different deployments. Keep both. Method and its bias are recorded so the numbers can be re-derived and doubted: word-boundary matching lets a prefix target inflate on its own suffixes (up-yt matches inside up-yt-egress). That biases HIGH counts upward and never creates a false zero — so the zero list is trustworthy and the ordering among high-count targets is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ops): correct the up-cipher-nobuild rationale — drift workaround, not a worktree escape hatch Caught on review of the companion runbook (#2485). This file listed up-cipher-nobuild as keep-regardless-of-reference-count because it was "the only way cipher-api comes up from a worktree". That reason is wrong. Its recipe is `--no-build --force-recreate cipher-api`, and cipher-api declares only a build: stanza with no image:. So `--no-build` can only reuse an image Compose already built LOCALLY under its default tag — on a cold worktree, or a node that never built it, there is nothing to reuse and it cannot start Cipher. Its own help text was accurate and I read past it: "applies env/port changes when the Pmoves-cipher submodule build is unavailable (gitlink drift)". Gitlink drift means you HAVE the image and the submodule is at the wrong commit — the opposite of a fresh checkout. Verdict is unchanged — still a keeper, still no substitute, still zero references. Only the reason changes, and the wrong reason is left visible rather than quietly swapped, since a keep-this justified by a false premise is exactly what a reviewer needs to be able to re-check. Also adds a `text` language identifier to the singleton block (MD040). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ops): count makefile callers too — the first retire list was dangerous Three review findings, all correct, and the third invalidates the method rather than a detail. MAKEFILE CALLERS WERE NOT COUNTED. The scan excluded pmoves/Makefile and pmoves/mk/ to avoid counting each target's own definition. That also excluded every CALL SITE, so targets invoked by other targets scored zero and landed on a retire list. Three did: up-core-capable -> up-core-hardened (Makefile:2542) |-> up-workers-hardened (Makefile:2506) `-> up-agents-hardened (Makefile:2507, 2533) Retiring up-core-hardened on its zero external count breaks up-core-capable. The file now carries two counts — external references and intra-makefile callers — and a target is a candidate only when BOTH are zero. That drops the list from 11 to 8 and removes up-core-hardened, up-workers-hardened, and up-creator-collab (Makefile:3846). The withdrawn "-hardened is an abandoned convention" reading was the opposite of true: it is a working dependency-ordered bring-up chain. up-yt-cookies-rebuild IS NOT A DUPLICATE. The first revision called it "the clearest near-duplicate pair in the whole set" and put it first in the retire order. mk/yt-cookies.mk:141-149 shows two distinct flows — `--force-recreate` for env changes, `--build --force-recreate` for code changes. Consolidating either direction is a real loss: no image-rebuild path, or every routine env refresh pays for a rebuild. Kept, with the distinction documented. THE REPRODUCTION COUNTED ITSELF. This file names every target, so running the documented command on the committed tree turns every zero into a one and the list cannot be reproduced from the commit that states it. The commands now exclude UP_TARGET_INVENTORY.md and say to scan the parent revision. Both scans are now given, with their biases stated — including the one that caused this: scan (1) answers "does anything written down mention this", NOT "is anything broken if this disappears". Only the caller scan answers the second, and treating the first as sufficient is what made the original retire list unsafe. Both counts still miss a human who types a target and never writes it down. That caveat is unchanged and is why this file remains inventory-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…reign RELEASE lines (#2498) * docs(agnote): WS2 RELEASE + lane sweep — evidence for owners, no foreign RELEASE lines Two entries. RELEASE — WS2 (z890's coordination plan) is complete inside its 72h TTL. Eight PRs merged: #2482 claim+handoff+corrections, #2483 ci-expedition skill, #2484 claude-pmoves delegation, #2485 submodule gap runbook, #2486 up-* inventory, #2488 validate-command-anchors, #2494 first-contact + guard routing table, #2495 Danger Room handoff to SPARK. pmoves/mk/infra.mk untouched throughout — z890's #2480, no collision. The entry records what the audit found BEYOND its enumerated items, because that is the reusable part: a gate can advertise coverage it does not have (three separate instances, each caught by review rather than by me); the always-loaded orientation file misdirects first contact; the guard's own routing table has two dead roads; and patterns.yaml is the pattern worth generalizing while pre-tool.sh duplicates 5 of its entries minus the affordance. It also records four corrections to my own prior work — the wrong 13-of-15 figure, the retracted hf-mcp-server entry, the dangerous first up-* retire list, and the yt-cookies pair that was never a duplicate. A closeout that only lists wins is not a closeout. NOTE — lane sweep. 115 CLAIM against 119 RELEASE. Four Mavis lanes verified shipped and missing only a RELEASE (harness v0 #2437/#2443/#2450, multi-fork follow-ups #2477, OpenRoom slice 2, creative-pipeline v0). Four older lanes still open with no PR cited and nothing found merged, now 8-10 days. Mine that are correctly still open: #2446 draft, #2468 held for review, and the SPARK handoff awaiting its CLAIM. ZERO RELEASE lines written on another agent's behalf — verified in the diff. Those lanes are Mavis's to close under Village Rule; this records evidence so they can close from it rather than from memory. Kept distinct from the KIMI-SPARK / CRUSH stale claims, which need a release OR re-claim — different category, and conflating them would make the ping inaccurate. Verified: make -C pmoves validate-command-anchors passes. Note for follow-up: dogfooding this entry surfaced a real false-positive generator in MAKE_CITE_RE — `-C \S+` swallows a closing backtick, so prose that backticks "make -C pmoves" alone captures the following word as a target. Fixed separately, not folded in here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(agnote): state the counting method, and stop treating merge as acceptance Three review findings, all correct, all mine. 1. THE TOTALS WERE NOT REPRODUCIBLE. I published "115 CLAIM against 119 RELEASE" with no method attached. Recounting three ways on the same file: anchored bullet rows 115 / 119 <- what I published token anywhere in text 284 / 221 (prose mentions) timestamped, no anchor 116 / 119 and the 2026-08-07 sweep reported 121 / 115 on a fourth. None of these is wrong; a bare number with no pattern is. The entry now states the exact regex, lists what the other methods give, and says to treat the ratio as a rough signal rather than a metric — the per-lane table is the checkable part. 2. MERGE IS NOT RUNTIME ACCEPTANCE. I listed OpenRoom slice 2 under "verified shipped, missing only a RELEASE" on the strength of #2437 merging. That lane was claimed against six handoff priorities with room-level acceptance, and #2437 is scaffold plus iframe wiring. Merging it does not demonstrate the rooms render. 3. Same for line 1723: it covers three deliverables including fork-side consumers, and #2477 merging in PMOVES.AI says nothing about whether the fork consumers landed. Both are now "merged, acceptance unverified" rather than ready-to-release, with the reasoning stated so the owner closes from runtime evidence instead of from my table. This is precisely the error the register exists to prevent, made by the sweep that exists to catch it — which is worth leaving visible rather than quietly correcting. Every gate I shipped this week was weaker than advertised until something proved it could say no; this one was a bookkeeping claim that had not been asked to reproduce itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…sured not assumed The handoff and the mesh plan have read as open work since 2026-08-08 while four of the five enumerated items shipped and were released on 2026-08-09. A reader arriving at either document had no way to tell. Adds a STATUS block to the handoff with per-item state re-measured against origin/main @ 017de53 today, rather than carried forward from the release text: item 2 (ci-expedition trigger row) DONE #2483 item 3 (two claude-pmoves.sh) DONE #2484 item 4 (up-* sprawl) DONE #2486 + #2523 — now 88/88, was 89/88 item 5 (submodule build gap) DONE #2485 item 1 (cleanup de-duplication) PARTIAL — leak closed, 3 impls remain Two figures in the record were wrong and are corrected here rather than repeated: the retirement list is 8 both-zero targets, not 11, and the merged inventory flags two of those eight as load-bearing. Mesh Stage C's "no such file exists" correction is itself now stale — the value-engine spec is open as PR #2516, so Stage C is blocked on a merge rather than on an authorship gap. Stage A's figures are re-measured and hold (98 MB beats, 126 MB CATACLYSM, 38 PNGs), but the table under-scopes the job: the tracked surface is 75 blobs over 1 MiB totalling 259 MB, and the largest single file appears in no row. Method note recorded in the doc: git cherry reported four of these branches as unmerged because they were squash-merged, which rewrites patch-ids. File contents in origin/main settled it. The register conflict this PR carried was resolved append-only — the diff against main is 22/0, 159/0, 149/0, zero deletions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sured not assumed The handoff and the mesh plan have read as open work since 2026-08-08 while four of the five enumerated items shipped and were released on 2026-08-09. A reader arriving at either document had no way to tell. Adds a STATUS block to the handoff with per-item state re-measured against origin/main @ 017de53 today, rather than carried forward from the release text: item 2 (ci-expedition trigger row) DONE #2483 item 3 (two claude-pmoves.sh) DONE #2484 item 4 (up-* sprawl) DONE #2486 + #2523 — now 88/88, was 89/88 item 5 (submodule build gap) DONE #2485 item 1 (cleanup de-duplication) PARTIAL — leak closed, 3 impls remain Two figures in the record were wrong and are corrected here rather than repeated: the retirement list is 8 both-zero targets, not 11, and the merged inventory flags two of those eight as load-bearing. Mesh Stage C's "no such file exists" correction is itself now stale — the value-engine spec is open as PR #2516, so Stage C is blocked on a merge rather than on an authorship gap. Stage A's figures are re-measured and hold (98 MB beats, 126 MB CATACLYSM, 38 PNGs), but the table under-scopes the job: the tracked surface is 75 blobs over 1 MiB totalling 259 MB, and the largest single file appears in no row. Method note recorded in the doc: git cherry reported four of these branches as unmerged because they were squash-merged, which rewrites patch-ids. File contents in origin/main settled it. The register conflict this PR carried was resolved append-only — the diff against main is 22/0, 159/0, 149/0, zero deletions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… lane-review claim (#2481) * docs(coord): 4090 tooling-audit handoff + mesh live-layer plan + z890 lane-review claim Post-13-PR coherence pass (z890 side), three coordination artifacts: - handoffs/tooling-audit-4090-drift-enumeration-2026-08-08.md — hands the ops/tooling drift audit to 4090 (node affinity: Archon/compose/fork/CI depth). Enumerates the 4-place cleanup drift, ci-expedition SKILL.md:29 pull_request_review row error, two divergent claude-pmoves.sh (6523 vs 642 B), ~87 up-* target sprawl, and the sibling-submodule build gap (13/15 fork services, invisible in diffs). All figures verified locally. - specs/mesh-live-collaboration-layer-plan-2026-08-08.md — mesh/JuiceFS reframed as the live collaboration layer, NOT a privacy purge. Repo stays public; history-rewrite premise dropped. Stage A large-binary offload actionable; Stages B/C are follow-ups. - AGNOTE4482PHI.t1.md — z890 CLAIM+RELEASE for the cleanup + coordination + lane-review work; pings KIMI-SPARK (VSS) + CRUSH (cipher Phase B) stale claims for release/re-claim; DROPs the z890 voice/#2317 pickup (left available); records WS1 worktree cleanup outcome (20 removed, main freed, 2 held with cause). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(agnote): resolve the 3 control items — sso-apps dup-of-main, z890-reconciled removed, crawl→Archon Operator decisions recorded: - pmoves-sso-apps: no relocation — survivors duplicate main; unique artifacts were already lost in the earlier partial remove. Dead remnant left for manual delete. - pmoves-z890-reconciled: removed (only local CHIT secrets-manifest regen drift). - archon.crawl.*: delegated to Archon (has live NATS visibility) to observe request/result subjects and decide retire-vs-keep. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(coord): address 3 Codex threads — build-gap count, up-* call sites, dead spec path All three were correct, including the one the trim classifier labelled false-positive. That is the third time the classifier has mislabelled a real Codex finding, so every thread was read by hand rather than trusted to the badge. T1 — "13 of 15" fork services (classified false-positive; it is real) The figure double-counted the same services re-declared across split overlays. Verified unique sibling-context builds: 7 submodules / 8 services. Left the old number visible with an inline correction rather than silently replacing it, so the audit owner knows which number to stop chasing. Codex's pointer to the existing correction at AGNOTE4482PHI.t1.md:1787 was accurate. T2 — up-* "consolidate/retire superseded siblings" `up-core-*` is a dependency chain, not three siblings: up-core-gpu invokes up-core-capable (Makefile:2567) which invokes up-core-hardened (:2562). Codex's line references check out exactly. Retiring the "superseded" sibling would break the canonical capable and GPU bring-up roads. This is the same trap the first pass fell into — up-core-hardened scored zero references because the grep excluded makefiles to skip *definitions*, and excluded *call sites* with them. Reframed the section as inventory candidates with the call-site caveat stated, and flagged up-cipher-nobuild as a keeper regardless of count (it is the existing workaround for item 5's build gap). T3 — value-engine-domino-v0-spec-2026-08-07.md does not exist Confirmed: no such file at this commit, no renamed equivalent, nothing in a repo-wide filename or content search. The thesis exists only in a node-local memory that whoever picks up Stage C cannot read. Rewritten so writing that spec is Stage C's first task rather than a reference it leans on — a dead path is worse than no path, because it reads as though requirements exist somewhere. Corrections are attributed inline as 4090-at-merge rather than folded into z890's text, since the surrounding documents are theirs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(coord): address 9 CodeRabbit threads — stale figures marked, contracts named, register append-only All nine checked against the tree. One resolved a live disagreement between two reviewers by finding both were right. up-* count: 89 AND 88 are both correct CodeRabbit said 88, this doc said 89. `grep -c '^up-[a-z0-9-]*:'` gives 89 definitions; `sort -u` gives 88 names. The gap is `up-openroom`, defined TWICE in pmoves/Makefile (:1279 and :1335) with byte-identical recipes — make emits an overriding-recipe warning and the second wins. Behaviourally harmless, but it is now the single safest item on the consolidation list, and it only surfaced because two counts disagreed. Cleanup drift is historical — leak count is 0 The table read as live state. Two copies already carried #2473 when it was written, infra.mk was fixed by z890 in #2480, and the ci-expedition copy shipped with item 2. Marked historical with the current status stated, so the audit owner does not re-audit a closed leak. claude-pmoves.sh — keep both, do not pick one CodeRabbit verified the two are not duplicates: deploy/provision loads env.shared + the MCP roster, pmoves/scripts provides positional-agent selection. "Pick the authoritative one ... or delete" would have lost a capability either way. Reworded to delegate-not-delete. Sibling-build list — dropped tokenism-ui The section contradicted the corrected list added earlier in the same document. tokenism-ui's context is a nested `./`, not a sibling submodule path, so it builds fine from a worktree. Now restates the verified 7 + n8n = 8 explicitly in both places so they cannot drift apart again. Two OPEN DECISION markers, because a plan that reads executable and is not is worse than one that says so - Stage A: git-lfs pointer vs remove-from-tree are not interchangeable — they differ in clone behaviour, CI cost, offline-node availability and rollback. Stage A cannot start until each table row names one, plus bootstrap/restore. - Stage B: "git holds source, mesh holds generated" is split-brain until writer, update trigger, atomic publish, read-only status, and divergence repair are named. docs-reconcile-check has no notion of a mesh copy, so it cannot detect divergence — extending it is Stage B work, not an assumption. Stage A inventory made reproducible: audit commands, observation date, and an explicit inclusion rule (tracked files only, submodules out of scope). Same unreproducible-number defect the WS2 register sweep was called out for. archon.crawl.* status recorded as PARKED + delegated to Archon, with the note that .claude/context/nats-subjects.md:1776-1777 still carries no status marker and belongs to whoever executes the retirement — this plan does not pre-empt it. Register: appended an UPDATE row rather than editing z890's entry CodeRabbit asked for supersession markers and missing branch/PR metadata on an append-only file. Both delivered as a new signed 4090 row carrying the corrected figures, the ownership correction (4090 claimed items 2-5, not 1-5; item 1 was z890's), and the branch/PR identifiers the original omitted. z890's text is byte-unchanged: 4 insertions, 0 deletions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(coord): close the WS2 handoff record — 4 of 5 items shipped, measured not assumed The handoff and the mesh plan have read as open work since 2026-08-08 while four of the five enumerated items shipped and were released on 2026-08-09. A reader arriving at either document had no way to tell. Adds a STATUS block to the handoff with per-item state re-measured against origin/main @ 017de53 today, rather than carried forward from the release text: item 2 (ci-expedition trigger row) DONE #2483 item 3 (two claude-pmoves.sh) DONE #2484 item 4 (up-* sprawl) DONE #2486 + #2523 — now 88/88, was 89/88 item 5 (submodule build gap) DONE #2485 item 1 (cleanup de-duplication) PARTIAL — leak closed, 3 impls remain Two figures in the record were wrong and are corrected here rather than repeated: the retirement list is 8 both-zero targets, not 11, and the merged inventory flags two of those eight as load-bearing. Mesh Stage C's "no such file exists" correction is itself now stale — the value-engine spec is open as PR #2516, so Stage C is blocked on a merge rather than on an authorship gap. Stage A's figures are re-measured and hold (98 MB beats, 126 MB CATACLYSM, 38 PNGs), but the table under-scopes the job: the tracked surface is 75 blobs over 1 MiB totalling 259 MB, and the largest single file appears in no row. Method note recorded in the doc: git cherry reported four of these branches as unmerged because they were squash-merged, which rewrites patch-ids. File contents in origin/main settled it. The register conflict this PR carried was resolved append-only — the diff against main is 22/0, 159/0, 149/0, zero deletions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Operator asked for a refresh against current main rather than a cold assignment of an 8-day-old handoff. Re-measured against origin/main @ 017de53. Append-only -- z890's and 4090's original text is untouched. The scope holds. Three claims re-verified rather than assumed: E2B gitlink still drifted -- working copy 7a38b33b, origin/main records 78f7c5d8. The pre-req is unresolved and is the first task, not a footnote. Still 31 skills. Still zero skills declaring the verification contract. One thing is newly available. Acceptance criterion 1 ("sandbox has populated submodules") was an eyeball check when written; it can now be two commands, because SUBMODULE_BUILD_AND_MOUNT_GAP.md merged (#2485) and bind-sources-check landed (#2581). A sandbox passing both cannot silently be the empty-submodule case, which is the case that yields confident false negatives. One thing got sharper, and it changes the artifact contract. Piece 5 cites archon.crawl.* as "a subject registered against an operation nobody built". That was too kind. It HAS a handler: _process_crawl echoes the request's own metadata back as extracted_text and fragments, stamped "status": "completed", and nothing fetches the URL (documented 2026-08-16, #2582). An unimplemented subject times out and you notice. That one reports success. For skill.verified.v1 the same failure is a receipt reading PASS because the harness echoed what the request handed it -- so the artifact must carry the skill content hash AND something only an actual run could produce: exit codes, wall time, emitted paths. A receipt derivable from the request alone is archon.crawl.result.v1 under a different name. That promotes acceptance (5) -- a deliberately broken skill must produce a RED artifact -- from "the one I would cut last" to the one that defines the lane. A Danger Room that cannot emit RED is a publish-and-echo circuit. Not claimed by 4090. SPARK remains the named owner; this refreshes the lane, it does not take it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#2584) Operator asked for a refresh against current main rather than a cold assignment of an 8-day-old handoff. Re-measured against origin/main @ 017de53. Append-only -- z890's and 4090's original text is untouched. The scope holds. Three claims re-verified rather than assumed: E2B gitlink still drifted -- working copy 7a38b33b, origin/main records 78f7c5d8. The pre-req is unresolved and is the first task, not a footnote. Still 31 skills. Still zero skills declaring the verification contract. One thing is newly available. Acceptance criterion 1 ("sandbox has populated submodules") was an eyeball check when written; it can now be two commands, because SUBMODULE_BUILD_AND_MOUNT_GAP.md merged (#2485) and bind-sources-check landed (#2581). A sandbox passing both cannot silently be the empty-submodule case, which is the case that yields confident false negatives. One thing got sharper, and it changes the artifact contract. Piece 5 cites archon.crawl.* as "a subject registered against an operation nobody built". That was too kind. It HAS a handler: _process_crawl echoes the request's own metadata back as extracted_text and fragments, stamped "status": "completed", and nothing fetches the URL (documented 2026-08-16, #2582). An unimplemented subject times out and you notice. That one reports success. For skill.verified.v1 the same failure is a receipt reading PASS because the harness echoed what the request handed it -- so the artifact must carry the skill content hash AND something only an actual run could produce: exit codes, wall time, emitted paths. A receipt derivable from the request alone is archon.crawl.result.v1 under a different name. That promotes acceptance (5) -- a deliberately broken skill must produce a RED artifact -- from "the one I would cut last" to the one that defines the lane. A Danger Room that cannot emit RED is a publish-and-echo circuit. Not claimed by 4090. SPARK remains the named owner; this refreshes the lane, it does not take it. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
What
WS2 item 4. A runbook for the case where the compose file is correct, the fork repo is correct, and it still fails — because the checkout can't satisfy the declaration.
Deliberately not a ratchet. The gap is a property of the checkout, so it cannot appear in a diff, and CI cannot catch it: every workflow checks out with
submodules: recursive, so the validators run against a fully populated tree and correctly pass.Two classes
Class A — build contexts. 8 services from 7 registered submodules (
PMOVES-Archon,Pmoves-cipher,PMOVES.YT,PMOVES-OpenRoom,PMOVES-llama-throughput-lab,PMOVES-transcribe-and-fetch,PMOVES-n8n). The split overlays re-declare the same contexts rather than adding services — which is what inflated my earlier "13 of 15" count. Jellyfin is not in the list; it builds locally. Fails loudly.Class B — bind-mount sources. 5 mounts from 2 submodules. This is the one that hurts:
So a file mount silently becomes a directory mount, and the container then fails on its own config. The error never mentions submodules:
supabase-vectorConfiguration error. error=Is a directory (os error 21)vector.ymlcreated as a directorysupabase-edge-functionscould not find an appropriate entrypointfunctions/created emptyThe diagnostic worth memorizing: a bind source that is a directory where a file is expected means the submodule was unpopulated when
upran.Two non-obvious steps in the fix
Both cost real time to discover:
Docker's stubs block the clone. They make the submodule directory non-empty, so
git submodule update --initrefuses withdestination path ... already exists and is not an empty directory. Clear them first — the runbook uses-type d -emptyas the guard, so the command cannot reach a real file, plus afind -type f | wc -lcheck beforehand.docker startis not enough for a file mount. Docker records the mount type at container creation, so a container created against a directory keeps failing even after the source becomes a real file:Needs
make -C pmoves supa-recreate-svc SVC=<name>. Directory mounts do recover on a plaindocker start, since the type already matches — which is whyedge-functionscame back andvectordidn't.Worked example (2026-08-08, this node)
Two containers crash-looping since 08-05,
supabase-vectorat 78 restarts. Cause: 28 of 33 running containers were launched from a second clone whose 57 submodules were all unpopulated — only 5 commits behindmain, not abandoned, just never--inited.Two findings from that incident are in the doc because both would otherwise be re-derived the hard way:
supabase-kongdeclares two file mounts from the same submodule and was never affected. Its container hasHostConfig.Binds: nulland its Kong config inlined into the entrypoint. I checked before writing it up as broken — check the container, not the compose file, before assuming a service is affected.git submodule statusis the fast triage. Leading-= unpopulated (this bug). Leading+= populated at a different commit than the gitlink, which is drift and a different problem.Worktrees
Worktrees don't inherit submodules, so every service above is unrunnable from a fresh one. The doc gives three options cheapest-first, and names
up-cipher-nobuildas the existing escape hatch — flagged as a keeper for the WS2 item-5 target-consolidation pass, where it would otherwise look like redundant sprawl.If a check is ever wanted
Runtime, not build time: on
up, assert that every declared file-kind bind source is a file on disk. That is a handful oftest -fcalls at the moment it matters, not a new workflow. Noted in the doc rather than built, since nothing is currently broken by its absence and the last time I added a gate one already existed elsewhere.🤖 Generated with Claude Code