AGENTICRAIL ENFORCEMENT SPECIFICATION v1.5 DATE: 2026-09-30 (amends v1.4, 2026-07-08; v1.4 amends v1.3, 2026-07-08; v1.3 amends v1.2, 2026-07-05; v1.2 amends v1.1, 2026-06-07; v1.1 amends v1.0, 2026-05-17) ENTITY: TUARA KURI LIMITED AMENDMENT: v1.5 adds step_order to the receipt fields, restates when the chain anchor advances, and names two denial codes the gate already returns. step_order is the step order the call was evaluated against, exactly as enforced: the caller's declared step_order, normalised (trimmed, lower-cased), or the default MSMD spine when the caller declared none. On every ALLOW it is the order the sequence is locked to, because a call presenting any other order is refused. It is signed with the rest of the receipt. Until v1.5 a receipt named the step that ran but not the order it was judged against, so the evidence could not distinguish a step that was never declared from a step that was declared and skipped. A sequence whose declared order leaves out a step, an approval for example, now shows that omission in every receipt it produces. step_order is a receipt field, not part of the pack, so pack_id derivation is unchanged. Receipts issued before this field existed do not carry it and remain valid. The chain anchor (the receipt that the next receipt's prev_receipt_id and prev_receipt_hash refer to) now advances when an ALLOW is decided, before the response is returned, and is withdrawn if the receipt's durable write then fails. Previously it advanced only after the durable write completed, which could land after the caller's next step; a caller that stepped quickly from far from the receipt store could then receive a receipt linked to an earlier predecessor than the one immediately before it. A DENY never becomes the anchor, and the anchor never remains on a receipt whose durable write failed. The denial-code list now names UNKNOWN_STEP (the step is not in the order the call was evaluated against) and STEP_ORDER_MISMATCH (the call presents an order other than the one the sequence is locked to). Both are returned by the live gate and were not named in v1.0-v1.4. No code is added to or removed from the implementation. Enforcement rules, decision set, pack_id derivation, and signature algorithm are UNCHANGED from v1.4. v1.0, v1.1, v1.2, v1.3, and v1.4 remain independently reproducible and are not edited. DECISIONS: ALLOW, DENY, HALT ENFORCEMENT RULES: 1. function missing or empty -> DENY:missing_function 2. action_type not permitted for function -> DENY:ACTION_NOT_ALLOWED 3. step !== function name -> DENY:FUNCTION_STEP_MISMATCH 4. Sequence already sealed -> DENY:SEALED_SEQUENCE 5. Nonce already used -> DENY:REPLAY_NONCE 6. Step out of order -> DENY:SEQUENCE_VIOLATION 7. Timestamp stale (|ts_ms - now| > 300s) -> DENY:STALE_TIMESTAMP 8. RECORD_RESULT at boundary with witnessed_pack_id not matching the real, durably-written, immediately-preceding receipt -> DENY:ARTIFACT_UNBOUND 9. All pass -> ALLOW MSMD SPINE: intake,disruption,instability,state_read,internal_driver,execution,boundary,settle RECEIPT FIELDS (top level, alphabetical, both ALLOW and DENY): attestation,decision,executed,key_id,meta,pack_id,payload_hash,prev_receipt_hash,prev_receipt_id,reasons,sealed,signature,signature_alg,step_order,ts_ms,version META SUB-OBJECT (nested under meta, alphabetical): action_type,function,model_id,policy_map_ids,sequence_id,step DENIAL CODES (enforcement-rule level, non-exhaustive — field-validation codes such as missing_schema_version/missing_nonce/etc. are documented at agenticrail.nz/spec/receipt-schema.json, not enumerated here): ACTION_NOT_ALLOWED,ARTIFACT_UNBOUND,FUNCTION_STEP_MISMATCH,REPLAY_NONCE,SEALED_SEQUENCE,SEQUENCE_VIOLATION,STALE_TIMESTAMP,STEP_ORDER_MISMATCH,UNKNOWN_STEP,missing_function PACK_ID: SHA-256 of canonical JSON (alphabetically sorted keys) PREV_RECEIPT_ID: pack_id of the previous receipt — an identifier reference (chain order), not a content hash of the predecessor. The anchor advances ONLY on decision===ALLOW, at decision time before the response is returned, and is withdrawn if that receipt's durable R2 write fails; a DENY or a failed write never remains "the last true thing that happened" (anchor timing restated in v1.5) PREV_RECEIPT_HASH: SHA-256 of the previous receipt's full canonical JSON, signature included — the actual hash chain, added 2026-07-08 (v1.3). An in-place edit to any earlier receipt changes its hash, breaking every subsequent prev_receipt_hash link even if prev_receipt_id references still match. Null for the chain's first receipt and for any link whose anchor predates this field — the report generator treats that as "not verifiable", not "broken". Surfaced live in the report's Chain Integrity table (Chain hash column) and JSON output (hash_chain: {verified, broken, not_verifiable}). STEP_ORDER: the step order the call was evaluated against, normalised as enforced (trimmed, lower-cased), or the default MSMD spine when none was declared. On every ALLOW, the order the sequence is locked to. Signed with the receipt; not part of the pack. Added in v1.5; absent from earlier receipts. PAYLOAD_HASH: SHA-256 of raw request body SIGNATURE: Ed25519 over canonical JSON (receipt minus signature field), base64-encoded (legacy receipts before 2026-06-07: HMAC-SHA256, hex-encoded) KEY_ID: k2_2026-06-07_ed25519 (active, Ed25519); k1_2026-02-22_01 (legacy, HMAC) PUBLIC KEYS: agenticrail.nz/spec/receipt-public-keys.json (Ed25519 - offline-verifiable by anyone) VERIFICATION: report.agenticrail.nz (no login, no operator required)