feat(cap,svc): IPC peer re-wiring on watchdog rebirth + dead-target unlink (ADR-0023) - #223
Merged
Merged
Conversation
…nlink; ADR-0023 Accepted Part 1 — generalize the epic #175 unlink (cap_revoke_spawn_channels -> cap_revoke_ipc_targets): on process death revoke EVERY CAP_RESOURCE_IPC cap naming the dead pid, tagged or not. No capability outlives the process it names — closes the recycled-pid authority leak for all IPC caps. Self-test survivor assert inverted (the untagged hand-minted cap must now die); the live-target and colliding-resource-id survivors keep guarding against an over-broad revoker. Part 2 — declarative ipc_peers[] on service_definition_t: start_slot re-mints declared pairs on every start inside the grant cli window. Two passes (my running peers; running services declaring me), each mint guarded by process_is_valid + generation (state==RUNNING alone is stale vs the reaper for ~2s — an unguarded mint would reintroduce the exact leak Part 1 closes). Per-direction to/from perms (the ADR-0019 key cap is from-only); pair mints are transactional (a half-minted pair is revoked, never left one-way); Pass 1 strictly before Pass 2 (first-fit slot order is load-bearing for swarm-svc's untargeted discovery). Converted: paradoxd/fieldsyncd/swarm-svc/qsh <-> ghostd + the swarm-svc->fieldsyncd key cap; citizens.c hand mints deleted. swarm_svc now re-forwards its cached session key when it observes a fieldsyncd pid change: the re-mint alone restores only the delivery path — a reborn fieldsyncd would be keyless-but-wired, emitting zero-tag frames keyed peers silently reject. ci-smoke: the scripted session delivers a delayed 'ghost' to the REBORN shell (separate write — cons_read batches a same-burst ghost into the dying shell's chunk) and gates the answer on the post-'QSH: reborn' log slice (the session prints 'ghost R=' twice before rebirth; a whole-log grep would pass vacuously). CAPUNLINK gate string updated with the inverted policy. Verified locally (WSL): full make ci-smoke PASSES end to end, reborn shell answers ghost at t+13s via the declarative re-mint. ADR-0023 flipped to Accepted; ADR index updated (0020-0022 were already Accepted in their files, index rows stale). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Implements ADR-0023 (merged in #222), flipping it to Accepted. Parts 1+2 ship together, per the ADR.
Part 1 — IPC caps die with their target
cap_revoke_spawn_channels→cap_revoke_ipc_targets: on process death, revoke everyCAP_RESOURCE_IPCcap naming the dead pid — the epic feat(kernel): spawn-time parent↔child IPC channels — the society self-assembles #175 origin-tag filter is dropped; the type + dead-target filters stay. No capability ever outlives the process it names (closes the recycled-pid authority leak forSYS_SEND_TOand theSYS_CAP_DERIVEtargeted-peer check).cap_revoke(hand)cleanup is dropped.Part 2 — declarative
ipc_peers[], re-minted on every startservice_definition_t.ipc_peers[]: peer service name + independentto_perms/from_perms(the ADR-0019 key cap is from-only — a symmetric field could not express it and a stray reverse cap would break fieldsyncd's first-match routing).start_slotmints declared pairs inside the same cli window as thegrant_*caps: Pass 1 (my running peers — first boot + my rebirth), then Pass 2 (running services declaring me — their-side declarations + boot-order gaps). Pass 1 strictly before Pass 2 (first-fit slot order is load-bearing for swarm-svc's untargeted discovery).process_is_valid+ generation (state==RUNNINGalone is stale up to ~2s vs the reaper — an unguarded mint would reintroduce the exact leak Part 1 closes).Key re-forward (fieldsyncd rebirth)
The re-mint restores only the delivery path — a reborn fieldsyncd would be keyless-but-wired, emitting seq=0 zero-tag frames keyed peers silently reject. swarm_svc now watches (~2s pacing) for a fieldsyncd pid change and re-forwards its cached session key.
CI gate (anchored + delivery-correct)
ghost(separate write, 15s) to the REBORN shell — a same-burst ghost dies in the old shell's 32-bytecons_readchunk withexit.QSH: rebornslice (awk '/QSH: reborn/{f=1} f') — the session already printsghost R=twice pre-rebirth, so a whole-log grep would pass with the feature entirely broken.Verification (local WSL, full runs)
make ci-smokePASSES end to end — includingSUCCESS: reborn shell re-acquired its ghostd IPC pair (declarative re-mint, ADR-0023); timed boot shows rebirth at t≈6s and the reborn shell answering ghost at t≈13s.ipc_peersdeclaration → boot healthy, rebirth happens, 0ghost R=answers, all three ghostsEPERM-denied, and the anchored gate fails — non-vacuous in both directions.Design was adversarially attacked pre-implementation (4 lenses; 3 blockers + 5 majors folded into the ADR — see #222).
🤖 Generated with Claude Code