{"method":"skalor-agent-rating/v1.3","vct":"https://skalor.xyz/vct/agent-rating/v1","subject":"An agent (agents.id, published as agents.mpp_did) together with the controller that stands behind it (agents.organization_id). The controller is part of the subject, not context: an agent identifier can be discarded and re-registered in one call, so a rating bound to the identifier alone would reset on every key rotation.","evidence_weighting":"Every decision carries an evidence weight of min(1, amount_usd / (0.10 × the agent's own per_transaction_limit)); agents with no ceiling set fall back to a $1.00 floor. A decision at or above a tenth of the agent's ceiling is one full observation; a dust payment is a fraction of one. Those weights, not row counts, are the n in every rate below. An agent is UNRATED until it has accumulated one full material observation.","scoring":"A rating is scored over the factors that are SCORABLE FOR THAT AGENT, and the denominator is published alongside the score rather than renormalised back to 1000 silently. `score` is points earned out of `denominator`, and is EXACTLY the sum of factors[].contribution — adding the published contributions up reproduces it, including when a ceiling binds. `uncapped_score` = round(1000 × score / denominator) restates it on the 0–1000 scale; `normalized_score` is that figure after every ceiling, and it is what tiers compare, so an agent scored over 800 points is not measured against a ceiling it cannot reach. A ceiling acts on the 0–1000 figure only and never restates `score`. Every ceiling that applies to an agent is published whether or not it binds: a reader who sees 700 and cannot see the ceiling has no way to know 1000 was never available.","denominator":"`denominator` is the points a rating was SCORED OVER: 1000 less every unscorable weight. It is NOT a claim that all of them were reachable by that agent, and it is deliberately not labelled 'achievable', because two factors are permanently unearnable on specific routes and stay inside it. /authorize-intent writes settlement_status = 'pending' on every cleared row and nothing in the system ever learns otherwise, so `settlement_reliability` holds exactly 50 of its 100 points forever on that route. /x402-pay ran no Gate 6 before September 2026, so `anomaly_history` holds 75 of its 150 for an agent whose observed decisions all cleared there before then; the rail now scores every decision it clears and writes the tier, so the factor moves from the agent's next decision on. Neither is made unscorable: their absent side lands on the 0.5 prior, so removing the weight would RAISE the normalized score of any agent above 50% — paying for the absence rather than describing it — and a null gate6_tier is in any case not separable from an agent having switched Gate 6 off, which is the agent's own choice and farmable. What a reader is owed is the fact, so every factor scored over zero evidence is published in `prior_held_factors` with its weight, what it contributed, and the route on which the evidence never arrives.","comparability":"A tier letter is only comparable against another rating with the same denominator. A rating scored over 800 points is a verdict on six factors; one scored over 1000 is a verdict on seven, and the same letter on the two is not the same claim. The response therefore publishes a `comparability` object keyed on denominator ≠ 1000, and `rating.tier` itself carries a trailing '*' when the denominator is reduced ('B*'), so a reader who ranks on the letter cannot silently rank the two together. `rating.tier_letter` carries the bare grade for callers that want it. UNRATED and REVOKED are never starred: their denominator is 0 because nothing was scored at all, which is an absence of a verdict rather than a narrower one.","data_provenance":"Every published rating states whether the rows behind it are observations of anything: 'live', 'synthetic_fixture' (an agent and history generated by scripts/seed_registry_fixtures.ts to populate the public registry with varied tiers) or 'test_harness' (a throwaway provisioned against production by scripts/smoke_test_e2e.ts). Those rows are answered rather than 404'd — they are deliberately on display at /registry, and a bureau that publishes non-binding ceilings and reduced denominators so a reader is never quietly misled must not answer 'not found' about something it is showing — and the notice is the first thing in the summary line. The same field appears on every item of /bureau-list.","evidence_eligibility":"A decision is evidence about an agent only if the agent's own controller submitted the intent. /authorize-intent and /x402-pay record who asked on every decision they write: transactions.metadata.caller_authenticated is true when the request carried an active API key owned by the agent's organisation (and bound to that agent, if bound at all), and false when it carried no key and the rail was running in advisory mode. On /x402-pay the agent is identified by the key alone — there is no agent_id in the request, and a request without a valid key is refused before anything is written — so every decision it records is true. A decision marked false is read by NOTHING in this method — no factor, no penalty, not the scorability predicate, not the denominator, not the anomaly history, not tenure — because the rail could not tell who sent it, and a rating that counted it would let anyone who learned an agent's uuid write denials into its record. The number of decisions refused on this ground, over the agent's full history and every approval status, is published as evidence.unauthenticated_decisions_excluded; those rows appear in no other published count. Decisions written before the rail recorded who asked carry no such value and are read exactly as before — history is not discarded retroactively, and the method says so rather than pretending to know who sent them. A request that presents a key which does not own the agent is refused outright and writes no decision at all, in every mode.","scorability":{"rule":"A factor is unscorable when the evidence it scores CANNOT EXIST on the agent's route — a property of the route or of the issuer set, never of the agent's own record. Its weight leaves the denominator and the reason is published. The predicate is monotone in evidence: producing more can make a factor scorable and never unscorable, so no agent can widen its exemption by producing less.","evaluated_over":"THE AGENT'S FULL DECISION HISTORY, never the observation window the scores are computed from. The predicate is a universal quantifier — 'not one of this agent's cleared decisions was decided by an issuing rail' — and a universal quantifier evaluated over a bounded recency window is a different statement: an agent that keeps transacting on a non-issuing rail would evict its own issuing-rail decisions from the window and open the exemption, taking 200 points out of its denominator along with the evidence that it had failed to attest. That is exactly the case this rule exists to refuse, arrived at from the other direction. It is answered by two exact COUNT(*)s over public.transactions, published as evidence.cleared_decisions_all_time and evidence.attestable_decisions_all_time so a third party can recompute the predicate itself. The window's own counts can only ever VETO an exemption, never grant one.","unscorable_codes":{"no_clearance_receipt_issuer_on_route":"attestation_coverage (200). Every one of the agent's cleared decisions was decided where no Ed25519 clearance receipt could be issued, as the rail recorded on the decision itself at decision time: transactions.metadata.attestation_receipt_available = false, or — on decisions written before the rails recorded availability (2026-09) — transactions.attestation_status = 'skipped' on a cleared row. /x402-pay was such a rail until then: it issued only an HS256 payment receipt. It now signs the same Ed25519 clearance receipt /authorize-intent does over every cleared decision whose Gate 0 runtime attestation matched, and records attestation_receipt_available = true on every decision it clears; its earlier receipt-less decisions keep their pre-change classification and are excluded from both sides of the factor rather than charged against the agent. No behaviour of the agent could have produced the evidence on such a decision, so the 200 points are removed rather than scored as a zero. One cleared decision where a receipt was available makes the factor scorable, permanently — permanently because the count is over history and a decision cannot be retired, not merely because it is recent."},"not_unscorable":"An agent that operates where clearance receipts ARE issued and has no attestations is a genuine zero on a scored factor and stays in the denominator. So is an agent with Gate 0 switched off, one that submits no runtime block, and one whose submissions drift — including on /x402-pay, which signs no receipt over a decision whose runtime did not match but records that one was available, so that decision is unproven rather than excused. Those are the agent's own record, not the route's. The other six factors are never unscorable: each puts unavailable evidence in the 'counts for neither side' bucket, which already lands on the 0.5 prior — the honest answer for no evidence — so removing one from the denominator would pay an agent for having none. attestation_coverage is the exception because its absent side is populated by the decisions themselves: every cleared-but-unproven decision counts against it, which makes an impossible route read as evidence of absence.","held_at_the_prior":"A factor scored over NO evidence at all sits at exactly the 0.5 prior — half its weight, inside the denominator — and is published in `prior_held_factors` with the reason. That is not the same as unscorable, and must not be read as one: the weight stays. Where the absence is a route fact the entry says so, and those are the two cases `denominator` above names."},"factors":[{"key":"mandate_compliance","weight":300,"rule":"cleared decisions (approval_status IN approved, cleared_by_firewall) against denials for a mandate breach (vendor allowlist, per-transaction limit, daily budget, sanctions, Gate 6 block, Gate 0 mismatch, kill switch); Laplace-smoothed, confidence n/50. Treasury and settlement failures are excluded — they are the operator's problem, not the agent's behaviour. Decisions still pending count for neither side. SINCE v1.3 a decision Gate 5 parked for a human (denial_reason = 'REQUIRES_HUMAN_APPROVAL') that a human then APPROVED counts on the compliant side at its materiality: the agent asked for something above its threshold by the route its mandate set for asking, and its principal said yes. One a human DENIED, or nobody has resolved, counts for neither side — a no from the principal is not a breach by the agent. A resolution counts only when POST /gate5-resolve recorded it (transactions.metadata.operator_resolution carrying key_id): a key of the organisation that owns the agent, never the agent's own key. The row's approval_status stays 'denied', because no payment was released, and the approval is not counted in evidence.cleared_decisions — it is published as evidence.parked_decisions_approved"},{"key":"attestation_coverage","weight":200,"rule":"of the cleared decisions for which the deciding rail recorded that an Ed25519 clearance receipt was available (transactions.metadata.attestation_receipt_available = true, or attestation_status ≠ 'skipped' on decisions predating that key), those carrying BOTH a Gate 0 runtime attestation matching the agent's registered expectation AND a signed receipt, against those carrying neither or only one; Laplace-smoothed, confidence n/30. Cleared decisions for which no receipt could be issued are excluded from both sides, and if EVERY cleared decision is one of those the factor is unscorable — see `scorability`"},{"key":"anomaly_history","weight":150,"rule":"Gate 6 tiers over the observed decisions: safe counts clean, watch ×0.5, warn ×1, critical ×2 count against; Laplace-smoothed, confidence n/50. The tier vocabulary is the scorer's own (safe | watch | warn | critical, the only values transactions.gate6_tier can hold) and every tier it emits is mapped, so a clean decision is credited rather than falling through. Decisions where Gate 6 did not run count for neither side, so disabling Gate 6 earns nothing"},{"key":"operator_review","weight":100,"rule":"of the decisions a human operator reviewed with an outcome, those vindicated against those confirmed true_positive; Laplace-smoothed, confidence n/10, at each decision's materiality. Two kinds of review reach it. A Gate 6 flag labelled false_positive or benign is vindicated, one labelled true_positive is confirmed. SINCE v1.3, a decision Gate 5 parked that a human APPROVED is vindicated too — a human looked at what the agent asked for and endorsed it — and one a human DENIED counts for neither side. A denial is not the Gate 5 analogue of a true positive: Gate 5 is a threshold, not a suspicion, so there is no flag for a denial to confirm, and what reaches it has already passed the allowlist, the limits, the sanctions screen and the kill switch. Charging a denial would also make human review cost an agent points that raising the threshold would not, which pays controllers to review less. An agent no human has ever reviewed scores the neutral prior, which is not a pass"},{"key":"settlement_reliability","weight":100,"rule":"settlement_status 'confirmed' against 'failed'; Laplace-smoothed, confidence n/20. Rows still at 'pending' count for neither side"},{"key":"controller_standing","weight":100,"rule":"1.0 less 0.5 per executed slash (capped at 1.0 — two executed slashes exhaust the factor), less 0.25 per pending or disputed slash (capped at 0.5 — an unadjudicated slash is a judgement in progress, and can take at most half the factor), less 0.3 for any active revocation recorded as fraud, compromise or policy violation. Slashes and revocations are counted across EVERY agent the controller stands behind, this one included, and THIS IS THE ONLY PLACE the controller's slash record is charged: it costs every agent under the controller the same amount, once, bounded by this factor's 100 points, and is never charged again as a per-agent penalty. The detail string always states the counts. Standing at or above the 0.5 prior is scaled by the controller's own record (material decisions / 200, weighted the same way as the agent's own); standing below the prior is not scaled at all, so a fresh shell organization cannot discount a slashed history"},{"key":"tenure","weight":50,"rule":"(days since the agent's first observed decision / 90) × min(1, material decisions / 100) — elapsed time earns nothing without observed activity"}],"penalties":[{"key":"mandate_breach_incidence","weight":0,"rule":"a tariff of −40 per scope breach (an unlisted vendor, a paused agent, a runtime the mandate was not written against) and −15 per budget breach (per-transaction or daily limit), SCALED SINCE v1.2 by how often this agent breaches: multiplied by min(1, breach rate / 0.10), where the breach rate is the materiality-weighted breaches over the materiality-weighted decisions that cleared or breached — the same two sums mandate_compliance scores, so a dust breach weighs a fraction of one at the ceiling on both sides, and a sanctions denial counts toward how often this agent breaches even though it is charged on its own penalty and never on this tariff. A rate of 10% or more pays the full tariff, so nothing that deserved the cap under v1.1 escapes it. Floored at −5 per breach, so a breach never rounds to nothing, and capped together at −240. An agent with no cleared or breached decisions at all has nothing to measure its breaches against and pays the full tariff. The detail string always states the rate, the two weighted sums behind it, the multiplier, and which of the scaled tariff, the floor or the cap produced the number. Compliance measures how often the agent stayed inside its mandate; this measures how much of a habit stepping outside is, and the floor measures that it happened at all"},{"key":"confirmed_true_positives","weight":0,"rule":"−60 per decision a human operator confirmed as a true positive, capped at −240. FLAT PER EVENT, and deliberately not rate-scaled the way mandate_breach_incidence is: a human looked at this decision and said it was real. That is a judgement somebody stood behind, not a gate firing on a rule, and it does not become less true because the agent also did a lot of other things. Human review is scarce by nature — operator_review reaches full confidence at n=10 — so the reviewed decisions are not a sample of the agent's traffic and a rate over them would measure how much reviewing was done, not how the agent behaved"},{"key":"ofac_matches","weight":0,"rule":"−150 per decision blocked at Gate 1.5 for a sanctions-list match, capped at −300. FLAT PER EVENT, and the clearest case for staying flat: sanctions are strict liability. One attempt to pay a sanctioned counterparty is disqualifying whatever else the agent did, and an agent that reaches for a sanctioned party rarely is not less sanctioned than one that does it often. Scaling this by volume would also price the deterrent backwards — the way to make a sanctions hit cheap would be to transact more"},{"key":"slash_events","weight":0,"rule":"−150 per executed slash (cap −300) and −75 per pending or disputed slash (cap −225) recorded against THIS AGENT'S OWN STAKE (slash_events.agent_id = the agent). Agent-level since v1.1. A slash against another agent under the same controller is not charged here — it reaches this agent once, through controller_standing, and nowhere else. FLAT PER EVENT: a slash is an adjudication, not a decision, and it is not drawn from the population of the agent's decisions at all, so there is no denominator to divide by. Inventing one — slashes over decisions — would let an agent dilute an adjudicated slash with traffic, which is exactly the laundering an incidence penalty exists to refuse"}],"tiers":{"_basis":"compared against normalized_score — score restated on the 0–1000 scale, after ceilings","_qualification":"A tier computed over a reduced denominator is published with a trailing '*' (e.g. 'B*') and carries comparable_with_full_denominator_ratings = false. It is not the same claim as the unstarred letter and must not be ranked against it. See `comparability`.","A":"≥ 800","B":"≥ 600","C":"≥ 400","D":"< 400","UNRATED":"less than one full material observation","REVOKED":"an active, unrescinded revocation stands against this agent"},"caps":{"_publication":"Every ceiling that applies to an agent is published on its rating with `binding` set to whether it is what held the score down, and the uncapped figure alongside it. A ceiling that only becomes visible once it bites is the same failure as an unreachable factor.","controller_none":"700. The controller is known only as a row id — no proof it corresponds to an identifiable party. Tier A is out of reach until it proves at least domain control. This is a statement about the evidence, not about any particular agent.","controller_domain":"850. The controller published a challenge Skalor issued at https://<domain>/.well-known/skalor-controller.json, proving it operates the domain it trades under. That is real evidence and it reaches tier A. It still says what this party runs, not who they are, so the ceiling stays below the top.","controller_kyb":"1000. A named operator checked the controller's legal identity and the record says who checked and when. Uncapped: nothing is being withheld for missing identity.","_lifecycle":"A rung is a present-tense claim, so its proof ages out. 'domain' is valid for 90 days from organizations.verified_at and 'kyb' for 365; each then counts for 14 more days of grace, during which every rating says when it lapses. After that the ceiling is computed as 'none' (700), the ceiling's code is lapsed_controller_verification, and its reason says when and why it fell. A domain rung is renewed by POST /controller-verify {\"action\":\"recheck\"}, which re-fetches the same published document; a kyb rung by an operator repeating the check. The published `verification` block carries verified_at, expires_at, grace_ends_at and state (current | grace | lapsed). Not a scoring change: the caps per rung and the formula are as above."},"notes":["An agent with no observations is UNRATED, never rated badly. An agent whose decisions breach its mandate is rated badly, because that is evidence.","Rates measure how often; penalties measure that it happened at all. Since v1.2 that sentence describes THREE of the four penalties, not all four. A confirmed true positive, a sanctions match and an adjudicated slash each record a judgement somebody stood behind, and each bites whatever the denominator — which is why the first two appear twice, once as a rate and once as an incidence. A mandate breach is a different kind of event: it is a denial, produced by a gate the agent's own principal configured, over which nobody adjudicated anything and on which no money moved. It is also the only one of the four drawn from a population this method already counts, so 'how often' has an answer here and does not for a slash. Its tariff is therefore scaled by the agent's breach rate, and only its −5-per-event floor still measures that it happened at all.","Rate-scaling the breach tariff means a low-rate agent with a long record pays less for the same event than a thin one, and that is the intent rather than a side effect. An agent that breaches once in five hundred material decisions has shown a counterparty something different from one that breaches once in three, and the published rating should be able to tell them apart. What the two still share is the floor: the fact of the breach costs the same either way, and it is the part of the penalty that does not recover with clean volume. There is no time decay — the rate itself is the recovery path, because it falls as an agent works cleanly, which is recovery earned by behaviour rather than by the calendar.","Stake is deliberately excluded. agent_stakes rows are is_demo = TRUE: the amounts are database numbers with no capital behind them, and a demo number is not evidence. Slash EVENTS still count, because those record a judgement that was made about behaviour — and each one counts in exactly two places: as a penalty on the agent it was recorded against, and once in the controller_standing of every agent under the same controller. Under v1 a controller's slashes were also charged in full to every sibling, so one agent's slashes were paid N times over while controller_standing reported none; v1.1 closed that.","Every factor's detail string states what it measured and the denominator it measured against, so a published score explains itself without a second call.","A score is only comparable against its own denominator. 738 of 800 and 738 of 1000 are different claims, and both are published rather than flattened into one number. The tier letter carries that difference too, because a reader ranks on the letter.","The published score is the sum of the published contributions, always. A response that invites a reader to recompute must not hand them a total they cannot reproduce from the parts beside it, so a binding ceiling reduces normalized_score and never restates score.","An agent whose route cannot produce a factor's evidence is not thereby better than one that produced it. It is better than one that could have and did not, because that is what the record shows — and the reason is published so a counterparty can price the gap itself.","Every rung of controller_standing reads a column that exists and is written. Controller identity is measured — organizations.verification_level, see caps — and the ceiling it sets is published on every rating.","Human review is the controller's word, and has been since v1: a Gate 6 label and (since v1.3) a Gate 5 resolution are both recorded by a key of the organisation that owns the agent, and the method cannot tell a considered review from a rubber stamp. Two things bound what that trust can buy. The agent's own key cannot resolve its own parked decision — an agent approving its own escalation is not a human reviewing anything — and operator_review is worth 100 points, smoothed against ten full reviews, so it can rise at most 50 points above the prior — about 42 of them after ten full approvals, and the last 8 only asymptotically. A controller that lowers its agent's threshold to manufacture parked decisions and approves them earns on mandate_compliance exactly what raising the threshold would have earned, and on operator_review no more than that range. The counts are published — evidence.parked_decisions_approved and parked_decisions_denied — so a reader can see how much of a rating rests on self-review.","Only the agent's own controller can put evidence into its record. A decision submitted without proving control of the agent is excluded from everything and counted in evidence.unauthenticated_decisions_excluded — see `evidence_eligibility`. An agent whose callers send no key does not score badly; it accrues no evidence until they do."]}