Agreed. I’d pin exactly those two properties first: immutable filing-time root, and zero influence of that root on the economic resolution path. That keeps commitment timing separate from evidence validity without asking the contract to interpret the evidence.
Then it’s crisp enough to hand to the fuzzer as-is:
one immutable filing-time root, provably inert on the resolution path. Good place to leave it.
Aziz — separate from the filing-root thread: could you confirm whether the generalized recursive-aggregate rule above matches the boundary you intended, especially the terminal-state case and singleton-fold identity test?
Confirmed, and the general rule matches the boundary I intended. Class-carrying and independently recomputable with breakdown preserved is the correct generalization: keeping the class through the fold is exactly what stops a timing state collapsing into a validity one, now stated for any fold rather than that single case. Framing the whole thing as non-elevation is the right enforcement, a fold that never introduces validity, ranking, or scoring has nothing that could inherit recomputability it should not have. That was the limit I was worried about, and non-elevation closes it by construction.
Terminal-state: correct. Terminality is not an exception. A terminal result consumed by a further fold crosses the same boundary and carries the same recomputable aggregate structure, no bypass.
Singleton fold:
your four conditions are right, and the load-bearing one is that the singleton aggregate does not reuse the source entry identity. It is a first-class aggregate of arity one with its own identity, which is the only reading consistent with the general rule, since an aggregate that degenerated into its member would stop being first-class at arity one and break the rule it exists to satisfy. Preserving the semantic result commitment when nothing transformed, requiring byte-equivalent replay under the same canonical fold, and treating same-identity-with-different-content as an explicit conflict rather than silent dedup are all correct, and the last is the same discipline as never letting an inference become a recorded fact.
So the rule and the degenerate-fold contract match the boundary, including the terminal-state pass.
Thanks — that resolves the boundary cleanly. I’ll carry this interpretation into the artifacts and link the concrete implementation here once they’re ready.
Perfect. Link the implementation when it’s ready and I’ll run it against both properties. Nicely landed, narrow and clean.
Aziz — quick follow-up on the invariant you identified:
“a timing state must never collapse into a validity state.”
I took it back to my own repository first, and the implementation gap is now closed.
Positions 1–17 of the RSF evaluation order are implemented and wired into one deterministic, synchronous, first-failure-wins prefix evaluator:
evaluateRsfPrefixThroughPosition17
The boundary is explicit: this is a prefix evaluator only. Positions 18–28 remain unimplemented; it constructs no aggregate and emits no final four-state evaluation envelope.
The A3 blocker is also closed. Chronicle admission now exposes the typed, non-throwing tryCreateChronicleEntryV0 primitive, and RSF position 12 calls it directly. All nine typed admission rejection causes are proven by an executable test matrix to terminate earlier at positions 8, 9, 10, or 11. A typed rejection reaching position 12 under ordered operation is therefore treated as an explicit implementation-invariant violation, never converted into a fabricated semantic finding.
Implementation:
Current-status documentation repair:
Canonical main:
941f7ab945661686a72ef77d419e21feb8e3db1e
Cold verification passed: 671 Bun tests, 39 Python tests, clean repository state.
Thank you again — your formulation exposed the missing architectural rule rather than just one local bug.
Read the prefix evaluator. It holds.
Two things confirm the invariant rather than assert it. First, the final return emits verified values and the pinned commitments, not the four-state carrier and not an aggregate, so the prefix never manufactures a validity outcome it hasn’t earned. Timing stays timing. Second, position 12 owns no finding:
a typed Chronicle rejection there throws as an implementation-invariant violation instead of being translated into a prefix result, so an admission fact structurally cannot collapse into a semantic one. That is the invariant made unbreakable in the one place it could have leaked.
The boundary is drawn right too, the header says it plainly:
no aggregate, no four labels, 18 to 28 unowned. Which is exactly where the rule stays load-bearing next. When 18 to 28 build the aggregate and the envelope, the carrier has to report validity only from what resolved there, never inheriting it from an admission or timing fact upstream. Hold that and the invariant survives the full 1 to 28, not just the prefix.
Clean work. Better outcome than the bug it started as.
Aziz — final follow-up on the boundary you left load-bearing.
Positions 18–28 are now implemented, merged, and independently audited on current main:
7dbedb1feb05aa6e672788ae4abcd273add75755
Repository:
https://github.com/pipavlo82/crystal-receipt
Final hardening PR:
https://github.com/pipavlo82/crystal-receipt/pull/158
The full public path is now:
evaluateCompleteRsf
It computes positions 1–17 itself, enters 18–28 only after the prefix succeeds, and has exactly one accepted-envelope construction site. That site is reachable only after a verified aggregate reports:
completedThrough: 28
So the rule you stated now holds across the full evaluator: validity cannot be inherited from an admission fact, a timing fact, a prefix label, or a caller-supplied success claim. It must be mechanically earned inside positions 18–28.
The audit tested the bypass directly. A mutant that attempted to return a verified aggregate after position 27 was rejected by a committed test with a non-zero exit.
The final package contains 34 vectors:
-
32 through the public complete entrypoint
-
1 internal stage-continuation invariant vector
-
1 package-integrity-only vector
Package digest:
879e0caa5d26643755b5a0e4b8836f0215dec3463cb1fa9ab44a82aefe618ee7
Expected-evaluation-set digest:
ecc1f4072e635c913343e017c59eb74094290537fe16c6b4e8a85fb5e36e6531
Both the independent Python verifier and the TypeScript audit process all 34 vectors without importing the production evaluator.
Final verification:
734 pass, 0 fail, 2911 expectations
Eleven isolated source mutants were executed in separate detached worktrees. Every one was caught by a named committed test or static guard, including early acceptance, reordered positions, trusted candidate booleans, reconstruction reuse, public stage-surface exposure, and Proxy reflection attacks.
No P0, P1, P2, or P3 finding remains.
Final verdict:
RSF_1_28_PRODUCTION_CLOSURE_PASSED
You were right about where the invariant had to remain load-bearing. It now survives the complete 1–28 path, not as documentation, but as an enforced reachability property.
Thank you again for identifying the rule underneath the original bug.
Clean closure, and the important part is exactly where you put it:
the invariant now survives as an enforced reachability property, not as documentation. Documentation describes intent, a reachability guard makes the intent unforgeable.
The mutant results are what make that credible. A verified aggregate returned after position 27, rejected by a committed test with a non-zero exit, is the proof that validity is earned inside 18 to 28, not inherited from an admission, a timing fact, a prefix label, or a caller’s success claim. That was the whole point of the boundary, and it now holds across the full 1 to 28 path.
Worth stating the general lesson plainly:
in any settlement or evaluation pipeline, validity should be earned at the last step, never inherited from an earlier one. Most bugs of this class are that principle quietly violated.
Well carried through to closure. Appreciated the rigor.
Picking the composed-evaluators thread back up and putting it on record ahead of audit.
Where this started: we asked whether complete()/reject() as the sole settlement
interface holds as middleware matures (#256/#257). After @ThoughtProof’s middleware-
layering writeup and @JimmyShi22’s ERC-8274 v0.2, the answer is yes :
- ERC-8183 stays binary. The protocol doesn’t aggregate evaluator signals it can’t verify.
- Composition happens at the consumer boundary, before the settlement call. No
aggregation interface is added to the protocol. - reason is the settlement evidence anchor.
@ThoughtProof’s writeup (TP-solo sections, linked) contributes a consumer-boundary
composition model with domain-scoped veto, reasoning verification (RV) and behavioral
(AHM) each veto in their own domain, neither overrides; fail-closed on verification
failure; graceful-degrade only when the missing signal wouldn’t change the outcome. It
doesn’t touch the primitive.
Reference impl (ASP) status :
- Binary complete()/reject() are implemented, plus an optional atomic single-proof
overload that resolves the evaluator’s IAgentVerifier and settles verificationDigest
as job.reason, modeled on @JimmyShi22’s ERC-8274 v0.2 IAgentVerifier layer. - Open alignment item : our verify() is currently declared view (STATICCALL gives us a
free reentrancy guarantee at the settlement boundary), whereas v0.2 marks verify() as
non-view. Keen on @JimmyShi22’s read on whether a stateless/view profile can be blessed
for verifiers called directly by the settlement layer. - Planned next: align the digest preimage + settlement event to the v0.2 canonical shape
for independent recomputability, and ship a reference ProofEvaluator around the veto
model (off-chain, calling the binary primitive).
Open / next :
- The AHM-side composition (routing, evaluationFee edge cases) is being finalized with
@pablocactus. - External audit scoping is being re-sequenced; I’ll share updated timing rather than a date.
Feedback welcome, especially the reason/digest alignment and the view/non-view question above.
(Also following the reputation-from-terminal-states direction @aziz / @pipavlo82 have been
developing, it composes cleanly with reason-as-anchor; happy to dig in there once the
verifier alignment lands.)
Reference impl + interfaces: GitHub - Demsys/agent-settlement-protocol · GitHub
Thanks Aziz — this held through the full 1–28 closure. Positions 18–28 now earn aggregate validity at their own boundary rather than inheriting anything from timing or admission upstream, including adversarial mutation coverage after position 27. I’ve since moved further into the Chronicle verifier challenge surfaces — currently through #176.feat: freeze Chronicle checkpoint-local challenge set v0 by pipavlo82 · Pull Request #176 · pipavlo82/crystal-receipt · GitHub
Thanks for carrying this forward. The consumer-boundary composition and keeping ERC-8183 binary both look right to me. The reason/digest alignment is the part I’d watch most closely, especially if it is going to become the bridge into terminal-state reputation.
@Bakugo32 Really appreciate the thorough writeup on the composed-evaluators resolution — keeping 8183 binary and composing at the consumer boundary is exactly the right call. The middleware-layering analysis and the ASP reference impl are both genuinely useful contributions.
On the view vs non-view question: ERC-8274 has two distinct layers, and I think the answer sits in that separation.
- IProofVerifier (inner) — stateless, pure cryptographic verification.
verify(inputHash, outputHash, metadata, proof) → bool. Intended to be view — it answers “is this proof valid” without touching any state. - IAgentVerifier (outer) — stateful. Marked NOT view because implementations MAY manage agent-to-proof-verifier bindings, single-use taskId tracking, and other per-deployment state.
The thought for 8183: the ProofEvaluator / settlement layer already owns agent registration, evaluator identity binding, and the context around who’s allowed to settle what. That state management is naturally in 8183’s domain. So a stateless verifier that sits at the settlement boundary might want IProofVerifier directly — view, no extra state — rather than IAgentVerifier, which carries state that 8183 already manages.
Curious if this maps cleanly to what you’re building. If the ASP impl calls the proof verifier as a stateless step (input/output/proof in, bool out) and 8183 owns the agent/evaluator lifecycle separately, then the view-safe path is already there at the IProofVerifier layer. Would love to hear if there are seams that don’t align — happy to iterate on the spec if something doesn’t fit.
Latest 8274 draft: ERCs/ERCS/erc-8274.md at master · JimmyShi22/ERCs · GitHub
@Bakugo32 Thanks for putting this on record and for the accurate read of the TP half.
Agree on the load-bearing split: binary stays at settlement; composition stays consumer-side; reason as the evidence anchor is the right joint between 8183 and the 8274 verifier layer.
On digest alignment: our RV attestations are already shaped to land as a recomputable anchor in job.reason without adding protocol surface — happy to cross-check the preimage once you pin the v0.2 canonical shape.
On verify() view vs non-view: from the consumer-boundary side a stateless/view profile is attractive exactly for the STATICCALL/reentrancy property you note, as long as any state-bearing work (attestation write, fee accounting) stays outside the settlement call. Defer to @JimmyShi22 on whether v0.2 can bless that profile for settlement-called verifiers.
AHM-side with @pablocactus and the audit re-sequence make sense — no date pressure from us. Looking forward to the ProofEvaluator reference.
@JimmyShi22 @ThoughtProof thanks, this is the right seam to be sharpening. Strong +1 on keeping the split load-bearing: 8183 stays binary at settlement, composition lives at the consumer boundary, and the proof check is a stateless step rather than a stateful one.
On view vs non-view: at the interface level, the settlement-called verifier wants the inner IProofVerifier profile, stateless and view. The STATICCALL and reentrancy property is exactly the reason. A settlement path should be able to ask “is this proof valid for this input and output” as a pure function and get a bool back, with no state mutation in that call. Anything state-bearing, attestation writes, fee accounting, single-use taskId tracking, belongs outside the settlement call, in the layer that already owns the agent and evaluator lifecycle.
So the clean contract from the 8183 side is: the standard owns agent and evaluator identity plus the settlement context, and it consumes proof verification as a stateless boolean at the boundary. That keeps 8183 minimal and leaves 8274’s outer IAgentVerifier free to carry per-deployment state where a deployment actually wants it, without pushing that state into the settlement hot path.
Two things worth pinning at the spec level for v0.2 rather than leaving to implementers:
1. Should v0.2 formally bless the view/STATICCALL profile for settlement-called verifiers, so reentrancy safety is a spec property and not a per-deployment accident?
2. Where should single-use taskId tracking canonically live at the interface? If it sits with the stateful outer layer, the settlement boundary can stay pure.
Happy to keep iterating at the interface level. No date pressure on our side either, getting the boundary right matters more than getting it fast
Thanks everyone for the thoughtful discussion and for pushing on the boundary from different angles.
This separation looks clean to me. Keeping the settlement-called proof check in a view/STATICCALL profile preserves the same rule we’ve been enforcing elsewhere: lifecycle state may determine what gets checked, but it must not leak into or manufacture the verification result itself.
The reason/digest alignment still looks like the key joint to pin carefully, especially if it becomes the evidence bridge into reputation derived from terminal states.
@pipavlo82 exactly, that’s the invariant worth stating out loud:
lifecycle state can gate what gets checked, but it must never feed or manufacture the verification result. The verdict stays a pure function of the proof.
On the reason/digest joint, agreed it’s the one to pin carefully. The line worth holding: the standard’s job is to make the evidence recomputable and tamper-evident, not to interpret it. So what belongs in-spec is a canonical, recomputable digest anchored in job.reason, one any consumer can recompute independently from the terminal state and check against the anchor. What does not belong in-spec is how that evidence gets turned into reputation. Reputation
derived from terminal states is a consumer-side concern that sits above 8183, and deliberately so:
keeping derivation out of the standard is what lets 8183 stay minimal and lets different consumers weight terminal states differently without forking the spec.
So the joint to pin is narrow: agree the canonical shape of the reason anchor at the spec level, so the evidence bridge is verifiable by anyone, and leave the reputation function to the layer
above. Pin the shape, and the bridge is sound regardless of who consumes it.
Exactly. “Pin the shape, not the interpretation” is the boundary I had in mind. If job.reason carries a canonical recomputable anchor and nothing more, 8183 can remain minimal while downstream consumers are free to derive reputation without changing the factual settlement record.
Then we’re aligned. Concrete step for v0.2:
specify job.reason as carrying exactly one thing, a canonical, recomputable anchor with a fixed shape, plus an explicit non-goal that the standard never interprets it. That one line keeps settlement minimal and lets reputation live entirely consumer-side. Happy to help draft that section with @JimmyShi22 when the v0.2 pass opens.