diff --git a/docs/integrators/README.md b/docs/integrators/README.md index b5eadb2..b9b77eb 100644 --- a/docs/integrators/README.md +++ b/docs/integrators/README.md @@ -16,11 +16,11 @@ The guides are complementary to: | Guide | Mechanism | Mechanism type | Status | |---|---|---|---| | [m001-enh.md](m001-enh.md) | Credit Class Approval Voting | Scoring (0-1000 composite, 3-way recommendation) | ✅ Written | +| [m008.md](m008.md) | Data Attestation Bonding | Scoring (4-factor, no recommendation) | ✅ Written | +| [m009.md](m009.md) | Service Provision Escrow | Dual-guard scoring (score AND confidence) | ✅ Written | +| [m010.md](m010.md) | Reputation Signal | Event-driven decay-weighted average | ✅ Written | | [m012.md](m012.md) | Fixed Cap Dynamic Supply | Supply dynamics (BigInt arithmetic, phase-gated multipliers) | ✅ Written | | [m014.md](m014.md) | Authority Validator Governance | Validator performance (re-normalized weighted score) | ✅ Written | -| m008.md | Data Attestation Bonding | Scoring | ⏳ TODO — follow m001-enh template | -| m009.md | Service Provision Escrow | Dual-guard scoring (score AND confidence) | ⏳ TODO — follow m001-enh template | -| m010.md | Reputation Signal | Stake-weighted endorsement + challenge lifecycle | ⏳ TODO | | m011.md | Marketplace Curation | 7-factor quality scoring + collections | ⏳ TODO | | m013.md | Value-Based Fee Routing | Fee computation + pool distribution | ⏳ TODO — follow m012 template | | m015.md | Contribution-Weighted Rewards | Stability + activity tiers | ⏳ TODO | diff --git a/docs/integrators/m008.md b/docs/integrators/m008.md new file mode 100644 index 0000000..ba47f0d --- /dev/null +++ b/docs/integrators/m008.md @@ -0,0 +1,132 @@ +# Integrator guide: m008 Data Attestation Bonding + +**Mechanism:** Data Attestation Bonding +**Canonical spec:** [`mechanisms/m008-attestation-bonding/SPEC.md`](../../mechanisms/m008-attestation-bonding/SPEC.md) +**Reference implementation:** [`mechanisms/m008-attestation-bonding/reference-impl/m008_score.js`](../../mechanisms/m008-attestation-bonding/reference-impl/m008_score.js) + +## 1. What this mechanism does + +m008 scores bonded attestations on a 0-1000 composite. An attestation is a signed claim about ecological data (methodology validation, credit issuance, baseline measurement, project boundary) backed by a REGEN bond. The score reflects how trustworthy the attestation is *as evidence* — it combines the adequacy of the bond, the attester's reputation, the completeness of the evidence document, and the inherent risk of the attestation type. An integrator uses m008 to decide whether to accept an attestation into a registry, flag it for challenge, or reject it outright. + +Four weighted factors drive the composite: + +- `bond_adequacy` × 0.30 — bond amount relative to the minimum for this attestation type +- `attester_reputation` × 0.30 — attester's M010 reputation (default 300 when no history, matching the cautious default in `mechanisms/m008-attestation-bonding/SPEC.md` §5.2) +- `evidence_completeness` × 0.25 — completeness of the supporting evidence document +- `type_risk` × 0.15 — risk factor assigned to the attestation type (higher = more risky) + +Unlike m001-enh, m008 does NOT return a recommendation — it's a pure score. The integrator decides the threshold. + +## 2. What you give it + +```js +import { computeM008Score } from "./m008_score.js"; + +const result = computeM008Score({ + attestation: { + attestation_id: "att-001", + attestation_type: "BaselineMeasurement", // ProjectBoundary / BaselineMeasurement / CreditIssuanceClaim / MethodologyValidation + attestation_iri: "koi://attestation/soil-baseline-plot-42", + bond: { amount: "1500", denom: "uregen" }, + }, + factors: { + // Scoring inputs — each 0..1000, clamped. Defaults: + // bond_adequacy: 0, attester_reputation: 300, evidence: 0, type_risk: 0 + bond_adequacy: 750, + attester_reputation: 680, + evidence_completeness: 1000, + type_risk: 600, + + // Confidence flags. Note: iri_resolvable and type_recognized default to + // TRUE when unset (the check is `!== false`, not === true), while + // reputation_available and has_prior_attestations default to FALSE + // (the check is truthy). + reputation_available: true, + iri_resolvable: true, + has_prior_attestations: true, + type_recognized: true, + }, +}); +``` + +**Schemas:** [`m008_attestation.schema.json`](../../mechanisms/m008-attestation-bonding/schemas/m008_attestation.schema.json) and [`m008_quality_score.schema.json`](../../mechanisms/m008-attestation-bonding/schemas/m008_quality_score.schema.json). + +## 3. What you get back + +```js +{ + score: 769, // 0..1000 composite, clamped (225 + 204 + 250 + 90) + confidence: 1000, // 0..1000, derived from availability flags + factors: { // clamped factor values echoed back + bond_adequacy: 750, + attester_reputation: 680, + evidence_completeness: 1000, + type_risk: 600, + }, +} +``` + +There is **no `recommendation` field**. m008 is advisory-only at the scoring layer — the integrator decides what to do at each score tier. The SPEC suggests the following rule of thumb: + +| Score | Interpretation | +|---|---| +| `>= 700` | Strong evidence — acceptable without further review | +| `400..699` | Moderate — flag for human review | +| `< 400` | Weak — consider challenge or reject | + +This is a guideline, not enforcement. Downstream tooling can apply its own threshold. + +## 4. Common error modes + +### 4a. Missing attester history + +If the attester has no prior attestations, `attester_reputation` defaults to **300** (below neutral, reflecting that the agent has no basis to trust the attester). Set `reputation_available: false` so confidence drops from 1000 to 750 (3/4 flags). If the evidence is strong despite the unknown attester, the composite can still clear the 700 threshold purely on `bond_adequacy + evidence_completeness` contributions. + +### 4b. Unresolvable IRI + +Set `iri_resolvable: false`. This is one of only two confidence flags that explicitly respects `false` — the guard is `!== false`, so leaving it unset counts as available. This means: + +- `iri_resolvable: true` → counts (confidence gains 250) +- `iri_resolvable` undefined → counts (default is "available") +- `iri_resolvable: false` → does NOT count + +The same asymmetry applies to `type_recognized`. Pinned by [`vector_v0_empty_factors_defaults`](../../mechanisms/m008-attestation-bonding/reference-impl/test_vectors/vector_v0_empty_factors_defaults.input.json). + +### 4c. Bond-heavy attack vector + +A well-funded attacker might try to "buy" a high score by pouring REGEN into a bonded attestation with no real evidence. With `bond_adequacy = 1000` and `type_risk = 1000` (MethodologyValidation) but zero reputation and zero evidence, the composite is `0.30×1000 + 0×0.30 + 0×0.25 + 0.15×1000 = 450` — well below the 700 threshold. Bond alone cannot carry the composite into the "strong evidence" tier. Pinned by [`vector_v0_bond_heavy_evidence_zero`](../../mechanisms/m008-attestation-bonding/reference-impl/test_vectors/vector_v0_bond_heavy_evidence_zero.input.json). + +### 4d. Explicit `0` vs missing + +An explicit `attester_reputation: 0` is preserved — the nullish-coalescing default of 300 only fires when the key is `null` or `undefined`, not when it's `0`. This is different from the `||` operator's behavior. If you're migrating from a data source that returns 0 ambiguously, normalize upstream. + +### 4e. Type risk is a known-type lookup, not a computed value + +`type_risk` in the input is the final 0..1000 factor score, not the raw attestation type. The SPEC table maps types to fixed values: + +| Type | type_risk | +|---|---| +| `MethodologyValidation` | 1000 | +| `CreditIssuanceClaim` | 800 | +| `BaselineMeasurement` | 600 | +| `ProjectBoundary` | 400 | + +The reference impl exports `getTypeRiskFactor(type)` and `getMinBond(type)` helpers for this — use them when you're computing factors upstream. + +## 5. Runnable example + +The reference implementation ships with 7 test vectors (1 sample + 6 edge cases covering the zero floor, maximum ceiling, overflow clamping, empty-factors defaults, type-contribution isolation, and the bond-heavy attack vector). The self-test discovers every vector in `test_vectors/` automatically: + +```bash +node mechanisms/m008-attestation-bonding/reference-impl/m008_score.js +``` + +Expected output: + +``` +m008_score self-test: PASS (11 attestations across 7 vectors) +``` + +--- + +Canonical spec: [`mechanisms/m008-attestation-bonding/SPEC.md`](../../mechanisms/m008-attestation-bonding/SPEC.md) §5. diff --git a/docs/integrators/m009.md b/docs/integrators/m009.md new file mode 100644 index 0000000..78a8b07 --- /dev/null +++ b/docs/integrators/m009.md @@ -0,0 +1,120 @@ +# Integrator guide: m009 Service Provision Escrow + +**Mechanism:** Service Provision Escrow +**Canonical spec:** [`mechanisms/m009-service-escrow/SPEC.md`](../../mechanisms/m009-service-escrow/SPEC.md) +**Reference implementation:** [`mechanisms/m009-service-escrow/reference-impl/m009_score.js`](../../mechanisms/m009-service-escrow/reference-impl/m009_score.js) + +## 1. What this mechanism does + +m009 reviews milestone deliverables inside a service escrow agreement and returns one of three recommendations: **APPROVE**, **NEEDS_REVISION**, or **FLAG_FOR_CLIENT**. A service escrow agreement locks client funds against a set of milestones; each milestone must be reviewed before the escrowed funds are released. An integrator uses m009 to provide a consistent first-pass review of a milestone submission, letting a human arbiter focus attention on the cases the agent can't auto-approve. + +Unlike m001-enh (which has a single-guard REJECT branch), m009 uses a **dual-guard** recommendation model: APPROVE requires BOTH a high score AND high confidence, while FLAG_FOR_CLIENT fires when EITHER score is low OR confidence is low. This asymmetry protects both sides of the deal — the client never has a low-confidence submission auto-approved, and the provider never has a thin-evidence review auto-rejected. + +Four weighted factors drive the composite: + +- `deliverable_quality` × 0.40 — methodology compliance and technical quality of the submission +- `evidence_completeness` × 0.25 — completeness of the supporting evidence package +- `milestone_consistency` × 0.20 — consistency with prior milestones in the same agreement +- `provider_reputation` × 0.15 — provider's M010 reputation (default 300 when no history) + +## 2. What you give it + +```js +import { computeM009Score } from "./m009_score.js"; + +const result = computeM009Score({ + milestone: { + milestone_id: "ms-001", + escrow_id: "escrow-001", + provider: "regen1provider001", + amount: { amount: "1000", denom: "uregen" }, + }, + factors: { + // Scoring inputs — each 0..1000, clamped. Defaults: + // quality: 0, evidence: 0, consistency: 0, provider_reputation: 300 + deliverable_quality: 850, + evidence_completeness: 800, + milestone_consistency: 750, + provider_reputation: 700, + + // Confidence flags — all use strict `=== true` (unlike m008 which + // uses `!== false` for two of its flags). This means UNSET counts + // as FALSE for m009. Set every flag explicitly if you want it to + // count. + reputation_available: true, + iri_resolvable: true, + has_prior_milestones: true, + spec_available: true, + }, +}); +``` + +**Schemas:** [`m009_milestone_review.schema.json`](../../mechanisms/m009-service-escrow/schemas/m009_milestone_review.schema.json) and the agreement lifecycle schema in the same directory. + +## 3. What you get back + +```js +{ + score: 795, // 0..1000 composite, clamped (340 + 200 + 150 + 105) + confidence: 1000, // 0..1000 (count of `=== true` flags / 4) + recommendation: "APPROVE", // APPROVE | NEEDS_REVISION | FLAG_FOR_CLIENT + factors: { // clamped factor values echoed back + deliverable_quality: 850, + evidence_completeness: 800, + milestone_consistency: 750, + provider_reputation: 700, + }, +} +``` + +**Recommendation rules — read carefully, the branches are NOT symmetric:** + +| Condition | Recommendation | +|---|---| +| `score >= 700` AND `confidence >= 750` | `APPROVE` | +| `score < 400` OR `confidence < 250` | `FLAG_FOR_CLIENT` | +| otherwise | `NEEDS_REVISION` | + +- APPROVE requires BOTH conditions (a conjunction). A perfect score with low confidence does NOT auto-approve — it falls through to NEEDS_REVISION for human review. +- FLAG_FOR_CLIENT fires when EITHER side of the OR is true. A perfect 1000 score with zero confidence flags FIRES — the client must review it manually, and the provider does not get auto-approved on evidence the system cannot verify. +- Everything else is NEEDS_REVISION — the middle state where neither guard wants to auto-decide. + +## 4. Common error modes + +### 4a. High score, low confidence → NEEDS_REVISION, not APPROVE + +A submission with a perfect 1000 score but only 2 of 4 flags true (confidence 500) does NOT auto-approve. The APPROVE branch requires confidence `>= 750`. The submission falls through to NEEDS_REVISION — human review required. Pinned by [`vector_v0_high_score_low_confidence_revision`](../../mechanisms/m009-service-escrow/reference-impl/test_vectors/vector_v0_high_score_low_confidence_revision.input.json). + +### 4b. High score, zero confidence → FLAG_FOR_CLIENT + +A submission with score 800 but zero confidence flags (confidence 0) does NOT approve — it fires FLAG_FOR_CLIENT because the confidence guard on the FLAG branch is `< 250`. This protects the client from an agent approving a "high quality" submission the system cannot verify at all. Pinned by [`vector_v0_confidence_floor_forces_flag`](../../mechanisms/m009-service-escrow/reference-impl/test_vectors/vector_v0_confidence_floor_forces_flag.input.json). + +### 4c. Score exactly at 400 is NEEDS_REVISION, not FLAG + +The FLAG_FOR_CLIENT predicate is `score < 400` — strict inequality. A score of exactly 400 does NOT fire the flag. It falls through to NEEDS_REVISION. A future refactor that changes `<` to `<=` would silently flip this boundary, which is why [`vector_v0_boundary_revision_exact_400`](../../mechanisms/m009-service-escrow/reference-impl/test_vectors/vector_v0_boundary_revision_exact_400.input.json) pins it. + +### 4d. Unlike m008, the `=== true` guard is strict + +All four m009 confidence flags use strict `=== true`. Unset flags count as FALSE, not TRUE. If you're porting from m008 (which uses `!== false` for two of its flags), re-audit your flag-setting code — `iri_resolvable: undefined` counts in m008 but NOT in m009. + +### 4e. First-time provider gets the reputation default + +`provider_reputation` defaults to **300** when unset, not 500. This is lower than m008's attester default because the SPEC treats first-time providers as higher risk in escrow agreements. Set `reputation_available: false` to also drop the confidence contribution. + +## 5. Runnable example + +The reference implementation ships with 7 test vectors covering the happy path (sample), the dual-guard APPROVE boundary at (700, 750), the score-high/confidence-low fallback, the exact 399 FLAG boundary, the exact 400 REVISION boundary, the confidence-floor-forces-flag case, and the overflow clamp. The self-test discovers every vector in `test_vectors/` automatically: + +```bash +node mechanisms/m009-service-escrow/reference-impl/m009_score.js +``` + +Expected output: + +``` +m009_score self-test: PASS (11 milestones across 7 vectors) +``` + +--- + +Canonical spec: [`mechanisms/m009-service-escrow/SPEC.md`](../../mechanisms/m009-service-escrow/SPEC.md) §5. diff --git a/docs/integrators/m010.md b/docs/integrators/m010.md new file mode 100644 index 0000000..d4998b6 --- /dev/null +++ b/docs/integrators/m010.md @@ -0,0 +1,122 @@ +# Integrator guide: m010 Reputation Signal + +**Mechanism:** Reputation Signal +**Canonical spec:** [`mechanisms/m010-reputation-signal/SPEC.md`](../../mechanisms/m010-reputation-signal/SPEC.md) +**Reference implementation:** [`mechanisms/m010-reputation-signal/reference-impl/m010_score.js`](../../mechanisms/m010-reputation-signal/reference-impl/m010_score.js) + +## 1. What this mechanism does + +m010 computes a reputation score for a subject (credit class, project, verifier, methodology, or address) by aggregating endorsement signals from signalers, with exponential time decay so old signals fade out. An integrator uses m010 to power an "is this subject trustworthy?" check — the output feeds M001-ENH credit class approval, M011 marketplace curation, and any other downstream mechanism that needs a legitimacy signal for a subject. + +The v0 implementation is an **advisory, decay-weighted average**: each endorsement level (1-5) is normalized to 0-1 and weighted by `exp(-ln(2) × age_hours / halfLifeHours)`. Stake weighting is reserved for v1 when on-chain stake data is available. The v0 output is normalized to `[0, 1]`; the v1 target is 0-1000. + +Unlike the other scoring mechanisms in this suite, m010 is **event-driven** rather than factor-driven — you pass it a list of historical signals plus a scoring point-in-time, and it computes the score from the events. No factors to tune, no defaults to remember. + +## 2. What you give it + +```js +import { computeM010Score } from "./m010_score.js"; + +const result = computeM010Score({ + as_of: "2026-02-18T12:00:00Z", + events: [ + { + signal_id: "sig-001", + timestamp: "2026-02-01T00:00:00Z", // when the signal was submitted + endorsement_level: 5, // integer 1..5, higher = more positive + signaler: "regen1signaler001", + status: "active", // active | resolved_valid | withdrawn | challenged | escalated | resolved_invalid | invalidated + }, + { + signal_id: "sig-002", + timestamp: "2025-12-01T00:00:00Z", // older → larger decay + endorsement_level: 4, + signaler: "regen1signaler002", + status: "active", + }, + { + signal_id: "sig-003", + timestamp: "2026-02-10T00:00:00Z", + endorsement_level: 1, // a strong negative endorsement + signaler: "regen1signaler003", + status: "resolved_valid", // challenged but resolved in the signal's favor — still counts + }, + ], + halfLifeHours: 336, // optional; default 14 days (14*24 = 336) + useStakeWeighting: false, // reserved for v1 +}); +``` + +**Contributing statuses:** only `active` and `resolved_valid` signals contribute to the score. Signals with status `withdrawn`, `challenged`, `escalated`, `resolved_invalid`, or `invalidated` are excluded entirely (not decayed-to-zero — **not counted at all**). + +**Schemas:** [`m010_signal.schema.json`](../../mechanisms/m010-reputation-signal/schemas/m010_signal.schema.json) for the event shape, [`m010_challenge.schema.json`](../../mechanisms/m010-reputation-signal/schemas/m010_challenge.schema.json) for the challenge lifecycle state machine. + +## 3. What you get back + +```js +{ + reputation_score_0_1: 0.7543, // v0: 0..1 (four decimal places) +} +``` + +**v0 formula:** + +``` +decay(t) = exp(-ln(2) × (as_of - t) / halfLifeHours) +contribution(e) = (endorsement_level(e) / 5) × decay(e.timestamp) +score = sum(contribution(e)) / sum(decay(e.timestamp)) +``` + +The normalization is a weighted average, not a weighted sum — a subject with 100 unanimous 5-star endorsements gets the same score as a subject with 1 recent 5-star endorsement (both land at 1.0). **The score measures signal quality, not signal volume.** If you need a volume signal, aggregate the event count separately. + +**v1 target formula** (not yet implemented): + +``` +score_1000 = sum(stake × decay × level / 5) / sum(stake × decay) × 1000 +``` + +The interface already accepts a `useStakeWeighting` flag and an optional `stake` field on each event for forward compatibility. Today the flag is ignored and the score is un-weighted. + +## 4. Common error modes + +### 4a. Empty event list + +`events: []` returns `{ reputation_score_0_1: 0.0 }` — not an error, just "no signal". Downstream should distinguish this from a score of 0.0 achieved through actual negative endorsements. The SPEC suggests using a `has_signal` flag upstream to carry the distinction. + +### 4b. Signal in the future + +If an event's `timestamp` is after `as_of`, the computed `age` would be negative. The reference impl clamps age at 0 via `Math.max(0, ageH)` — future-dated signals are treated as fresh (decay = 1.0) rather than producing an `exp()` blow-up. In practice this only happens when you're scoring "as of now" against a clock-skewed signal; always prefer a deterministic `as_of` that's strictly in the past. + +### 4c. Unsupported status + +If a signal has a status the mechanism doesn't recognize, the reference impl's guard is `!contributingStatuses.has(status)` — unknown statuses are excluded. If you're reading signals from a data source that might surface newer status values than this version of m010 knows about, the signal gets quietly dropped. Always normalize status upstream rather than assuming the mechanism will handle new values. + +### 4d. Half-life mismatch between consumers + +The default half-life is 336 hours (14 days). If you change this in one consumer, every other consumer reading the same signal set will produce a different score. Keep the half-life consistent across downstream consumers — it's a governance parameter, not a per-call tuning knob. Any proposal to change the value should go through the Tokenomics Working Group. + +### 4e. Stake weighting is a no-op in v0 + +Passing `useStakeWeighting: true` in v0 does nothing. The `stake` field on events is also ignored. The interface is forward-compatible with v1, but today's output will be identical whether you pass the flag or not. + +## 5. Runnable example + +The reference implementation ships with 5 test vectors covering the sample, the challenge lifecycle (challenge → escalated, challenge → edge timing, challenge → resolved). The repository also registers m010 in the root `scripts/verify.mjs` with **dedicated verification scripts** (`scripts/verify-m010-reference-impl.mjs` and `scripts/verify-m010-datasets.mjs`) that go deeper than the other mechanisms' self-tests. + +To run the mechanism's own self-test: + +```bash +node mechanisms/m010-reputation-signal/reference-impl/m010_score.js +``` + +To run the full per-mechanism verification: + +```bash +node scripts/verify.mjs +``` + +The v0_sample fixture yields `reputation_score_0_1: 0.5488` — use that as a sanity check for your own integration. + +--- + +Canonical spec: [`mechanisms/m010-reputation-signal/SPEC.md`](../../mechanisms/m010-reputation-signal/SPEC.md) §5. diff --git a/mechanisms/m014-authority-validator-governance/SPEC.md b/mechanisms/m014-authority-validator-governance/SPEC.md index 2b57006..d6f6605 100644 --- a/mechanisms/m014-authority-validator-governance/SPEC.md +++ b/mechanisms/m014-authority-validator-governance/SPEC.md @@ -63,7 +63,8 @@ Confidence is derived from data availability: - No data: confidence = 0.0 ### 5.3 Performance threshold -- Validators with `performance_score < 0.70` are flagged for review. +- `POA_ELIGIBILITY_SCORE = 0.80` — the minimum composite `performance_score` a CANDIDATE must clear to join the Authority set. Stored as an on-chain governance parameter (see §10), not hardcoded in client tooling; integrators should read the current value rather than the 0.80 default. +- Active validators with `performance_score < 0.70` are flagged for review (below the review threshold but above the eligibility floor). - Validators with `uptime < 0.995` (99.5%) are flagged for probation consideration. ### 5.4 Compensation allocation @@ -185,6 +186,7 @@ authority_set: | `min_validators` | 15 | Layer 4 | Security minimum | | `term_length` | 12 months | Layer 3 (Human-in-Loop) | Significant governance decision | | `min_uptime` | 99.5% | Layer 2 (Agentic + Oversight) | Operational parameter | +| `POA_ELIGIBILITY_SCORE` | 0.80 | Layer 2 | Minimum composite score for Authority-set eligibility | | `probation_period` | 30 days | Layer 2 | Operational parameter | | `composition_ratios` | 5/5/5 minimum per category | Layer 3 | Structural governance decision | | `performance_bonus_share` | 10% of validator fund | Layer 2 | Operational adjustment |