The problem
Agent operators will be scored. The party holding the losses wants to know which operators cost it money, and it controls registration. That combination is the problem, not the scoring.
Name the parties. The party holding the losses says one operator has a poor record and prices or refuses it accordingly. The operator says the record is wrong, incomplete, or built from findings it never saw. An insurer or registry asked to rely on the score has the score and nothing behind it.
The failure is not that anybody is lying. It is that a count is easy to get wrong in ways that all point the same direction. A finding that was later reversed stays in the tally. A finding about a different operator gets attributed because two identifiers looked similar. A finding from outside the window gets pulled in because a boundary moved. None of that requires bad faith.
The second failure is completeness. If half the findings never reach the counting party, the record is a statement about half, and presented without qualification it looks like a statement about everything. Silence from a source is indistinguishable from a source with nothing to report.
The third failure is reidentification. A record that reports fine grained buckets leaks. If a bucket holds two outcomes and the reader knows the market, the two are identifiable, and so are the counterparties behind them. The same goes for the operator and for amounts, so any instrument that needs those published will not be adopted.
When the scored party is autonomous, the numbers change the problem. A book of a dozen counterparties can be reviewed by hand. An operator fleet produces findings continuously, the tally becomes the only thing anyone reads, and whatever that tally is derived from had better be checkable.
What the receipt binds
The schema is at https://thehiveryiq.com/.well-known/schemas/conduct-record-v1.json. The signed body has receipt_type, schema, the boundary, and one object, conduct_record.
| Field | Meaning |
|---|---|
record_id | Identifier matching cr_ plus 16 to 64 hex characters. |
compiled_at | The UTC instant this record was compiled. |
window_start and window_end | The window bounds as UTC instants. |
operator_pseudonym | Keyed pseudonym for the operator, under the window salt. |
pseudonym_algorithm | Fixed to hmac_sha256_v1. |
pseudonym_scope | Fixed to per_operator_per_window. |
receipt_set_digest | Canonical digest over the sorted set of counted entries. |
considered_receipt_count | How many receipts were supplied, 0 to 100000. |
counted_receipt_count | How many of those named this operator, 0 to 100000. |
outcome_buckets | Up to four rows, each an outcome_class and a count of at least 5. |
reported_outcome_count | The sum of the reported buckets. |
suppressed_bucket_count | How many buckets fell below the floor, 0 to 4. |
suppressed_outcome_count | How many outcomes sat in those buckets. |
k_anonymity_floor | The declared floor, minimum 5, maximum 1000. |
coverage_class | Fixed to supplied_set_only. |
evidence_access_class | One of confidential_independent, issuer_only or public_commitment_only. |
salt_commitment | Commitment to the 32 byte window salt. |
The four outcome classes are single_party, shared, no_violation_found and indeterminate, and the buckets must appear in that fixed order with no repeated class.
Note that considered and counted are separate numbers, so the record shows how many supplied receipts named somebody else. Note also that coverage_class is a schema constant, so there is no value it can hold that claims completeness.
What is deliberately not in the signed body
No operator identifier, no window salt, no counterparty, no amount, no receipt content. The receipt holds one pseudonym, one set digest, six counts, a floor and two classes. Three constructions in src/typed/conduct-record.js do the work.
The salt commitment is a plain SHA-256 over the UTF-8 label conduct.record/salt/v1, a zero byte, and the raw 32 salt bytes.
The operator pseudonym is HMAC-SHA256 keyed by the raw salt bytes, over a message built from the domain label conduct.record/operator/v1 followed by a zero byte and then the canonical JSON of an object holding just the operator identifier. Because the salt is per window, the same operator gives one stable pseudonym inside a window and an unlinkable one in the next. That is what pseudonym_scope records, and both it and the algorithm are schema constants.
The receipt set digest is a canonical set digest under the label conduct.record/receipt-set/v1. Each counted entry holds the digest of the attribution body, the instant it was attributed, and its outcome class. Entries are sorted by their canonical JSON bytes before hashing, so one set of findings gives one digest regardless of arrival order. Canonical JSON is the shared rule: keys sorted recursively, arrays in order, no whitespace, and every code unit above U+007F escaped as a lowercase \uXXXX sequence, then digested as lowercase hex SHA-256 over the UTF-8 bytes.
Counting is deterministic and narrow. A supplied receipt is counted only when its confidential evidence names the operator identifier with the role agent_operator. Everything else is considered and skipped. The counted entries are tallied into the four classes in fixed order.
The floor is applied by a pure function. A class with zero outcomes produces no bucket. A class at or above the floor is reported in full. A class below the floor is not reported at all, and instead contributes one to suppressed_bucket_count and its count to suppressed_outcome_count. The floor carries a fixed minimum of five in three places at once: the schema minimum on k_anonymity_floor, the schema minimum on every reported bucket count, and the gate that recomputes against the greater of the declared floor and five.
The receipt envelope uses the shared Ed25519 construction over the ASCII string hive-receipt <receipt_id> <payload_sha256> <ts>, with hive-receipt as the domain separation label.
The gates
Verification runs in this order and stops at the first failure.
- SCHEMA. Validates the envelope against the conduct record v1 schema, including the boundary constant, the fixed algorithm and scope, the fixed coverage class, and the minimum of five on both the floor and every bucket count. A failure means the artifact is malformed.
- ISSUER_KEY_MATCH. Resolves
envelope.key_idin the trusted keyring, checks the key is authorised for this receipt type, and checks it was usable at the envelope timestamp. A failure means the signer is not trusted here. - PAYLOAD_DIGEST_VALID. Recomputes the canonical digest of
signed_bodyagainstpayload_sha256. A failure means the body was altered after signing. - SIGNATURE_VALID. Requires Ed25519 and verifies the signature over the signing string using the resolved key. A failure means that key did not produce it.
- WINDOW_BOUNDS_ORDERED. Requires
window_startstrictly earlier thanwindow_end, andwindow_endat or beforecompiled_at. A failure means the record covers a window that had not closed. - SALT_BINDING. Requires the disclosed salt to recompute to
salt_commitment. A failure means the pseudonym cannot be checked. - OPERATOR_PSEUDONYM_BINDING. Requires a non empty operator identifier in the evidence, the algorithm and scope constants, and a matching recomputed pseudonym. A failure means the operator named is not the operator committed.
- ATTRIBUTION_SET_INTEGRITY. Requires the supplied attributions to be an array whose length equals
considered_receipt_count, rejects a repeated receipt identifier, runs the full Fault Attribution verifier over every one of them, keeps the ones whose evidence names this operator as the acting agent, and requires the kept count to equalcounted_receipt_count. A failure names the receipt that broke and the gate it broke on. - WINDOW_MEMBERSHIP. Requires every counted attribution to fall inside the window bounds. A failure names the instant that fell outside.
- RECEIPT_SET_DIGEST_RECOMPUTE. Recomputes the canonical set digest over the counted entries and compares it to
receipt_set_digest. A failure means the counted set is not the set committed. - OUTCOME_BUCKET_RECOMPUTE. Tallies the counted entries by class, applies the floor at the greater of the declared floor and five, requires the receipt's buckets to match row for row on class and count, and requires the fixed class order with no repeat. A failure prints the buckets the evidence produces.
- SMALL_GROUP_SUPPRESSION. Requires the declared floor to be at or above five, no reported bucket below the declared floor, and both suppressed counts to match the recomputation. A failure names the offending bucket.
- COUNT_COHERENCE. Requires
reported_outcome_countto equal the sum of the reported buckets, reported plus suppressed to equalcounted_receipt_count, counted not to exceed considered, andcoverage_classto besupplied_set_only. A failure names the arithmetic that does not close. - NO_OPERATOR_IDENTITY_LEAK. Walks every string in the signed body against its approved opaque form and checks every bucket class against the closed set. A failure means something readable reached a field that must stay opaque.
- CONDUCTRECORD_BOUNDARY_PRESENT. Requires the boundary text to equal the fixed string. A failure means the non attestation was edited or removed.
The order of gates 11 and 12 is deliberate. Recomputing against the greater of the declared floor and five means a receipt declaring a floor of two cannot make the recomputation expect a small bucket to be reported. The low declaration is then caught one gate later by SMALL_GROUP_SUPPRESSION, which names the real fault. Recomputing against the declared floor alone would have reversed that. There is a comment at the gate and a test for the case.
Mint refuses every field this list recomputes, refuses a floor below five, refuses a repeated receipt, refuses an attribution whose evidence does not verify, and refuses a counted attribution outside the window.
The boundary
This receipt attests only that a named observer verified the supplied fault attribution receipts, counted the ones that name one keyed operator pseudonym inside a committed window, and reported those counts by outcome class with no bucket below the stated floor. It does not attest that the supplied set is complete, that any omitted outcome does not exist, that the operator is well or badly run, that the record predicts anything, or that any registration, pricing, or admission decision is justified by it. It discloses no operator identity, no counterparty, and no amount.
That string is a schema constant inside the signed bytes and it is checked again by its own gate. It is also restated as a field a machine reads, because coverage_class is pinned to supplied_set_only.
The clause about completeness earns the instrument its credibility. A record built from ten findings and a record built from all findings look identical in shape, and the receipt refuses to let the first pass as the second. What it exposes instead is the gap between considered and counted.
The clause about the operator being neither well nor badly run is the one people find strange, because a track record looks like exactly that judgment. It is not. Four counts over one window are counts. Whether they mean an operator is careless depends on volume, on the transaction mix, and on judgment that belongs to an underwriter. The clause about prediction closes the same door from the other side, because a count over a past window says nothing about the next one.
Enumerating all of that makes the receipt more useful in a negotiation. An operator handed this record knows what it can dispute: whether the supplied set was the right set. Everything downstream of that is recomputed and settled, which is a much shorter argument than the one that starts with a spreadsheet.
Adversarial cases
These are the real cases in test/conduct-record.test.mjs, which holds 41 tests, all passing.
A suppressed bucket added back to the report fails OUTCOME_BUCKET_RECOMPUTE. This is the reidentification attack. Take a genuine record with a bucket of two below the floor and put that bucket back into the report. The recomputation applies the floor itself and finds the extra row. An inflated bucket count and buckets out of the fixed class order fail the same gate, so neither the counts nor the ordering can be shaped after the tally.
A defense in depth floor check fails SMALL_GROUP_SUPPRESSION. A record declaring a floor below five is refused by the gate that names the floor rather than by the bucket recomputation, because the recomputation already used the fixed minimum. A forged suppressed bucket count and a forged suppressed outcome count fail the same gate, since understating suppression would hide how much of the record is missing.
A supplied attribution that no longer verifies fails ATTRIBUTION_SET_INTEGRITY. This is the gate that makes the instrument honest rather than fast. Every finding is reverified in full, not trusted because it carries a signature. A withheld attribution and a forged counted receipt count fail the same gate, because both counts are recomputed from the supplied array, and so does the same attribution supplied twice.
A counted attribution outside the window fails WINDOW_MEMBERSHIP. Pulling activity in from a neighbouring window is how a total gets shaped, and it is caught against the declared bounds. A forged receipt set digest fails RECEIPT_SET_DIGEST_RECOMPUTE, because the counted set is digest bound.
A substituted operator fails OPERATOR_PSEUDONYM_BINDING, and a forged pseudonym scope fails the same gate, since claiming a wider scope would make one window's pseudonym look linkable across windows. A substituted salt fails SALT_BINDING, which is the first attack on any pseudonym scheme.
A forged reported outcome count fails COUNT_COHERENCE, and so does a forged coverage class, so a record that tried to claim complete coverage is refused by name. A window that closes after the record was compiled and a window that ends before it starts both fail WINDOW_BOUNDS_ORDERED.
A declared floor below the fixed minimum fails SCHEMA, and a reported bucket below five fails SCHEMA. Both are caught before any gate runs, which is the first of the three layers on the floor. A raw operator identifier in a signed field fails NO_OPERATOR_IDENTITY_LEAK, and a boundary mismatch fails CONDUCTRECORD_BOUNDARY_PRESENT. Both are named as defense in depth in the tests.
Five positive cases anchor the behaviour. Thirteen verified attributions mint and verify. A bucket below the floor is suppressed and never reported. A higher floor suppresses more buckets and reports fewer outcomes. A receipt naming another operator is considered and not counted, which is what makes the two counts worth carrying separately. An operator with nothing above the floor reports no bucket at all and the receipt still verifies, because a record that can report nothing is a valid record.
Structural tests confirm the design. The minted envelope contains neither the salt, nor the operator identifier, nor any counterparty. Operator pseudonyms never repeat across window salts.
Performance
From hive-verifier-api/benchmarks/results-latest.json, measured at 2026-08-10T08:00:47.634Z at commit a22b4d88f6e041d6f593381b6e310a8d527d5b4b over 200 iterations:
| Measure | Value |
|---|---|
| Mint p50 | 57.965 ms |
| Mint p95 | 63.128 ms |
| Verify p50 | 29.206 ms |
| Verify p95 | 31.485 ms |
| Artifact size | 1828 bytes |
Local harness measurement on one machine and one Node version. The run recorded Node v20.20.1 on linux/x64 with two Intel Xeon cores at 2.90 GHz. It is a reproducible measurement of this code, not a production latency guarantee.
This is an order of magnitude slower than the other instruments in this run and the reason is ATTRIBUTION_SET_INTEGRITY. The fixture supplies thirteen attribution receipts, and every mint and every verify runs a full Ed25519 verification plus the complete fault attribution gate list over each one. The cost scales with the number of supplied receipts and the schema permits up to 100000, so a large window will cost far more than the fixture. That cost buys the property the instrument exists for, since a record built from unchecked findings is the artifact it was written to replace. The receipt itself is small, at 1828 bytes, because the work is all on the input side.
How to run it
This type has no mint route. It is produced as an export and only verified here, so there is nothing to authenticate against. The verify route is live on the production host. It is open, needs no account, and costs nothing to call.
- Verify route:
/verify/conduct-record - Demonstration route:
/demo/conduct-record, carrying the keysreported,raised_floor,below_floorandsuppressed_bucket_restored
curl -sS -X POST https://thehiveryiq.com/v1/verify/conduct-record -H 'content-type: application/json' -d '{"receipt": <a receipt of this type>}'
The reported case verifies with buckets of six and five reported and one bucket of two suppressed. The raised_floor case verifies at a floor of six, reporting only the bucket of six. The below_floor case verifies with nothing clearing the floor. The suppressed_bucket_restored case fails at OUTCOME_BUCKET_RECOMPUTE, and since it cannot be minted it is built by minting the honest case, editing the body and re-signing with the demonstration key.
Where it sits in the canon
Family: risk evidence. Category: typed receipt contract.
It sits on top of the Fault Attribution Receipt, alongside the Recovery Determination Receipt. A determination is about one loss and produces a direction. A conduct record is about a pattern across a window and produces counts. Both reverify the findings they rest on.
It borrows two mechanisms already in the canon. The keyed pseudonym under a committed window salt is what the Cross Acceptor Receipt uses to count acceptors without naming them. The small group suppression is what the Portfolio Exposure Receipt uses to report concentration without disclosing who is in the book. This receipt applies both to attribution outcomes for one operator, and it shares its honest weakness with the Cumulative Mandate Receipt, which also counts over a supplied set it cannot know is complete.
It does not replace an admission decision, an underwriting file or a rating. The boundary rules all three out in the signed bytes. Patent pending.
Status
production_deployed.