AgentAbuse Working draft

Overview

What is an Abuse Report

The thirteen-field schema, the category taxonomy, delegation topology, and the two-tier verification that keeps an unconfirmed citation honest.

Where it fits

Receipts record what happened. Envelopes record what was authorized. Neither records a claim that either one reflects harm — that claim, when it exists, has historically lived unsigned, in a support ticket or a chat log. An abuse report is the accusation complement to both: it doesn't replace a receipt or an envelope, it points at one, by hash, as the basis for a claim that it reflects harm.

Receipts say what an agent did. Envelopes say what an agent may do. A report says someone claims one of those was harmful, and grounds that claim in the other two. Together they give a system four tenses of accountable evidence — capability, decision, history, and accusation — each independently verifiable, none requiring trust in the party that produced it, and none silently overreaching into what it cannot prove.

The thirteen fields

A report is a JSON object with exactly these thirteen members. A document with any additional member, any missing member, an accused_role, category, or delegation_position outside its closed set, or a malformed evidence_refs is not a report and MUST fail verification.

FieldTypeRequirement
protocolstring, "abuse-report/0.3"REQUIRED
reporter_idstring, non-emptyREQUIRED
accused_idstring, non-emptyREQUIRED
accused_rolestring, one of agent | principalREQUIRED
categorystring, closed vocabulary or otherREQUIRED
evidence_refsarray, non-empty, of {type, hash}REQUIRED
narrativestring, non-emptyREQUIRED
delegation_positionstring, one of upstream | downstream | lateral, or nullREQUIRED; null when no topology claim is asserted
delegation_refstring (64-char lowercase hex SHA-256) or nullREQUIRED; null when the filer relied on standing authority
prev_hashstring (64-char lowercase hex SHA-256) or nullREQUIRED; null only for a reporter's first report
issued_atstring, RFC 3339 timestampREQUIRED; supplied by the issuer, validated by parsing
sigstring (128-char hex Ed25519 signature)REQUIRED
pubkeystring (64-char hex, raw 32-byte Ed25519 public key)REQUIRED

delegation_position and delegation_ref are always present, never omitted — the same convention prev_hash already uses. A well-formed report has thirteen members whether or not it makes a delegation claim; the fields are just null when it doesn't.

The category vocabulary

A closed, versioned set: unauthorized_scope_expansion, deceptive_output, resource_abuse, collusive_behavior — an agent accused of abusing a principal — and injection_attempt, capability_extraction, harassment — a principal accused of abusing an agent — plus other, where narrative carries the substantive claim. Unchanged in abuse-report/0.3. A new category is a lightweight, maintainer-approved protocol revision — real narratives that used other, argued as behavioral not sectoral — never added silently to an unchanged protocol value. See Docs & Spec for the full governance process.

Evidence references

Each element of evidence_refs is exactly {type, hash} — a pointer, never a bundled payload. type is receipt or envelope; hash is computed the same way the cited artifact's own reference implementation computes it. A report may cite more than one artifact — real abuse is often a pattern across several receipts, not one isolated event — but the reporter MUST be a party to every cited item, not just one; otherwise an unrelated citation could be smuggled in alongside a legitimate one.

Delegation topology

accused_role answers one question — is the accused a computational agent or a human principal? delegation_position answers a different one: where does the accused sit relative to the filer in the delegation chain — upstream (the accused authorized or supervises the filer), downstream (the filer delegated to the accused), or lateral (a peer relationship, no delegation link at all)? The two fields are orthogonal, not redundant: "upstream" alone doesn't say whether you're dealing with a human at the chain's root or a supervising agent one hop up, and accused_role alone can't express a peer-to-peer accusation with no delegation relationship — lateral is expressiveness the old binary genuinely couldn't reach.

The filer of a report is itself almost always an agent acting under delegation, not a human typing directly. delegation_ref — a hash pointer to the DAT that authorized the filer, or null for standing authority, the exact semantics sm-arp's own authority_chain already uses — is what makes that inheritance checkable: a verifier holding the DAT can trace the filer's signing key back to the human who authorized it.

Self-asserted, not verified. Neither field is independently checked against real chain data in this version — sm-arp's own authority_chain is itself descriptive-only as of its v0.1, so verifying delegation topology here first would verify against a foundation that doesn't exist yet. A real verify_delegation() is a natural future addition once that foundation lands.

Canonicalization & signature

The signing input is the JSON serialization of the report with the sig member removed, using: keys sorted lexicographically at every level; the compact separators "," and ":" (no insignificant whitespace); UTF-8 encoding. sig is the Ed25519 signature (RFC 8032) over those canonical bytes, hex-encoded; pubkey is the raw 32-byte Ed25519 public key, hex-encoded. Identical inputs produce byte-identical reports, and hostile input never raises out of the verifiers.

report {
  protocol             "abuse-report/0.3"
  reporter_id          the filer's identity
  accused_id           the accused party's identity
  accused_role         agent | principal
  category             closed vocabulary, or "other"
  evidence_refs        [ { type: receipt|envelope, hash } , ... ]   (non-empty)
  narrative            the filer's account (required)
  delegation_position  upstream | downstream | lateral | null
  delegation_ref       SHA-256 of the DAT behind the filer, or null
  prev_hash            SHA-256 of this reporter's prior report, or null
  issued_at            RFC 3339, caller-supplied
  sig                  Ed25519 over the canonical body, sig removed
  pubkey               the reporter's raw Ed25519 public key
}

Report hash and the chain

The report hash is the lowercase-hex SHA-256 of the full report — including sig — under the same key ordering and separators. A successor report's prev_hash must equal the report hash of the same reporter's immediately preceding report. A reporter's chain is reconstructed the same way an agent's envelope chain is: rejecting a gap (a deleted interior report), a fork (two reports claiming the same predecessor), a foreign-chain splice (a prev_hash reaching into another reporter's chain), and duplicates or zero/multiple genesis reports. Since abuse-report/0.3, this chain isn't reports-only — see Retraction, below.

Retraction

A retraction is a separate, eight-field object — not a report variant — a reporter withdrawing one of their own prior reports: protocol, reporter_id, retracted_report_hash (the report being withdrawn, by hash), reason (required, non-empty), prev_hash, issued_at, sig, pubkey. It carries no accused_id, category, or evidence_refs — none of those apply to a statement about a prior accusation rather than an accusation itself.

Reports and retractions from one reporter share one hash chain — a reporter can't selectively hide a retraction any more than they could hide a report. order_chain accepts either kind, mixed, type-dispatched by field-set shape. A new resolver, retraction_status, walks an already-ordered chain and returns the report hashes currently retracted: a report is retracted if a later valid retraction in that same chain names it.

Retraction doesn't erase. Retracting a report never touches its own signature — verify_report on the original still returns true forever. You cannot un-sign something, only say, also signed, that you withdraw it. There is no un-retraction (once issued, permanent) and no partial retraction of a single evidence_refs entry — retracted_report_hash always names one whole report.

Two-tier verification

A report's signature and structure can always be checked, with no external lookups. Whether its citation is real — whether the hash resolves to something, and whether the reporter was really a party to it — can only be checked by someone who holds the cited artifact. Conflating the two would mean either rejecting reports nobody happens to be able to fully audit yet, or silently trusting citations that were never actually confirmed. Both are wrong, so the check is split:

Structural

Always available

Format, canonical signature, and chain integrity across whatever set of a reporter's reports the verifier holds. No access to the cited evidence required.

Evidence citation

Requires holding the artifact

Recomputes the artifact's hash, checks its declared type, and confirms the reporter is a real party to it. Returns confirmed, hash_mismatch, type_mismatch, or not_party.

A citation nobody has checked this way at all — the verifier simply doesn't hold the artifact — is unconfirmed, a distinct, honest state. Treating "not checked" as either "passed" or "failed" is a misuse of the primitive, not a property it provides.

What it does not do

Adjudication

Signs the accusation, not the verdict

A report is an accusation, not a finding. No report, however well-formed and evidence-confirmed, determines guilt or innocence.

Reputation coupling

No automatic score consequence

A confirmed report does not by itself change any reputation score. A future integration is a named direction, not built here.

Distribution & storage

Deployment policy, not this primitive

Who collects, stores, or distributes reports is outside this specification, the same boundary drawn around who runs a reputation auditor.

Out of scope. Whether an action actually happened, and whether it was authorized, are both outside this primitive — those are agency receipts' and agent envelope's questions. An abuse report only ever claims: someone accused this, grounded in that.