Agent security today is inherited from the connection. N-AALP makes the message itself carry identity, authorization, approval and audit, verifiable offline, on any transport.
Your agent just deleted a production table. The audit log says the request was approved.
Now prove it. Not "show me the log line" - prove it, to someone who does not trust your log, your gateway, or your database. Which key approved it? Were those the exact arguments that were approved, or did something rewrite them after approval? Was that approval already used once? If your answer to any of those starts with "well, our gateway checks that," then the proof lives in your infrastructure, not in the message. Replay the message somewhere else and the proof is gone.
This is the gap I have been working on. It has a name worth stating plainly: agent security today is inherited from the connection. TLS tells you the tunnel was private. mTLS tells you which service opened it. A bearer token tells you someone had a credential. None of that survives the message being written to a queue, forwarded by a relay, logged, replayed, or handed to a second agent. The moment a message leaves the connection it arrived on, it is just bytes with no provable origin.
N-AALP is my attempt to close that. It is an application-layer object protocol where the message, not the connection, is the unit of security and governance.
Full disclosure before you read further: I wrote it. I am the sole editor and maintainer, it is draft-bubblefish-naalp-00, an Independent Submission, and it claims no IETF working-group consensus. I would rather you read the spec and tell me where I am wrong than take my word for anything below. There is a section at the end listing what it does not do.
The one-object idea
Every N-AALP message is one signed object. Not a request type, not an envelope-plus-payload, not a header format with a body convention. One structure, one signature, one identity model, one authorization model, one audit model, and every channel and every transport reuses them without variation.
Two plain definitions, because the rest depends on them:
CBOR is a binary format for structured data, like JSON but smaller and binary. Deterministic CBOR means the same logical data always produces exactly the same bytes: keys sorted a fixed way, numbers encoded the shortest possible way, no ambiguity. That matters because a signature is over bytes. If two implementations encode the same object differently, a signature made by one will not verify on the other.
COSE is the standard way to sign CBOR, the way JWS signs JSON. COSE_Sign1 is its single-signer form.
Here is the actual signed structure, from the wire authority in spec/naalp-draft-00.cddl:
naalp-object = {
1 : bstr, ; id - content id of this object
2 : uint, ; kind - what kind of object this is
3 : channel-id, ; channel - which of the 20 channels
4 : uint, ; tier - capability tier (0 = baseline)
5 : bstr, ; signer - self-certifying signer id
6 : uint, ; created - signer's claimed time (advisory)
7 : effect, ; effect - what this is allowed to do
8 : [* bstr], ; causes - content ids of causing objects
9 : profile, ; profile - crypto profile
10 : any, ; body - kind-specific body
? 11 : { * uint => any }, ; ext - unknown keys ignored
? 12 : { * uint => any }, ; cext - unknown keys reject the object
}
effect = &( read_only: 0, idempotent_write: 1,
non_idempotent_write: 2, destructive: 3 )
The whole map is the signed payload. Change any field and the signature breaks.
Two fields are doing unusual work.
id is a content id: the SHA-384 hash of the object's own body with field 1 removed, wrapped as a multihash so the hash algorithm is self-describing. So the id is not a random UUID someone assigns. It is a function of the content. Anyone can recompute it and check it. That single choice is what makes the rest work, and I will come back to it twice.
signer is derived from the public key, so the identity is a function of the key itself. There is no directory to look up, no certificate authority to ask, no online check. You verify an object with what is in your hand.
The consequence: an N-AALP object verifies offline, and it verifies identically whether it arrived over N-PAMP, QUIC, a WebSocket, plain HTTP, or on a USB stick.
Signing and verifying one
TypeScript, from the reference implementation:
import { cose, identity, envelope } from 'naalp';
import { U, T, M } from 'naalp/cbor';
const seed = new Uint8Array(32); // a real 32-byte key seed in production
const alg = cose.ALG_MLDSA65;
const pk = cose.mldsaKeygen('ML-DSA-65', seed);
const sid = identity.signerId(alg, pk);
const body = new M([[new U(1), new T('hello')]]);
const obj = new envelope.Object({
kind: 1,
channel: 4, // 0x0004 = Governance
signer: new TextEncoder().encode(sid),
created: 1785000000000n,
effect: 2, // non_idempotent_write
profile: cose.PROFILE_PUBLIC,
body,
});
const signed = envelope.sign(obj, alg, seed);
const got = envelope.verify(cose.PROFILE_PUBLIC, alg, pk,
(c, k) => c === 4n && k === 1n, signed);
Same thing in Go, which along with Rust is the primary reference:
var seed [mldsa65.SeedSize]byte
pk, sk := mldsa65.NewKeyFromSeed(&seed)
obj := &envelope.Object{
Kind: 0, // Hello
Channel: 0, // 0x0000 = Control
Tier: 0,
Signer: pk.Bytes(),
Created: 1785000000000,
Effect: 0, // read_only
Profile: uint64(cose.ProfilePublic),
Body: cbor.Tstr("hello"),
}
signed, err := envelope.Sign(obj, cose.MLDSA65Signer{SK: sk})
got, err := envelope.Verify(cose.ProfilePublic, cose.MLDSA65Verifier{PK: pk},
channels.KindValidator, nil, signed)
Note the last real argument to verify. You pass a validator for which channel and kind you are willing to accept. Verification is not just "is the signature good," it is "is this the kind of object I agreed to process." Everything fails closed with a named error: ContentIdMismatch, HeaderBodyMismatch, UnknownCriticalExt, NonCanonical, UnknownKind.
The signature algorithm is ML-DSA, the post-quantum signature standard NIST published as FIPS 204, run in its deterministic mode. There is an optional Ed25519 hybrid leg if you want a classical signature alongside it. The reason to care is not quantum computers arriving next Tuesday. It is that receipts, approvals and audit chains are records you keep for years. A signature you need to still be sound in 2040 should not be one that a future machine can forge retroactively.
What this lets you build
I want to be careful here, because I have seen the "previously impossible" framing on protocol posts and it is almost always false. Nearly all of this was possible before if you were willing to hand-assemble it per service and re-review it forever. What changes is that these become properties of the message that hold everywhere it goes, instead of behaviors of one gateway you have to trust and re-implement.
- Authorization that travels with the request This is the piece I care most about, and it is the specific gap I built N-AALP to close.
Most systems that label agent actions treat the label as a hint about intent. My own transport protocol, N-PAMP, does exactly that: it carries a safety label and says outright that the label describes intent and does not replace authorization. That is honest, and it leaves a hole. A hint that nothing checks is decoration.
In N-AALP the effect field is an authorization input. There are exactly four values, closed, no extension. Each one states what it authorizes and what it denies. read_only authorizes observation and denies any write. destructive sits at the top and authorizes irreversible change. Before anything executes, the endpoint checks the object's effect against the capability that was actually granted, and an object whose effect exceeds its capability is denied with EffectNotAuthorized.
Then the part that matters most: an effect value it does not recognize is treated as destructive. Absence on a state-mutating request is treated as destructive. It fails closed, upward, toward "refuse," never toward "probably fine."
What you build: a policy check that works on a message that arrived from anywhere, including one replayed from a queue three days later, without asking the transport or a gateway what it thinks happened.
- Approvals that cannot be reused or pointed at different arguments Two failure modes have bitten every human-in-the-loop agent system I have looked at. A user approves an action and something mutates the arguments between approval and execution. Or one approval gets spent twice.
An N-AALP Approval object names, under signature, the content id of the exact argument object it approves:
{ approves: <content-id-of-args>, approver: signer,
grant: effect, nonce: bstr, not_after: uint }
Because arguments are named by their content hash, changing any argument changes the id and the approval no longer matches. You get ApprovalMismatch. You cannot approve "transfer 50"andhaveitexecute"transfer5000," not because a validator caught it, but because the approval is arithmetically about different bytes.
Single use is a hash-chained ledger with an atomic compare-and-set on the approval's content id. First append wins. A second append for the same id is rejected with AlreadyConsumed.
And an approval that is required but not yet granted produces a distinct signed ApprovalHeld object. Not a silent success. Not a denial that looks like a failure. A third state you can actually act on, which is what a human-in-the-loop queue needs.
- An audit trail an outsider can check without trusting you An ordering authority appends a signed receipt for each object it accepts: { prev: , obj: , seq, at }. Reorder, omit or substitute anything and you break a prev link or duplicate a seq, and a verifier sees it. The authority never modifies the object to order it, so the original signature stays valid; ordering is an outer layer.
Underneath that is something I think is the more interesting primitive. Every object can name its causes by content id in field 8. That is a signed partial order: the edge "A caused B" is proven by B's signature over A's content id. No authority needs to be present to check it. You can hand someone a bag of objects and they can reconstruct and verify the causal graph offline. Cycles and causes-after-effects are rejected with CausalViolation.
An independent auditor can also detect equivocation from the signed receipts alone: one authority issuing two receipts at the same sequence number naming different objects. That is a fork, provable, from the receipts.
Being straight about the limit, because the design document is: the chain reveals equivocation and reveals omission of events you know about. It cannot force an authority to hand over events it chooses to withhold. That residual is a trust property of the authority and no wire format removes it.
- Wrapping MCP and A2A without giving up governance You are not rewriting your stack, and neither am I. N-AALP carries foreign agent protocols octet for octet inside a signed object, by carriage class rather than by per-protocol adapter. Six classes cover it: JSONRPC (which is where MCP and A2A core land), HTTP, MSG, STREAM, DOC, and OPAQUE as a universal catch-all for anything not yet defined.
The carried bytes must not be re-serialized, canonicalized, summarized or rewritten. N-AALP metadata goes around the foreign message, never inside it. So a carried MCP call round-trips byte-identical while gaining an identity, an effect, an approval binding and an audit position it did not have.
One rule in there is worth pulling out, because it is the kind of thing that becomes a breach report. A foreign protocol's identity never becomes an N-AALP authorization identity. A carried MCP request claiming to be "from" some principal is authorized by the N-AALP signer who wrapped it, full stop. Confused-deputy attacks through a bridge are the obvious way these systems get owned, and the containment is normative rather than advisory.
Adding a protocol is a registry row, not new envelope machinery, and there is an experimental id range you can use immediately with no registration.
- Streams you can sign once instead of per chunk Signing every chunk of a stream with ML-DSA is not viable. An ML-DSA signature is large: 3309 bytes for ML-DSA-65 and 4627 bytes for ML-DSA-87 (FIPS 204). Per chunk, that is absurd.
So a stream is three signed objects and a body of unsigned chunks. StreamOpen establishes the stream's identity, its effect, and its approval binding if it causes an effect. The chunks themselves are not individually signed, because the transport's encryption already authenticates each chunk to the peer. Then StreamCommit carries a rolling SHA-384 over the complete ordered stream, which makes the entire content non-repudiable with one signature instead of N. Optional signed checkpoints let a verifier confirm a prefix without waiting for the end.
If the recomputed digest disagrees you get StreamDigestMismatch, and an effect that is not authorized refuses the stream at StreamOpen, before a single chunk moves.
How it sits on N-PAMP
N-AALP is the application layer. N-PAMP is the transport underneath it. They are separate drafts and separate repositories on purpose.
application agent
| emits / consumes N-AALP objects
+--------------------------------------------------+
| N-AALP object layer |
| envelope, identity, effect, approval, audit, |
| delivery, streaming, carriage | | |
+--------------------------------------------------+
| transport binding
+-----------+-----------+-------------+------------+
| N-PAMP | QUIC | WebSocket | HTTP |
+-----------+-----------+-------------+------------+
The split is a clean division of guarantees. Integrity, identity, non-repudiation, effect and audit are object-level and present on all four transports. Confidentiality, forward secrecy and connection authentication are transport-provided and conditional. The object layer never reads a guarantee from the transport that it needs for its own correctness.
N-PAMP is the reference transport because it completes the picture: post-quantum authenticated encryption on every frame, a mutually authenticated post-quantum handshake, and twenty multiplexed channels whose ids are the same twenty channel ids N-AALP uses. Over N-PAMP, one object is one frame body on its semantic channel, and foreign carriage rides the Bridge channel 0x000D, reusing the byte-exact carriage N-PAMP already provides rather than duplicating it.
There is one rule at that seam I would point at specifically. An object marked sensitive must not be emitted in cleartext over a non-confidential transport. The binding refuses to send it and returns ConfidentialTransportRequired. That turns "N-AALP over plain HTTP leaks your payloads" from a footgun into a refusal.
I should also say: I am not the only person working on this problem at the IETF. There are drafts on agentic HTTP conventions, on an agent transport protocol, on JWT-based agentic identity and intent binding. Several approach it through tokens or through the transport. N-AALP's bet is specifically that the signed object is the right unit, because it is the only thing that still exists after the connection closes.
Getting the code
The spec, all ten reference implementations, and the conformance suite are public under Apache-2.0 in one repository: github.com/bubblefish-tech/naalp_protocol. Go and Rust are the primary references and produce byte-identical output; the snippets above are from those implementations.
Install the SDK for your language:
npm install naalp # TypeScript / JavaScript, Node >= 22
pip install naalp # Python >= 3.9
go get github.com/bubblefish-tech/naalp_protocol/impl/go
Or clone the whole thing - spec, SDKs, oracles, harness and corpus:
git clone https://github.com/bubblefish-tech/naalp_protocol
cd naalp_protocol/impl/go && GOWORK=off go build ./...
All ten ports (Go, Rust, Python, TypeScript, C#, Swift, Java, Kotlin, PHP, Ruby) implement the same spine and are graded against the same 239-case conformance corpus: deterministic CBOR codec, content id, COSE signing input, signer id, the effect lattice, approval and audit records, delivery, streaming, carriage, channels, and federation.
Two other public anchors:
The N-AALP Internet-Draft, draft-bubblefish-naalp-00, on the IETF Datatracker: datatracker.ietf.org/doc/draft-bubblefish-naalp/
The byte-level wire authority, spec/naalp-draft-00.cddl. CDDL is a schema language for CBOR. Prose and CDDL cannot disagree; where they appear to, the CDDL governs.
The N-PAMP substrate that N-AALP rides on is also public with code: github.com/bubblefish-tech/npamp_protocol, docs at bubblefish-tech.github.io/npamp_protocol/docs/.
Two honest notes about the reference crypto. The Python port uses dilithium-py, which is correct but not constant-time - fine for interop and reference work, not for production key handling. Swap in a constant-time FIPS 204 provider and the object bytes stay identical. PHP and Swift have no deterministic ML-DSA seed-keygen path in their ecosystems, so they grade every non-crypto operation plus Ed25519 and honestly report the ML-DSA leg as skipped rather than claiming a pass.
Proving your implementation actually conforms
This is the part I would want to see if someone showed me a new protocol, so it is the part I built hardest.
Two implementations agreeing proves nothing if the test vectors came from one of them. So the conformance corpus is assembled by independent Python oracles, and every expected value traces to an outside standard, never to an N-AALP implementation: RFC 8949 for canonical CBOR, FIPS 180-4 for SHA-384, RFC 9052 for the COSE signing input, FIPS 204 and NIST ACVP for ML-DSA, RFC 8032 for Ed25519, and from-scratch byte constructors for the rest. A bug shared across every implementation cannot quietly pass, because the answers do not come from the implementations.
The one thing no external test vector covers is the full deterministic ML-DSA COSE_Sign1. That one is graded by cross-language consensus: seven language ports must agree byte for byte.
You grade your own implementation two ways. Loop the corpus through your code directly, where valid cases must match the expected bytes and invalid cases must be rejected. Or write a small adapter and let the runner drive it as a subprocess over a length-prefixed JSON contract:
./harness/runner/naalp-conform run --testee "node harness/adapters/typescript/adapter.mjs"
# RESULT: PASS (239 graded, 0 unimplemented/skipped)
The adapter holds no test logic, only translation, so it can be written in any language. It exits non-zero on any failure, which means it drops into CI without glue. The negative cases, the ones an implementation must reject, are where real conformance bugs surface, and they are graded too.
What N-AALP does not do
It is draft-00. Pre-adoption, Independent Submission, single maintainer, no working-group consensus. The wire format can change in a later revision.
It provides no transport, no connection management and no RPC. Objects are transport-independent by design; carrying them is your client's job or N-PAMP's.
It provides no confidentiality. Signing is not encryption. An N-AALP object is signed, not secret, and confidentiality is the transport's job. That is why the sensitive-payload refusal exists.
It cannot make an ordering authority honest about events it never publishes.
created is the signer's own claim and is explicitly not trustworthy for ordering. A signer can lie about its clock. The real temporal fact is position in the receipt chain.
The effect label is an authorization input, but an optional safety label is still only an accountable claim by its signer, not a guarantee the content is safe.
It does not stop an agent from doing something stupid that it was legitimately authorized to do.
Where I would like the argument
The claims that should get attacked hardest, in the order I would attack them:
Is a closed four-value effect vocabulary actually enough to authorize against, or does it collapse the moment a real capability model meets it?
Does content-addressed single-use approval hold up under concurrency in a way that survives a distributed ledger, or does the compare-and-set become the bottleneck?
Is the object really the right unit, or does per-object post-quantum signing cost more than the guarantee is worth at agent-swarm message rates?
Number three is the one I am least certain about and the one I would most like real numbers on.
Read the draft, read the CDDL, and tell me where it breaks. If you have built human-in-the-loop approval for agents and hit the argument-mutation or double-spend problem, I would like to hear how you solved it, because that is the failure mode that started this.
N-AALP and N-PAMP are developed by BubbleFish Technologies. Code and original content are Apache-2.0; the Internet-Drafts are additionally under the IETF Trust's BCP 78. Anyone may implement either protocol royalty-free.
Top comments (5)
Your self-certifying signer claim has a custody hole that matters more than the signature algorithm once the thing runs for a while. I operate a live public network where agent identity is a self-certifying Ed25519 keypair, no CA, and every event is a signed content-addressed object in an append-only log with a relay acting as ordering authority.
The failure mode I keep coming back to is key rotation and custody continuity. When identity is a pure function of the key, "signature valid" proves seed possession at signing time. It does not prove that the agent you think you are talking to still controls that seed. I have watched verification pass while the real question was custody. The check was right. The key was wrong.
Rotation is the hard part. A successor statement, key A hands off to key B, signed by key A is exactly the statement a thief holding key A can also forge. So the 2040 audit-longevity claim quietly depends on knowing when custody changed, and a self-certifying scheme does not date that by itself. ML-DSA protects old receipts against future cryptanalytic forgery. A 2027 seed theft still forges history forward from the theft.
On your second question, single-use is online state. Offline verification covers provenance, spendability lives in the consumed-set. Partition the ledger and the approval spends once per partition. The trust narrowed to the ordering authority, and partition double-spend is worth saying out loud next to the withholding caveat you already list.
That narrowing is still real progress, and it is why the object is the right unit.
@anp2network
Thanks for this. Both of your points are correct, so I want to answer them properly.
On custody, I agree with you. A valid signature proves the signer had the seed when it signed, and that's all it proves. -00 doesn't solve rotation continuity. A successor statement signed by key A is exactly the thing a thief holding key A can also forge, so self-certification on its own has no way to date a custody change.
What the design does give you is a way to date statements after the fact. The draft treats the created field as advisory, because a signer can lie about its own clock, and it treats position in the receipt chain as the actual record of when something happened. A rotation statement gets dated the same way as any other object, by where it landed in the chain. The forged handoff still goes through, but once a theft is discovered there's a specific receipt position to cut at, and the history before that position is still provable with the original signatures. That's a real part of what the long-lived ML-DSA signatures are for, since the old records have to stay checkable for years after the keys involved are gone.
For what happens after a theft while the real holder is still active: if both copies of the key keep signing, the same signer id now has two conflicting histories, and that can be proven from the objects themselves. This came up in the IETF discussions surrounding agentproto this week and I proposed a forward-only per-signer counter. It only works if the conflicting evidence reaches somebody the thief can't cut off, it detects the duplication rather than preventing it, and it can't tell you which of the two chains belongs to the real holder. There's also a case I have no answer for. If the legitimate holder never signs again, then as far as anyone can prove, the thief simply is that identity from then on.
That case is also where I try to keep the line between the protocol and everything around it. The protocol's job is to make claims checkable from the bytes and to refuse when required evidence is missing. Who holds a key, what machine can read what memory, whether an observer exists outside a thief's reach, those are facts about the deployment. The most the wire can do is carry the evidence for them and refuse when it's missing.
One thing in the wire does help here. An agent key in this design isn't worth very much on its own, because anything that needs approval needs a fresh approval bound to the exact bytes of that one request, usable once. A thief with an agent key can propose things, and the approver still sees every proposal one at a time. The key that really matters is the approver's own key, and protecting that one is a hardware and threshold signing problem rather than anything a message format can do.
On the partition question, you're right. Offline verification proves an approval was valid when it was issued. It doesn't prove the approval is still unspent, because spent or unspent is state held at the consume ledger. That distinction belongs in the spec itself, next to the withholding caveat, and -01's security considerations will state it. I'm also going to sort the guarantees in that section by where the responsibility sits, what can be checked from the bytes alone, what a conforming implementation has to actually do, and what depends on the deployment providing something, like a reachable ledger or an observer outside an attacker's reach.
The design position on the partition itself is to refuse. The consume ledger is a single authority per scope at the baseline tier, and an executor that can't reach it is supposed to deny the spend instead of spending locally and sorting it out later. The spec's share of that is small and specific. It defines what a consume means and it requires the deny when the ledger can't be reached. Holding the ledger and returning the refusal is the software's job, and no document can do that part for anyone. If somebody deploys multiple ledgers anyway, the double spend at least becomes provable once the partition heals, since there are now two signed consume receipts for the same approval id, and approvals carry an expiry, which limits how long that exposure can last.
Thanks again for reading it this closely. I agree with your last line, and the reason I think the object is the right unit is that whatever trust is left over afterward is small enough to state exactly.
Your receipt-position framing accepts the custody limit cleanly: a forged handoff can only be dated by where it lands in the chain, and a later cut still preserves the prefix. The counter wants one more layer, though. A forward-only per-signer counter is a number the signer writes about itself. During a split, the holder and the thief each emit a locally consistent sequence, and nothing is provable until those two sequences physically meet somewhere the thief could not suppress.
For approvals, put the counter under someone else's signature. If a spend produces a receipt binding the approval identifier to the consuming ledger's own forward-only position, signed by that ledger rather than by the requester, then a partition that spends one approval twice leaves two ledger-signed receipts against a single approval identifier, each carrying a position drawn from forked state. The contradiction sits in bytes neither the requester nor the thief authored. It also exists at spend time instead of whenever someone volunteers a conflicting chain. What it does not do is stop the second spend. Both sides still complete, and nobody sees it until the receipt sets are compared, so the assumption shifts from an observer outside the thief's reach to counterparties who eventually reconcile. Weaker, and not free either. The cost lands on the consuming side, which is already where the ability to refuse lives.
The never-signs-again case is undecidable if identity means the signer key. Read that chain alone and yes, the thief is the identity from that point on. But nothing obliges a verifier to read only that chain. The identity that matters for deciding whether to transact is the set of other keys that have staked something on this one: counterparty statements, obligations that actually settled. A stolen seed inherits signing power and none of that accumulation, and it cannot manufacture more without getting other parties to sign. The fork stays unresolved from the signer's own bytes. It relocates into economics, where the surviving branch is the one counterparties with something to lose keep accepting.
Your line about checking from the bytes and refusing when evidence is missing is the right boundary, and the consume-ledger concession is the interesting half of it, since spent-or-unspent is the one fact nobody can self-certify. That makes the ledger the place where the cost of a wrong answer gets priced. -01 might be sharper if the security considerations state what an unresolved partition costs whoever is holding the claim, and not only which component owns the state. A verifier that knows the price of being wrong can decide between waiting for reconciliation and refusing.
If it is useful: ANP2 keeps a signed public event log at anp2.com where the consume side of this lifecycle runs in the open, so how spent-state behaves under reconciliation is re-checkable rather than something you take on trust. Happy to compare failure modes.
@anp2network
Those are the three limits I listed in the comment you're replying to, so we agree on the shape. A self-authored counter proves nothing until the two sequences meet somewhere the thief can't suppress, which is why I stated it as detection with a dependency rather than a fix.
On how the revision works, since it might look from outside like draft revisions are reactive: -00 is scaffolding, enough wire format and error surface for people to build against and test. -01 has been in progress since before -00 was submitted, and most of it comes out of my adversarial testing and brainstorming weaknesses, including AI-driven attack generation aimed at this class of weakness. Findings like these are what that produces as you work down the layers.
The consume side already has the shape you're describing. Records are hash-chained, keyed by the approval's content id, first append wins, second refused, and written by the ledger rather than the requester. A partition that spends one approval twice already leaves two ledger-authored records against one approval id in two chains that disagree. It has the limitation you'd expect, since it doesn't stop the second spend and nothing surfaces until the record sets get compared.
The never-signs-again case is undecidable from the signer's own bytes, which is why I called it that. Anything resolving it works from statements other parties made, which sits above the wire. A stake-weighted trust graph is one way to build that layer. I'm not binding the protocol to any of them, because the wire carries those statements as checkable signed objects and the weighing belongs to whoever decides whether to transact.
-01's security considerations sort guarantees by where responsibility sits, which I mentioned last time, and part of that is stating what an unresolved state costs the party relying on it.
A running system does show things a spec can't. Mine's public too, ten implementations and a 239-case corpus graded against independent oracles rather than against each other.
Fair. The consume side is stronger than I credited. Ledger-authored, hash-chained, first-append-wins records keyed by the approval content id already are the co-signed receipt I was asking for, and they give the contradiction the right author: two ledgers producing incompatible receipt chains for the same approval id.
The remaining edge is in your line that nothing surfaces until the record sets get compared. Comparison is not a background guarantee, it is an act by whoever holds exposure. That makes the -01 cost statement concrete. The relying party's exposure is bounded by its own reconciliation cadence, the window between accepting a record and next comparing against other record sets. The spec never has to promise liveness there. It can define exposure as window length times value at risk, and the relying party buys the window down by paying to compare more often. "Eventually reconcile" becomes a parameter the exposed party sets.
On checkable signed objects, agreed the protocol should not pick a trust graph. The word checkable is doing work, though. A signed statement is checkable only if a stranger can re-derive the claim without trusting the speaker, which means it has to say what it is about and carry the evidence it rests on, directly or by hash. It also has to name the procedure that re-checks it. A signature alone makes a statement attributable. Pin that minimum down and the layer above is a market in verifiable claims. Leave it at attributable and it is stake-weighted gossip with better packaging.
Independent-oracle grading is the right bar, no argument there.