import type { ReceiptVerificationStatusV1, ReceiptVerificationV1 } from './receipt.js'; import type { ReceiptStageOptionsV1, ReceiptStageResultV1 } from './stage.js'; import type { CoreDecisionOutputV1, JsonValue, ReceiptV1 } from './types.js'; /** The decision material a verifier must hold to bind a decision to a receipt. * * Why the decision OUTPUT alone is not enough, since that is the obvious * expectation: receipt.decision_ref is a digest over the whole * DecisionRefInputV1, which is the action reference plus the four component * digests. The output contributes only one of those components. Supplying just * the output would make the binding check unimplementable, and an unbindable * operand is exactly the substitution hole this verifier exists to close. * * action_ref is deliberately absent: it is taken from the receipt, so a * decision built for a different action cannot bind, and the caller cannot * quietly supply an action_ref that disagrees with the receipt it is checking. */ export interface DecisionEvidenceV1 { authority_state: JsonValue; policy_input: JsonValue; decision_context: JsonValue; decision_output: CoreDecisionOutputV1; } /** The stage options this composite accepts, plus the predecessor record. * * `predecessor` is added here rather than on ReceiptStageOptionsV1 on purpose: the stage * layer judges one record against its own section 5.3 rules and holds no second artifact, * so a predecessor there would be an input nothing reads. Every existing field is * unchanged and is still forwarded to verifyReceiptV1 as before. */ export interface ReceiptWithDecisionOptionsV1 extends ReceiptStageOptionsV1 { /** The policy-decision record this receipt's prev names, when the caller holds it. * * OPTIONAL, and omitting it is the default. THE OPT-IN IS THE PRESENCE OF THE PROPERTY, * not its value: the axis is checked whenever `predecessor` is an own property of the * options object, even where that property holds undefined or null. Absent, the axis is * reported `not_checked` and the composite's valid, status and errors are exactly what * they were before this option existed. * * Present and holding undefined or null, the caller asked for the binding and could not * supply the record, so the axis is NOT ESTABLISHED: `not_established`, `indeterminate`, * never valid. `{ predecessor: store.get(receipt.prev) }` with a lookup miss is exactly * that case, and reading it as an omitted option would hand back a valid composite for a * binding the caller requested and nobody performed. * * See verifyReceiptPredecessorV1 for what supplying a record does and does not establish, * including that its signatures are NOT verified here. */ predecessor?: ReceiptV1 | null; } /** Per-stage outcome of the composite verification. * * Stages run in order and short-circuit: a stage that does not run leaves its * flag false and contributes no error code, so the errors array names the first * thing that actually failed rather than a cascade. */ export interface ReceiptWithDecisionVerificationV1 { valid: boolean; /** Section 5.6 lines 1225-1228. Invalid dominates: a structural failure anywhere makes * the composite invalid. Otherwise an unsupported or indeterminate sub-result carries * through, so a caller cannot read a valid composite out of an axis that was never * established. */ status: ReceiptVerificationStatusV1; receipt: ReceiptVerificationV1; /** The section 5.3 stage result for this record, which decides which cross-document * checks below apply at all. */ stage: ReceiptStageResultV1; decision_ref_present: boolean; decision_ref_bound: boolean; /** For a policy-decision record, whether receipt.result is byte-identical under JCS to * the decision_output supplied. Section 5.4 lines 1183-1184 define decision_output as * the exact CoreDecisionOutputV1 carried in the policy-decision receipt, so a decision * whose output differs from the receipt's own result is not the decision this receipt * carries, however well its digest binds. `not_applicable` for the other stages. */ decision_output_bound: boolean | 'not_applicable'; /** `not_applicable` on the stages where the comparison is deliberately not made: the * window belongs to the decision, and the draft states no relation between it and the * issuance time of a later record. Reporting true there said a check had passed that * never ran, which is the same overstatement in the other direction. */ temporal_relation_valid: boolean | 'not_applicable'; /** The section 5.3.3 prev binding for an action-result record, run only when the caller * opts in by giving `options` a `predecessor` property. OPT-IN HARDENING, not a draft-03 * conformance check: draft-03 states the prev linkage without a BCP 14 keyword, so it * does not require a verifier to make this comparison. * * `not_checked` where the option carries no such property, which is the default and * leaves every other field exactly as it was before this axis existed. `not_applicable` * where the property is present on a record that is not an action-result. `false` makes * the composite invalid under the error code `predecessor_not_bound`. * * `not_established` where the property is present but holds undefined or null: the caller * asked for the binding and supplied no record, so nothing was compared. That is the * primitive's `indeterminate`, carried through under section 5.6 line 1228, and it makes * the composite `indeterminate` and not valid. The three non-boolean states are kept * apart because they are three different things: nothing was asked, nothing applies, and * something was asked that could not be established. Reporting false for any of them * would say a link had been refused when nothing was compared. */ predecessor_bound: boolean | 'not_checked' | 'not_applicable' | 'not_established'; errors: string[]; } /** * Verify a receipt together with the decision it references. * * Receipt verification establishes the integrity and semantics represented by the * receipt. It does not authorize dispatch, consume receipt_id, enforce single-use, * recheck revocation or time at dispatch, or reserve spend. Those obligations belong to * the enforcement boundary. * * Six stages, each with its own error code: * * 1. `receipt_invalid` structural and cryptographic verification, delegated * unchanged to verifyReceiptV1. The full sub-result is returned under * `receipt` so the caller keeps the per-signature detail. A sub-result that is * unsupported or indeterminate rather than invalid carries its own status through, * so an unresolvable signing key does not come back as a composite failure. * 2. `stage_invalid` the section 5.3 rules for this record's own receipt_type, * delegated to validateReceiptStageV1. Which of the checks below apply is decided * here and never by the caller. * 3. `decision_ref_absent` decision_ref is conditional on ReceiptV1, so a receipt * that carries none cannot be checked against a decision. Passing a decision * for such a receipt is an error, never a pass: silently succeeding would * report a relation that was never examined. * 4. `decision_ref_mismatch` the reference binding. The decision digest is * recomputed through buildDecisionRefV1, the same builder that produced it, * including the normalize-before-hash step, and must equal * receipt.decision_ref exactly. Without this a receipt for decision A could * be checked against an unrelated decision B chosen for its convenient * valid_until. * 5. `decision_output_mismatch` for a policy-decision record, receipt.result must be * the exact CoreDecisionOutputV1 the decision carries (section 5.4 lines 1183-1184), * compared as canonical bytes. The digest binding alone did not establish this: the * decision_ref commits to a digest of the output, and nothing compared that output * with the result the receipt itself carries and signs. * 6. `predecessor_not_bound` OPTIONAL and off by default. Only when the caller opts in, * the section 5.3.3 prev binding for an action-result record, delegated unchanged to * verifyReceiptPredecessorV1. This is OPT-IN HARDENING: the draft STATES that prev is * the consumed policy-decision receipt_id (lines 1104-1105) and lists prev validation * among a verifier's checks (line 1219) without a BCP 14 keyword on either, so it is * not required of a verifier and is not enabled unless asked for. * * THE OPT-IN IS THE PRESENCE OF THE `predecessor` PROPERTY on the options object, not * the value it holds. With no such property, `predecessor_bound` is `not_checked` and * this function's valid, status and errors are byte-identical to what they were before * the option existed. With the property present and holding undefined or null, the * caller asked for a binding it could not supply the record for: the axis is * `not_established` and the composite is `indeterminate`, carrying the primitive's own * `predecessor_not_supplied` code. The distinction is the whole point of reading * presence rather than value. `{ predecessor: store.get(receipt.prev) }` puts a lookup * miss in that property, and treating it as an omitted option would report valid for a * check the caller requested and nobody ran. * * The predecessor's own signatures are NOT verified here; a caller that wants them * checked runs this verifier over the predecessor as well. * 7. `valid_until_not_after_issued_at` the temporal relation, checked only * once the operands are known to belong together. Both timestamps are * validated as exact UTC milliseconds and then compared as instants, never * as strings. A deny decision carries a null valid_until by rule (line 1090), so * there is no window to compare and its absence is not a failure. Reporting one * made every correct deny decision fail this verifier. * * The binding checks run BEFORE the temporal one on purpose. A temporal result * computed over an unbound pair is not evidence about this receipt at all. The predecessor * check is a binding check, so it sits with the others and ahead of the temporal one for * the same reason. */ export declare function verifyReceiptWithDecisionV1(receipt: ReceiptV1, decision: DecisionEvidenceV1, resolveKey: (signer: string, keyId: string, issuedAt: string) => string | undefined, options?: ReceiptWithDecisionOptionsV1): ReceiptWithDecisionVerificationV1; //# sourceMappingURL=composite.d.ts.map