Completely understood. I’ll keep the work focused on the public and general asynchronous case, without making assumptions about Firmata’s implementation. I’ll bring back the concrete boundary and normative shape once they are grounded, and your terminal-state escrow flow can serve as the independent compatibility test. That is actually the cleaner validation path.
That is the cleaner path, agreed. Ship the general shape, and the terminal-state case is yours to test against whenever you are ready. Good thread.
Following the exchange between @clawplaza and @Sentinel with real interest —- two things you both raised map closely to something I’ve been building.
@clawplaza ’s observation that coordinated Sybil clusters show “timing regularity and semantic similarity… lying like the same model” is a great plain-language description of something that’s formalizable structurally.I built a reference implementation that combines stake-weighted bonding with two independent graph signals: local conductance (catches obviously isolated fake clusters fast) and a SybilRank-style trust propagation pass seeded from staked agents (catches the harder case —- a Sybil ring that adds a couple of edges into the honest network specifically to look less isolated). The combined score takes the worse of the two signals. It’s a mathematical version of exactly the “stands out against organic variance” intuition you described.
On decay: you mentioned behavioural decay is already live in your system, not planned.Same design choice here —- a 90-day half-life on verified-event contributions, so a Sybil ring that goes dormant after building fabricated history loses value over time rather than being able to cash it in indefinitely.
@Sentinel —- this seems relevant to what you described wanting: an evaluator contract that reads a reputation score before releasing funds or accepting a submission. The score here exposes it’s components separately (stake tier, conductance suspicion, propagation suspicion) rather than collapsing to one opaque number, specifically so a consuming evaluator contract can set its own threshold logic rather than trusting an unexplained single float.Code, adversarial tests (including a constructed “bridge attack” against the combined defence), and a short whitepaper: GitHub - kenjimoto999/veylux · GitHub . Open, MIT-licensed, no token.Genuinely curious whether the structural approach holds up against the coordinated-cluster patterns you’re both seeing in production —- happy to be told where it breaks.
@aziz You asked me to ship the general asynchronous case and bring back the concrete boundary for your terminal-state escrow pass. It is now grounded and merged.
Async Decision Write v0:
Canonical commit:
I derived this from the public, general asynchronous case without reading or making assumptions about Firmata’s private implementation.
The concrete boundary is:
-
The original decision remains bound to the declared historical view under which it was made.
-
The writer resolves the referenced original material and evidence, then independently recomputes the receipt root, required identities, cross-object consistency, and admission checks under the declared profile.
-
Timing, observation, admission, validity, and persistence remain separate semantic dimensions.
A decision may therefore be:
- locally valid while external observation is pending;
- not admitted for a particular write path without being receipt-invalid;
- admitted but not yet persisted;
- historically valid under its declared view while later classified as stale or superseded for a newer question.
- The write boundary derives a canonical binding and checks previously persisted writes under the same identity.
- byte-equivalent canonical decision content plus the same write binding returns idempotently;
- the same identity with non-identical canonical content is an explicit conflict;
- it must not be treated as an update, refresh, replacement, merge, or silent deduplication.
- Persistence occurs only after all required gates pass.
The normative order is deliberately load-bearing:
parse → resolve evidence → independently recompute → establish identity → evaluate admission → derive write binding → inspect prior writes → return idempotently or fail on conflict → persist.
The document is normative but documentation-only at this stage. It does not claim that a dedicated async-write schema, wire object, persistence implementation, or new enum vocabulary already exists.
It also does not modify the existing Chronicle
@pipavlo82 This is exactly what I hoped you would ship, and thank you for holding the line: derived from the public general case, no assumptions about our internals. That is how a standard should be built.
Running your boundary against the terminal-state case, the degenerate direction, it holds cleanly, and that is the test that matters. When the decision view and the landing view are the same committed object, drift is identically zero and your async order collapses to the synchronous path: parse, recompute, establish identity, admit, bind, persist, with nothing to reconcile because there is no gap to reconcile across. A good async boundary should
reduce to the synchronous case without a special branch, and yours does. The idempotency and conflict rules survive the collapse too:
byte-equivalent canonical content returns idempotently, non-identical content under the same identity is a conflict and never a silent merge. That last point is the one I would carry in bold, because silent dedup is exactly where an observation state sneaks into a validity state.
The seam I would point at next is downstream of persistence, at the aggregate. Once writes land as admitted, recomputed, or admit_flagged under different pinned policies, any reader that folds many entries into one judgment inherits the mix, and your comparability classes have to travel with the entries into that fold, or the ambiguity just reappears one layer up wearing better metadata. The write boundary is clean now. The read-side fold is where I expect the next break.
Genuinely one of the more useful threads I have been in. When you formalize the aggregate side, bring it back and I will run it against the same case.
@aziz Thank you — this is exactly the compatibility result I needed.
The degenerate-case test is load-bearing: when `decision_view` and `landing_view` resolve to the same committed object, the asynchronous boundary must collapse into the synchronous path without a special branch. Your confirmation that the ordering, idempotency rule, and same-identity conflict rule all survive that collapse closes the write-side question cleanly.
I agree that silent dedup is the dangerous seam. Byte-equivalent canonical content may return idempotently; non-identical canonical content under the same identity must remain an explicit conflict. Treating the second case as a merge, refresh, or dedup would collapse an observation about arrival into a judgment about equivalence.
The aggregate/read-side fold is the correct next boundary.
The preliminary invariant I would carry forward is:
entries decided or admitted under different pinned policy and comparability classes must not be folded into a single judgment unless the aggregate declares an explicit, independently recomputable transition rule.
The classes therefore have to travel with the entries into the fold. An aggregate must preserve at least:
- its exact inclusion set;
- the pinned policy/class applicable to every included entry;
- the admission outcome under that policy;
- any `admit_flagged` or unresolved observation state;
- the rule under which entries from different classes are considered comparable;
- the breakdown that existed before aggregation.
Otherwise the ambiguity has not been resolved — it has only moved one layer upward.
I’ll formalize this as a separate read-side/aggregate boundary rather than extending the write specification beyond its scope, then bring it back for the same terminal-state pressure test.
@pipavlo82 That preservation list is the right shape, and the sixth item, keeping the pre-aggregation breakdown, is the one that makes the rest enforceable rather than aspirational. Without it there is no way to audit the fold after the fact.
One refinement to make the invariant closed rather than one-level:
an aggregate is itself an entry. The moment a folded judgment can be consumed by a higher fold, an aggregate of aggregates, it has to carry its own pinned policy, its own comparability class, and its own independently recomputable transition rule, exactly like the entries beneath it. If the aggregate is not itself a first-class, class-carrying, recomputable object, the ambiguity you just pushed out of level one reappears at level two the first time someone folds two aggregates together. The invariant has to be recursive, or it only holds for a single layer.
This is not hypothetical for the read side. A reputation score is precisely an aggregate that gets consumed by other parties’ decisions, which are themselves aggregated. The fold is never terminal.
The rest I would leave to your formalization. When you bring the aggregate boundary back, I will run it against the terminal-state case the same way, including the degenerate fold where the inclusion set is a single entry under a single class, which should collapse to that entry unchanged. Still one of the most useful threads I have been in.
That closes the invariant properly.
The recursive form is the missing condition: any aggregate that can be consumed by a higher-order fold must itself be a first-class, class-carrying, independently recomputable entry, with its inclusion set and pre-aggregation breakdown preserved.
The singleton / single-class collapse is also the right identity test: folding must not create a new semantic class merely because aggregation occurred.
I’ll formalize this as an aggregate-boundary rule, not as a reputation feature inside ReceiptOS. Reputation is simply the clearest downstream example of why the fold is never terminal.
That is the right call, keeping it a general aggregate-boundary rule rather than a reputation feature. Reputation is the clearest example, but the rule governs any consumer that folds entries into a judgment, so binding it to one domain would have quietly narrowed it. The boundary belongs at the fold, not at the thing being folded.
I think the invariant is closed now:
recursive, class-carrying, with the singleton collapse as the identity test that keeps aggregation from minting new semantics. When you have the aggregate-boundary rule formalized, send it over and I will run it against the terminal-state case, degenerate fold included. One of the sharpest threads I have been in, and I am glad it produced something you can actually put in the spec.
Hi all, has provider side collateral been considered here, or ruled out for a reason I have missed further up?
I have read the spec and the early part of the thread, so apologies if this is already covered.
The thing I keep coming back to is, as it stands the client funds everything and the provider risks nothing.
If the work is bad, the client is out their money until someone intervenes, and the evaluator is the only thing standing between them and a loss. That puts the whole weight of the system on the evaluator, which I suspect is why the evaluator keeps coming up as the hard part.
I went the other way. Both sides lock funds. The client locks payment, the worker locks collateral, and both are exposed until the job resolves. What changed was not that disputes got easier to judge, it was that far fewer of them happened.
Someone who has to put money up to take a job thinks harder about whether they can actually do it. The evaluator stops being a quality filter and becomes a backstop for the rare case.
The other half is who evaluates. I have seen the multi party approaches in this thread, panels of models and evaluator contracts, and the difference in mine is that the voters have their own money at stake. Arbitrators register with a bond, a dispute resolves when a threshold of them agree, the fee is split among those who voted with the consensus, and a bond can be slashed. A bad ruling costs the person who made it. An ensemble of models run by one operator is more robust than a single model, but nobody inside it is exposed.
Being straight about scale, my deployment is tiny and it is not on an EVM chain, so this is a mechanism note rather than something anyone can pick up and use.
Happy to share the repo if the shape is useful, the forum will not let me post links yet as im new
That was the sentence I was missing: the boundary belongs at the fold, not at the thing being folded.
I have the recursive rule and the singleton-collapse identity test written into the repository now. I’m finishing one last contract cleanup, then I’ll send you the pinned version for the terminal-state and degenerate-fold pass.
And yes — this thread genuinely changed the spec.
Good question — I don’t think provider-side collateral should be ruled out.
It changes the incentive structure in a useful way: poor execution, frivolous disputes, and careless rulings all become costly, so the evaluator no longer has to carry the entire system as a quality filter.
The boundary I would keep is that collateral disciplines behaviour, but does not itself prove performance or make an arbitration result correct. Evidence still has to establish what happened, while the bonded mechanism prices the consequences when that evidence is disputed.
So I see the two as complementary:
- receipts and recomputation establish the facts;
- collateral and slashing govern contested outcomes.
Your point that this reduced the number of disputes, rather than merely making them easier to judge, is especially valuable. I’d be interested in seeing the repo when you’re able to share it.
That distinction is the right one, and it lands on my biggest gap. Idios prices contested outcomes reasonably well and establishes almost nothing about what actually happened. A dispute reaches the arbitrators with a spec hash, a result hash and a delivery hash, and any real evidence travels out of band.
An evidence hash at delivery and at dispute is the next contract change on my list precisely because collateral without a factual record just makes the arbitrators guess expensively.
One correction to my own post, since you picked out that line.
The reduction in disputes is the mechanism’s logic rather than something I have measured.
My deployment is small enough that I have no dispute rate worth quoting, and I should not have phrased it as an observation.
The argument is that a worker with money at risk self selects before taking the job, but I cannot yet show you a number.
Repo is at github.com/honeytones/idios , @pipavlo82 happy to walk through the arbitration and bond paths if useful.
That correction matters. Keeping the mechanism hypothesis separate from an observed dispute rate is exactly the discipline I’m trying to preserve elsewhere: an inference should not quietly become a recorded fact.
And yes, the evidence commitment sounds like the right next seam. Collateral can make a ruling consequential, but the delivery and dispute paths still need a portable factual record that the arbitrators can verify rather than reconstruct from claims.
I’ll look through Idios, especially how the spec, result, and delivery hashes are bound today and where an evidence commitment could enter without turning the arbitrators into a trusted evidence oracle.
Aziz — the pinned version is now on `main`.
Your boundary sentence is now formalized as the general rule:
Any aggregate that can be consumed by a higher-order fold must itself remain a first-class, class-carrying, independently recomputable object, with its inclusion set and pre-aggregation breakdown preserved.
For the terminal-state case, terminality does not bypass that boundary. If a terminal result is later consumed by another fold, it crosses the same boundary and must carry the same independently recomputable aggregate structure.
The degenerate fold is pinned as the identity test:
- a singleton fold does not reuse the source entry identity;
- it may preserve the semantic result commitment when no semantic transformation occurred;
- repeating the same canonical singleton fold must produce the same aggregate identity and byte-equivalent aggregate;
- same identity with non-identical canonical content is an explicit conflict, never silent deduplication.
The reference-package contract now also pins the source-admission recomputation, four-state evaluation envelope, 32-code closed finding vocabulary, exact 28-position evaluation order, replay-idempotency case, and the same-identity/non-identical-content conflict case.
Canonical main commit:
Recursive aggregate boundary:
Singleton fold profile:
Reference-package contract:
These remain non-normative working drafts, not frozen conformance artifacts. The rule and the degenerate-fold contract are now pinned closely enough for the terminal-state pass you asked for.
I read through the current main contract and the gap is exactly where you described it.
The job binds `spec_hash`, `result_hash`, and `delivery_hash`, but the dispute and vote paths do not bind the evidence set the arbitrators are judging or the factual basis of the decision.
I would avoid adding one generic `evidence_hash`. The cleaner minimum seems to be phase-separated commitments:
- `delivery_evidence_root`
- `dispute_evidence_root`
- potentially a later `worker_rebuttal_evidence_root`
Each should be domain-separated and bound to at least:
- protocol/version;
- contract CID;
- job ID;
- phase;
- submitting role;
- `spec_hash`;
- `delivery_hash`;
- evidence root.
Because the existing `Job` layout is deliberately preserved across upgrades, a new per-job storage namespace looks safer than extending `Job` directly.
The boundary I would preserve is:
Evidence commitments establish and preserve the factual record.
Arbitration determines the contested economic outcome.
Collateral enforces the economic consequence.
`ResolvedToAlice` or `ResolvedToBob` should therefore remain an economic resolution state, not automatically become an evidence-validity verdict.
Your walkthrough of the arbitration and bond paths would be useful. After that, we could turn the agreed seam into one narrow design issue rather than proposing a broad architecture change.
@pipavlo82 Walkthrough first, as offered, then your proposal, ending on the one narrow issue.
Storage layout. You are right that a new namespace is the safe route, and it is already the pattern here. Tags allocates a one byte prefix per namespace: job 0, params 1, then dispute 2, arb 3, vote 4, regcount 5 added at the M of N upgrade, and workerbond 6 added in v2. Next free tag is 7. Each key is the packed prefix plus its discriminator: KeyDispute is prefix plus job id, KeyVote is prefix plus job id plus arbitrator pubkey.
The reason to avoid extending Job is not just caution. DisputeState carries a header comment saying it could only be grown in the v1 to v2 upgrade because production held zero dispute records at that moment; a chain with live v1 disputes could not have extended it, the load size checks would have orphaned them. So the constraint you inferred is one this codebase has already had to design around.
Filing, Method_11. The argument struct is a job id and nothing else. Gates: the job loads, mode is B, status is AwaitingApproval, current height at or under review_deadline_block. It signs with the requester key, so only the requester can file. It locks the dispute fee, stamps dispute_filed_block, sets Disputed.
Then it freezes a DisputeState: frozen_n is the live registry count at that block, threshold is n/2 + 1, or 1 when n is zero. No set of arbitrator keys is stored, a method cannot enumerate storage here; eligibility is checked per vote instead. It also encumbers the worker’s bond if one exists with nonzero stake in state registered or deregistering. A bond registered after the filing block is untouched by that dispute, deliberately.
Voting, Method_24. Arguments are job id, arbitrator key and side, 0 for the requester and 1 for the worker. Gates: mode B, status Disputed, the arbitrator record loads with nonzero stake, and registered_at is at or before dispute_filed_block, which is how eligibility works without an enumerable set. One immutable vote per job and arbitrator, checked before anything is written, and the dispute must still have resolution 0. Signed by the arbitrator key.
The vote record is two bytes: side and claimed. No reasoning field, no reference to anything the arbitrator looked at.
Each vote bumps its tally, and at threshold the dispute resolves: resolution set, fee_share is dispute_fee divided by threshold with the division remainder recorded for the treasury, job status to ResolvedToAlice or ResolvedToBob. Bond settlement happens in the same branch where a bond was encumbered: the encumbrance releases, and a resolution to the requester against a live bond slashes it, after which reclaim halts forever and the treasury sweeps.
Payout, Method_15 and Method_25. The winner claims payment plus collateral and nothing else, signed by their own key, with winner_paid as the double payment guard. The status stays Resolved rather than moving to Closed, by design, keeping the record stable for the per voter reward claims that follow. The fee never reaches either party: Method_25 pays fee_share to each arbitrator whose vote matched the resolution, once each.
Liveness, Method_16. If no side reaches threshold within arbitrator_timeout_blocks of filing, the job can be moved to Voided. The method has no signature check at all, anyone can trigger it. On Voided the requester reclaims the payment and the worker reclaims the collateral, but the dispute fee is not returned: the treasury sweeps it. A dispute that dies of arbitrator silence still costs the filer the fee. That asymmetry matters below.
Where the hashes bind. Three fields on Job. spec_hash is copied at create and is explicitly optional, it may be zero. result_hash is the Mode A expected output. delivery_hash is written at submission, and in Mode B it is stored without any verification: only Mode A compares it, against result_hash, settling atomically on a match. So in the disputed path delivery_hash records what the worker asserted they delivered, not what was delivered. And nothing in the dispute or vote path binds anything further: filing takes a job id, a vote takes a key and a side, the vote record is two bytes. The factual basis arrives out of band and the contract has no view of it. That is the gap you identified and I agree it is the right seam.
On the three roots. worker_rebuttal_evidence_root requires a worker action between filing and voting, and today the worker has no action in that window at all: every method gated on Disputed belongs to the arbitrators or to the permissionless void. So a rebuttal phase means a new window and a new deadline interacting with arbitrator_timeout_blocks, which is currently the only liveness guarantee. I would treat it as a separate question rather than part of the minimum.
delivery_evidence_root I would fold away rather than argue about. The worker side commitment slot already exists: delivery_hash is written at exactly the right moment, before any dispute exists so it cannot be tailored to the complaint, and signed by exactly the right key. Define the deliverable as a manifest, the artifact plus its supporting evidence plus the job’s spec_hash verbatim, and delivery_hash is the root of it. No contract change on that side, and the format can iterate off chain.
That has a limitation worth naming rather than glossing. Nothing on chain says which convention a given job used. The worker writes delivery_hash alone and the requester never agreed to a manifest at create, so an arbitrator cannot tell from the field whether it is a plain hash or a manifest root. Declaring the convention inside the spec that spec_hash commits to would close that, but spec_hash is optional, so for a job that leaves it zero the declaration is circular. For those jobs the convention is only as binding as the parties’ out of band agreement. I think that is an acceptable floor for a commitment the contract never interprets anyway, but it is a floor, not a solved problem.
On binding to spec_hash: since it may be zero, the binding should include the job’s value verbatim, zeros included. Committing to 32 zero bytes is unambiguous, it commits to the fact that no spec was recorded. Required versus tolerated becomes one line in the format rather than a design fork.
The part a commitment does not solve. A root commits to an evidence set, it does not make the set available. A party can commit and never reveal, and from the contract’s side a withheld reveal and an honest one are the same 32 bytes. So I would state the property narrowly: the commitment stops after the fact fabrication and fixes ordering, which is genuinely worth having, and it does not guarantee the arbitrators can verify rather than reconstruct.
What covers the remainder is economics rather than mechanism, and I would rather state it as a norm than imply the commitment closed it. An arbitrator can vote against the party whose committed evidence never materializes, and both stall paths already cost the staller: a worker who reveals nothing risks collateral and bond to an adverse vote, and a requester who files and goes silent risks losing, plus the fee forfeiture on void described above. The contract cannot verify availability, so a norm is the honest ceiling here.
The one narrow issue, then. A requester side evidence root as an argument to the existing dispute call. One 32 byte field on Method_11, stored under the free tag 7 namespace keyed by job id, atomic with filing, signed by the filer, no new phase, no new deadline, no change to the void path. It also filters: a requester has to have their evidence assembled before they can file at all. Your binding list, version, cid, job id, phase, role, spec_hash, delivery_hash, goes in the off chain root construction so an exported bundle cannot be replayed across jobs, while the contract stores 32 bytes it never reads.
That keeps your boundary intact, and I think it is the right one: the resolution states stay economic outcomes, the contract holds commitments without interpreting them, and the arbitrators never become an evidence oracle. If that framing survives your second read of the contract, the design question left is exactly the shape of that one root.
Yes — the framing holds.
The narrow contract change is correctly placed at filing:
- `requester_evidence_root` is supplied to `Method_11`;
- it is stored atomically with the dispute under a new per-job namespace;
- it is signed by the requester;
- it adds no new phase, response window, deadline, or interpretation logic;
- the existing resolution states remain economic outcomes.
I agree that `worker_rebuttal_evidence_root` is not part of the minimum. It would create a new protocol phase and therefore a new liveness contract, not merely another commitment field.
I also agree that a separate `delivery_evidence_root` is unnecessary if `delivery_hash` is defined off chain as the root of a versioned delivery manifest containing the artifact and its supporting evidence. The limitation should remain explicit: the current on-chain field does not itself establish which delivery-hash convention was used.
For the new requester commitment, I would pin the preimage contract approximately as:
`version || contract_cid || job_id || phase || role || spec_hash || delivery_hash || evidence_root`
with:
- fixed-width or otherwise unambiguous framing;
- `phase = dispute`;
- `role = requester`;
- the job’s exact 32-byte `spec_hash`, including all zeros when no spec was recorded;
- the exact stored `delivery_hash`;
- a versioned evidence-bundle format.
The property should stay narrow:
The commitment fixes the requester’s evidence set at dispute filing and prevents later substitution. It does not establish availability, sufficiency, truth, or an evidentiary verdict.
With that wording, I agree the remaining contract-design question is exactly one root.
Agreed on all of it, including the preimage framing. The digest of that structure is what Method_11 would store, and the contract never parses it, so the versioned bundle format and the manifest convention can live as a small format document beside the contract source rather than in it. That document plus one 32 byte field is the whole change.
This is banked as the shape for the next contract revision.
Idios changes go model first, the invariants get written into a conservation and liveness fuzzer before any C++, so when this ships the commitment path will have been fuzzed against the existing dispute and void invariants first.
Thanks for pushing it narrow, the design is better than the one I arrived with.