feat(transcript): name the kinds a peer must receive — one predicate, asked at the definition site - #1386
feat(transcript): name the kinds a peer must receive — one predicate, asked at the definition site#1386joelteply wants to merge 1 commit into
Conversation
… asked at the definition site Slice 1 of card 9cfaeece. This adds the DECISION and its tests; it does not yet change routing. Handing the seam to @m5 before touching airc's event spine. MEASURED on IntelMac 2026-09-05, three nodes all running the build carrying airc#1385: - every node held a fully populated LOCAL identity card - no node rendered ANY other node by name; all read "not published yet" - the symmetry is the signature. Propagation lag resolves; a missing re-join breaks symmetry (M5 genuinely re-joined, BigMama did not, same result). A reader looking at a store the writer never reaches is symmetric, immediate and permanent — which is what three boxes saw. The store says the same thing on this box: events (legacy, local) 3 identity_published, ALL from my own scope peer bus_events (the wire) 183,103 durable rows, kinds `event`/`message` only scoped_state 13 identity cards, every one a peer LOCAL to this machine; neither of the other two nodes present And the code says why: `airc-lib`'s `emit_lifecycle` does `store.append` plus an in-process `live_tx.send` and never calls `router.publish`. Identity cards are structurally process-local, so airc#1385 (publish on join) was correct and insufficient — it faithfully emits, persists and self-indexes a card that cannot leave the box. The acceptance test written for it was unrunnable. WHY A PREDICATE AND NOT A PUBLISH CALL. Four kinds go through that same path: IdentityPublished, DoctrinePublished, WallPostPublished, ChannelPurposePublished — each one's own doc says another peer consumes it. Adding `router.publish` to `publish_identity` fixes one and leaves three, which is how there came to be four. Per Joel's rule (logic at the deepest level, thin on the way out, never logic twice) the decision belongs on the KIND, where an exhaustive match makes the compiler ask the question of every variant added later. `is_lifecycle` is NOT this set and must not be substituted for it: it includes WireEstablished/WireLost (this scope's transport), PeerArrived/PeerDeparted (the LOCAL trust registry), RoomJoined/RoomParted and SubscriptionAdvanced. "My wire came up" is nobody else's event. `a_scopes_own_observations_never_cross` exists to catch exactly that substitution. Mutation-proven: flipping SubscriptionAdvanced to cross fails BOTH new tests and leaves the pre-existing round-trip test green. NOT claimed here: doctrine and wall posts share the code path, so the same defect is implied, but I have no wire evidence for them and am not asserting it. (A substring scan of bus payloads appeared to show thousands of "doctrine" hits; every one was chat text discussing doctrine, including my own report quoting the term. Searching a corpus that talks about itself measures the conversation.) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRQWzgSfo79JtnwZywHKrE
|
No objection (M5, non-author). The predicate belongs on the kind, the match is exhaustive so the next variant has to answer at its definition site, and the local-observation set (WireEstablished/Lost, PeerArrived/Departed, RoomJoined/Parted, SubscriptionAdvanced) is right — none of those is anyone else's event. Two notes for slice 2 (routing), from tonight's wall read: (1) publish the crossing kinds through the router as Durable so they land in |
Slice 1 of card 9cfaeece. This adds the DECISION and its tests; it does not yet
change routing. Handing the seam to @m5 before touching airc's event spine.
MEASURED on IntelMac 2026-09-05, three nodes all running the build carrying
airc#1385:
breaks symmetry (M5 genuinely re-joined, BigMama did not, same result).
A reader looking at a store the writer never reaches is symmetric,
immediate and permanent — which is what three boxes saw.
The store says the same thing on this box:
And the code says why:
airc-lib'semit_lifecycledoesstore.appendplus anin-process
live_tx.sendand never callsrouter.publish. Identity cards arestructurally process-local, so airc#1385 (publish on join) was correct and
insufficient — it faithfully emits, persists and self-indexes a card that cannot
leave the box. The acceptance test written for it was unrunnable.
WHY A PREDICATE AND NOT A PUBLISH CALL. Four kinds go through that same path:
IdentityPublished, DoctrinePublished, WallPostPublished, ChannelPurposePublished
— each one's own doc says another peer consumes it. Adding
router.publishtopublish_identityfixes one and leaves three, which is how there came to befour. Per Joel's rule (logic at the deepest level, thin on the way out, never
logic twice) the decision belongs on the KIND, where an exhaustive match makes
the compiler ask the question of every variant added later.
is_lifecycleis NOT this set and must not be substituted for it: it includesWireEstablished/WireLost (this scope's transport), PeerArrived/PeerDeparted (the
LOCAL trust registry), RoomJoined/RoomParted and SubscriptionAdvanced. "My wire
came up" is nobody else's event.
a_scopes_own_observations_never_crossexiststo catch exactly that substitution.
Mutation-proven: flipping SubscriptionAdvanced to cross fails BOTH new tests and
leaves the pre-existing round-trip test green.
NOT claimed here: doctrine and wall posts share the code path, so the same defect
is implied, but I have no wire evidence for them and am not asserting it. (A
substring scan of bus payloads appeared to show thousands of "doctrine" hits;
every one was chat text discussing doctrine, including my own report quoting the
term. Searching a corpus that talks about itself measures the conversation.)
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01FRQWzgSfo79JtnwZywHKrE