|
| 1 | +""" |
| 2 | +tests/test_enterprise_scenario_001.py |
| 3 | +
|
| 4 | +Enterprise Scenario Pack v0.1 — Scenario 001 |
| 5 | +AI-Generated External Email — Missing Authority Token |
| 6 | +
|
| 7 | +SCOPE: NON_EXEC / REVIEW_ONLY |
| 8 | +CLAIM: Synthetic refusal test. Not enterprise-certified. Not deployed. Not compliance proof. |
| 9 | +
|
| 10 | +This test suite demonstrates a repeatable synthetic refusal for one bounded |
| 11 | +enterprise-shaped scenario. It proves refusal, receipt generation, and replay |
| 12 | +stability on the demonstrated path only. It does not prove production |
| 13 | +enforcement, external system control, compliance, or path-universal coverage. |
| 14 | +""" |
| 15 | + |
| 16 | +import copy |
| 17 | +import hashlib |
| 18 | +import json |
| 19 | +from datetime import datetime, timezone |
| 20 | + |
| 21 | + |
| 22 | +# --------------------------------------------------------------------------- |
| 23 | +# CommitGate — minimal synthetic implementation for test surface |
| 24 | +# --------------------------------------------------------------------------- |
| 25 | + |
| 26 | +REQUIRED_FIELDS = [ |
| 27 | + "actor", |
| 28 | + "action_type", |
| 29 | + "recipient_scope", |
| 30 | + "payload_hash", |
| 31 | + "authority_token", |
| 32 | + "expiry", |
| 33 | + "nonce", |
| 34 | +] |
| 35 | + |
| 36 | +_sent_messages = [] |
| 37 | + |
| 38 | + |
| 39 | +def _compute_state_hash(state: dict) -> str: |
| 40 | + serialised = json.dumps(state, sort_keys=True).encode() |
| 41 | + return hashlib.sha256(serialised).hexdigest() |
| 42 | + |
| 43 | + |
| 44 | +def _write_receipt(attempt: dict, missing_field: str, decision: str, reason: str) -> dict: |
| 45 | + return { |
| 46 | + "receipt_id": f"RCP-{datetime.now(timezone.utc).strftime('%Y%m%d')}-001", |
| 47 | + "scenario_id": "ESP-001", |
| 48 | + "attempted_action": attempt.get("action_type", "UNKNOWN"), |
| 49 | + "actor": attempt.get("actor", "UNKNOWN"), |
| 50 | + "action_type": attempt.get("action_type", "UNKNOWN"), |
| 51 | + "recipient_scope": attempt.get("recipient_scope", "UNKNOWN"), |
| 52 | + "payload_hash": attempt.get("payload_hash", "UNKNOWN"), |
| 53 | + "missing_field": missing_field, |
| 54 | + "decision": decision, |
| 55 | + "refusal_reason": reason, |
| 56 | + "timestamp": datetime.now(timezone.utc).isoformat(), |
| 57 | + "downstream_send": False, |
| 58 | + } |
| 59 | + |
| 60 | + |
| 61 | +def commit_gate(attempt: dict, state_before: dict) -> dict: |
| 62 | + """ |
| 63 | + CommitGate — STRUCTURE_FIRST, FIRST_FAIL evaluation. |
| 64 | + Returns a result dict with decision, receipt, and state metadata. |
| 65 | + """ |
| 66 | + state_snapshot_before = copy.deepcopy(state_before) |
| 67 | + hash_before = _compute_state_hash(state_snapshot_before) |
| 68 | + |
| 69 | + for field in REQUIRED_FIELDS: |
| 70 | + if field not in attempt or attempt[field] is None or attempt[field] == "": |
| 71 | + receipt = _write_receipt( |
| 72 | + attempt, |
| 73 | + missing_field=field, |
| 74 | + decision="DENY", |
| 75 | + reason=( |
| 76 | + f"{field} absent — no valid DecisionRecord for this actor, " |
| 77 | + "action_type, recipient_scope, and payload at gate time" |
| 78 | + ), |
| 79 | + ) |
| 80 | + hash_after = _compute_state_hash(state_snapshot_before) |
| 81 | + return { |
| 82 | + "decision": "DENY", |
| 83 | + "missing_field": field, |
| 84 | + "downstream_send": False, |
| 85 | + "receipt_written": True, |
| 86 | + "receipt": receipt, |
| 87 | + "sent_messages": list(_sent_messages), |
| 88 | + "before_state_hash": hash_before, |
| 89 | + "after_state_hash": hash_after, |
| 90 | + "state_mutated": hash_before != hash_after, |
| 91 | + } |
| 92 | + |
| 93 | + # All fields present — ALLOW path. Not triggered in this scenario. |
| 94 | + _sent_messages.append(attempt) |
| 95 | + receipt = _write_receipt(attempt, missing_field="", decision="ALLOW", reason="all fields valid") |
| 96 | + hash_after = _compute_state_hash(state_snapshot_before) |
| 97 | + return { |
| 98 | + "decision": "ALLOW", |
| 99 | + "missing_field": None, |
| 100 | + "downstream_send": True, |
| 101 | + "receipt_written": True, |
| 102 | + "receipt": receipt, |
| 103 | + "sent_messages": list(_sent_messages), |
| 104 | + "before_state_hash": hash_before, |
| 105 | + "after_state_hash": hash_after, |
| 106 | + "state_mutated": False, |
| 107 | + } |
| 108 | + |
| 109 | + |
| 110 | +# --------------------------------------------------------------------------- |
| 111 | +# Fixtures |
| 112 | +# --------------------------------------------------------------------------- |
| 113 | + |
| 114 | +INVALID_ATTEMPT_MISSING_AUTHORITY = { |
| 115 | + "actor": "agent://morpheus-draft-bot-v1", |
| 116 | + "action_type": "SEND_EXTERNAL_EMAIL", |
| 117 | + "recipient_scope": "external:partner-domain.com", |
| 118 | + "payload_hash": "sha256:a3f8c2d1e7b904561ac38fd2e190bba47c2d3f8a1e056b7c9d4e2f1a8b3c7d90", |
| 119 | + "authority_token": None, # MISSING — triggers refusal |
| 120 | + "expiry": "2026-05-12T23:59:59Z", |
| 121 | + "nonce": "nonce-abc-001", |
| 122 | +} |
| 123 | + |
| 124 | +INITIAL_STATE = { |
| 125 | + "sent_count": 0, |
| 126 | + "last_action": None, |
| 127 | + "pending_receipts": [], |
| 128 | +} |
| 129 | + |
| 130 | + |
| 131 | +# --------------------------------------------------------------------------- |
| 132 | +# Tests |
| 133 | +# --------------------------------------------------------------------------- |
| 134 | + |
| 135 | +def test_missing_authority_token_denies_send(): |
| 136 | + """ |
| 137 | + ASSERT: A missing authority_token produces decision == DENY |
| 138 | + and missing_field == authority_token. |
| 139 | + """ |
| 140 | + result = commit_gate(INVALID_ATTEMPT_MISSING_AUTHORITY, copy.deepcopy(INITIAL_STATE)) |
| 141 | + assert result["decision"] == "DENY", ( |
| 142 | + f"Expected DENY, got {result['decision']}" |
| 143 | + ) |
| 144 | + assert result["missing_field"] == "authority_token", ( |
| 145 | + f"Expected missing_field='authority_token', got '{result['missing_field']}'" |
| 146 | + ) |
| 147 | + |
| 148 | + |
| 149 | +def test_no_downstream_send_occurs(): |
| 150 | + """ |
| 151 | + ASSERT: downstream_send is False and sent_messages list is empty. |
| 152 | + """ |
| 153 | + result = commit_gate(INVALID_ATTEMPT_MISSING_AUTHORITY, copy.deepcopy(INITIAL_STATE)) |
| 154 | + assert result["downstream_send"] is False, ( |
| 155 | + "downstream_send should be False — no email should leave the system" |
| 156 | + ) |
| 157 | + assert result["sent_messages"] == [], ( |
| 158 | + f"sent_messages should be empty, got: {result['sent_messages']}" |
| 159 | + ) |
| 160 | + |
| 161 | + |
| 162 | +def test_receipt_is_written(): |
| 163 | + """ |
| 164 | + ASSERT: receipt_written is True and receipt contains expected fields. |
| 165 | + """ |
| 166 | + result = commit_gate(INVALID_ATTEMPT_MISSING_AUTHORITY, copy.deepcopy(INITIAL_STATE)) |
| 167 | + assert result["receipt_written"] is True, "receipt_written should be True" |
| 168 | + receipt = result["receipt"] |
| 169 | + required_receipt_fields = [ |
| 170 | + "receipt_id", "scenario_id", "attempted_action", "actor", |
| 171 | + "action_type", "recipient_scope", "payload_hash", "missing_field", |
| 172 | + "decision", "refusal_reason", "timestamp", "downstream_send", |
| 173 | + ] |
| 174 | + for field in required_receipt_fields: |
| 175 | + assert field in receipt, f"Receipt missing field: {field}" |
| 176 | + assert receipt["decision"] == "DENY" |
| 177 | + assert receipt["downstream_send"] is False |
| 178 | + assert receipt["missing_field"] == "authority_token" |
| 179 | + |
| 180 | + |
| 181 | +def test_state_hash_does_not_change(): |
| 182 | + """ |
| 183 | + ASSERT: before_state_hash == after_state_hash and state_mutated is False. |
| 184 | + A refusal must not alter system state. |
| 185 | + """ |
| 186 | + result = commit_gate(INVALID_ATTEMPT_MISSING_AUTHORITY, copy.deepcopy(INITIAL_STATE)) |
| 187 | + assert result["before_state_hash"] == result["after_state_hash"], ( |
| 188 | + "State hash changed on refusal — state mutation detected" |
| 189 | + ) |
| 190 | + assert result["state_mutated"] is False, ( |
| 191 | + "state_mutated should be False after a DENY" |
| 192 | + ) |
| 193 | + |
| 194 | + |
| 195 | +def test_replay_is_stable(): |
| 196 | + """ |
| 197 | + ASSERT: Running the same invalid attempt twice produces the same |
| 198 | + decision and the same missing_field — refusal is deterministic. |
| 199 | + """ |
| 200 | + first = commit_gate(INVALID_ATTEMPT_MISSING_AUTHORITY, copy.deepcopy(INITIAL_STATE)) |
| 201 | + replay = commit_gate(INVALID_ATTEMPT_MISSING_AUTHORITY, copy.deepcopy(INITIAL_STATE)) |
| 202 | + assert first["decision"] == replay["decision"], ( |
| 203 | + f"Replay decision mismatch: {first['decision']} vs {replay['decision']}" |
| 204 | + ) |
| 205 | + assert first["missing_field"] == replay["missing_field"], ( |
| 206 | + f"Replay missing_field mismatch: {first['missing_field']} vs {replay['missing_field']}" |
| 207 | + ) |
0 commit comments