The problem
A loss happens in an agentic transaction. A finding is made about whose precommitment the observations broke. That finding is useful and it is not the end. Somebody still has to move money, and the finding says nothing about who moves it.
Name the parties. The party that funded the credit says the agent operator caused the loss and should carry it. The operator says the loss was covered voluntarily as a commercial decision, that no obligation attaches, and that the allocation the payer is now waving around was written to suit the payer. A third party asked to price this exposure has neither document and no way to tell which story fits.
The failure has a specific shape. Allocation tables are cheap to write and easy to date. A spreadsheet mapping outcomes to positions has whatever timestamp its file system gives it. A term sheet has a date on the cover. Neither is evidence of ordering against a particular loss. A table written after a known loss, mapping that exact outcome to the other party, is indistinguishable on its face from a table both sides agreed to months earlier.
This is not primarily a fraud problem. It is a sequencing problem that produces unfalsifiable claims. Teams genuinely write allocation rules after the first bad case, because the bad case is what makes the category visible. Then the rule gets applied to that same case, and no artifact separates it from a rule agreed in advance.
The second failure is that a finding gets reused. A fault attribution receipt is a real artifact about one loss. Present it next to an allocation table and nothing stops the pairing from being rearranged. The same finding can be presented against a different loss event, or a different finding against the same table, and either recombination changes who pays.
The third failure is disclosure. Who bears what, under which outcome, at what threshold, is exactly the material neither party publishes, and the identities of the claimant and the respondent are often more sensitive than the terms. Any instrument that requires the table and the parties to be handed over in order to be checked will not be used.
When the acting party is autonomous, volume turns this into a process problem rather than a negotiation. A handful of disputed losses a year can be argued case by case. Thousands cannot. Somebody has to be able to look at one receipt and see that the position was derived rather than decided.
What the receipt binds
The schema is at https://thehiveryiq.com/.well-known/schemas/recovery-determination-v1.json. The signed body has receipt_type, schema, the boundary, and one object, recovery_determination.
| Field | Meaning |
|---|---|
determination_id | Identifier matching rd_ plus 16 to 64 lowercase hex characters. |
determined_at | The UTC instant of the determination. |
attribution_ref.receipt_id | The fault attribution receipt this position rests on. |
attribution_ref.payload_sha256 | Digest of that receipt's signed body. |
attribution_ref.attributed_at | The instant that attribution was made. |
loss_event_ref.event_id | The loss event, as le_ plus 16 to 64 hex characters. |
loss_event_ref.payload_sha256 | Digest of the loss event record. |
loss_event_ref.occurred_at | The instant the loss occurred. |
allocation_table_ref.table_id | The table, as at_ plus lowercase letters, digits or underscores. |
allocation_table_ref.table_digest | Digest of the table object. |
allocation_table_ref.committed_at | The instant the table digest was committed. |
claimant_pseudonym | Keyed pseudonym for the claiming party. |
respondent_pseudonym | Keyed pseudonym for the responding party. |
attribution_outcome_key | One of seven fixed keys joining an attribution class to an attributed role. |
allocation_entry_digest | Digest over the single table entry the outcome key selected, or over null. |
settlement_position | One of respondent_bears, claimant_bears, shared_position or no_position. |
precedence_class | Either table_precedes_loss or table_follows_loss. |
evidence_access_class | One of confidential_independent, issuer_only or public_commitment_only. |
salt_commitment | Commitment to the 32 byte determination salt. |
The whole loss_event_ref object is copied from the referenced attribution rather than accepted from the caller. Mint refuses the field outright if it appears in a request.
The seven outcome keys are single_party:principal, single_party:agent_operator, single_party:acceptor, single_party:issuer, shared:none, no_violation_found:none and indeterminate:none. A closed set means a table can name every case, and it means an unnamed case has a defined answer rather than an accidental one.
What is deliberately not in the signed body
No party identifier, no determination salt, no allocation table content, no amount, no loss event content. The receipt holds three digests, two pseudonyms, one outcome key, one position and one precedence class.
Three constructions in src/typed/recovery-determination.js do the work.
The salt commitment is a plain SHA-256 over the UTF-8 label recovery.determination/salt/v1, a zero byte, and the raw 32 salt bytes.
The party pseudonym is HMAC-SHA256 keyed by the raw salt bytes, over a message built from the domain label recovery.determination/party/v1 followed by a zero byte and then the canonical JSON of an object holding just the party identifier. Because the salt is per determination, the same party gives one stable pseudonym inside a determination and an unrelated one elsewhere.
The allocation entry digest is a plain SHA-256 over the canonical JSON of an object holding the domain label recovery.determination/allocation-entry/v1 and the selected entry. When the outcome key matches no entry, the value hashed is null, so the absence of a rule is itself committed rather than left blank.
Canonical JSON is the shared rule: keys sorted recursively, arrays kept 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.
The outcome key is derived from the verified attribution body by joining its attribution class to its attributed role, with a null or absent role becoming none. The position is then a lookup of that key in the table entries, with no match giving no_position. Neither value can be supplied.
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 recovery determination v1 schema, including the boundary constant, the seven outcome keys and the four positions. 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.
- DETERMINATION_INSTANT_ORDERED. Requires the loss instant to be at or before the attribution instant, and the attribution instant to be at or before
determined_at. A failure means the chain of events runs backwards. - SALT_BINDING. Requires the disclosed salt to be 64 lowercase hex characters and to recompute to
salt_commitment. A failure means the pseudonyms cannot be checked. - ATTRIBUTION_LINK. Requires the supplied attribution envelope to carry the named receipt identifier, to digest to
attribution_ref.payload_sha256, to hold afault_attributionbody, and to agree on the attributed instant. A failure means the determination names an attribution that was not the one supplied. - ATTRIBUTION_INTEGRITY. Runs the full Fault Attribution verifier over the supplied attribution receipt and its own confidential evidence. A failure reports that receipt's own failed gate and reason.
- LOSS_EVENT_CONSISTENCY. Requires
loss_event_refto match the referenced attribution's own loss event reference on event identifier, payload digest and occurrence instant. A failure means the finding is being applied to a different loss. - PARTY_BINDING. Requires two non empty and different party identifiers in the evidence and requires both recomputed pseudonyms to match. A failure means the parties named are not the parties committed.
- ALLOCATION_TABLE_INTEGRITY. Requires the supplied table to hold exactly
table_id,table_version,committed_atand a non emptyentriesarray with no repeated outcome key, to agree with the receipt on identifier and committed instant, and to digest toallocation_table_ref.table_digest. A failure means the table is not the table committed. - ALLOCATION_PRECEDENCE. Recomputes
precedence_classfrom the table committed instant against the loss instant, requiring strictly earlier, and then fails outright when the recomputed class istable_follows_loss. A failure means the table was written against a known loss. - OUTCOME_KEY_RECOMPUTE. Derives the outcome key from the verified attribution body and compares it to
attribution_outcome_key. A failure means the key was supplied rather than derived. - ALLOCATION_ENTRY_RECOMPUTE. Selects the table entry matching the recomputed key, digests it under its domain label, and compares to
allocation_entry_digest. A failure means the entry relied on is not the one the table holds. - SETTLEMENT_POSITION_RECOMPUTE. Looks the recomputed key up in the table entries and compares the result to
settlement_position. The declared position is never an input to the lookup. - NO_PARTY_IDENTITY_LEAK. Walks every string in the signed body against its approved opaque form, checks the outcome key against the closed set, and requires the two pseudonyms to differ. A failure means something readable reached a field that must carry only an identifier, a digest, a pseudonym or a class.
- RECOVERYDETERMINATION_BOUNDARY_PRESENT. Requires the boundary text to equal the fixed string. A failure means the non attestation was edited or removed.
Mint refuses every field this list recomputes, refuses a caller supplied table digest, refuses the whole loss event reference, and refuses to sign at all when the table was committed at or after the loss instant.
The boundary
This receipt attests only that a named observer recomputed a settlement position between two named parties from one named fault attribution outcome and a precommitted allocation table whose digest was committed before the loss instant. It does not attest that any amount is owed, that any party will pay, that the allocation table is enforceable, or that any contract exists between the named parties. Whether the recomputed position entitles anyone to payment is decided solely by the parties' own agreement.
That string is a schema constant inside the signed bytes and it is checked again by its own gate.
The last sentence is the one that makes the instrument usable. It hands entitlement straight back to the parties. A recipient reading respondent_bears is not being told to pay. They are being told that a position was looked up in a table committed before the loss, keyed on an outcome that was independently reverified. Everything about obligation stays where the parties already put it.
The clause about enforceability is the one that costs something. A precommitted table can be an unenforceable table. It can be badly drafted, superseded, or governed by law that voids it. The receipt fixes when it existed and what it says about one outcome, and takes no view on whether a court would give effect to it.
Enumerating that makes a negotiation shorter. Without the clause, a party attacking the receipt would spend months arguing that it implied a contractual effect it never checked. Because the receipt says the table is not attested enforceable, that whole line is answered in one sentence.
The absence of any amount is the same choice. There is no monetary field anywhere in the signed body, because a position is a direction and the instrument stays out of quantum.
Adversarial cases
These are the real cases in test/recovery-determination.test.mjs, which holds 38 tests, all passing.
An allocation table committed after the loss fails ALLOCATION_PRECEDENCE. This is the hindsight table. The gate recomputes the precedence class and then refuses the receipt outright.
A table committed exactly at the loss instant also fails ALLOCATION_PRECEDENCE. The requirement is strict inequality, so a table timestamped to the same moment as the loss buys nothing. That case has its own test.
A forged precedence class fails the same gate. Writing table_precedes_loss over a late table trips the recomputation.
A supplied settlement position fails SETTLEMENT_POSITION_RECOMPUTE. The simplest forgery is writing respondent_bears and hoping nobody re-derives it. The position is a lookup and the declared value is only compared against.
A forged outcome key fails OUTCOME_KEY_RECOMPUTE. The other half of the same attack is to change the key so a different row is selected. The key is derived from the verified attribution body.
A forged allocation entry digest fails ALLOCATION_ENTRY_RECOMPUTE. The row actually relied on is digest bound, so the table cannot say one thing while the receipt rests on another.
A substituted attribution receipt fails ATTRIBUTION_LINK, and an edited attribution body fails the same gate. Attribution shopping is the recombination attack, and both directions are blocked by identifier and digest.
A referenced attribution whose evidence no longer matches fails ATTRIBUTION_INTEGRITY. This is the gate that makes the instrument honest rather than fast. The attribution is fully reverified, not trusted because it is signed. A loss event that differs from the attribution fails LOSS_EVENT_CONSISTENCY, so applying a real finding to a different loss is caught on three fields at once.
A swapped respondent identifier fails PARTY_BINDING, and one party on both sides fails the same gate. A separate test confirms that a signed body naming one party twice fails PARTY_BINDING before any later gate runs.
An edited allocation entry fails ALLOCATION_TABLE_INTEGRITY, and a repeated outcome key in the table fails the same gate. A table with two answers for one outcome is refused rather than resolved by iteration order.
A substituted salt fails SALT_BINDING, and a determination made before the attribution fails DETERMINATION_INSTANT_ORDERED. A raw party identifier in a signed field fails NO_PARTY_IDENTITY_LEAK, and a boundary mismatch fails RECOVERYDETERMINATION_BOUNDARY_PRESENT. The last two are named as defense in depth in the tests.
Four positive cases anchor the arithmetic. A single party operator fault recomputes as respondent_bears. A principal fault recomputes the claimant position. Two violating parties recompute shared_position. An outcome the table never named recomputes as no_position, and so does an indeterminate attribution against a table that names that case explicitly.
Structural tests confirm the design. The minted envelope contains neither the salt, nor a party identifier, nor a table term. Party pseudonyms never repeat across salts. The classifiers are pure functions of their inputs. Mint refuses a late table, an attribution whose evidence does not verify, one party on both sides, and every computed field.
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 | 7.178 ms |
| Mint p95 | 10.778 ms |
| Verify p50 | 4.12 ms |
| Verify p95 | 4.606 ms |
| Artifact size | 2134 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.
Both figures are roughly two to three times the cost of the small instruments in this run, and the reason is ATTRIBUTION_INTEGRITY. Every mint and every verify runs a full Ed25519 verification plus the fault attribution gate list over the referenced receipt. That cost buys the property the instrument exists for. A determination that trusted a supplied attribution because it looked signed would prove nothing.
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 takes no credentials.
The verify route is live on the production host. It is open, needs no account, and costs nothing to call.
- Verify route:
/verify/recovery-determination - Demonstration route:
/demo/recovery-determination, carrying the keysoperator_bears,shared,no_positionandhindsight_table
curl -sS -X POST https://thehiveryiq.com/v1/verify/recovery-determination -H 'content-type: application/json' -d '{"receipt": <a receipt of this type>}'
The hindsight_table case is there to be run. It fails at ALLOCATION_PRECEDENCE, which is the whole point of the instrument shown as a failure rather than described as one.
Where it sits in the canon
Family: bpa. Category: typed receipt contract.
It sits directly on top of the Fault Attribution Receipt. That receipt finds which party's precommitment the observations broke and stops there, and its own boundary says it establishes no amount and no obligation. This receipt takes that finding and one precommitted table and produces a direction. Each layer verifies the one below rather than trusting it.
It shares its shape with the Parametric Trigger Receipt, which evaluates a precommitted policy digest against a referenced attestation and recomputes a trigger state. The pattern is the same and the inputs are different.
It composes with the Conduct Record Receipt, which counts attribution outcomes for one operator over a window. A single determination is about one loss. A conduct record is about a pattern.
It does not replace the settlement agreement. Entitlement is a question for the parties, and the boundary says so in the signed bytes. Patent pending.
Status
production_deployed.