feat: manage a running offbook instance from any directory (D-032, R-044–R-047) - #19
Merged
Merged
Conversation
…-aware pidAlive, probeServer (R-044)
…tate-dir hermeticity (R-045)
…y; guarded failed-boot clear (R-044)
…arFailedBoot (R-044)
… ours fallback (R-045)
…k, port attribution (R-045)
…on; heal host rule (R-045)
…ows resolution (R-045)
…port identity mode (R-045)
…andling in down (R-045)
…ide resolution (R-045)
…veCtrlPort (R-045)
…s escalation (R-045)
…nt-gated handoff (R-044)
…stancy pin (R-044, R-045)
…ce, onboarding skill (R-047)
- adoption.md $10 staleness: merge the doubled 'reads ... boot record' clauses into one (resolve, then read services.yaml via the RESOLVED run dir's boot record). - adoption.md $10 + SKILL.md port-conflict recipe: restore the trailing '; check the others separately if they persist' clause to the quoted unproven-owner hint, matching doctor.ts/index.ts byte-for-byte.
…y what it checked
Two D-032 defects found by the final whole-branch review.
launchDetached reclaimed on `existing.live === false`, which is also true
for an alive pid with a silent control port (a wedged instance, or one
inside its 30s boot window) and never looked at `host`; the clear was
unguarded and its note ("pid N is gone") was false whenever the pid was
alive. So the natural retry `offbook up` deleted a wedged instance's
runfile and pointer -- breaking M12's promise that `offbook down` still
stops it -- and on a shared network home an `up` from the wrong machine
deleted a foreign live instance's records. Now: row 10 refuses with M10,
row 3 refuses with the new M23, and only row 5 (provably dead, local)
reclaims, through guarded site #2. `up`'s refusals stay exit 1, matching
its already-running convention (an operational error, not a selector
refusal).
`topics --json --run-dir <dir>` with nothing live printed M11: "no runfile
in .offbook, and nothing else is running on this machine". Under an
explicit --run-dir the resolver deliberately never scans the registry, so
both clauses were unchecked claims -- and this is the exact command the
onboarding skill's step 6 mandates as the first-light check. cmdTopics now
mirrors targetFor's explicit-path carve-out (extracted as runDirRefusal),
naming the directory actually checked. The human demo fallback (M0 gate
ii) is untouched.
…dentity claims
contracts.md §5 carried two clauses that disagreed: P7's "a stale runfile
(pid dead, or the control port silent) is auto-reclaimed" against D-032's
deletion law ("a live-pid record is only ever skipped"). Contracts is
canonical, so P7 is amended to the law it contradicted, and the
supersession is recorded as a one-sentence appendix on D-032 (the existing
grep-evidence fence is untouched).
D-032's release note and daily-loop.md both claimed `--run-dir` consumers
see behavior byte-identical to pre-D-032. True of live-instance output;
not true of refusals, which now carry the --json envelope (and, after this
round, the run-dir-qualified wording inside it). Both now claim pinned
targeting semantics and point scripted consumers at the exit code and
error.code instead of the wording. R-043's body loses the stale
"run-dir-qualified message" phrasing for the same reason.
The 2026-08-19 full campaign left 88 undetected mutants across resolve.ts, registry.ts and runfile.ts. 67 are killed here with behavioral tests; 21 are argued (and measured) equivalent — see .superpowers/sdd/mutation-kill-report.md for the per-mutant disposition. New behavior pinned: row 10 inertness on both the cwd and the registry path (a foreign-host runfile is never reclaimed and sets foreignSeen), the boot file's naming of skipped instances and its fallbacks, token-over-runDir proof, --run-dir preferring the dir's own runfile, a silent cwd instance not hiding the registry's live one, row 9 refusing to heal from a mismatched runDir or a foreign host and re-recording the canonical pointer when it does heal, the tiebreak running on the served projectDir with stage 2 strict, the demo note naming exactly the passed-over demo, attributeCtrlPort's negatives, registry shape/name/dedupe rules, and runfile's shape check, clear idempotence and probe classifications.
…ation The focused verification run (95.44% overall) left 22 survivors. 21 are the accepted equivalents argued in the hardening round; the 22nd, runfile.ts:138 (probeServer's AbortSignal.timeout), was never triaged — it was killed in the first campaign and only surfaced here. It is a real gap, not an equivalent: without the signal the identity probe never gives up on a listener that accepts and never answers, so every verb that resolves an instance hangs with it. Killed with a bound-not-hung test against a raw accept-only listener. Records: D-032's obligation (1) is marked discharged in place (grep-evidence fence untouched) and a mutation appendix carries both score sets and all 21 accepted equivalents with their arguments; AGENTS.md's status sentence replaces the obligation clause with the discharged outcome.
…view ruling) M10 is pinned exit-2 in the catalog and D-032 enumerates wrong-host under the refusal contract; shipping the same catalog id with two exit codes per verb reintroduces the drift class the catalog exists to prevent. launchDetached returns a wrong-host sentinel both call sites map to exit 2; the demo --serve flow is now asserted, not just intended. M23 (already-running family) stays operational at exit 1.
…o mirror to R-045
mutation gate: pass (score 100.00, break 100)Mutated 4 file(s): src/cli/guard.ts, src/cli/registry.ts, src/cli/resolve.ts, src/cli/runfile.ts |
… gate is deterministic at 100
…precise attribution
…independent The gate's second CI run flipped the registry dedupe MethodExpression mutant from killed to survived: its killing test discriminated only when readdir presented the non-canonical twin first (tmpfs does, ext4 did not). scanPointers now sorts its listing; the dedupe is pinned in BOTH scan orders via name-constructed twins plus a return-order pin, each verified by hand-applying its mutant. The gate's port-collision nondeterminism (EADDRINUSE crash-kills at concurrency 4, sandbox-orphan poisoning) is recorded as open intake with the STRYKER_MUTATOR_WORKER namespacing seam as the recommended fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Manage a running offbook instance from any directory (D-032, R-044–R-047). Previously every management verb had to run from the directory where
offbook upwas started — an undocumented cwd premise that confused first-light and daily use.--watchrespawns) + host recorded in the runfile and boot file;GET /v1/serverserves the identity (plain 404 on pre-D-032 servers);probeServerclassifies answerers asserver/legacy/silent.${OFFBOOK_STATE_DIR:-${XDG_STATE_HOME:-~/.local/state}/offbook}/instances/, a cwd-first-then-registry resolver implementing a 10-row instance state table, guarded compare-and-act mutation at every deletion site, a deletion law (a live-pid record is only ever skipped, never reaped), and self-heal for dangling pointers.down,status,logs,topics,specs update, doctor) resolve through one front door;up [dir]starts a project's instance without cd; refusals print paste-ready--run-dirselector tables with a 0/1/2 exit-code contract and typed--jsonenvelopes;downgains identity-gated kill-safety (compare-and-signal, token-verified SIGKILL escalation, ESRCH-in-race = success).Notable finds along the way
--watchrespawn inherited a deleted launch cwd (posix_spawn resolves the caller's cwd), dying silently with no successor. Fixed with explicit spawn cwd at both spawn sites plus an event-gated handoff.up's stale-runfile reclaim contradicting the deletion law the branch itself froze into contracts.md (it could delete a wedged or foreign-host instance's records);upnow refuses instead, with the dead-pid clear behind the guarded compare-and-clear discipline.probeServerhad no test bounding a hang on a listener that accepts but never answers; the retry bound is now pinned.Verification
check-docs0,lint0,typecheck0,demo-app:build0, fullbun test0 (674 pass / 0 fail).guard.ts100%,runfile.ts97.25%,resolve.ts95.14%,registry.ts93.67%; 68 of 88 undetected mutants killed by discriminating tests (each verified by hand-applying the mutant), the 21 measured equivalents recorded with arguments in the D-032 appendix.test/instance-discovery.test.tsrow map).~/.local/state(suite-wideOFFBOOK_STATE_DIRpin intest/preload.ts; the registry throws without an injected state dir).Process
22-task plan executed subagent-driven: fresh implementer per task, per-task spec+quality review, fix waves with re-reviews, a final whole-branch review (its three Important findings fixed and re-verified, including one exit-code adjudication: wrong-host refusals on
up/demo --serveexit 2 like every verb), and a mutation-hardening wave. Final review verdict: ready to merge, no residual concerns.