fix(#10,#11): controlled audit failure + pre-mutation record snapshot#18
Merged
fix(#10,#11): controlled audit failure + pre-mutation record snapshot#18
Conversation
Issue #10: wrap audit.append in _finish() so every gate exit returns a controlled GateResult. Audit failure now returns ERROR:AUDIT_APPEND_FAILED:<ExceptionType> instead of raising. Issue #11: snapshot authorised record fields before calling mutation_callback. _finish() uses the frozen snapshot for record_scope so post-callback mutation cannot alter the audit receipt. Claim boundary: v1 primitive hardening only.
Issue #10: test_audit_failure_on_deny_path_can_escape_without_gate_result renamed to test_audit_failure_on_deny_path_returns_controlled_gate_result. Now asserts controlled GateResult is returned (not exception raised). Covers deny path, HOLD-equivalent path (no record), and allow path. Issue #11: test_mutable_record_can_be_changed_after_validation_before_audit_receipt renamed to test_pre_mutation_snapshot_protects_audit_receipt. Now asserts audit receipt reflects authorised record, not post-callback drift. Claim boundary: v1 primitive hardening only.
This was referenced May 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Closes #10 and #11. No other scope changes.
Issue #10 — Audit failure on deny path must not escape as unhandled exception
Gap:
audit.appendcould raise inside_finish(), causing an uncontrolled exception to escape instead of returning aGateResult.Fix:
_finish()now wrapsaudit.appendin atry/except. If append raises,_finish()returns a controlledGateResult:allowed = Falsecode = "ERROR:AUDIT_APPEND_FAILED:<ExceptionType>"decision_idandtimestamppreservedThe caller is never misled:
allowed=Falsemeans the receipt was not confirmed written.Tests added:
test_audit_failure_on_deny_path_returns_controlled_gate_result(deny / no record path)test_audit_failure_on_scope_deny_returns_controlled_gate_result(scope mismatch deny path)test_audit_failure_on_allow_path_returns_controlled_gate_result(allow path — receipt not written → allowed=False)Issue #11 — Freeze DecisionRecord snapshot before mutation callback
Gap:
mutation_callbackreceived the liverecordmapping and could alter fields that_finish()later logged inrecord_scope, meaning the audit receipt could reflect post-callback drift rather than the authorised record.Fix: After all validation passes (structure, signature, timestamps, scope, nonce) but before
mutation_callback(record)runs,_snapshot(record)takes an immutable copy of the authorised fields._finish()now acceptsrecord_snapshotinstead ofrecordand uses the snapshot forrecord_scope. The callback cannot alter what the audit receipt says was authorised.Tests added:
test_pre_mutation_snapshot_protects_audit_receipt(callback mutatesobject_id→ receipt still logs authorised value)test_pre_mutation_snapshot_is_independent_of_original_dict(callback mutates actor, action, policy → receipt logs pre-mutation values)What was NOT changed
Test command
Claim boundary
This PR closes two v1 primitive hardening gaps.
It does not claim production readiness, path-universal coverage, compliance, certification, or adoption.