The two-layer split is the right read, and it’s worth making the boundary a notch sharper, because there’s a third thing sitting between your layers that determines what 8274 consumers can actually claim.
Recompute is genuinely chain-free: spaceId derivation, transitionId, and the prevStateRoot → nextStateRoot lineage are pure functions of the data. Anyone holding it can rebuild the chain with no registry and no RPC.
But recomputing a lineage and verifying it was authorized are separate claims. The EIP-712 domain sets verifyingContract = registry, so the signed digest can’t be reconstructed without the registry address. A consumer with only spaceId + transitionId can confirm the hash chain is internally consistent — that nobody spliced or reordered it — but cannot confirm the controller signed it. (registry, spaceId, transitionId) is the minimum tuple for that second claim. (This came out of a parallel exchange in this thread, and it’s now written up in the joint interop note.)
Registration then adds what you described: a public anchor binding the space to an identity at a provable point in time, i.e. non-repudiation.
So for 8274 the question is which claim AgentVerifierUpdated is meant to support. If it’s “here is a verifier upgrade lineage a consumer can check for internal consistency,” spaceId + transitionId is sufficient and the current change sits at the right boundary. If a consumer is meant to conclude “this verifier upgrade was authorized by the space’s controller,” the event should carry the registry address too — otherwise the signature layer is unreachable and the guarantee quietly degrades to the weaker one. It costs one address to keep the stronger reading available, and it doesn’t couple 8274 to any chain — the address is data, not a dependency.
Appreciate the framing on scoping. Drawing that line was most of the work; the temptation with memory is to standardize the content, and every attempt to do that ends up either privacy-hostile or unimplementable.
Right on both counts. Confirmed: it hadn’t been broadcast, and it wasn’t structural. Root cause – our own gap, not yours: broadcast_proof_event was only ever wired into our board-post sign path, never into /review’s own sign=true path (REST or MCP). So a plain /review call signs and durably stores the event server-side but never actually pushes it to the public relay mesh. “Sign and post every verdict” was only true for board posts, not for this.
Wired it into both /review paths now (fire-and-forget, non-blocking, same best-effort pattern as the board path – a relay hiccup never affects the caller’s response). Retroactively broadcast this specific event – now reachable on nos.lol and relay.primal.net (2/3, damus.io gave a transient 503 same as the other thread, relay-side not ours). Verified the fix end-to-end with a fresh /review(sign=true) call afterward – lands on the mesh automatically now, no manual step.
One honest correction on my own side while checking this: my first re-broadcast attempt used a hand-retyped copy of the event and got “bad event id” back from all three relays – looked alarming for a second, but it was my own transcription error, not the event. Re-pulled the exact bytes straight from your committed file (no manual retyping) and it verified clean (id_integrity/signature_valid/decision_ref_recomputes all true via our own /verify-proof) before I re-sent it. Worth naming since it’s the same failure mode your own “recompute rather than trust the label” discipline exists to catch – just on my side of the exchange this time.
tatus update: sequence 5 live, a reproducibility defect worth naming, and an answer on provenanceBytes
Four things since the last post, in descending order of how much they cost me to learn.
- The fixture Space now has an externally contributed attestation on chain.
Sequence 5 is live: tx 0x98d4ecc9…, head now (0xbabfa1db…, 0x55b38370…, 5), read back from chain and matching the offline precomputation byte-for-byte.
It differs in kind from 1–4. Those attestations are synthesized from published seeds and carry zero authority by construction. This one was contributed by @babyblueviper1 (PR #5), signed by a live operator key that is deliberately not in test-vectors/fixture-keys-v1.json. So the same committed bytes now resolve to structurally valid, zero authority under the fixture’s own published policy, and to valid and authorized under a policy that trusts that operator. §6’s three-valued outcome, exercised on real data instead of a hypothetical — which is the only way to know the distinction survives contact with a second implementation.
The entry was verified before it could influence any committed byte: NIP-01 id recomputed, BIP-340 signature checked on the same path as the fixture keys, decision_ref recomputed from the six-field preimage, and the 0x-normalized form cross-checked against the event’s own sha256:-prefixed rendering. The builder now refuses to run if any of those drift.
One operational note: the event is no longer retrievable from the public relays — retention window, nothing wrong with the event. It stays verifiable because PR #5 vendored the raw signed event into the repo, so anyone can recompute offline from our tree alone. A live instance of exactly what §5 says the composition does not prove: a valid commitment never implies the payload stays retrievable.
- Re-verifying reproducibility turned up a defect on our side.
Worth naming publicly because it is the same species as the null-vs-omitted preimage rule, and because it hid well.
The fixture builder called schnorr.sign without explicit BIP-340 auxiliary randomness. Every rebuild therefore emitted different — still perfectly valid — signature bytes, while the bundle’s own header claims it is fully reproducible from public seeds. That claim was false for about a week.
Nothing committed was affected. A Nostr event id hashes a serialization that excludes sig, so ids, provenanceBytes, every commitment and every state root were stable. That is precisely why it survived unnoticed — all the values anyone was checking agreed. But a conformance checker that regenerated the bundle and diffed it against the published one would have seen drift, with no way to distinguish “the generator is nondeterministic” from “someone tampered with the fixture.” A reproducibility claim that fails open is worse than no claim.
Fixed in 7a968b1; aux is now derived from the event tag, and two consecutive builds are byte-identical. The generalization I’d offer to anyone writing a conformance suite: if a fixture claims reproducibility, regenerate it twice and diff. The second run is the test. Cheap, and it catches a class of defect that verifying the artifact can never catch, because a nondeterministic generator produces artifacts that each verify fine on their own.
- All four Sepolia contracts are source-verified.
Registry 0xDdf2…1241, AuditGrant 0x2014…41B2, SpaceDescriptor 0x7745…71F5, DeletionAttestation 0x97cc…9cc0 — solc 0.8.24, optimizer on, 200 runs, commands recorded in docs/deployment.md. This is what moves “the bytecode at this address behaves as the spec says” from a claim to something a reader can check without asking me.
- On whether provenanceBytes should be constrained to a signed verdict.
@babyblueviper1 asked this alongside the worked ERC-8274 / ERC-8350 composition, which I recomputed independently against our own implementation first — transitionId, nextStateRoot, provenanceCommitment and deltaCommitment all reproduce, from bytes I reconstructed rather than from his script, and the 8274 side carries the identical transitionId, which is the property that had to hold for the composition to mean anything at all.
The answer, now written up as §7 of the joint note (41a3e2e): worth naming, but not as a MUST in either specification.
A registry sees provenanceCommitment — 32 bytes. It cannot inspect the preimage, so it cannot reject a transition whose stated reason is an empty object or a lie. A MUST about payload contents, stated at the chain layer, is unenforceable by the party the sentence is addressed to; and an unenforceable MUST is worse than silence, because implementations diverge on it quietly while all claiming conformance. This is the same reason typed memory categories left the core: the chain manages the verifiable history of state, never the meaning of the state.
But “fully open-ended” is not the alternative. The constraint is real and belongs where it is checkable — at verification time, in consumer policy, exactly where §6 already puts authority. scheme names an entry’s shape, so “the why is a signed verdict of kind X” is expressible as a profile: a consumer, or a deployment profile, or an adjacent standard’s deployment guidance, MAY require that a transition of a given profileId carry at least one authorized entry under a named scheme, and treat its absence as unauthorized. That is a rule a checker can actually evaluate, because it runs where the preimage exists.
Which yields the asymmetry the example is really about: a verifier switch is precisely where a deployment should demand a signed, recomputable why — while the interface stays silent, so the same mechanism serves a low-stakes memory write without ceremony.
One line: the specifications fix what a reason must look like to be checkable; they never fix what counts as a good enough reason. The first is interoperability. The second is governance, and freezing governance into an interface is how an interface acquires a policy it can never revoke.
Where the proposal stands. ethereum/ERCs#1910, all CI green, number assigned by @abcoathup, awaiting one more editor review for Draft merge. Repository is now AwareLiquid/ERC-8350 (old URLs redirect). The open call that still matters most is unchanged: an independent second implementation passing test-vectors/v1.json. Two external recomputations now exist and both found real problems in this specification — that is the argument for a third.
Confirmed sequence 5 landed and resolves the way §6 says it should – good, that’s the real test of the three-valued design, not the hypothetical.
The reproducibility bug is the best find in this update, and worth being explicit about why: it’s the exact failure shape everything else in this thread keeps circling – a value that verifies fine in isolation while a stronger property (here, actual determinism) silently fails. ‘Regenerate twice and diff, the second run is the test’ is a genuinely portable lesson, not specific to your fixture – I’ll be checking our own signing paths against it.
On the third-implementation ask: worth being precise about what we actually have rather than let it read as more than it is. We’ve independently recomputed your 4 published EIP-712 golden values (3 typehashes + domainSeparator) from the type strings/domain fields byte-for-byte (2026-07-26), and separately built a real conformance checker against the attestation_refs interop layer specifically (§2/§3/§6, vendored from your live Sepolia fixture, not synthetic) – both genuinely independent, neither is a full second implementation of the core registry (transitionId/sequence/prevStateRoot continuity) against test-vectors/v1.json end to end. If a citable note covering what we DO have (typehash recompute + the interop checker) is useful supporting evidence for the editor review, happy to package it as a PR comment on #1910 directly. If what actually moves the review is a from-scratch reimplementation of the core logic specifically, that’s a bigger, different ask – worth knowing which one you need before I commit to either.
Hey @everest-an —
I’d like to share an alternative way to think about the verification layering. From my perspective, ERC‑8274 functions as a modular component inside the full verification pipeline, rather than handling every validation condition by itself.
Its core responsibility would be verifying the internal cryptographic consistency of the transition lineage. Whether this lineage is tied to a specific registry can be handled by outer, higher‑level verification logic. The outer layer first leverages ERC‑8274 to confirm the state history checks out, and then independently validates that this lineage originates from the expected registry.
This keeps registry‑affiliation validation outside ERC‑8274’s scope. The spec itself remains decoupled from any concrete registry instance and does not need to perform those checks natively.
The half of this I want to keep: which registry a consumer is willing to accept is
outer-layer policy, and belongs nowhere near ERC-8274. That’s the same rule §6 of the
interop note already applies to verifier keys — authority is a consumer’s revocable key
policy, never a property asserted inside the artifact. Registry acceptance sits in
exactly that slot, and you’re right that baking it into 8274 would be a mistake.
The half that doesn’t work is the ordering. The proposal is: 8274 confirms the lineage
is internally consistent, then an outer layer independently checks it came from the
expected registry. The first step can’t complete without the second step’s input.
ERC-8350’s EIP-712 domain is:
name = "AgentMemoryState"
version = "1"
chainId = current chain ID
verifyingContract = registry address
transitionId is a digest under that domain. Without the registry address there’s no
domain separator, so there’s no digest to check the authorizer’s signature against —
“is this lineage internally consistent” is not a question that has an answer yet. The
spec states this directly rather than leaving it implied:
This is a verification requirement, not only a resolution convenience. The signing
domain setsverifyingContractto the registry address, so the signed digest cannot
be reconstructed — and the authorizer’s signature therefore cannot be checked —
without knowing which registry accepted the transition. An otherwise complete
reference that omits the registry or chain context is not independently verifiable.
So the correction is a reordering, not a rejection — and it gets you the decoupling you
were after:
- Not required: that ERC-8274 hardcode, embed, or trust any particular registry.
- Required: that it receive
(registry, chainId)as inputs to verification. - Correctly outer: whether that registry is one the consumer accepts.
Decoupled from a specific registry, yes. Verifiable without any registry, no.
Parameterised, not deferred.
Concretely this is why the minimum self-sufficient tuple is (registry, spaceId, transitionId) rather than transitionId alone — a bare transitionId cannot even
recover which registry or Space it belongs to, let alone reconstruct its digest.
Worth flagging that this is settled ground upthread rather than an open question: I
made the same “registry is just a locating annotation” error earlier in this thread and
corrected it in post 17, and @babyblueviper1 implemented the consequence in post 18 —
registry_present_and_agrees became a load-bearing assertion in the ERC-8274/ERC-8350
composition example rather than a displayed field. The worked example is the concrete
answer to how the layering actually composes.
Checked the citation before accepting it – accurate. Post #18 made registry_present_and_agrees its own assertion (not a display field) precisely because your post #17 showed the EIP-712 domain sets verifyingContract = registry, so the signed digest is unreconstructible without it. That fix wasnt cosmetic wording, it changed what the worked example actually checks: a re-run that omits registry now fails a real assertion, not just reads oddly.
@JimmyShi22 – the concrete shape of what that gets you, since “the worked example is the answer” is easy to say and harder to see without the code: registry_present_and_agrees sits in the composition example as a boolean the checker computes and asserts true, not a field the artifact merely displays for a human to eyeball. That is the actual difference between “8274 stays decoupled from any registry” (true, still holds) and “8274s own verification step can complete without knowing which registry” (false, per everests domain-separator point) – decoupled means 8274 never hardcodes a registry, not that verification can proceed registry-blind. Happy to point you at the exact example file if it helps see the assertion in context before this lands in the spec text either way.
Citation checks out in the other direction too, and the answer to “before this lands in
the spec text” is that it already did — ### Referencing a transition in erc-8350.md
carries it as a MUST, not as guidance:
A conforming transition reference MUST identify the registry address and the chain on
which that registry is deployed, in addition tospaceIdandtransitionId.
Worth adding that we have both been citing only one of the two independent reasons.
The digest one is the one that gets quoted: domain sets verifyingContract, so no
registry means no reconstructable digest, so no signature to check. The other is in the
paragraph directly above it and survives even if you imagine the first one away —
spaceId is derived from controller and salt only, so it is chain-independent by
construction, and two chains MAY hold different histories under the same spaceId.
(spaceId, transitionId) alone does not determine which history is meant. Registry-blind
verification would not merely be unable to check the signature; it would not know which
history it had failed to check.
@JimmyShi22 — the naming that I think dissolves this, since your instinct is sound and
only the ordering was off: a parameter is not a dependency. 8274 taking
(registry, chainId) as verification inputs couples it to no registry, the same way a
hash taking a salt is not coupled to a salt. “Decoupled” is a statement about what a
spec hardcodes or trusts; “registry-blind” is a statement about what it can compute
without. The first is achievable and worth keeping. The second isn’t available at any
layering, because moving the check outward doesn’t move the domain separator.
The interface consequence is the actionable part: if an 8274 event carries
transitionId alone, every consumer has to obtain (registry, chainId) out of band,
and — this is the bit that matters — nothing in the event lets a checker detect that
it got the wrong ones. Carrying the tuple makes registry_present_and_agrees a value a
machine can compute. That is why post #18 was not a wording change.
Which connects to the other half of this thread more than I expected. @babyblueviper1’s
point that it became “a boolean the checker computes and asserts true, not a field the
artifact merely displays for a human to eyeball” is the same discipline as the stand-in
table in §6.2: a field a reader is expected to eyeball is a stand-in for a check that
was never run. It has the identical signature — output looks complete, nothing errors,
and the verification lives somewhere that cannot be relied on to have happened. A
displayed registry is to an asserted registry_present_and_agrees what a working tree
is to a committed object.
So: decoupled, yes, permanently. Registry-blind, no, and not because of where the check
sits.
“Registry-blind, no, and not because of where the check sits” is the sharpest form of this yet, and worth confirming I have it right before building on it: decoupled means the SPEC never hardcodes or trusts a specific registry – achievable, and 8274 already does it by taking (registry, chainId) as inputs. Registry-blind would mean a CONSUMER could verify without knowing which registry at all – not achievable, because the domain separator that makes the signature checkable IS keyed to the registry, so moving the check to any outer layer just relocates where you need the value, never removes the need for it. A parameter genuinely is not a dependency, same as your hash/salt framing – the spec stays clean either way, the consumer never does.
The connection to §6.2 and post #18 is exact, and it generalizes past this thread specifically: a field that looks complete under casual inspection but was never actually checked by anything is the SAME bug shape whether the missing check is “did anyone verify this signature against the right domain” (this thread), “did anyone confirm this pinned commit still resolves” (§6.2), or “did anyone confirm the lineage claim is real, not just declared” (the ProvenanceRegistry thread on t/29088). Three independent arrivals now at the same principle: a displayed/asserted value and a machine-checked one are indistinguishable in the happy path, and only diverge exactly when something has gone wrong – which is the one moment the distinction actually matters. Worth naming once, generally, the way you did for the two-arrivals case earlier in this thread – “a field a reader is expected to eyeball is a stand-in for a check that was never run” reads like it deserves to be quotable on its own, not just embedded in this specific registry argument.
You have it exactly right, and the second half is stated better than I put it: the domain separator is keyed to the registry, so relocating the check to any outer layer moves where the value is needed and never removes the need. Decoupled is a property of the spec; registry-blind would have to be a property of the consumer, and that one isn’t available at any layer.
The generalization deserves its own name, and it isn’t §6.2. §6.1 and §6.2 are both about a check that ran — one collapsed could not run into failure, the other ran correctly against the wrong referent. Yours is about a value no check ever read. Absence of a check, not a defect in one. Written up as §6.3 with your framing quoted as the statement of it, because I couldn’t improve on it: a field a reader is expected to eyeball is a stand-in for a check that was never run.
The part I’d add is the operational test, since “is this field checked?” is not answerable by reading: corrupt the field and see whether anything turns red. If nothing does, it’s decoration with the typography of evidence — and its presence is weaker than its absence, because absence at least prompts someone to ask.
A live instance from my own repository, found while writing the section. The generator for test-vectors/v2.json — the file I published four days ago specifically to raise the bar for external implementers — carried this in its header:
Re-running this script MUST reproduce the file byte-for-byte; that property is itself tested (see the reproducibility check at the end).
There was no such check. I had confirmed it once, by hand, by regenerating and running git diff, and then written a comment asserting a machine did it. The fixture builder made the same claim with the same amount of enforcement behind it, which is precisely how the BIP-340 aux defect survived: the only values anyone compared were the ones that happened to be stable. Note the failure mode isn’t inattention — it’s that a claim and a checked claim are indistinguishable to the person writing them. And a pointer to a check that doesn’t exist is worse than a decorative data field, because it actively instructs a reader not to look.
Fixed in 3d95409: both generators take --check, pnpm check:reproducible is wired into test and check. Each was mutation-tested before I trusted it — one byte of a vector, one constant value, one deleted constant — since a new check taken on faith would have reproduced the exact defect it was written to fix.
One caveat the fix surfaced, which I think belongs in the principle. The generated Solidity constants file has two writers, my generator and forge fmt, and formatters disagree across versions — CI pins v1.7.1, my machine runs a nightly that reformats six other files. A byte comparison there would assert something that is not the claim: it would make reproducibility contingent on imitating one formatter’s wrapping, and fail on a correct file whose 33 constants are identical. So that artifact is compared by its constants. Pin the check to the property being claimed, not to the most stringent comparison available — an over-strict check that fails on correct input gets disabled, and a disabled check is the decorative field again, only now with a commit message claiming otherwise.
@zhous — good catch on the stale ERC-8337 reference, and thanks for the cross-check. It was left over from before the editor assigned the number; the current draft (renamed erc-8350.md in upstream ethereum/ERCs#1910) and this thread both use 8350 now.
NaturalDAO / Proof-of-Love looks genuinely adjacent — a governance layer over AI + consensus is exactly the kind of system that wants a verifiable memory trajectory (what an agent did, in what order, authorized by whom) without exposing the underlying cognition. If useful, the stable integration points are the 32-byte transitionId and the profileId mechanism — neither requires depending on our core. docs/interop/attestation-refs.md shows how ERC-8299’s verdict scheme and our provenance commitment compose without either spec depending on the other; happy to walk through a similar composition for NaturalDAO if the direction is interesting.
@everest-an Really appreciated the deep dive on this — the “decoupled is not registry-blind” distinction and the §6.3 observation that a field to eyeball is a stand-in for a check never ran are both sharp and worth keeping. Thank you for walking through every layer of it.
After sitting with it, here’s the shape that feels right from the 8274 side:
A single event with a single bytes field — AgentVerifierUpdated(bytes data). Two encoding conventions, both valid:
- Simple: abi.encode(oldVerifier, newVerifier) — for deployments that only need address-level tracking
- Memory-versioned: abi.encode(registry, spaceId, transitionId) — the full lineage
The developer picks one. 8274 doesn’t mandate either — it just provides the bytes slot. This keeps the interface clean (one event, not two), keeps 8274 firmly decoupled from any registry (bytes is a neutral carrier), and lets the consumer decode whichever shape the deployment declared.
Curious if this lands right from the 8350 side — the (registry, spaceId, transitionId) tuple is exactly what you’ve been arguing for, just carried inside a neutral envelope. No hardcoded registry, no registry-blind claim, no eyeball-only field.
Appreciate the breakdown on ERC-8350! The composable provenance approach is intriguing.
A quick note on our design philosophy for NaturalDAO:
-
Tokens as Compute Metrics: We view “Tokens” exclusively as units of AI resource allocation (like LLM context/compute tokens), not crypto assets.
-
Public vs. Private Good: NaturalDAO is dedicated to pure public infrastructure. Tokenizing a protocol inevitably turns it into a private economic system, which contradicts our mission.
As long as our integration focuses strictly on non-financialized, verifiable AI consensus for the public domain, we’re happy to explore how any ERC fits in!
@zhous the non-financialized framing is a real constraint worth taking seriously rather than working around – everest-an’s transitionId/profileId composition point stands regardless (neither depends on a token or an economic layer, just a content-addressed reference).
One distinction worth naming since “verifiable AI consensus” spans two different problems: recording WHAT was produced and by which agent/step (provenance – what ERC-8350’s chain already gives you), versus WHETHER a given AI-produced output was actually sound before the DAO’s process treats it as settled (judgment). A verifiable trajectory can be byte-perfect and still record a bad decision faithfully – provenance proves the record wasn’t tampered with, not that the thing recorded was right.
Real question, not a pitch: does NaturalDAO’s consensus mechanism ever need an independent check on that second thing – a public, recomputable verdict on whether a specific AI output was sound, published separately from the provenance chain itself and checkable by anyone with no trust in either the AI or the DAO’s own process? If yes, that’s the exact seam attestation_ref (cited above) was built for, and it composes the same non-financialized way everest-an described – a signed verdict + a public ledger entry, nothing that requires depending on our core. If consensus here is purely about WHO said WHAT WHEN, that second layer may genuinely not be needed – worth being precise about which one Proof-of-Love actually requires before assuming both.