AGENTICRAIL ENFORCEMENT SPECIFICATION v1.6 DATE: 2026-10-08 (amends v1.5, 2026-09-30; v1.5 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.6 adds decision_index to the receipt fields, so that a removed receipt of either decision is detectable, and states which receipts the chain covers. The chain (prev_receipt_id, prev_receipt_hash) runs through ALLOWED receipts only: the anchor advances on ALLOW, so a DENY points back to the last ALLOW but no receipt points to a DENY. Removing a DENY receipt therefore broke no link in the chain. decision_index is this decision's position among every decision recorded for the sequence, counted from 1 across ALLOW and DENY alike, and signed with the rest of the receipt. A removed receipt of either decision leaves a missing number, and the numbering cannot be closed up without re-signing every later receipt. The report states whether the numbers run unbroken; a missing number means the receipt is absent, never written or removed, and the report does not say which. Receipts missing after the highest number present are not detectable this way. Each numbered DENY is stored under its own name. pack_id carries no nonce and no time, so two refusals of the same step have the same pack_id; they are now stored as two receipts, where previously the second replaced the first. pack_id derivation is unchanged. decision_index is a receipt field, not part of the pack. Receipts issued before this field existed do not carry it and remain valid; a sequence open across the change is numbered from 1 at its first receipt after it. Enforcement rules, decision set, pack_id derivation, and signature algorithm are UNCHANGED from v1.5. v1.0, v1.1, v1.2, v1.3, v1.4 and v1.5 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,decision_index,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. DECISION_INDEX: this decision's position among every decision recorded for the sequence, from 1, ALLOW and DENY alike. Signed with the receipt; not part of the pack. A missing number is an absent receipt. The chain above covers ALLOWED receipts only; the numbering covers both. Null if the number could not be reserved. Added in v1.6; 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)