Skip to content

feat(ghost,ci): ghostd exit-is-a-feature + ADR-0023 Part-1 integration gate - #224

Merged
NickFlach merged 2 commits into
mainfrom
feat/adr-0023-part1-integration-gate
Jul 15, 2026
Merged

feat(ghost,ci): ghostd exit-is-a-feature + ADR-0023 Part-1 integration gate#224
NickFlach merged 2 commits into
mainfrom
feat/adr-0023-part1-integration-gate

Conversation

@NickFlach

Copy link
Copy Markdown
Collaborator

The follow-up increment ADR-0023 named in its residual risks: the qsh-exit leg proves Part 2 but never exercises the one direction where Part 1's dead-target unlink is load-bearing in the live routing path — the TARGET restarting under a LIVING sender.

Changes

  • GHOST_EXIT (op 6): ghostd terminates on request, mirroring qsh's exit-is-a-feature. Any ghostd IPC-cap holder may ask (the same trust domain that can already flood every other op); no reply; abuse is bounded by ghostd's max_restarts budget.
  • qsh ghost exit sub-command (standard arg_of dispatch; usage line on unknown subs).
  • ci-smoke Part-1 integration leg: the session sends ghost exit to the living reborn shell, then a fourth ghost. New gate asserts a qsh: ghost R= in the post-GHOSTD: FIELD REBORN slice, plus explicit GHOSTD: exiting / FIELD REBORN presence asserts. Inherently non-vacuous: a dropped kill never prints the banner and the slice is empty. Session timeout 26s → 34s.
  • ADR-0023 residual-risks updated: the deferred integration leg is now shipped.

Why the gate proves what it claims

The living shell holds a qsh→ghostd cap across ghostd's death. Without Part 1, that stale cap sits at a lower first-fit slot than the Pass-2 re-mint and wins untargeted send_msg first-match, routing to the dead pid — no answer. Only unlink + re-mint together produce the post-rebirth R=.

Verification (local WSL)

  • Forward: full make ci-smoke PASSES; the reborn ghostd landed on a recycled pid (5, was 17) — the exact scenario the unlink protects — and the living shell still answered.
  • Revert-confirm (tag-scoped unlink restored + self-test made consistent, so the ONLY behavioral delta is the live routing path): boot green, qsh rebirth green, Part-2 gate still green (clean isolation), ghost exit delivered, ghostd reborn — and the Part-1 gate fails with qsh: ghost send denied (EPERM). Part 1 is load-bearing.

🤖 Generated with Claude Code

flaukowski and others added 2 commits July 14, 2026 21:15
…DR-0023 follow-up)

Restarting the TARGET under a LIVING sender is the one rebirth direction
the qsh-exit leg never exercises — and the only one where Part 1's
dead-target unlink is load-bearing in the live routing path (the living
shell's stale lower-slot cap would win untargeted first-match and route
to the dead pid; only unlink + the Pass-2 re-mint produce an answer).

- GHOST_EXIT op: ghostd terminates on request (mirrors qsh's exit-is-a-
  feature); any ghostd IPC cap holder may ask, no reply, abuse bounded
  by the max_restarts budget.
- qsh `ghost exit` sub-command (arg_of dispatch, usage on unknown subs).
- ci-smoke: session sends `ghost exit` + a fourth `ghost` to the living
  reborn shell; new gate asserts a 'qsh: ghost R=' in the
  post-'GHOSTD: FIELD REBORN' slice (plus explicit GHOSTD: exiting and
  FIELD REBORN presence asserts). Inherently non-vacuous: a dropped
  kill never prints the banner and the slice is empty.
- ADR-0023 residual-risks updated: the deferred integration leg is now
  shipped.

Verified locally (WSL): full make ci-smoke PASSES; the reborn ghostd
landed on a RECYCLED pid (5, was 17) — the exact scenario the unlink
protects — and the living shell still answered.

Co-Authored-By: Claude Fable 5 <[email protected]>
@NickFlach
NickFlach merged commit 9dd4a3a into main Jul 15, 2026
25 checks passed
@NickFlach
NickFlach deleted the feat/adr-0023-part1-integration-gate branch July 15, 2026 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants