6. Layer 3: Memory
MMP defines three memory layers with graduated disclosure:
| Layer | Name | Shared | Description |
|---|---|---|---|
| L0 | Events | No | Raw events, sensor data, interaction traces. Local only. |
| L1 | Structured | Via evaluation | Content + tags + source. Shared via cmb frames, gated by SVAF (Layer 4). |
| L2 | Cognitive | Never (§2.7) | CfC hidden state vectors. Strictly local — never cross the wire. Drive the node’s own inference; peer influence arrives only as CMBs. |
L0 data MUST NOT leave the node. L1 data MUST be evaluated by SVAF before storage. L2 data
(CfC hidden state, h1/h2)
MUST NOT leave the node either — per the hidden-state locality invariant
(Section 2.7),
hidden state is strictly local and only CMBs cross the wire. The
state-sync frame that formerly carried these vectors is
deprecated; implementations MUST NOT emit it and SHOULD ignore it on receipt.
6.1 Storage Interface
Implementations MUST provide a storage interface for L1 CMBs. The SDK SHOULD define a pluggable storage protocol so agents can provide their own backend. The reference implementations provide a default file-based store; agents MAY replace it with any backend that satisfies the interface:
| Method | Access | Description |
|---|---|---|
| write(entry) | Write | Store a CMB created by this agent. Returns nil if duplicate key. |
| receiveFromPeer(peerId, entry) | Write | Store a remixed CMB after SVAF acceptance. |
| search(query) | Read | Keyword search across CMB category texts. |
| recentCMBs(limit) | Read | Most recent CMBs for SVAF fusion anchors. |
| allEntries() | Read | All entries for context building (capped by implementation). |
| count | Read | Total stored CMB count. |
| purge(retentionSeconds) | Write | Remove CMBs older than retention period. MUST preserve CMBs referenced by newer entries’ lineage. |
Read-only agents (audit, compliance, monitoring): implement write methods as no-ops. The agent observes the remix graph without modifying it. This is valid for agents whose role is to trace provenance, verify lineage integrity, or report on mesh activity.
6.2 Storage Backends
The protocol does not prescribe a storage backend. Agents choose based on their platform and requirements:
| Backend | Best for | Notes |
|---|---|---|
| Flat JSON files | CLI agents, daemons, prototyping | Default in reference implementations. Zero dependencies. Content-addressable filenames. |
| CoreData / SwiftData | iOS / macOS apps | Queryable, supports iCloud sync, handles retention via NSBatchDeleteRequest. |
| SQLite | Cross-platform, high volume | Indexed queries, ACID transactions, handles millions of CMBs. |
| Cloud (Supabase, DynamoDB) | Distributed teams, multi-device | Shared audit trail. Consider privacy — CMB category text is personal data. |
| In-memory | Testing, ephemeral agents | No persistence. Useful for unit tests and short-lived agents. |
6.3 Retention
Implementations MUST support configurable retention via retentionSeconds.
CMBs older than the retention period SHOULD be purged automatically.
See Section 19 (Configuration) for
per-profile retention defaults.
Purge MUST preserve graph integrity: a CMB reachable from any retained entry by recursively
following verified metadata.lineage.parents MUST NOT be
deleted, even if past retention age. The remix chain is the audit trail — breaking it breaks provenance.
The Canon tier New in 1.1.0.
A CMB whose lifecycle is validated or
canonical MUST NOT be evicted by age-based retention (compaction
or purge) while it holds that lifecycle. Committed cognition is the store’s reason to exist:
canonical requires validation plus remix by two or more agents, so a
small or single-operator mesh may never produce it — if only canonical
were protected, such a mesh would forget everything it validated within one retention period. Protection is
from purge, not from demotion: a validated CMB with no activity MAY still decay to
archived per the §6.4 lifecycle
(archiveAfterSeconds, §19), after which ordinary retention
applies — the escape valve that keeps the store bounded (implementation status:
§17.6).
canonical deliberately has no
inactivity decay: it records collective consensus (validation plus independent remix), and consensus does
not expire by silence — it leaves the Canon only by an explicit dismiss or archive under
validator-or-above authority (§6.5).
Regulated domains (legal, finance, health) MUST set retention according to their compliance requirements. The protocol does not define regulatory retention periods — consult jurisdiction-specific guidance (MiFID II, SEC Rule 17a-4, HIPAA, GDPR).
6.4 CMB Lifecycle
Each CMB progresses through a lifecycle that determines its influence on future SVAF evaluations. The lifecycle is driven by mesh activity — not by time alone.
| State | Temperature | Trigger | Anchor Weight | Description |
|---|---|---|---|---|
| observed | hot | Agent calls remember() | 1.0 | Initial observation. Subject to temporal decay. Active in SVAF fusion. |
| remixed | warm | Peer remixes this CMB (appears in lineage.parents) | 1.5 | Another agent found this signal relevant enough to produce new knowledge from it. Higher anchor weight in future SVAF evaluations. |
| validated | warm | Human acts on this CMB (marks decision as done) | 2.0 | A human confirmed this signal by acting on it. The validation CMB carries lineage.parents pointing to the validated CMB. Validated knowledge shapes future evaluations more than unvalidated signals. Protected from retention purge while validated (§6.3, Canon tier). |
| dismissed | cold | Human dismisses this CMB (not actionable) | 0.5 | A human reviewed and rejected this signal. Reduced anchor weight. Broadcasts to mesh as feedback — producing agent sees its signal was rejected. MUST NOT resurface as an actionable decision. |
| canonical | cold | Validated + remixed by 2+ agents | 3.0 | Collective consensus — multiple agents and a human agree this knowledge is significant. Protected from retention purge. Highest anchor weight. |
| archived | whisper | No remix for archiveAfterSeconds (default: 30 days) | 0.5 | No agent has found this signal relevant. Reduced anchor weight but preserved for lineage integrity. MAY be purged if no descendants reference it. |
The lifecycle branches at human judgment: observed → remixed → validated → canonical (upward path) or observed → dismissed (downward path). Dismissal is a terminal state — a dismissed CMB does not advance to validated or canonical. Without any activity, a CMB decays toward archived. Archived and dismissed CMBs MAY re-emerge if a future remix references them — re-entry resets the archive timer.
Validation is the key transition that connects human judgment to the mesh. When a human
acts on agent output (approves a decision, sends an email, completes a task), the action SHOULD
be recorded as a new CMB with lineage.parents pointing to the CMB that
prompted the action. This validation CMB enters the mesh like any other signal — agents
receive it via SVAF and adjust their understanding. The mesh learns from human actions without
special API calls or out-of-band configuration updates.
Anchor weight influences SVAF evaluation: when computing per-category drift against local anchors, canonical and validated CMBs contribute more to the fused anchor vector than observed or archived CMBs. This creates a natural hierarchy where human-confirmed knowledge and collective consensus outweigh raw observations — without overriding agent autonomy. Each agent still evaluates incoming signals through its own category weights.
6.5 Validation Authority
The transition from remixed to validated is
the most consequential lifecycle event — it commits human or authorised-agent judgment to the mesh and
permanently increases anchor weight from 1.5 to 2.0. This transition MUST be restricted to nodes with
appropriate lifecycle roles (Section 3.5).
When a receiving node processes a validation CMB (one whose lineage.parents
points to an existing CMB), it MUST resolve the author’s role against its own in-force set
(§6.6) at the moment it processes the CMB — never from the createdBy
string, never from the peer’s advertised handshake role, and never from a time the CMB claims:
- —If the author resolves to validator or above (validator, admin or the anchor), the parent CMB advances to
validated(if action completed) ordismissed(if not actionable). - —Otherwise the parent CMB advances to
remixedonly. The CMB is stored normally but confers no validation. - —A scoped grant (§6.6.2) confers this only on a parent CMB that is inside the grant’s scope, judged from that parent’s own signed fields. A receiver MUST NOT judge it from the validation CMB’s own fields or tags. When a validation CMB names several parents, each is judged on its own: a parent inside the scope advances, and one outside it does not. An issuer, which admits nodes, confers no validation anywhere.
The transition is the receiver’s own record, made once, against its in-force set at the moment it is made; a later revoke does not undo it in that store (§6.6.10). An outcome that several parties must agree on is not decided this way: the environment it belongs to decides it once and records it (§6.6.10).
This prevents agent-level spoofing of validation authority. An agent cannot self-promote to validator by including “founder” or “validator” in its CMB text categories. The authority is bound to the node’s cryptographic identity and key, through an in-force grant from the anchor or an admin (Sections 3.5.1 and 6.6).
Role verification & admission weight. Authority is the resolved role, never the advertised
one. A node MUST NOT grant any authority-weighted treatment — lifecycle advancement, or the elevated
origin admission weight the CMBs of a validator, an admin or the anchor receive (§6.4) — on the basis of a
handshake lifecycleRole or a createdBy
string. That origin weight MUST derive from the author’s role as resolved against the receiver’s
in-force set (§6.6) when the weight is applied, and the elevation
additionally requires a verified signature binding the CMB to that author. A single node that could
self-declare anchor would otherwise double the admission weight of
everything it emits — the highest-leverage poisoning primitive — which is exactly why the weight is
gated on the resolved role. Where no anchor is pinned there is no root of trust: an implementation has no
cryptographic authority to resolve and MUST treat all roles as unauthenticated — a closed/development
mode only. Production deployments MUST pin an anchor.
Dismiss vs. validate: These are distinct lifecycle transitions with different consequences.
Validate (Done): parent CMB advances to validated (anchor weight 2.0).
The mesh learns what humans value. Dismiss (Not actionable): parent CMB advances
to dismissed (anchor weight 0.5). The dismissal broadcasts as feedback —
the producing agent sees its signal was rejected, and similar future signals score lower in SVAF evaluation.
Both require validator role or above. Both broadcast to the mesh.
The effectiveness of this feedback depends on the content quality of the dismissal CMB —
see Section 11 (Feedback Modulation) for
normative content requirements.
Boundary attestation. The same validation authority governs cognition that crosses a mesh boundary. A gateway node (Section 5.10) that emits a lossy projection of its interior on the interior’s behalf SHOULD sign that boundary emission and record, in its admission attestation, the interior verdict aggregate it was derived from — so an outer admitter can weigh the boundary claim by earned authority exactly as it weighs any peer. Interior and boundary trust are the same mechanism at two scales; see Section 5.11.
6.6 Authority Lifecycle: Grants, Resolution & Revocation
Authority that cannot be lost is decoration. This section defines how a role is conferred, how a node decides which authority is in force, and how authority is withdrawn — the mechanism §6.5 gates on.
Authority is a function of a set. A node’s view of authority is computed from the set of verified
authority statements it holds, and from nothing else: not the order they arrived in, not the time on any clock,
not the session that delivered them. Statements name one another by the hash of their signed bytes, so the
graph they form is fixed when they are signed. Two nodes that hold the same statements under the same pinned
anchor MUST resolve the same authority. No timestamp takes part in resolution: a statement MAY carry a
time as information (issuedAt), and a node MUST NOT let that time,
its own clock or the order of arrival affect what is in force.
Why (informative). The rule this section replaces resolved a node’s role at a time T by replaying grants and revokes in the order of times their signers chose. Any role holder could grant further down, grants were gossiped in any order, and revoking a grantor cascaded. Every node therefore had to rebuild one timeline from signer-chosen times, statements by many writers, and arbitrary arrival order, with nothing to order them. That is consensus without a consensus mechanism. An implementation of it diverged on arrival order, accepted backdated revokes, could be flooded until honest records were evicted, and resolved some revoke patterns in exponential time. Resolving a set removes the ordering problem instead of patching it.
6.6.1 The anchor
- —A pinned key set with a threshold. The anchor is configured out of band as a set of n distinct Ed25519 public keys, 1 ≤ n ≤
ANCHOR_MAX_KEYS(16, §19.1), and a threshold t, 1 ≤ t ≤ n. Each key MAY be pinned with the nodeId of the node that holds it. The pin is configuration a receiver already trusts, never a claim on the wire: a node MUST NOT learn, change or persist its pin from any frame. - —The single anchor stays valid. A pin of one nodeId and its key, as earlier revisions defined it, is the set of one key with t = 1.
- —Anchor-level statements. A statement whose
authorisedByis"anchor"is valid only if it carries signatures over its payload (§6.6.3) by at least t distinct pinned keys, each verifying under §18.3.2. Each entry’s key is unique within the statement (§6.6.3, rule 1), so a copy that repeats a key is not well formed and none of its signatures is checked. Entries by keys that are not pinned, and signatures that do not verify, are not counted. A node stores and relays only the entries it counted. Each copy of a statement is judged on its own entries: a node MUST NOT combine entries from different copies, so that validity never depends on which copies a node happened to receive. The key holders collect their t signatures among themselves, out of band. - —The anchor is not a granted role. No grant confers
anchor. Under a threshold of 1 each pinned member alone is the anchor, whether the pin holds one key or several: its single signature makes an anchor-level statement valid, and a CMB it authors resolves as the anchor (§3.5) when its nodeId is pinned with its key. Under a higher threshold no single node is the anchor, and each key holder holds whatever roles in-force grants give it. - —Re-pinning. Replacing the pin out of band is the last resort. It re-judges every anchor-level statement against the new pin: a statement that still carries t valid signatures from the new set stays valid, and the rest fall away with everything they authorised. Statements do not name the pin. A re-pin that keeps a threshold of the old keys, for example to drop one compromised key of three, therefore keeps what those keys signed. A re-pin to new keys starts authority again. After a re-pin a node re-verifies the copies it holds: a copy that no longer counts is replaced by any copy that does, which anti-entropy brings from a node that holds one. A node keeps the statements it holds independently of its pin, and judges them again under the current pin whenever the pin changes or it loads them. It MUST NOT delete a statement because it fails under the current pin, so a re-pin, or a mistyped pin, destroys nothing: what fails under one pin counts again under a pin it meets.
- —No pin. Where no anchor is pinned, nothing is in force (§6.5), and there is no authority root, since a root names a pin (§6.6.7). A node with no anchor pinned therefore sends no
authority-digest, pulls nothing, answers noauthority-fetch, and stores and relays no authority statement it receives; statements it already holds stay as they are, to be judged when a pin is configured. It MUST NOT rate-limit or penalise a session for authority statements it cannot judge.
6.6.2 Roles and delegation
| Role | Granted by | May grant | May revoke / endorse | Lifecycle (§3.5) |
|---|---|---|---|---|
| anchor | pinned, never granted | admin, validator, issuer, any non-authority role | revoke any grant; endorse any grant or revoke that is not anchor-level | canonical (as an author, only under a threshold of 1) |
| admin | the anchor or an admin | admin, validator, issuer, any non-authority role | revoke and endorse below it on its own chain | validated, canonical, inside its scope |
| validator | the anchor or an admin | non-authority roles only | revoke below it on its own chain; no endorse | validated, inside its scope |
| issuer | the anchor or an admin | non-authority roles only | revoke below it on its own chain; no endorse | none |
| participant, extension roles | the anchor, an admin, a validator or an issuer | nothing | nothing | observed, remixed |
- —Delegating roles are
admin,validatorandissuer: their holders may sign statements, and a bucket counts their grants against its delegate quota (§6.6.6). Lifecycle authority (§3.5) belongs to admin (canonical) and validator (validated) only. An issuer has none, so a service that admits seats need not hold validation authority over anything. Non-authority roles areparticipantand extension roles, named as §7.3 names extension types (<extension>-<name>orx-<vendor>-<name>, lowercase, at most 64 characters). A non-authority role confers no lifecycle authority, no admission weight above participant, and no right to sign any statement. MMP authority roles are therefore the anchor, admin, validator and issuer. An extension MAY give a non-authority role meaning in its own domain, such as a seat in a hosted world; it MUST NOT turn one into an authority role. - —A node with no in-force grant is a participant. A
participantgrant adds only a vouched key and a signed record that someone above admitted the node. - —Scope. A grant MAY carry a signed
scope: an extension namespace and a path, such asxmesh-world:w1orxmesh-world:w1/region-a, of at mostAUTHORITY_SCOPE_MAX(256) characters (the grammar is in the schema). No scope is the whole mesh. A grant’s scope MUST equal or narrow its authorising grant’s: the same string, or that string followed by/and more segments. A grant under a scoped grant therefore MUST carry a scope, because leaving it out means the whole mesh, which would widen. An anchor-level grant may have any scope or none. A grant whose scope would widen its parent’s is invalid (§6.6.3, rule 4). Scope paths are opaque: they are compared exactly, byte for byte, and never normalised (no case folding, no percent-decoding, no resolution of.or..). No segment may consist only of dots, soxmesh-world:w1/../w2is not a scope at all, and nothing can look like a step up to a reader that would resolve it. Narrowing adds whole segments:xmesh-world:w10does not narrowxmesh-world:w1. The lifecycle authority and the elevated admission weight of a scoped grant apply only to CMBs inside its scope. The extension that owns a namespace defines which CMBs are inside each of its scopes, for example by room or by world. A receiver that does not implement a namespace treats no CMB as inside its scopes, so a scoped grant gives it no lifecycle authority. A scope does not change who may revoke or endorse a grant, nor what its holder may sign beyond keeping its own grants inside it. - —Depth. The anchor is at depth 0. An anchor-level statement is at depth 1, and a statement authorised by a grant at depth d is at depth d + 1. A statement deeper than
MAX_DELEGATION_DEPTH(4, §19.1) is invalid. Admins may grant admins, so the role table alone does not bound depth; the cap does. - —Why four. The deepest chain with a separately accountable party at every hop is anchor → deployment admin → world admin → seat issuer → seat. Those are: the anchor’s key holders; the operator of a hosting deployment; the operator of one world hosted there; the service that admits seats to that world, which holds the issuer role so that it admits seats without validation authority; and a seat. That is four grants. A fifth hop would add no new kind of boundary, because what it would express is expressed by granting wider, not deeper. Each extra hop is one more key whose compromise reaches everything below it, and one more link that every check walks. The cap also bounds every chain to four grants, so a revoke check walks at most three ancestors and a fetched chain is at most four statements. The issuer leaves the arithmetic of §6.6.6 as it is: it is a delegating grant in its world admin’s bucket, and its seats are non-authority grants in its own. The draft bound of 8 belonged to the time-replay rule, under which validators promoted validators.
- —What MMP authority governs (informative). Grants decide who may attach to a mesh and act in it: hosts, seats, publishers, validators. Roles inside a simulation or a game, such as a commander or a scout, are simulation data kept in that environment’s own records. They are not MMP grants. A world’s own laws, fines and verdicts are that world’s records too: they are not MMP validation, and MMP authority neither grants nor checks them. A seat grant is per admitted node: it names one nodeId and key, not a sitting, so a node that comes back keeps its seat until the grant is revoked. With the constants of §19.1 a world admin’s 16 issuers admit up to 16 × 256 = 4,096 nodes per grant it holds. Grants do not expire, and no expiry is intended: authority ends by revoke.
6.6.3 Statements
Three statements carry all authority. A grant confers a role on a subject, a nodeId together with its
Ed25519 key, optionally within a scope (§6.6.2). A revoke names, by id, the grants it removes. An
endorse names, by id, the grants and revokes to keep in force after a revoke has cut them off from the
anchor (§6.6.5). Each names in authorisedBy the authority it is made under: "anchor" for an
anchor-level statement, otherwise the id of the signer’s own grant. A signer that holds several grants names
the one whose role permits the statement and, for a revoke or endorse, one that stands above its targets (§6.6.4).
Every statement carries a nonce of 16 random bytes, so a fresh grant of the same role to the same subject
is a different statement.
{ "kind": "grant",
"authorisedBy": "anchor" | "auth-<64 lowercase hex>",
"subject": { "nodeId": "<uuid>", "key": "<Ed25519 public key, base64url>" },
"role": "admin" | "validator" | "issuer" | "participant" | "<extension role>",
"scope": "xmesh-world:w1", (optional; omitted means the whole mesh)
"nonce": "<16 random bytes, base64url>",
"issuedAt": 1786611600000, (optional; information only)
"sigs": [ { "key": "<base64url>", "sig": "<Ed25519 signature, base64url>" } ] }
{ "kind": "revoke", "authorisedBy": "...", "targets": ["auth-...", ...], "nonce": "...", "sigs": [ ... ] }
{ "kind": "endorse", "authorisedBy": "...", "targets": ["auth-...", ...], "nonce": "...", "sigs": [ ... ] } Canonical signed bytes and identity. lp and decimal are as in §8.8.4. Targets are unique on
the wire, and their wire order is not signed. A nodeId is signed in its canonical lowercase RFC 4122 text form,
8-4-4-4-12 lowercase hexadecimal digits. Any other spelling is not well formed, so one node has one spelling, and
one statement one id.
payload = UTF8("mmp-authority-v1\n") ||
lp(kind) ||
lp(authorisedBy) ||
grant: lp(subject.nodeId) || lp(subject.key) || lp(role) || lp(scope or "")
revoke, endorse: lp(decimal(targetCount)) || concat(lp(target) for bytewise-sorted targets)
|| lp(nonce) ||
lp(decimal(issuedAt) or "")
id = "auth-" || lowercase hex(SHA-256(payload))
sig = Ed25519(signing key, payload) (pure Ed25519, no prehash; unpadded base64url)
The id excludes the signatures. A statement re-signed by a hedged signer (§17.4), or carrying a different subset
of anchor signatures, is the same statement. A node MUST compute the id itself; no frame carries a
statement’s own id. A non-anchor statement carries exactly one signature entry, whose
key is the key that signed it. Every signature verifies under the one rule of §18.3.2.
Validity. A statement is valid when all four hold:
- Well formed. It validates against authority-frame.schema.json; every base64url field is canonical (a key and a signature decode to 32 and 64 bytes, a nonce to 16); its subject nodeId is canonical (above); its subject key is the encoding of an Ed25519 point of prime order (§18.3.2), so no grant can name the identity or any other small-order or mixed-order key;
issuedAt, if present, is at most 253 − 1; its scope, if present, follows the scope grammar; its targets are unique and number 1 toAUTHORITY_MAX_TARGETS(64); and the keys of its signature entries are unique, so a statement that repeats a key is not well formed. A node checks this rule before any signature work. The schema and the reference construction accept exactly the same shapes, with two exceptions JSON Schema cannot express: the point check, and unique keys across entries (the schema refuses only identical entries). - Rooted and shallow. Following
authorisedByfrom the statement through statements the node holds reaches"anchor"withinMAX_DELEGATION_DEPTHlinks. If a link within that walk is not held, the statement is pending, not invalid (§6.6.8). If the walk would need more links, the statement is invalid. - Signed. An anchor-level statement as §6.6.1 says. Any other by the key its signature entry names, which MUST be the subject key of its authorising grant, verifying under §18.3.2.
- Permitted. Its authorising statement is a valid grant (or the anchor), and that role permits it under §6.6.2: the role table for a grant, a delegating role for a revoke, an admin or the anchor for an endorse. A grant’s scope equals or narrows its authorising grant’s.
Validity is static: it depends on the statement and its chain, never on revokes, endorsements or quotas. A receiver MUST check it on ingest and MUST discard an invalid statement, which it MUST NOT store or relay.
6.6.4 Resolution
Over the set of valid statements a node holds:
- —The chain of a statement is the grants above it: its authorising grant, that grant’s authorising grant, and so on, up to and not including the anchor. A grant g is above s if g is on the chain of s. The chain is fixed by the hashes: it never depends on what is in force.
- —A bucket is the statements that share an
authorisedBy; the anchor-level statements form one bucket. Everything in a bucket is signed by one signer under one grant, at one depth. A bucket keeps statements: its own, and those its endorses rescue (§6.6.6). - —A revoke r may remove a grant g when r is anchor-level or is authorised by a grant above g. These are the anchor, and the holders of grants on g’s chain above it. No signer can remove its own grant, a grant above its own, or a grant on another branch.
- —An endorse e may rescue a grant or revoke s when s is not anchor-level, and e is anchor-level or authorised by a grant above s’s authorising grant. These are exactly the signers that may remove s’s authorising grant. An endorse is never rescued: an endorse that names an endorse does nothing to it.
- —A revoke or endorse acts target by target. A target it may not remove or rescue, a target the node does not hold, and, for a revoke, a target that is not a grant, are unaffected, and the statement still applies to its other targets.
- —A statement is cut by a revoke when its authorising grant is not in force and, walking up its chain past the grants that are dead, the first grant that is not dead is removed. When that first grant is over quota instead, the statement is cut by a quota.
- —A statement is alive when it is anchor-level or its authorising grant is in force. A grant or revoke that is not alive is rescued when it is cut by a revoke, an in-force endorse that may rescue it names it, and that endorse’s bucket keeps it (§6.6.6). A statement cut by a quota is never rescued. A grant is removed when an in-force revoke that may remove it names it.
- —A revoke or endorse is in force when it is alive and its bucket keeps it; a revoke also when it is rescued. A grant is in force when it is not removed, and either it is alive and its bucket keeps it, or it is rescued. Every in-force statement is kept by exactly one bucket: its own, or its rescuer’s.
Every statement therefore has exactly one status: invalid, pending, dead (valid, but neither alive nor rescued), removed, over quota (no bucket has room for it), or in force. The authority vector pins these statuses. A node MUST resolve its in-force set as this algorithm does, and MAY compute it incrementally, provided the result is the same:
resolve(H): H = the valid statements a node holds, as a set
index H: bucket each statement by its authorisedBy; list, for every id, the
revokes and endorses that name it as a target, in ascending id order
F := {} the in-force set
for d := 1 .. MAX_DELEGATION_DEPTH:
phase A: revokes and endorses at depth d
for each bucket B at depth d whose authorising grant is in F (or the anchor's):
keep(B, s) for each revoke and endorse s of B, in ascending id order
for each other bucket at depth d: its endorses are dead; its revokes that are
cut by a revoke are rescue candidates, the rest are dead
rescue(s) for each candidate s, in ascending id order
phase B: grants at depth d
for each bucket B at depth d whose authorising grant is in F (or the anchor's):
each grant g of B with removed(g) is removed; keep(B, g) for each other
grant, in ascending id order
for each other bucket: a grant not cut by a revoke is dead; one that is removed
is removed; the rest are rescue candidates
rescue(g) for each candidate g, in ascending id order
return F
keep(B, s): if B has kept Q(B) statements, or s is a delegating grant and B (not the
anchor's) has kept AUTHORITY_DELEGATE_QUOTA of them: s is over quota;
otherwise B keeps s and s is in F
rescue(s): for each endorse e in F that names s and may rescue it, in ascending id
order: keep(bucket of e, s), stopping at the first that has room;
s is over quota if one tried and none had room, dead if none could
cut by a revoke(s): past the dead grants on s's chain, the first is removed
removed(g): some revoke r in F names g, and r may remove g (r is settled: phase A)- —The order is well founded. An endorse that may rescue a statement sits at a lower depth than it, and the endorse’s own bucket is settled before any rescue it keeps. A revoke that may remove a grant sits at the same depth or lower, and phase A runs before phase B. Whether a statement is cut by a revoke or by a quota depends only on grants above it, at lower depths. A revoke can never remove a grant on its own chain, and an endorse can never rescue one, since either would need a statement above itself. So no statement’s standing depends on itself, and the rescues one bucket keeps are served in ascending id order.
- —Cost is linear. Each statement is validated once, walking at most four links. It is placed in one bucket once. Each (revoke or endorse, target) pair is checked once against a chain of at most four, and deciding whether a statement was cut off walks at most three grants. Resolution is therefore O(S + T) for S statements naming T targets in all, choosing the lowest ids of a bucket by linear-time selection (sorting a bucket, or the endorses that name one target, costs a logarithmic factor). No rule recurses, so the revoke patterns that made the replay rule exponential have nothing to multiply.
- —Roles follow the key. A node’s roles are those of the in-force grants whose subject is its nodeId and its key, each with its scope, plus
anchorfor each pinned member of a threshold-1 anchor (§6.6.1). Its lifecycle authority over a CMB (§3.5) is the highest of the roles whose scope contains that CMB: anchor or admin (canonical), then validator (validated). Issuer and the non-authority roles confer none.
6.6.5 Revocation and endorsement
- —Revocation is effective. A removed grant confers nothing. Everything it authorised, and in turn everything those authorised, is dead unless rescued. Removing an admin’s grant takes its whole subtree with it, in one statement.
- —Dead statements. A removed signer’s dependents are dead. A node MUST NOT relay a dead statement and MAY drop it, unless it is in the node’s live set (§6.6.7) as a grant on the chain of an in-force statement. A dropped statement that arrives again is resolved afresh like any other: it is dead unless an endorse has rescued it in the meantime, and a node that dropped it gets it back only from a node that kept it (§6.6.7).
- —Nothing is un-revoked. While its revoke is in force a grant stays removed. To restore authority, a signer above issues a fresh grant: the fresh nonce makes it a different statement. A fresh grant does not revive the old grant’s dependents, because they name the old id. Demotion is a revoke, followed if wanted by a fresh grant of a lesser role.
- —Rescue reconnects what a revoke cut off. An endorse keeps the grants and revokes it names, and everything that hangs from them, once a revoke has cut them off from the anchor. One use is when an admin leaves and the anchor keeps the validators it appointed. Another is when a key is retired and the grants its holder made should stand. Rescue never brings back a statement cut off by a quota: a grant over quota stays out, and so does everything below it. The endorser answers for what it names. Its bucket keeps what it rescues, counted like its own statements, and a rescued delegating grant takes one of its delegate slots (§6.6.6), so rescuing a delegate costs exactly what granting one would.
- —An endorse never restores its signer. The removed signer’s grant stays removed. Anything it signs afterwards is a new statement that no endorse names, because an endorse can only name statements that already exist: their ids are inside its signed bytes. A removed signer therefore cannot regain authority through an endorsement, and an endorse by a statement’s own signer rescues nothing: it is in force only while that signer’s grant is, and then the statement needs no rescue.
- —An endorse is never rescued. When an endorser’s own authority goes, what it rescued is cut off again. A signer above that wants it kept endorses it directly, which it may, since it stands above everything the endorser could reach.
- —An endorse does not override a revoke. A rescued grant that an in-force revoke may remove is still removed. When it rescues a subtree, an endorser SHOULD also endorse the removed signer’s revokes within it. Otherwise those revokes die, and what they removed returns.
- —An endorse of a statement whose authorising grant is still in force changes nothing until that grant is removed, so a signer may endorse before it retires a grant. To withdraw an endorse, remove its endorser’s grant, or revoke the endorsed grant directly; the endorser and anyone above it may. An endorser between a remover and the grant it removes keeps what it names: a remover that wants a whole subtree gone also revokes what is endorsed, or removes the endorser.
6.6.6 Quota
- —Per signer, per grant. A bucket keeps at most Q statements:
AUTHORITY_QUOTA(256) for a bucket authorised by a grant, andAUTHORITY_ANCHOR_QUOTA(4096) for the anchor’s. A bucket authorised by a grant also keeps at mostAUTHORITY_DELEGATE_QUOTA(16) delegating grants (admin, validator, issuer). A bucket keeps, first, its own statements, when its authorising grant is in force: revokes and endorses in ascending id order (bytewise), then the alive grants that are not removed, in ascending id order, skipping a delegating grant beyond the delegate quota. Then, at greater depths, it keeps the statements its endorses rescue, in ascending id order, while it has room for them under both limits. A statement no bucket keeps is over quota: it is not in force, a node MUST NOT relay it, and a node MAY drop it unless it is in its live set (§6.6.7). A receiver SHOULD report a signer whose bucket is full to its operator. - —Deterministic. Which statements a bucket keeps depends only on the statements, never on which arrived first. Dead statements and removed grants do not count against any quota. Which delegates survive near the cap is decided by statement id, not by the order a signer issued them in. A signer that needs particular delegates to stand keeps below the cap, or asks for a further grant.
- —One signer never displaces another. A bucket holds only statements signed under one grant, so a flood by one signer can only push out that signer’s own statements. Pending statements are held per session (§6.6.8) and never count against any bucket. A bucket keeps its own statements before any it rescues, so a rescue never displaces its endorser’s own statements either. The statements an endorser rescues compete only for that endorser’s room.
- —Removals first, so the quota fails closed. A flood of grants cannot push out a revoke. A signer that floods revokes displaces only its own grants.
- —Node capacity. A node MAY limit how many statements it stores. What it protects at the limit is what is in force, not a kind of statement. Validity is static: a removed signer can still sign any number of valid revokes, all of them dead, and an in-force holder can sign revokes beyond its quota. Protecting revokes by kind would let either crowd out honest in-force statements and grow storage without bound. When a node reaches its limit it first drops statements outside its live set, which it may drop anyway; dead and over-quota statements are ordinary candidates there, revokes and anchor-level statements included. Then it drops live statements in reverse authority order (§6.6.8): deepest first, grants before revokes and endorses at the same depth, and the highest id first. It never drops an in-force revoke or an in-force anchor-level statement. A revoke or an anchor-level statement that arrives at a full node is protected only if it would be in force once held. Then it displaces the last statement in that order that is not protected. Otherwise it is an ordinary candidate, like any other statement outside the live set. The protected set is bounded by the quotas of this section: in-force anchor-level statements by
AUTHORITY_ANCHOR_QUOTA(4,096), and in-force revokes by the 256 statements each bucket keeps, in buckets whose grants are themselves in force. So protection cannot grow storage without bound. A capacity refusal is not a relay failure, and a node MUST NOT rate-limit or penalise the session that delivered the statement. So “removals first” and “fails closed” stay true under a storage limit: what a full node loses is the deepest delegated authority, never a removal in force. A node that has dropped live statements no longer resolves the same set as its peers, and it SHOULD report this to its operator. A storage limit is the node’s own, outside resolution: the quota rules above decide what every node resolves alike, and this rule decides only what a full node gives up first. - —Why per grant rather than per key. First, it keeps selection well defined depth by depth. A key can hold grants at several depths, and counting per key would let its statements at one depth decide which of its statements survive at another. Second, it gives a way to compact. A signer near its quota, or one that needs more than 16 delegates, is given a further grant, which is a further bucket. To compact, its grantor then removes the old grant and endorses what should stand, and everything else under the old grant is dead and can be dropped. The anchor has no grantor, which is why its quota is larger; re-pinning is its compaction.
- —The bound, and why it holds. Below a holder X are the statements whose chain contains X’s grant. A signer above X may choose to rescue some of them; its own bucket keeps those, and they and what hangs from them are that signer’s choice, not X’s, so the figures below leave them out. Every other statement in force below X is kept by a bucket whose authorising grant is X’s grant or an in-force delegating grant below it. That is its own bucket when its authorising grant is in force, or its rescuer’s bucket, whose grant is in force because an endorse is never itself rescued. Each such bucket keeps at most Q = 256 statements and at most D = 16 delegating grants. Each in-force delegating grant below X is kept by exactly one such bucket and lies deeper than that bucket’s grant. So these buckets form a tree under X’s bucket in which every bucket has at most D children and depth strictly increases. Only grants at depth 3 or less have buckets, since a depth-4 grant’s statements would be at depth 5. A depth-1 holder therefore has at most 1 + D + D2 = 273 buckets below it, and so at most 273 × 256 = 69,888 statements and 273 × 16 = 4,368 delegating grants in force below it. A depth-2 holder has at most 17 buckets, 4,352 statements and 272 delegating grants; a depth-3 holder one bucket, 256 statements and 16 delegating grants. All of it dies with one revoke of X’s grant. The authority vector carries the attacks that broke the first version of this rule, each within these figures, and a tree that reaches the figure exactly.
6.6.7 Properties and the authority root
- —Order independence. The same set gives the same answer. Every rule above refers only to which statements are held, their ids, their signatures and their fixed chains. The evaluation order is fixed by depth and id, not by arrival. Statements that are invalid, pending, dead, removed or over quota have no effect on which statements are in force. The node keeps those of them that are grants on the chains of in-force statements, because it verifies those statements with them. Together with the in-force set they make the node’s live set, which a node MUST keep. Whether it keeps or drops the rest changes nothing for the set it holds now. A dropped statement can matter later, though: an over-quota grant comes into force when a lower one leaves its bucket, and a dead one when an endorse for it arrives. A node that dropped it gets it back only from a node that kept it, so a node SHOULD keep over-quota and dead statements while it has room. A signer whose statement was dropped everywhere sends it again.
- —No cycles. A statement names others only by ids inside its own signed bytes, and an id is a hash of those bytes. A statement can therefore only name statements that already existed when it was signed. A cycle would need a statement whose bytes contain, directly or through others, its own hash, which SHA-256 makes infeasible. The references form a DAG. The depth cap bounds every walk even against a statement crafted to loop.
- —Anti-entropy is set union. Statements are immutable and resolution is a function of the set, so exchanging statements and taking the union is all that synchronisation does. No merge rule or tie-break between nodes is needed. Nodes compare authority roots to learn whether they agree (§6.6.8). Because held sets only grow and resolution is a function of them, repeated exchange between connected nodes converges, and it has converged when their roots match.
The authority root names one in-force set under one anchor:
pinDigest = lowercase hex(SHA-256(UTF8("mmp-anchor-pin-v1\n") ||
lp(decimal(threshold)) ||
lp(decimal(memberCount)) || concat(lp(member) for bytewise-sorted members)))
member = key || ":" || nodeId when the threshold is 1 and the pin names the node holding
the key; otherwise key
root = lowercase hex(SHA-256(UTF8("mmp-authority-root-v1\n") ||
lp(pinDigest) ||
lp(decimal(inForceCount)) || concat(lp(id) for bytewise-sorted in-force ids)))- —It is taken over the in-force set, not the held set. Held sets legitimately differ between nodes that agree: dead and over-quota statements may be dropped, and pending ones are held only in memory. In-force sets of nodes that agree are identical. The live set (the in-force set with the grants on its members’ chains) is also enough to recompute the in-force set, so a root can be checked from statements alone.
- —It names the pin. Authority is relative to the anchor. Two different pins never share a root, and a re-pin changes it. Under a threshold of 1 a member’s nodeId is part of the pin, because it decides who is the anchor. Under a higher threshold nodeIds decide nothing and are left out of the digest, so two nodes that pin the same keys and threshold share a root whether or not they annotate them.
- —Checking a claimed root. To check a root against a set of statements, a node verifies every statement against the pin, resolves the set, and recomputes the root of the resulting in-force set. The claim holds if and only if the two roots are equal. Supplying the live set is enough. A statement added to it, or left out, that changes what is in force changes the root.
- —Binding a run or a decision to a root. An environment here is an application at Layer 7 (§14) that runs work and records its outcomes: a Core Secure participant (§17.1) in that role, such as XMesh. MMP defines no separate conformance class for it, so the two requirements that follow bind such an application and are checked by its own tests, not by the MMP vectors. An environment that binds a run, an episode or a recorded decision to an authority root MUST keep that root’s live set itself. Peers serve only their current set (§6.6.8), so an old root’s statements cannot be fetched later. Inside a deterministic run the environment MUST apply mesh weights (§6.6.10) against the root it admitted for the current tick, never against its node’s live in-force set.
- —Runs and ticks (informative). A run binds to a root, not to “the current set”. A replay against a later, larger set, one that includes a revoke that arrived afterwards, would otherwise resolve differently. When authority changes during a run, the environment admits the new root into its own input stream at a tick it chooses, so that a replay is deterministic. When a seat’s grant dies during a run, the hosted world decides the consequence inside the run at the tick it admits the new root, for example taking the seat’s agent out of play. MMP requires only that the change enter at one tick. That ordering, and that consequence, are the environment’s job, not MMP’s.
6.6.8 Frames, gossip and anti-entropy
Four peer-scope frames (§7.1, authority-frame.schema.json) carry the set, on confirmed sessions only (§5.2). A statement is verified on its own signatures and chain, whichever session delivered it. A session confers no authority on what it carries, and a session that delivers a statement which fails verification SHOULD be rate-limited.
{ "type": "authority-statement", "statement": { ...one statement... } }
{ "type": "authority-digest", "root": "<64 lowercase hex>", "count": 7 }
{ "type": "authority-fetch", "reqId": "af-<16 hex>", "ids": ["auth-...", ...] }
{ "type": "authority-fetch", "reqId": "af-<16 hex>", "after": "" }
{ "type": "authority-set", "reqId": "<the request's reqId>",
"statements": [ ... ], "missing": ["auth-...", ...], "next": "<opaque cursor>" }- —Gossip. When a statement first enters a node’s in-force set, the node relays it once, as an
authority-statement, to its other sessions. The same holds for a statement that enters because another arrived, such as a pending statement whose chain completed. A node MUST NOT relay a statement that is not in force, except as a chain member inside anauthority-set. A revoke is relayed as soon as it enters, since the revoke window (§6.6.12) runs until it arrives. - —Budget. A receiver keeps a budget per session for authority statements: the verification work it will do for that session over time. Every statement a session delivers spends that session’s budget, whether it was asked for (an answer to a fetch, a page of a pull) or not, and its cost is never forgiven below zero. The asker paces its own requests so that its budget for the session can absorb the worst-case answer. It starts a pull, asks for the next page, or sends a by-id fetch only when the session’s budget can pay for a full page:
AUTHORITY_PAGEstatements, each at the worst-case cost of verifying a statement under keys it has not seen. An answer within what was asked is therefore never dropped for budget. A statement nobody asked for, beyond the budget, is dropped unverified. - —Pending statements. A valid-looking statement whose chain is not complete (§6.6.3, rule 2) is pending. Before it verifies anything in such a statement, the receiver checks that it has room to hold one more pending statement on that session; a statement it could not hold is dropped unverified. It then checks the statement’s single signature, under §18.3.2, with the key its entry names, and discards it if that fails. Checking a key the node has not seen costs one scalar multiplication, paid from the session’s budget. It then MAY hold the statement in memory while it fetches the missing link from the session that delivered it. Held statements are keyed by id and signing key, so a forged copy cannot displace a genuine one. At most
AUTHORITY_PENDING_MAX(64) are held per session. They are never persisted, relayed or counted against a quota, and they are released afterAUTHORITY_PENDING_TIMEOUT(10,000 ms) or when the session closes. A node keeps at most one fetch in flight per (session, missing id), and sends a fetch only when the session’s budget allows (Budget, above). An anchor-level statement is never pending: the pin is always at hand. - —Fetching by id.
authority-fetchwithids(1 to 64) asks for those statements. The responder answers with oneauthority-setcarrying, in authority order, the closure of what was asked: each named statement in its live set with its chain; then every in-force revoke and endorse that names a statement already in the answer, each with its chain; and so on, the namers of namers, until a pass adds nothing or the answer reaches the page bound. Ids it does not serve are listed inmissing, including those of statements it holds that are not in its live set (dead, over quota or pending): a dead statement is served only as a chain member of a live one. An answer carries at mostAUTHORITY_PAGE(64) statements. A removed grant is served only with the revoke that removes it, so a fetch never hands out a removed grant as if it were in force. - —Authority order is ascending depth; within a depth, revokes and endorses before grants; within each, ascending id. In that order every statement arrives after everything that can change its standing: its chain, the revokes that may remove it, and the endorses that may rescue it. A receiver can ingest a page as it arrives.
- —Anti-entropy. A node sends
authority-digest, its root and in-force count, when a session is confirmed. It sends it again after its in-force set changes, at most once a second per session. A node whose root differs from the peer’s pulls the peer’s live set. It sendsauthority-fetchwithafter: "", then pages with thenextcursor of each answer until an answer carries none. It starts the pull, and asks for each page, only when the session’s budget can pay for a full page (Budget, above). Each page holds at most 64 statements, in authority order. Every statement received is ingested as gossip is. The peer, seeing the same mismatch, pulls in the other direction. A node keeps at most one such pull in flight per session. A responder SHOULD pace its answers per session (for example 4 a second, burst 16), and MAY refuse a fresh full pull on a session within 60 s of the last. The cursor is opaque, at most 128 characters. Statements that change while a pull is running are reconciled by the next digest. A pull that stops early, because the session closed or a page did not come, resumes from its last cursor. While the two roots still differ after a pull, a node repeats anti-entropy with backoff (for example doubling from one second up to a few minutes) rather than giving up after a fixed number of tries; a matching root, or a change to either set, resets the backoff. - —Persistence. A node SHOULD persist its live set and MAY persist more. Persisted statements have no integrity of their own. On load, a node MUST re-verify every statement against the pinned anchor, chain by chain from the top, and resolve the set afresh. It MUST NOT trust a status or a root read from disk. It keeps the statements that fail under the current pin, and judges them again under the next (§6.6.1). No receipt time is kept, because none is used.
6.6.9 Authority follows the key
- —A grant confers its role on its subject’s nodeId and key together. A CMB or attestation carries its author’s role only when the key that verifies its signature (§18.3.1) is that grant’s subject key. A grant that names the same nodeId with another key confers nothing on the holder of the first.
- —A statement’s signing key comes from its authorising grant, or from the pin, and never from a node’s key registry. That is what makes resolution the same at every node, whatever keys each has bound.
- —Grants as a binding source. An in-force grant is a source a key registry MAY bind its subject from (§3.4), as a view over what is in force now: the binding lasts only while the grant is in force, and goes with it unless another source binds the same key. It binds only an unbound nodeId. If the nodeId is bound to a different key, that is an identity conflict (§3.4): it is recorded and reported, and the binding is unchanged. The grant stays in force, since resolution does not read the registry, but at that receiver it confers nothing usable, because no record that verifies under the bound key is signed by the grant’s key.
- —The receiver’s own nodeId. A receiver MUST NOT take a binding for its own nodeId from any grant, because it knows its own key (§3.1.3). A grant that names the receiver’s nodeId with a foreign key confers nothing on the receiver, and the receiver SHOULD report it: someone has vouched a foreign key for its identity.
6.6.10 Using authority: agreed outcomes and mesh weights
- —Outcomes that must be agreed are decided once, by the environment. Some outcomes several parties must agree on: that a piece of work was validated, that a payment is due. Such an outcome is decided once, by the environment the decision belongs to (the application or runtime that runs the work), against that environment’s in-force set at that moment. It is recorded there, bound to the authority root it was decided under (§6.6.7). The record is the outcome. A node MUST NOT re-derive or re-judge such an outcome from mesh gossip, and MUST NOT treat a later change in its own in-force set as reversing it. (Informative: XMesh keeps such a record for the work it runs.)
- —Mesh weights are judged by the receiver, when they are used. Everything else authority does in the mesh is a weight a receiver applies: the origin admission weight (§6.4), lifecycle advancement in its own store (§6.5), and the weight of an admission, tether or grounding attestation (§6.7, §15.8, §17.2). A receiver judges each against its own in-force set at the moment it applies it. A transition it has already made in its own store is its own record, and a later revoke does not undo it. A weight it applies afresh, each time it weighs an attestation, uses the in-force set of that moment. A scoped grant’s weight applies only to CMBs inside its scope (§6.6.2). Inside a deterministic run the environment uses the root it admitted for the tick, not the moment (§6.6.7). A statement carries no signed time that could place it inside a period of trust: once a signer’s grant is not in force, nothing that signer signs, whatever time it claims, carries authority.
6.6.11 Migration from the time-replay rule
- —The old frames confer nothing. The earlier
role-grantandrole-revokeframes and records, as signed by sym 0.14.0 and earlier, are retired (§7.1, legacy). A node MUST NOT resolve authority from them and MUST NOT emit them. The old revokecutoff, the grant timegrantedAt, resolution at a time T, and the receipt-time rule are removed. No statement carries a cutoff, and no receipt time is stored. - —Anchor grants map one for one. For each old grant signed by the anchor that the anchor’s holder still regards as in effect, the anchor issues one grant: the same subject nodeId and key (
granteeKey),authorisedBy: "anchor", and the role mapped (validatorstaysvalidator; the grantedanchorrole becomesadmin). A single-key anchor is the 1-of-1 set, so the node holding that key can issue these without co-signers. An old grant with nogranteeKeyhas no subject key. It cannot be carried over until the key is known from a confirmed handshake or an out-of-band pin. Migrated grants carry no scope, since no old grant had one. - —Deeper chains carry over within the cap. Once a grantor holds its own new grant, it re-issues each old grant it signed that it still regards as in effect. Each re-issued grant names the grantor’s new grant in
authorisedBy, provided the role table of §6.6.2 permits it and the depth stays withinMAX_DELEGATION_DEPTH. An old link that does not fit, such as a validator that promoted a validator, does not carry over by itself. Its grantee needs a grant from the anchor or an admin. Each signer re-issues only its own grants, so migration needs no agreement between nodes. An old grant that an old revoke had cleared is simply not re-issued. - —Pre-0.14 grant entries are legacy claims, unchanged. A key that a pre-0.14 implementation (sym 0.13) recorded for a nodeId from a grant, or from an unproven hello, is a legacy claim (§3.4). It is the key the receiver expects for that nodeId: a session proving it binds it, and one proving a different key is an identity conflict. It verifies nothing, binds nothing by itself and confers no authority.
- —A flag day. This is a flag day, not a gradual migration. A node running this rule ignores the old frames, so until grants are re-issued under the new rule it resolves every node, itself included, as a participant, whatever the old rule granted. A node running the old rule ignores the four new frames as unknown types (§7) and keeps the old authority. Each sees only its own kind. The anchor’s holder SHOULD migrate first and re-issue at once, and each grantor SHOULD re-issue as soon as it holds its own new grant, so the gap lasts no longer than the mesh’s upgrade.
6.6.12 Residuals
- —The revoke window. Until a node holds a revoke, it treats the removed grant as in force. Gossip relays a revoke as it enters; anti-entropy bounds the rest. A node learns a revoke at the latest at its next digest exchange with any peer whose in-force set includes it, and those exchanges happen when a session is confirmed and whenever a set changes. A partitioned node stays exposed until it reconnects. An environment deciding an agreed outcome (§6.6.10) SHOULD bring its in-force set up to date with a node it trusts to be current before it decides.
- —The threshold holders are the root. Any t anchor keys together can grant, revoke and endorse anything. Compromise of t keys is root compromise, recovered only by re-pinning out of band. Fewer than t keys can do nothing alone; under a threshold of 1, therefore, every pinned key alone is the root.
- —A compromised holder acts until it is removed. Within its quota and delegate quota, and within the figures of §6.6.6, a compromised authority holder can grant, revoke and endorse below itself until a signer above removes its grant. Endorsement then keeps what should stand.
- —Verification costs one scalar multiplication per new key. Checking that a key is of prime order (§18.3.2) costs one multiplication by L for each key a node has not seen before; under a cofactorless verifier R costs only a byte comparison. A flood of statements under fresh keys is bounded by the session’s budget, which every statement a session delivers spends, asked for or not, and which the asker paces its own requests to (§6.6.8), and by the quotas once chains are held. The pending limits bound how many statements a session can have held, not how many checks it can cause.
- —Grants are visible. Every node that holds the set can read who holds which role. Authority statements are not confidential.
Identity vs. authority. This section withdraws authority. A compromised signing key is a different failure: MMP does not define key rotation (§3.4). A node whose key is compromised generates a fresh identity and is granted again under its new key. Revoking the old key’s grants contains the damage in the meantime, and endorsing what the old key granted keeps what should stand.
§6.7 — New in 1.1.0 — work layer
Section 6.7 is a normative addition in 1.1.0 (the work layer): it defines how a real-world outcome is recorded against cognition and what that record may — and may not — do to lifecycle. It changes no 1.0.x wire contract; a 1.0.x node treats grounding CMBs as ordinary CMBs.
6.7 Grounding — Outcomes Carried by Lineage
Validation (§6.4–§6.5) records judgment — someone with authority committed to a CMB. Grounding records evidence — reality answered: the tests passed, the work shipped, the prediction held, or it did not. The two are deliberately distinct: a fast, self-referential mesh can mechanize coherence only; connecting cognition to the world requires an external outcome carried by lineage.
The grounding CMB. A grounding CMB is an ordinary CAT7 CMB whose
intent is ground, whose
lineage.parents contains the CMB(s) it grounds, and whose
commitment carries the outcome, prefixed
verified: or failed:.
Any other commitment form is not a recognised outcome. It is emitted, signed (§8.7, §18.3.1), broadcast,
SVAF-evaluated, and remixed like any other CMB — no new frame, no new field. A receiver that verifies
signatures MUST reject an unsigned grounding CMB like any other unsigned CMB.
Repeat verification and the redundancy band. A verification report about a row the receiver
already holds typically scores high alignment against exactly that row, so under an unmodified §9.2
gate, the better established a row, the harder it becomes to ground or re-ground it — repeat
verifications are progressively refused as redundant, and the accepted-grounding stream of any one row
self-quenches (a packing bound: only finitely many reports can each clear the redundancy
separation, ever, absent retention purges). Sustained outcome tracking is load-bearing for the mesh
being a learner rather than an accumulator, so acceptance of a grounding is exempted from exactly one
band: a receiver MUST NOT refuse a recognised grounding CMB (signed, verified,
intent = ground, recognised outcome prefix, lineage naming a
target the receiver holds) solely because it is redundant against its target row or against
previously admitted groundings of that target. The reject band (foreign content), the signature
requirement, and the receiver’s trust weighing all stand unmodified. This is an
acceptance-side rule and does not weaken the §15.7 anti-echo emission gate (§15.7.2:
what legitimises a grounding emission is the fresh outcome observation behind it); spam through the
waiver is bounded by the content address itself — a byte-identical repeat confirmation carries the
same cmb- key and deduplicates, so only distinct
verification reports pass, and implementations MAY additionally rate-cap accepted groundings per
(target, author) pair.
The failure channel is load-bearing. The signed outcome pair is not symmetric decoration:
when recall preferentially re-uses highly-weighted rows, the
failed: channel is the mechanism that
makes preferential sampling self-correcting — a stale favourite’s absorbed grounding traffic
drives its weight down — while a positive-only mesh locks onto early favourites at
chance-level precision. An agent that observes a failure outcome MUST NOT suppress it while
continuing to emit success outcomes for the same class of work; selective success-only grounding
defeats the self-correction the outcome channel exists to provide. (Informative: the self-correction
additionally requires outcome reports to be better than chance — miscalibrated reporting that is
wrong more often than right converts the same coupling into entrenchment.)
Consuming the outcome stream (informative). A consumer that scores rows by their accepted
groundings faces a bias–variance choice with a known theory. Pure accumulation (all-time counts)
is the efficient estimator only while the useful set is stationary; under drift its staleness bias makes
its ranking degrade toward chance. A recency-decayed signed sum tracks drift with bounded risk; its
half-life trades noise against lag, with the optimum scaling as
(V / (4μ²δ²))^(1/3) in the drift rate δ
— a fixed half-life therefore pays a longer dominance horizon
(Θ(δ−1) instead of Θ(δ−2/3)), and the tuned
horizon is recoverable online by estimating drift with a growing-window slope probe (a fixed pair of
probe timescales degenerates back to the fixed-half-life exponent; the growing-window form recovers the
tuned one even on sparse signed ±1 outcome streams). Decayed sums should be
mass-normalised (one extra scalar) — the raw zero-initialised form carries an initialization
transient that delays its advantage by a log factor. Zero-clamping per-node sums before cross-node
aggregation discards the negative evidence the failure channel carries; consumers that clamp should know
the self-correction analysis above assumes the signed form.
An outcome is an attestation, not a fact. The grounding CMB asserts that its author observed the outcome. Its weight follows the author’s resolved authority (§6.5–§6.6) exactly as any other CMB’s does; the reserved intent value adds semantics, never authority.
Groundedness is receiver-relative. A CMB is grounded, in the view of a given node, iff a recognised grounding CMB targeting it (directly, or via a verified direct-parent path through the node’s admitted remixes, §15.2) is present in that node’s own store. There is no global grounded state; a node MUST NOT treat cognition as grounded on the strength of a grounding entry it never admitted. Grounding runs upward only — a grounding CMB grounds the CMBs its lineage points at, never descendants of those CMBs: a remix of verified cognition is not itself verified.
Outcomes are observations of a changing world. When several recognised grounding CMBs target the same
cognition in one store, the latest by stored time wins: a later
failed: un-grounds what an earlier
verified: established (a regression must surface, not be shadowed by
history). “Stored time” is the receiver-local time the entry entered the evaluating store
— never the author-asserted metadata.createdTimestamp, which is unwitnessed
(§6.6) and would let a backdated or future-dated attestation game the ordering. Authority modulates
whether to act on an attestation, not the temporal ordering of observations — and latest-wins
applies within an authority tier, not across tiers. Hard-gate reading (normative): a
failed: from an author below the authority of the standing
verified: MUST NOT un-ground it — a below-validator
failed: cannot overturn a validator-or-above
verified:. This is the tested-effective form: a soft
authority-weighted vote is defeated by sheer low-authority volume, whereas the tier gate holds a
validated outcome against any number of below-tier failed: reports
(see the §18.4 threat note on outcome griefing). Within a tier — equal authority, or a genuine
same-authority regression — latest-wins still applies, so a real regression surfaces.
Grounding never advances lifecycle by itself. §6.5 stands unweakened: a CMB cannot self-grant effect,
and a grounding CMB is a CMB. A node MAY advance its own store’s entries to
validated on grounding evidence — evidence-based
validation — but only as an explicit act under validator-or-above authority (§6.5–§6.6),
never as an automatic consequence of receiving or reading a grounding CMB, and never as a side effect of a
query. The elevating authority is accountable for the judgment; it SHOULD require
verified: polarity and weigh the grounding author’s resolved
authority before acting. Self-reported outcomes from unauthenticated or participant-rank authors
SHOULD NOT trigger elevation.
The team Canon (informative). No shared store exists. A member’s Canon is the validated/canonical ∪ grounded cognition of its own store — including remixes its own SVAF admitted from teammates. What a cockpit renders as a “team Canon” is the emergent overlap of members’ Canons, read from same-host stores only (a remote member’s store is sovereign and is never fetched). Adoption is meaningful precisely because every admission was autonomous.
Q&A
Why a pluggable storage interface instead of prescribing a backend?
Agents run on different platforms with different constraints. A CLI agent on a server uses flat files. An iOS app uses CoreData with iCloud. A compliance agent needs a cloud database with audit logging. The protocol defines what to store and how to query it — not where to put it.
Can an agent use read-only storage?
Yes. Audit and compliance agents observe the remix graph without modifying it. They implement write methods as no-ops and read from shared storage. This is how regulators trace the decision chain without participating in it.
What happens when a protected CMB’s last descendant is purged?
If its protection came from lineage (a newer entry referencing it), it is no longer protected and will be purged in the next retention cycle — that protection is dynamic, it follows the live graph. Canon-tier protection (§6.3) is different: a CMB at validated/canonical lifecycle is exempt from age purge regardless of graph state, for as long as it holds that lifecycle.
How does human validation enter the mesh?
When a human acts on an agent’s output (approves a decision, completes a task), the action is recorded as a new CMB with lineage pointing to the signal that prompted it. This CMB enters the mesh like any other signal — agents evaluate it through SVAF and adjust their understanding. No special API, no out-of-band config. The mesh learns from human actions through the same channel it learns from agents.
Why do validated CMBs have higher anchor weight?
A human acting on a signal is the strongest confirmation that the signal was correct and actionable. Giving validated CMBs higher anchor weight means future SVAF evaluations are shaped by confirmed knowledge rather than speculation. This does not override agent autonomy — each agent still applies its own category weights. It means the anchors against which incoming signals are compared are more trustworthy.
Why must validation authority be identity-bound?
If any agent could advance a CMB to validated by producing a CMB with lineage, an agent could dismiss founder decisions or fake human approval. Binding validation to cryptographic node identity (Section 3.5) ensures only explicitly authorised nodes — the founder’s node or promoted agents — can affect lifecycle transitions. The content of the CMB (perspective, intent) is informational; the authority comes from who created it.
Can an agent earn validator role automatically?
The protocol defines the grant mechanism (Sections 3.5.1 and 6.6) but does not prescribe automated promotion criteria. An implementation MAY define heuristics (e.g. promote after N remixes cited by peers), but the grant itself MUST be a signed grant statement from the anchor or an admin, naming the authority that permits it. This keeps the trust chain auditable.
Why does authority carry no time?
Every node would have to agree on one history of who held what when, built from times the signers chose themselves and statements that arrive in any order, and nothing in a mesh orders them. Resolving a set instead gives every node holding the same statements the same answer, with no history to agree on. Where a moment matters, the environment that decides records the authority root it decided against (Section 6.6.7).
What does an endorse add that a fresh grant does not?
Continuity. A fresh grant is a new statement with a new id, so everything signed under the old grant still names the old id and dies with it. Once a revoke has cut the named statements off, an endorse keeps them, and everything that hangs from them, without giving their signer its authority back. It costs the endorser what granting them would: its bucket keeps them, and a rescued delegate takes one of its delegate slots.
Why can an endorse not bring back a grant that was over quota?
Because the quota is what bounds a compromised holder. If an endorse could reconnect what a quota dropped, a holder could issue any number of delegates, let the quota drop the extras, and endorse what hangs under them, each with a full bucket of its own. Rescue exists for the case a quota never creates: a subtree cut off because a signer above it was removed.