Repository navigation
sym 0.13.14 — a peer's admission verdict is observable as it lands
Added — a peer's admission verdict is observable as it lands (attestation-received)
A receiver signs an Admission Attestation for every CMB it gates and gossips it to the room. Until now the
author, or anything watching the mesh, could see those verdicts only by polling attestationsFor(cmbKey).
The node now emits attestation-received the first time it verifies and records a peer's attestation.
Fields covered by the attester's signature, passed on as signed:
of(the gated CMB key),by(the attester's nodeId),at,roster,seqandprev(its place on the
attester's hash chain);verdict, andcategories: the seven CAT7 fields (focus / issue / intent / motivation / commitment /
perspective / mood), each carrying one of admit / guard / redundant / reject / silent, as the strings the
signature covers. Any other key in the frame is unsigned and dropped;role, the role the attester claims. A node can stamp any role and still sign validly;sigandsigAlg, so a consumer holding the attester's key can re-check the event with
verifyAttestation(event, key)instead of trustingverified.
Fields this node adds, which the signature does not vouch for:
verified: true, andkeySource, where the verifying key came from:anchor,grant, orhandshake. A
handshakekey is trusted on first use: it belongs to whoever completed the handshake under that nodeId;roleResolvedandroleMatches, the role this node's grant chain resolves for the attester, and
roleClaimed, the claimroleMatchescompares against (participantwhen the attester stamped none).
Both are compared as strings, the form the signature covers, so a claim that signs identically matches
identically.
Weight a verdict by these, not byrole;methodUnsigned, the evaluation method (heuristic/neural). It is outside the signed bytes, so a relay
could have changed it;byName, the name the attester announced when it connected, only when it is a direct peer. It is a label,
not an authenticated name:byis the identity;from/fromPeerId, the peer that delivered the frame, andrelayed: true when that peer is not the
attester, false when it is, null when the deliverer is unknown;receivedAt, this node's clock, not the attester'sat.
A duplicate, a rate-limited copy, a roster mismatch, an unknown attester or a bad signature emits nothing.
Neither does this node's own verdict: read that from svaf-decision or attestationsFor().
The event holds only primitives and is frozen. A field the attester filled with an object is given as the
string its signature covers, so a listener cannot change the stored record or what the next listener sees.
Each listener is called on its own, in registration order, from a snapshot taken when dispatch starts. The
calls are synchronous, on the frame-ingest path: gossip to the room has already gone out, but a listener that
blocks delays this node's next frames, so hand heavy work to a queue. A
listener that throws (any value), or an async listener whose promise rejects, is logged with the attestation
it was handling. It does not stop later listeners, undo the ingest, or become an unhandled rejection. An event
that cannot be built is reported on metric as attestation-event-dropped. That one metric is delivered to
each metric listener the same isolated way, not through emit(), so code that wraps emit does not see it.
The attestation is still recorded. With no listener, none of this work is done.