diff --git a/SPEC.md b/SPEC.md index 9117aa6..ca89094 100644 --- a/SPEC.md +++ b/SPEC.md @@ -359,7 +359,8 @@ Trustless escrow enforcement requires the settlement contract to read *what the ```jsonc "outcome": { "escrow_id": "0x…", - "basis": "ruling", // "ruling" (challenged) | "uncontested_assertion" (happy path) | "mutual_settlement" (unanimous waiver) + "basis": "ruling", // "ruling" (challenged) | "uncontested_assertion" (happy path) | + // "mutual_settlement" (unanimous waiver) | "formula_split" (small-claims, §9.4) "release_bps": 7000, // basis points to the provider "refund_bps": 3000, // basis points back to the payer "penalty_bps": 0, // additional penalty applied @@ -369,11 +370,12 @@ Trustless escrow enforcement requires the settlement contract to read *what the } ``` -There are **three enforcement paths**, and the `basis` field tells the escrow contract which one applies: +There are **four enforcement paths**, and the `basis` field tells the escrow contract which one applies: - **Challenged path** (`basis: "ruling"`): `basis_anchor` is the Arbiter/panel **Ruling** hash, and the `enforce` Object **MUST** be signed by the entitled Arbiter/panel. The off-chain Ruling carries the human-readable rationale. - **Uncontested path** (`basis: "uncontested_assertion"`): there is no arbiter. `basis_anchor` is the asserting party's `assert` hash; the `enforce` Object is **signed by the asserting party**; and the escrow contract **MUST** verify that the asserter's bond was posted at `assert` time (§9.4), that `challenge_window` has elapsed since the assertion's anchor timestamp with **no valid `dispute` anchored** in between (valid = filed by a bound Thread party with the challenger bond, §9.4), and that the numeric directive **matches the asserted outcome** (full release on an undisputed completion `assert`; full refund on an undisputed non-performance `assert`). - **Mutual path** (`basis: "mutual_settlement"`): no window needs to elapse. `basis_anchor` is the hash of the co-signed `settle` (§9.4) or `rescind` (§7.4) Object carrying the agreed directive; the escrow contract **MUST** verify **assent to this exact directive from every challenge-entitled party's bound chain account** (per the §10.1 party bindings — via per-party `escrow.approve_settlement` calls or one transaction co-signed by all bound accounts, profile choice) and that the executed directive matches the assented one. Consent is what replaces the window: the parties the window protects have all waived it. +- **Formula path** (`basis: "formula_split"`, small-claims profile only, §9.4): `basis_anchor` is the recorded `dispute`'s anchored hash; the `enforce` MAY be anchored by either party; the escrow contract **MUST** verify that a valid `dispute` is recorded for this escrow and that the directive **equals the pre-agreed `split` formula** recorded in `escrow.open`'s `conditions` at funding time. The parties pre-consented to the formula in the accepted head — no arbiter is involved. **Contract-readable dispute state (normative).** The uncontested path requires the escrow contract to verify the **absence** of a `dispute` within the window — and a smart contract cannot scan a ledger or prove a negative over a generic event stream. For escrow-backed Threads, profiles **MUST** therefore realize `dispute` and `enforce` anchors as **stateful, contract-readable records in the same composable state space as the escrow** — e.g., anchored via a call on the escrow/registry contract, keyed by `thread_ref`/`escrow_id` — rather than as generic event/message primitives. See §13.1 (requirement 6) and the Hedera caveat in §13.4. @@ -665,7 +667,7 @@ When parties disagree about whether terms were met, resolve it through a forum t ### 9.2 The arbitration clause -Disputes are only fair if the forum is fixed *before* the conflict. A contract's `terms.dispute` (see §7.3) **MUST**, when present, specify: the `arbiter` (or a selection rule/panel), a `process_profile`, a `challenge_window`, an `evidence_window` and a `ruling_deadline` (liveness bounds, §9.4), a `bond` sizing rule when `escrow.required` (§9.4), escrow handling, and appeal availability — `appeal.allowed`, **plus `appeal.appeal_window` whenever `appeal.allowed = true`** (its omission when appeals are enabled makes the `RULED → FINALIZED` transition ambiguous and **MUST** be rejected). When `appeal.allowed = false`, a Ruling is final on issuance. Absent a clause, ANP provides no on-chain enforcement path — only the anchored evidence trail. +Disputes are only fair if the forum is fixed *before* the conflict. A contract's `terms.dispute` (see §7.3) **MUST**, when present, specify a `process_profile` and the fields that profile requires. For the **default optimistic profile** (`urn:anp:dispute:optimistic-v1`): the `arbiter` (or a selection rule/panel), a `challenge_window`, an `evidence_window` and a `ruling_deadline` (liveness bounds, §9.4), a `bond` sizing rule when `escrow.required` (§9.4), escrow handling, and appeal availability — `appeal.allowed`, **plus `appeal.appeal_window` whenever `appeal.allowed = true`** (its omission when appeals are enabled makes the `RULED → FINALIZED` transition ambiguous and **MUST** be rejected). When `appeal.allowed = false`, a Ruling is final on issuance. The **small-claims profile** (`urn:anp:dispute:small-claims-v1`, §9.4) instead requires only a `challenge_window` and a pre-agreed `split` formula. Absent a clause, ANP provides no on-chain enforcement path — only the anchored evidence trail. **Neutral selection (to avoid re-creating the single-evaluator weakness).** A single pre-named arbiter is just a single evaluator agreed in advance; if one side insists on a captured arbiter, neutrality is lost. The default `process_profile` therefore offers — and above a value threshold **SHOULD** mandate — one of two neutral-selection mechanisms instead of a fixed `arbiter`: - **VRF draw from a bonded pool:** the arbiter is drawn from a named, bonded pool at dispute time via the same grind-resistant VRF construction as §8.3 — the seed **MUST** combine the `dispute` Object's anchored hash with post-anchor chain randomness — so neither party chooses *or grinds* the draw. @@ -714,6 +716,15 @@ FINALIZED ──enforce──► settled - **Appeal — exactly once (m7).** If `appeal.allowed`, a Ruling MAY be escalated **once** to the final panel within `appeal_window`; the panel's ruling is final. Deeper multi-tier appeals are a separate, non-default profile. - **Enforcement — verifiable, not cooperation-free (C1).** `enforce` anchors the numeric settlement directive (§6.2.1); the escrow contract acts on it. For the contract to check "elapsed undisputed" trustlessly, `dispute`/`enforce` anchors are contract-readable state per §6.2.1/§13.1. The **required signer is determined by `outcome.basis`**: the entitled Arbiter/panel for `basis: "ruling"`, or the asserting party for `basis: "uncontested_assertion"` (where the contract instead checks that the challenge window elapsed undisputed). Enforcement is **automatic and verifiable given the appropriate valid signature, a posted bond, and an expired challenge window** — it does *not* require the *losing* party's cooperation, but it *does* rest on the chain account's security and, in the challenged path, the (bonded, pre-agreed) Arbiter's honesty. ANP makes that residual trust explicit, bonded, and auditable rather than eliminating it. +**Small-claims profile (`urn:anp:dispute:small-claims-v1`).** Below an economic floor the optimistic profile is dead: arbiter fees plus two bonds dwarf a 0.50-unit escrow (§7.3's own example), and §10.3 lets arbiters decline underfunded Threads — leaving exactly the micro-value cases the protocol targets (§1.3) with anchored evidence but no usable recourse. The small-claims profile is the first-class answer for such Threads: + +- **Selection & required fields.** `terms.dispute.process_profile = "urn:anp:dispute:small-claims-v1"`; RECOMMENDED whenever the escrow value cannot cover the §9.4 bond floor plus a realistic arbiter fee. The clause requires only a `challenge_window` and a **`split`** — a pre-agreed §6.2.1-style formula for the disputed case (e.g., `{ "release_bps": 5000, "refund_bps": 5000 }`, or a full refund). +- **Happy path unchanged.** `assert` → unchallenged window → `enforce` (`basis: "uncontested_assertion"`); mutual settlement (M9) applies as everywhere. +- **Disputed path — formula, not forum.** A valid `dispute` opens no evidence phase. Once the dispute is recorded, either party MAY anchor `enforce` with **`basis: "formula_split"`** (§6.2.1): `basis_anchor` is the dispute's anchored hash, and the directive **MUST** equal the clause's `split`, which the escrow contract checks against the formula recorded in `escrow.open`'s `conditions` at funding time. No arbiter, no evidence phase, no appeal. +- **Bonds are OPTIONAL** here (sized as `bps_of_escrow`, no fee floor — there is no fee to fund); a profile MAY require a symbolic bond as spam friction. +- **The deterrent is reputational — stated honestly.** A party that systematically disputes to capture a favorable `split` is fully visible (every `assert`/`dispute`/`enforce` is anchored) and prices itself out via the §5.4 reputation interface. The profile trades enforcement strength for cost: it fits **repeat-player ecosystems**; against one-shot or adversarial counterparties, use the optimistic profile, prepayment, or no escrow. +- A **single bonded, VRF-drawn juror** variant (flat micro-fee, no appeal) is deferred to v0.4. + ### 9.5 Differentiation ANP aims to be stronger than a single evaluator's binary sign-off (as in Virtuals ACP / the proposed ERC-8183) by **supporting** neutral arbiter selection (VRF / M-of-N panels, §9.2), an explicit single-appeal tier, bonded incentives with slashing, and evidence drawn from the same notarization machinery — all chain-neutral. These are *mechanisms the protocol provides*, not a claim that neutrality is automatically achieved: their effectiveness depends on pool governance and honest final panels (§16.6). The honest framing is "richer, configurable dispute resolution with explicit, bonded trust," not "trustless arbitration." @@ -754,7 +765,7 @@ ANP uses the **Settlement Layer's native asset and/or stablecoins**. Native **EU - **Bond custody.** Bonds are held by the profile's `bond` contract (§10.1), denominated in the Thread's settlement asset, posted as a precondition to act, and released (`bond.release`) once obligations are discharged. A bond is never held by a counterparty. - **Party bindings.** `escrow.open` records each party's DID→chain-account binding; the escrow contract uses it to verify that a `dispute` recording (§6.2.1) and bond postings come from bound party accounts — the on-chain half of the authorized-writer rule (§6.1). - **Slash distribution.** A slash is computed against the proven harm, capped at the bond, and directed per the final ruling's `outcome`: typically the **harmed party** is made whole first, then **arbiter/witness fees**, with any remainder **burned or returned to the bond pool** (profile choice). Burning a portion is RECOMMENDED to deter collusion (a colluding pair cannot fully recycle a slashed bond between themselves). -- **Fee shortfall.** If the losing party's bond is smaller than the arbiter fee, the shortfall is drawn from that party's **reachable escrow funds** — the share the directive would otherwise release or refund *to that party*. Where the losing party has no escrowed funds of its own (the asymmetric case: e.g., a losing performing party in a fully-refunded Thread), the shortfall is drawn from its `performance_bond` (§7.3) if posted; if still insufficient, the deficit is recorded as negative reputation and (in profiles that require it) the party's minimum bond for future Threads rises. Arbiters MAY decline Threads whose bonds do not cover their fee schedule. +- **Fee shortfall.** If the losing party's bond is smaller than the arbiter fee, the shortfall is drawn from that party's **reachable escrow funds** — the share the directive would otherwise release or refund *to that party*. Where the losing party has no escrowed funds of its own (the asymmetric case: e.g., a losing performing party in a fully-refunded Thread), the shortfall is drawn from its `performance_bond` (§7.3) if posted; if still insufficient, the deficit is recorded as negative reputation and (in profiles that require it) the party's minimum bond for future Threads rises. Arbiters MAY decline Threads whose bonds do not cover their fee schedule — the **small-claims profile** (§9.4) exists precisely for Threads below this economic floor. - **Reputation.** The reputation interface (§5.4) lets repeated honest behavior compound into selection advantage — and lets misbehavior be priced in. --- @@ -777,6 +788,7 @@ ANP uses the **Settlement Layer's native asset and/or stablecoins**. Native **EU | **Arbiter stalling / stranded escrow** | `evidence_window` + `ruling_deadline` are REQUIRED (§9.2/§9.4); timeout → fee/bond forfeiture and VRF replacement or escalation; appeal-tier timeout → first-instance ruling final. | | **Unfunded `execute` (performing against empty escrow)** | `EXECUTED` requires funded on-chain escrow matching `execute.body.escrow_id` (anchor-on-deposit RECOMMENDED); an `execute` unfunded past `funding_deadline` is void and the Thread reverts (§7.4). | | **Unbacked penalties** | Optional `performance_bond` posted at `execute`, sized to maximum penalty exposure; Verifiers flag terms whose penalty exposure exceeds the collateral reachable by `enforce` (§7.3/§10.3). | +| **Micro-value Threads without economic recourse** | Small-claims `process_profile`: pre-agreed formula split on dispute, reputation-fed outcomes, no arbiter/fees (§9.4) — honestly scoped to repeat-player ecosystems; optimistic profile otherwise. | | **Mandate-chain abuse / DoS** | Bounded `max_depth`, caching, signature-verification limits per Thread. | | **Retroactive mandate revocation / status-list rollback** | Anchor-time evaluation of authority; revocation is ex nunc only; Verifiers retain the signed, dated status-list credential as evidence; RECOMMENDED periodic status-list snapshot anchors (§5.3). | | **Mutable schema / trust-list references (semantic TOCTOU)** | All binding references are content-addressed `{id, hash}` pairs pinned in the accepted head (§7.3/§5.4); schema & suite registries are versioned, hash-identified artifacts selected by `anp_version` (Appendix A); Verifiers reject hash mismatches. | @@ -857,7 +869,7 @@ Selection is a Profile decision; the abstract requirements (§13.1) are the gate An implementation conforms to ANP v0.3 if it satisfies the **Core** requirements and at least one **Pillar** and one **Profile**. - **Core (MUST):** ANP Object envelope incl. linkage classes (§6.1), canonicalization & signature input (§6.3), anchoring pattern (§6.2/§6.4), DID-based identity (§5.1), VC-based roles/mandates (§5.2/§5.3), suite agility with a PQC-capable suite available (§6.5). -- **Pillar profiles (implement ≥1):** Contracting (§7), Notarization (§8), Dispute Resolution (§9). Dispute Resolution conformance requires the optimistic `process_profile` at minimum. +- **Pillar profiles (implement ≥1):** Contracting (§7), Notarization (§8), Dispute Resolution (§9). Dispute Resolution conformance requires the optimistic `process_profile` at minimum; the small-claims profile (§9.4) is OPTIONAL. - **Settlement profiles (implement ≥1):** the IOTA reference profile (§13.2) or another profile meeting §13.1, exposing the settlement interface (§10.1). - **Interop profiles (OPTIONAL):** EVM/ERC-8004/EAS/x402 bindings (§13.3). @@ -920,7 +932,7 @@ Conformance levels: **Minimal** = Core + exactly one of {Notarization, Contracti Normative JSON Schemas (to be finalized in v1.0) will cover: - the **Object envelope** (§6.1) — the full `type` enum (incl. `memorandum`, `assert`, `revoke`, and the non-anchored `receipt`), the **chain/attachment linkage classes** with per-attachment target fields, `signers[].authority` (mandate | accreditation), and the `proof[]`↔`signers[]` correspondence; -- per-`type` `body` schemas — Pillar I `terms` (incl. `governing_suite`, `escrow` with `funding_deadline`, `performance_bond`, `acceptance_criteria`, `milestones[]`, `dispute` with `challenge_window`, `evidence_window`, `ruling_deadline`, `bond`, `timeout_default`, and `appeal.appeal_window`), the `accept` body `{accepts_hash}`, the `approve` body `{approves_hash}`, the `execute` body `{escrow_id}`, the `amend` body (replacement `terms`), the `rescind` body (agreed §6.2.1 directive), and the `assert` body (completion | non-performance, contract-head ref, claimed outcome, evidence anchors, optional `milestone`); Pillar II `statement` per `subject_kind` with a measurement value object `{quantity, unit (UCUM), scale}`, the `witnessing` flag, the `witness` body `{attestation_hash, verdict, reason_hash}`, and the `revoke` body `{revokes, reason_hash, status_list_update}`; Pillar III the `evidence` body (`{dispute_hash, …}`), `rule`, and the `settle` body (agreed §6.2.1 directive, optional `milestone`); +- per-`type` `body` schemas — Pillar I `terms` (incl. `governing_suite`, `escrow` with `funding_deadline`, `performance_bond`, `acceptance_criteria`, `milestones[]`, `dispute` with `process_profile`, `challenge_window`, `evidence_window`, `ruling_deadline`, `bond`, `timeout_default`, `split` (small-claims), and `appeal.appeal_window`), the `accept` body `{accepts_hash}`, the `approve` body `{approves_hash}`, the `execute` body `{escrow_id}`, the `amend` body (replacement `terms`), the `rescind` body (agreed §6.2.1 directive), and the `assert` body (completion | non-performance, contract-head ref, claimed outcome, evidence anchors, optional `milestone`); Pillar II `statement` per `subject_kind` with a measurement value object `{quantity, unit (UCUM), scale}`, the `witnessing` flag, the `witness` body `{attestation_hash, verdict, reason_hash}`, and the `revoke` body `{revokes, reason_hash, status_list_update}`; Pillar III the `evidence` body (`{dispute_hash, …}`), `rule`, and the `settle` body (agreed §6.2.1 directive, optional `milestone`); - the **Mandate** VC (§5.3) incl. `constraints.fx_ref`, per-`scope` caps, and the `status_list` reference; - the **quorum** object (§8.3) `{witness_pool, selection, witness_set, n, m, witness_window}`; - the **Anchor record** (§6.2) `{object_hash, object_type, thread_ref, status, anchored_by, timestamp, locator, outcome}` and the **outcome directive** (§6.2.1) `{escrow_id, basis, release_bps, refund_bps, penalty_bps, fee_source, milestone, basis_anchor}`; @@ -963,6 +975,7 @@ Seller asserts "delivered, criteria met" → `ASSERTED`. Buyer files `dispute` w - **Amendment & mutual rescission (§7.2, §7.4, §10.1; review issue #15):** new co-signed `amend` (replacement terms become the new head; mandate checks re-run; escrow adjusted via `escrow.adjust` before the amendment is effective) and `rescind` (terminal `RESCINDED` state; agreed split settles via `basis: "mutual_settlement"`). Unilateral termination after binding acceptance remains impossible. - **Hash-pinned schemas & trust lists (§5.4, §7.3, §11, Appendix A; review issue #16):** every schema and trust-list reference in a binding context is now a content-addressed `{id, hash}` pair; the snapshot pinned in the accepted head governs the Thread, so registry/list edits cannot retroactively change contract semantics. The per-version schema registry and the suite registry are published as versioned, hash-identified artifacts selected by `anp_version` — no central live registry to capture. - **Milestones / partial performance (§6.2.1, §7.3, §7.4, §9.3, §9.4, §10.1; review issue #22):** optional `terms.milestones[]` (amounts summing to `escrow.amount`, per-milestone deadlines/criteria/window overrides); `assert`/`settle` MAY be milestone-scoped; an uncontested or mutually settled milestone triggers a partial, tranche-scoped `enforce` (`outcome.milestone`) while the Thread stays `EXECUTED`; milestone-scoped disputes freeze only their tranche. +- **Small-claims process profile (§6.2.1, §9.2, §9.4, §10.3, §11, §14; review issue #21):** new OPTIONAL `urn:anp:dispute:small-claims-v1` for Threads below the bond-floor + arbiter-fee economics — happy path unchanged; a dispute triggers a **pre-agreed formula split** (`basis: "formula_split"`, checked by the escrow contract against the formula recorded at funding) instead of an evidence/arbiter phase; bonds optional; deterrence is reputational and honestly scoped to repeat-player ecosystems. The single-juror VRF variant is deferred to v0.4. - **Funded escrow gates EXECUTED (§7.2, §7.3, §7.4, §11; review issue #11):** `execute` carries `escrow_id`; Verifiers treat a Thread as `EXECUTED` only with on-chain escrow funded to `terms.escrow.amount`; **anchor-on-deposit** (escrow contract emits the `execute` anchor on funding) is the RECOMMENDED atomic construction; an `execute` unfunded past `funding_deadline` is void and the Thread reverts to `ACCEPTED`/`APPROVED`. - **Performance bond & penalty collateral (§7.3, §10.3, §11; review issue #19):** optional `performance_bond` posted by the performing party at `execute`, sized to maximum penalty exposure; Verifiers SHOULD flag terms whose penalties exceed the collateral reachable by `enforce`; the §10.3 fee-shortfall rule now covers the asymmetric no-escrow case. - **IOTA profile: on-chain hash handling (§13.1, §13.2, Appendix A; review issue #25):** Move has no 384-bit hash natives — anchors carry tagged 384-bit digests as opaque bytes (store/compare works; recomputation does not). New §13.1 **hash-capability declaration**: profiles name a native recomputation hash or route verification into the dispute path; the suite registry records per-profile flags.