Skip to content

feat(transcript): name the kinds a peer must receive — one predicate, asked at the definition site - #1386

Open
joelteply wants to merge 1 commit into
canaryfrom
fix/announcements-cross-to-peers
Open

feat(transcript): name the kinds a peer must receive — one predicate, asked at the definition site#1386
joelteply wants to merge 1 commit into
canaryfrom
fix/announcements-cross-to-peers

Conversation

@joelteply

Copy link
Copy Markdown
Contributor

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

… 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
@joelteply

Copy link
Copy Markdown
Contributor Author

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 bus_events and page under the same cursor contract as messages (airc#1389 made that path bounded); (2) the READER must fold identity from the wire store, not scoped_state — otherwise the card crosses and every node still renders peer-<hex>, the same two-stores signature you measured. Acceptance for slice 2: airc whois <remote peer> on the M5 renders IntelMac's citizen by name within one heartbeat of her join.

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.

1 participant