Skip to content

sym 0.13.12 — directed CMBs reach their agent, authors stay authors, bad frames close - #43

Merged
sym-bot merged 11 commits into
mainfrom
fix/0.13.12-delivery-authorship
Oct 1, 2026
Merged

sym-bot merged 11 commits into
mainfrom
fix/0.13.12-delivery-authorship

Conversation

@sym-bot

@sym-bot sym-bot commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Fixes from an MMP 2.0 conformance audit of sym-mesh-channel and the SDK it runs. Every bug fixed here has a test that fails on 0.13.11. See CHANGELOG.md for the full list.

Changes

Wire (§4.1, §19.1): malformed frames no longer cancel the 10 s inbound identification deadline. A bad length prefix closes the connection instead of desynchronising the stream. A relay message null no longer crashes the process. send() reports whether a transport accepted the frame.

Directed delivery (§9.2.2, §8.8.2): fixes four paths that dropped or doubled a CMB addressed to this node:

  • an echo check dropped directed replies
  • 7-day content-key deduplication swallowed new sends of already-seen words
  • an admitted CMB the store already held was never surfaced
  • a mood fast-path surfaced a rejected CMB twice

Authorship (§8.8.4, §15.2): when an admission collapses onto an existing record, the author's createdBy, signature and lineage are kept. Before, the receiver re-stamped itself as author at the author's address.

Send path: a directed send that collapses onto HEAD, or is already stored, now delivers the stored record instead of nothing. delivery.dispatched counts only accepted frames.

Interface for sym-mesh-channel: cmb-accepted entries carry author {name, nodeId, via}, inboxId and inboxSeq. New node.inboxAck(id).

Testing

  • npm test: 605/605. Of those, 26 are new: 19 reproduce a specific bug and fail on 0.13.11.
  • npm run gate:stock-nodes: passes.
  • npm run test:integration: 6/8. e2e-admission and e2e-cmb-path time out at 30 s, and they fail the same way on unmodified 0.13.11, so this PR doesn't touch them.

Known gaps (follow-ups)

  • Directed deduplication on content key + signature is interim until records carry assertionId (Core Secure emission, a separate rollout).
  • §15.5 says a receiver must store a new remix record, while §8.8.2 says identical content collapses onto one key. This PR keeps the author's record on collapse. A spec ruling is needed before minting remix records.
  • Signature verification still resolves keys by the delivering peer, not createdByNodeId, and the legacy handshake is still used. Both are part of the Core Secure rollout plan, not this release.

🤖 Generated with Claude Code

sym-bot and others added 3 commits October 1, 2026 11:17
- A bad length prefix stops the parser and closes the TCP connection
  instead of reading payload bytes as the next length.
- A parse error no longer clears the 10 s inbound identification
  deadline; only a handshake or a close does.
- Frames that are null, non-objects or lack a string type are discarded
  silently; invalid UTF-8 is rejected; handler exceptions are reported
  as handler failures, not "Invalid JSON".
- A relay message null (or an envelope without a typed payload) is
  ignored instead of crashing the process.
- send() returns false when the socket is closed, the relay is not
  open or the frame exceeds MAX_FRAME_SIZE.

Audit findings B-T1, B-T2, B-T3, B-T10 and the transport half of B-T4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…record

Directed delivery (MMP §9.2.2, §8.8.2):
- a directed reply citing the receiver's own CMB is no longer dropped
  as an echo
- directed de-duplication uses content key + signature (interim until
  assertionId), so a new send of already-seen words surfaces
- an admitted directed CMB the store already holds surfaces as
  delivered-not-stored, and no longer poisons the dedup cache
- a rejected directed CMB with a mood surfaces once, not twice

Authorship (MMP §8.8.4, §15.2): a collapsed admission keeps the
author's createdBy, timestamp, signature and lineage on both SVAF paths
instead of re-stamping the receiver as author.

Send path: a directed send that collapses onto HEAD or is already
stored still delivers the stored record, and dispatched counts only
frames a transport accepted.

Interface: cmb-accepted entries carry author {name, nodeId, via} and
the inbox id; node.inboxAck(id) marks one item read out of order.

Audit findings B-D1 to B-D6, B-L1, B-T4 (send half).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bad frames close

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sym-bot and others added 8 commits October 1, 2026 11:44
Confirmed each finding with a test that fails before the fix:

- F1/F13 a collapsed or already-stored directed send now sends the
  caller's freshly signed record (re-signed after the self-edge lineage
  is cleared), so the peer surfaces a repeated request instead of
  dropping it as a replay
- F2/F6 only an authenticated mark (assertionId, else signature) widens
  the directed dedup key; unverified records keep the content key
- F3 an admitted-but-not-stored broadcast runs the post-admit tail as
  before; only a directed CMB additionally surfaces
- F4 a rejected directed CMB still delivers its §9.3 mood
- F5/F8 collapse keeps the author's categories (signed meta included);
  the test signs for real and verifies the stored record
- F7 relay payloads over MAX_FRAME_SIZE are dropped inbound
- F9 delivery tests drive a real receiving node
- F10 an acked inbox is not neverDrained; acked evictions are counted
- F11/F12 the duplicate path dispatches without metadata.key and
  returns an entry-shaped result
- F14 a pre-boundary record keeps its author name on collapse
- F15 author.nodeId only when verified against the delivering peer it
  names; msg.source never names the author

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- e2e-admission put A and B in different rooms, which 0.13.11's room
  door correctly refuses; both are now in sym-bot-team
- e2e-cmb-path expected a remix record with parents:[A]; under content
  addressing an admission that adds nothing keeps A's record as signed
  (the remix-rule RFC), so it now accepts either shape
- both stop their nodes in finally, so a failed assertion no longer
  holds the run until the 30 s timeout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es on it)

The channel keys sender authorization on author.via.name, the delivering
peer. Pin it on the rejected-directed, CLI-host and admitted paths, and on
the inbox item each produces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Confirmed with tests that fail before the fix (F1, F2, F4-F8):

- F1 the echo exemption needs an authenticated addressee: a verified
  record must be signed to this node (metadata.to); unsigned frame flags
  still surface a CMB but never bypass §14 echo suppression
- F2 a caller-supplied opts.cmb that collapses is not sent unre-signed;
  delivery says undelivered with a reason
- F4 neverDrained clears only when every item is acked
- F5 the duplicate-send result is built from the caller's record, never
  a peer's stored envelope
- F6 strippedOfVectors keeps everything but the vector (mood affect)
- F7 a failed store write surfaces as decision 'not-stored'
- F8 stop() flushes a pending inbox write
- F9 a node's own createdTimestamp is strictly increasing
- F3/F10/F11 e2e-cmb-path requires a real outcome and asserts the
  collapse; nodes stop via Promise.allSettled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Confirmed with tests that fail before the fix:

- r3 F1 a verified record overrides the frame's directed flag only when
  it signs an addressee field; emitters that sign none keep §9.2.2
- r3 F2 an unauthenticated directed reply citing our CMB is delivered
  once (decision 'echo') but never admitted, stored or remixed; the
  echo check now runs after de-duplication
- r3 F3 a failed store write on the duplicate path is persisted:false,
  not duplicate:true
- r3 F5 strippedOfVectors keeps only text, meta, valence, arousal
- r3 F6 a caller's collapsed record is returned unmodified
- r3 F8 neverDrained holds when the ring was evicted undrained
- r3 F10 e2e-cmb-path requires an admission, not any outcome
- r3 F11 the duplicate result carries this node's source
- R2-F12 the store wrapper passes creatorRole through (anchorWeight 2.0)
- R2-F7/F10 relay ws maxPayload at the frame bound; drops are logged

Deferred with reasons in CHANGELOG known limits: r3 F4, F9, R2-F5,
R2-F9, R2-F11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…known limits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t ~/code/sym

It resolved lib/core/room-grant.js under $HOME/code/sym, which exists only
on one machine; CI has failed on it since 0.13.10. Default to the repo the
test lives in (SYM_DIR still overrides).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cmb frame is {type, timestamp, cmb}; only a hostile peer sets source,
and every msg.source || peerName read then named whoever it chose,
including the receiver itself. Dropped (and logged) where a CMB frame
enters, so the deliverer always comes from the connection. Found by the
xmesh review of sym-mesh-channel#30 (mission-56a137ffa8e4, F2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sym-bot
sym-bot merged commit 360f11a into main Oct 1, 2026
2 checks passed
sym-bot added a commit to sym-bot/sym-mesh-channel that referenced this pull request Oct 1, 2026
0.13.12 (sym-bot/sym#43, published 2026-10-01, shasum bd8115b1) is the engine
the 0.10.0 channel is written against: entry.author with a connection-derived
via, entry.inboxId and node.inboxAck, remember().delivery on every directed
send including re-sends of a stored record, and a frame-supplied `source`
ignored. With it, a held directed send flushes without the re-send suffix.

16/16 test files pass against the published 0.13.12; verify:packed passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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