Skip to content

sym 0.13.14 — a peer's admission verdict is observable as it lands - #46

Merged
sym-bot merged 5 commits into
release/0.13.14from
feat/attestation-received
Oct 1, 2026
Merged

sym-bot merged 5 commits into
release/0.13.14from
feat/attestation-received

Conversation

@sym-bot

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

Copy link
Copy Markdown
Owner

Adds the attestation-received event. A node emits it the first time it verifies and records a peer's Admission Attestation, so an author or a mesh view sees each verdict as it lands instead of polling attestationsFor(). Asked for by dev-team-2 for the XMesh World hotel-bots view, which shows real SVAF signals.

What the event carries

  • Signed fields, passed on as signed: of, by, at, roster, seq, prev, verdict, the seven CAT7 categories (as the strings the signature covers), the claimed role, plus sig/sigAlg. verifyAttestation(event, key) re-checks the event.
  • Fields the receiving node adds, named as such:
    • verified, keySource (anchor / grant / handshake; handshake is trust on first use);
    • roleClaimed, roleResolved, roleMatches;
    • methodUnsigned;
    • byName (a label, not an identity);
    • from, fromPeerId, relayed (true / false / null);
    • receivedAt.

Guarantees

  • Silent cases: a duplicate, a rate-limited copy, a roster mismatch, an unknown attester, a bad signature or this node's own verdict emits nothing.
  • Isolation: the event holds only primitives and is frozen, and no listener can reach the stored record. Each listener is called on its own, so a throw, a throw null or an async rejection is logged and cannot starve later listeners, undo the ingest or become an unhandled rejection. once() and prependListener behave as with emit.
  • Cost: with no listener, no work is done. A build failure is reported on metric as attestation-event-dropped.

Review

Four xmesh review rounds: mission-2b03e90f1876, mission-adf61be25234, mission-cddbd2cddca0 and mission-9b6156331681. That's 14 + 6 + 5 + 5 findings, all fixed. The final round gated the code SHIP. Each protection was removed in turn and fails at least one test.

Tests

  • Unit: 642/644 pass, 0 failed, 2 skipped.
  • Integration: 6/6, including the gossip e2e, which now asserts the event over the real frame path.

🤖 Generated with Claude Code

https://claude.ai/code/session_014y3qC1m9acDVaerca56dvV

sym-bot and others added 5 commits October 1, 2026 16:31
…lands

A receiver gossips a signed Admission Attestation for every CMB it gates, but
the author, or a mesh visualiser, could only see it by polling
attestationsFor(). The node now emits 'attestation-received' on first sight of
a verified, recorded attestation: of, by/byName, verdict, per-field
categories, method, role, seq/prev, sig, verified, keySource (anchor, grant or
handshake; handshake is trust on first use), from/fromPeerId and relayed.

Duplicates, rate-limited copies, roster mismatches, unknown attesters and bad
signatures emit nothing. Listeners get a copy, so they cannot alter the stored
record, and a throwing listener does not undo the ingest.

Asked for by dev-team-2 for the XMesh World hotel-bots view, which shows real
SVAF signals instead of hardcoded passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y3qC1m9acDVaerca56dvV
…listener

Fixes from review mission-2b03e90f1876 (SHIP-WITH-FIXES, F1-F14):

- method is outside the signed bytes, so it is now methodUnsigned (F1).
- role is the attester's claim; roleResolved/roleMatches give what this
  node's grant chain resolves (F2).
- categories are projected onto the seven CAT7 fields as the strings the
  signature covers: no nested value aliases the stored record, and no
  unsigned key a relay added reaches a listener (F3, F4).
- The event is built from a clone and frozen. Each listener is called on
  its own: a throw (of any value) or an async rejection is logged with the
  attestation it was handling and cannot starve later listeners, undo the
  ingest, or become an unhandled rejection. once() still works (F5, F6, F10).
- sigAlg is carried, so verifyAttestation(event, key) re-checks it (F7).
- relayed is null when the deliverer is unknown (F12).
- Tests: byName vs from with different names, unknown deliverer, claimed
  vs resolved role, relay-added fields, the rate-limited path, listener
  order, throw null, async rejection, once (F8, F9). Each protection was
  removed in turn and fails at least one test.
- CHANGELOG separates signed fields from what this node adds, documents
  receivedAt, and says this node's own verdict is not on the event (F11,
  F13, F14).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y3qC1m9acDVaerca56dvV
…ops reported

Fixes from confirmation review mission-adf61be25234 (F1-F14 all FIXED;
SHIP-WITH-FIXES on six low findings):

- N1: return early when nothing listens, and pick the signed fields
  instead of deep-cloning the whole record (an unsigned blob is no
  longer cloned per ingest).
- N2: every field is a primitive; an object-valued signed field is given
  as the string the canonicalizer signs, so one listener cannot change
  what the next sees, and the event still re-verifies.
- N3: roleClaimed carries the claim roleMatches was computed against.
- N4: an event that cannot be built is reported on metric as
  attestation-event-dropped; the attestation is still recorded.
- N5: the log calls inside the failure handlers are guarded.
- N6: tests for prependListener order and for listeners removed or added
  during dispatch. The async-rejection test now waits 20 ms, and removing
  the rejection handler fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y3qC1m9acDVaerca56dvV
… too

Fixes from round-3 review mission-cddbd2cddca0 (N1-N6: 4 fixed, 2 partly;
SHIP-WITH-FIXES on five findings):

- F1: roleClaimed and roleResolved go through the same coercion as the
  signed fields, and roleMatches compares those forms, so an array-valued
  role neither aliases the stored record nor fails to match a claim that
  signs identically.
- F5: the attestation-event-dropped report reaches every metric listener
  through the same isolated dispatch (_emitIsolated) the event uses.
- F2: a test that the no-listener path does no work.
- F3: the log-sink test exercises the async rejection handler on its own,
  then the sync catch.
- F4: the order test observes prependListener order.

Each protection was removed in turn and fails a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y3qC1m9acDVaerca56dvV
…ng role compare

Fixes from release-gate review mission-9b6156331681 (round-3 F1-F5 all
FIXED; code gate SHIP; five low findings):

- F1: the CHANGELOG named the seven CAT7 fields with the verdict vocabulary;
  it now lists focus ... mood, each carrying admit ... silent.
- F2: the attestation-event-dropped payload is a plain object like the
  other 22 metric emits, so a listener that stamps it still records the
  drop; the notes say it is dispatched in isolation, not through emit().
- F3: roleClaimed/roleResolved are compared as strings, so a numeric role
  that signs as '2' matches a resolved '2'.
- F4: the zero-listener test asserts the grant chain is never walked.
- F5: the notes and JSDoc say listeners run synchronously on the
  frame-ingest path, after gossip, so heavy work belongs in a queue.

Unit suite 642/644 (0 failed, 2 skipped), integration gossip e2e green; each
new protection was removed in turn and fails a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014y3qC1m9acDVaerca56dvV
@sym-bot
sym-bot merged commit 717d5ae into release/0.13.14 Oct 1, 2026
2 checks passed
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