The problem
An agent takes an instruction from a person, writes a summary of it, shows the summary back to the person, gets a confirmation, and forwards the summary to whoever will act on it. Three artifacts and two joins. Every party downstream treats the forwarded summary as the authenticated instruction.
Name the parties. The party that receives the summary says it acted on an authenticated instruction. The person says they never saw that version. The operator says they showed something and got a confirmation. Nobody outside the operator holds the presentation event, because it happened inside the operator's own interface.
The failure is that the join has no artifact. The receiving party holds one string. The operator holds a log saying a confirmation happened. Nothing ties the string the receiving party holds to the string the person actually saw. Both records can be internally consistent and still describe two different documents.
The second failure is ordering, and it is subtler. Suppose the two strings match. That still leaves the possibility that the confirmation was recorded before the summary was rendered, or captured afterwards from an event that had already been forwarded. Both shapes appear in real systems, usually as a retry, a queue replay, or an interface that records a tap before it finishes drawing. A confirmation outside the window is not evidence about the window, and a log with three rows in it will not object.
The third failure is clocks. The three instants come from at least two devices and none of them is reliable. A confirmation recorded a second before the presentation may be a genuine ordering violation or a phone whose clock is four seconds out. Without a declared external reference and a stated tolerance, an ordering claim built from device timestamps is not a claim anyone can test.
The fourth failure runs the other way. Nobody wants to publish the summary. It is the content of a personal instruction and it frequently names third parties, prices and preferences. A scheme that needs the text handed to a verifier will not get used, so an instrument that wants adoption has to compare two artifacts it cannot read.
When the party producing the summary is autonomous, the shape of the risk changes. A human intermediary paraphrasing a request produces one version and remembers it. An agent generates a fresh summary for every instruction, at machine speed, and can regenerate a plausible one afterwards that agrees with every log it also wrote.
What the receipt binds
The schema is at https://thehiveryiq.com/.well-known/schemas/intent-affirmation-v1.json. The signed body has receipt_type, schema, the boundary, and one object, intent_affirmation.
| Field | Meaning |
|---|---|
affirmation_id | Identifier matching ia_ plus 16 to 64 lowercase hex characters. |
recorded_at | The UTC instant this record was written. |
subject_pseudonym | Keyed pseudonym for the affirming subject. |
presented_artifact.artifact_digest | Digest of the opaque record of what was shown. |
presented_artifact.presented_at | The instant it was shown. |
affirmed_at | The instant the affirmation was taken. |
transmitted_artifact.artifact_digest | Digest of the opaque record of what was sent. |
transmitted_artifact.transmitted_at | The instant it was sent. |
artifact_equality_class | Either digests_equal or digests_differ. |
ordering_class | Either affirmation_bracketed or affirmation_outside_bracket. |
time_reference.reference_id | The external time reference, as tr_ plus lowercase letters, digits or underscores. |
time_reference.reference_digest | Digest of the offset snapshot. |
time_reference.drift_bound_ms | The declared tolerance in milliseconds, 1 to 86400000. |
observed_drift_ms | The widest absolute offset the snapshot carried, 0 to 86400000. |
drift_class | Either within_declared_bound or exceeds_declared_bound. |
affirmation_channel_class | One of interactive_display, voice_playback, messaging_thread or embedded_webview. |
evidence_access_class | One of confidential_independent, issuer_only or public_commitment_only. |
salt_commitment | Commitment to the 32 byte affirmation salt. |
The two artifact digests sit in separate objects, each with its own instant. That separation is what makes the equality check a real comparison rather than a restatement of one value in two places.
What is deliberately not in the signed body
No artifact content, no subject identifier, no affirmation salt, no raw offsets. The receipt holds three digests, one pseudonym, three instants, four classes and two integers.
Three constructions in src/typed/intent-affirmation.js do the work.
The salt commitment is a plain SHA-256 over the UTF-8 label intent.affirmation/salt/v1, a zero byte, and the raw 32 salt bytes.
The subject pseudonym is HMAC-SHA256 keyed by the raw salt bytes, over a message built from the domain label intent.affirmation/subject/v1 followed by a zero byte and then the canonical JSON of an object holding just the subject identifier. Because the salt is per affirmation, the same person gives one stable pseudonym inside a receipt and an unrelated one under a different salt.
The artifact digest is a plain SHA-256 over the canonical JSON of an object holding the domain label intent.affirmation/artifact/v1 and the artifact record. The record is required to hold exactly artifact_id, content_sha256 and rendering_class, with content_sha256 required to be 64 lowercase hex characters and rendering_class drawn from full_text, summary_card, spoken_readback and structured_fields. That is the enforcement point for the promise that the summary never arrives. There is no field in the accepted record that could hold it.
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 time reference snapshot is required to hold exactly reference_id, a source_class drawn from roughtime, ntp_stratum_one, rfc3161_authority and gnss_receiver, and three integer millisecond offsets, one per instant, each no wider than one day. The snapshot itself is digested with the shared payload digest and only the digest is signed. The observed drift is the widest absolute value of the three offsets, so a receipt cannot pass by averaging a good clock against a bad one.
Equality, ordering and drift are all deterministic. Equality compares the two artifact digests. Ordering requires the presentation instant to be strictly earlier than the affirmation instant and the affirmation instant to be strictly earlier than the transmission instant. Drift compares the recomputed widest offset to the declared bound.
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 intent affirmation v1 schema, including the boundary constant, the four channel classes and the drift bounds. 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.
- RECORD_INSTANT_ORDERED. Requires all four instants to be valid UTC instants and requires the transmission instant to be at or before
recorded_at. A failure means the record claims to describe something that had not happened when it was written. - SALT_BINDING. Requires the disclosed salt to be 64 lowercase hex characters and to recompute to
salt_commitment. A failure means the pseudonym cannot be checked. - SUBJECT_BINDING. Requires a non empty subject identifier in the evidence and requires the recomputed pseudonym to match. A failure means the subject named is not the subject committed.
- PRESENTED_ARTIFACT_INTEGRITY. Requires the presented record to hold exactly the three allowed fields in the allowed forms and requires its recomputed digest to equal
presented_artifact.artifact_digest. A failure means the record supplied is not the record committed. - TRANSMITTED_ARTIFACT_INTEGRITY. The same check against the transmitted record and
transmitted_artifact.artifact_digest. - ARTIFACT_EQUALITY_RECOMPUTE. Recomputes the equality class from the two independently derived digests, compares it, and then fails outright when the class is
digests_differ. A failure means the artifact affirmed is not the artifact transmitted. - AFFIRMATION_ORDERING_RECOMPUTE. Recomputes the ordering class with strict inequality on both sides, compares it, and then fails outright when the class is
affirmation_outside_bracket. A failure names all three instants. - TIME_REFERENCE_INTEGRITY. Requires the snapshot to hold exactly the five allowed fields in the allowed forms, to agree with the receipt on reference identifier, and to digest to
time_reference.reference_digest. A failure means the offsets relied on are not the offsets committed. - OBSERVED_DRIFT_RECOMPUTE. Recomputes the widest absolute offset from the snapshot and compares it to
observed_drift_ms. A failure means the drift figure was supplied rather than derived. - DRIFT_BOUND_SATISFIED. Recomputes the drift class against
time_reference.drift_bound_ms, compares it, and then fails outright when the class isexceeds_declared_bound. A failure means the three instants cannot be placed against the reference at all. - CHANNEL_CLASS_DECLARED. Requires the channel class to be one of the four declared values. A failure means an undeclared channel reached the body.
- NO_AFFIRMATION_CONTENT_LEAK. Walks every string in the signed body against its approved opaque form: identifiers against their patterns, digests against 64 lowercase hex, classes against their enums. A failure means something readable reached a field that must carry only an identifier, a digest or a class.
- INTENTAFFIRMATION_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, including all three nested digests, and it refuses to sign an affirmation that is outside the bracket, over two different artifacts, or wider than the declared drift bound.
The boundary
This receipt attests only that the digest of the artifact presented for affirmation equals the digest of the artifact transmitted as the authenticated intent, and that the affirmation instant falls strictly between the presentation instant and the transmission instant, with all three instants placed against a declared external time reference and drift bound. It does not attest that a human was present, that anyone read, understood, or agreed to anything, that the presented summary is a faithful summary of what any person said, that the affirmation was freely given, or that the presented artifact was rendered legibly. The channel and the time reference are recorded as declared and are not verified here.
That string is a schema constant inside the signed bytes and it is checked again by its own gate.
The clause about faithfulness is the one that matters most and the one people find surprising. The receipt says nothing about whether the summary matches what the person actually said. That is a comparison between a rendered artifact and a human utterance, and no deterministic recomputation reaches it. What the receipt fixes is that the artifact a person was shown is the artifact somebody else received, and that the confirmation landed between the two.
Writing that down makes the instrument stronger where a dispute actually happens. Two arguments get separated. The first is whether the document travelled intact through the affirmation step, and this receipt closes that completely. The second is whether the document described the request correctly, and that argument is now isolated and can be had on its own terms. A broader claim would have merged them, and the weaker half would have dragged the stronger half down with it.
The clause about legibility is smaller and honest. A summary card with black text on a black background has a digest like any other. Somebody who wanted to treat a receipt as proof of informed consent would be over reading it, and the receipt says so.
The last sentence covers the channel and the time reference. The receipt fixes the offsets against their committed snapshot and checks them against a bound, and it does not verify that the named source is what it claims to be. If source independence matters to the parties, they know from day one that they need to establish it another way.
Adversarial cases
These are the real cases in test/intent-affirmation.test.mjs, which holds 39 tests, all passing.
An affirmation before the presentation fails AFFIRMATION_ORDERING_RECOMPUTE, and an affirmation after the transmission fails the same gate. Both shapes of backfill are blocked, and both are refused at mint as well. An affirmation at the presentation instant fails the same gate, because the requirement is strict inequality, so a confirmation timestamped to the same millisecond as the render buys nothing. A forged ordering class trips the recomputation before the outright refusal.
A transmitted artifact that is not the presented one fails ARTIFACT_EQUALITY_RECOMPUTE. This is the substitution attack and the reason the instrument exists. The two digests come from two separately supplied records, so the comparison is real. A forged equality class fails the same gate.
An edited presented artifact record fails PRESENTED_ARTIFACT_INTEGRITY, and an edited transmitted artifact record fails TRANSMITTED_ARTIFACT_INTEGRITY. Neither record can be changed after its digest is signed. Artifact text supplied in place of a digest also fails the first of those, because the record rejects anything in content_sha256 that is not 64 lowercase hex characters.
A forged observed drift fails OBSERVED_DRIFT_RECOMPUTE. Understating the clock spread would let a violation hide inside tolerance. The widest offset is recomputed from the committed snapshot.
Drift wider than the declared bound fails DRIFT_BOUND_SATISFIED, and a forged drift class fails the same gate. Mint refuses the first case too.
An edited time reference snapshot fails TIME_REFERENCE_INTEGRITY, and a snapshot for another reference fails the same gate. Reference shopping is blocked on both digest and identifier.
A substituted subject fails SUBJECT_BINDING, a substituted salt fails SALT_BINDING, and a record written before the transmission fails RECORD_INSTANT_ORDERED.
An undeclared channel class fails CHANNEL_CLASS_DECLARED. A raw subject identifier in a signed field fails NO_AFFIRMATION_CONTENT_LEAK, and a boundary mismatch fails INTENTAFFIRMATION_BOUNDARY_PRESENT. All three are named as defense in depth in the tests.
Four positive cases cover the channels: an interactive display, a spoken readback on the voice channel, a messaging thread, and an embedded webview. All four recompute the class affirmation_bracketed, which is the point. The instrument does not care which surface the confirmation came from, only where it sits in the sequence.
Structural tests confirm the design. The minted envelope contains neither the salt, nor the subject, nor the artifact content digest. Subject pseudonyms never repeat across salts. The classifiers are pure functions of their inputs. Mint refuses every computed field and both nested digest fields.
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 | 2.811 ms |
| Mint p95 | 5.254 ms |
| Verify p50 | 1.939 ms |
| Verify p95 | 2.248 ms |
| Artifact size | 2133 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. The verification path does no nested receipt verification, which is why the figures sit close to the cheapest instruments in the set. The cost is fixed with respect to the size of the artifacts, because the artifacts never arrive.
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/intent-affirmation - Demonstration route:
/demo/intent-affirmation, carrying the keysinteractive_display,voice_playback,messaging_threadandlate_affirmation
curl -sS -X POST https://thehiveryiq.com/v1/verify/intent-affirmation -H 'content-type: application/json' -d '{"receipt": <a receipt of this type>}'
The late_affirmation case fails at AFFIRMATION_ORDERING_RECOMPUTE. It cannot be minted, so it is built by minting the honest case, editing the body and re-signing with the same demonstration key.
Where it sits in the canon
Family: authority. Category: typed receipt contract.
It closes the join that the surrounding instruments leave open. The Mandate Conformance Receipt compares a transaction to the authority in force and states in its own boundary that it does not attest the displayed terms correspond to anything a person actually read. The Divergence Record compares two values by digest and takes no view on their order. This receipt fixes equality and order together, over artifacts it never sees.
It sits after the Intent Verifiability Receipt in a purchase, which classifies whether the conditions in an instruction could be checked at all, and before the conformance and attribution instruments that judge what was done under it.
It does not replace an authorisation control. It observes and reports.
It does not replace a consent record and it is not evidence of informed consent, and the boundary rules that out in the signed bytes. Patent pending.
Status
production_deployed.