Skip to content

test(parity): prove the gitignored-delete gate (AN-1) on Codex (#408) - #664

Closed
harshitagrawal2O wants to merge 1 commit into
DobermanCore:mainfrom
harshitagrawal2O:test/parity/gitignored-delete-codex
Closed

test(parity): prove the gitignored-delete gate (AN-1) on Codex (#408)#664
harshitagrawal2O wants to merge 1 commit into
DobermanCore:mainfrom
harshitagrawal2O:test/parity/gitignored-delete-codex

Conversation

@harshitagrawal2O

Copy link
Copy Markdown
Contributor

Pull Request

Slice

What this PR does

Flips "Deleting unrecoverable gitignored data is gated (AN-1)" from ◻ to ✅ on Codex in the parity matrix.

The guarantee was proven on Claude Code and the MCP proxy but not on Codex, so on that host it rested on "same spine, should hold". Codex reaches the rule through its own adapter (to_normalize_input, gate-by-default rather than allowlist-abstain) and its own captured payload shape, which is exactly the distance a parity cell exists to measure — the shared spine is the reason it probably holds, not evidence that it does.

Adds the Codex twin of test_hosthook_claude_pre.py's test_unrecoverable_gitignored_delete_requires_auth, driven by the captured pre_bash.json fixture. rm data/app.db — a local database file, gitignored by convention and unrecoverable by git, which is precisely what AN-1's lexical operand gate (_UNRECOVERABLE_DELETE_GLOBS) exists to catch — must:

  • not abstain (the failure mode that would let it sail through as an ordinary delete),
  • resolve to deny, because no approval channel is reachable from a test process — the fail-closed contract,
  • be an [AUTH] step-up, not an objective block, so the cell proves the gate rather than some unrelated hard block catching the same string,
  • not echo the operand in the host-visible reason.

Mutation-checked

The bar the issue sets for a ✅. With _rm_targets_unrecoverable_data stubbed to return False, the new test goes red:

>       assert out is not None, "an unrecoverable-data delete must not abstain"
E       AssertionError: an unrecoverable-data delete must not abstain
E       assert None is not None

So it detects the guarantee being removed, rather than passing because something else in the chain happens to deny. Reverted before commit (git checkout --, working tree confirmed clean apart from the test file).

Tests added (run in CI)

  • tests/unit/test_hosthook_codex.pytest_unrecoverable_gitignored_delete_requires_auth, marked @pytest.mark.guarantee("gitignored-delete-gate", host="codex").
  • docs/PARITY.md regenerated with python -m tools.parity.generate_parity; --check passes.

Changelog

  • changelog.d/<PR>.<type>.md fragment added, or this change is invisible to users — test-only, no user-visible behaviour change. Per changelog.d/README.md ("Skip the fragment entirely for changes no user can see"), no fragment. The matrix itself is the user-visible artifact and it is regenerated in this PR.

Public-release safety (doberman-core only)

  • Contains nothing from the "not allowed" list: no enterprise/hosted code, no proprietary detection, no customer data, no secrets, no commercial-license code
  • Core still builds/tests/runs with NO enterprise package installed

Security checklist

  • Fails closed on error / uncertainty — the test asserts the fail-closed outcome (an unreachable approval channel denies) rather than relying on it incidentally
  • No secret, full file, or unredacted prompt logged or committed — the test asserts the operand never reaches the host-visible reason
  • Any guardrail/learning change is raise-only (no silent loosening) — no rule changed; this PR is test-only
  • Every BLOCK/AUTH carries reason codes + a human explanation — asserted ([AUTH] in the reason)
  • doberman-core does not import doberman_enterprise

Edge cases covered / Deviations from plan / Risks introduced

  • Asserts the tier, not just the denial. A test that only checked permissionDecision == "deny" would still pass if some unrelated hard block caught rm data/app.db, which would mark the cell ✅ on the wrong evidence. Asserting [AUTH] pins it to the step-up path AN-1 actually produces.
  • Redaction is asserted on both the full operand and its basename. Checking only data/app.db would miss a reason that leaked app.db alone.
  • Runtime. The AUTH walks the real prompter chain before every channel reports unavailable, so this test costs a few seconds — the same shape and cost as the existing Claude Code twin and the other AUTH-path Codex tests in this file.
  • No production code touched, so there is no behaviour risk. The only risk is the ordinary one for a parity cell: it proves the guarantee for this payload shape, not for every Codex tool spelling. apply_patch deletes are a separate surface and are not claimed here.

Related: #198 extends this same area with a directory-level gate (PR #653). This PR proves the existing lexical file gate and is independent of it.

🤖 Generated with Claude Code

…manCore#408)

The parity matrix showed AN-1 proven on Claude Code and the MCP proxy but
unproven on Codex, so the guarantee rested on "same spine, should hold" rather
than on evidence. Codex reaches the rule through its own adapter and its own
captured payload shape, which is exactly the gap a parity cell exists to close.

Adds the Codex twin of test_hosthook_claude_pre.py's
test_unrecoverable_gitignored_delete_requires_auth, driven by the captured
pre_bash.json fixture: `rm data/app.db` must not abstain, must resolve to deny
(no approval channel is reachable from a test process, the fail-closed
contract), must be an [AUTH] step-up rather than an objective block, and must
not echo the operand in the host-visible reason.

Mutation-checked: with `_rm_targets_unrecoverable_data` stubbed to return False
the test goes red (the delete abstains entirely), so it detects the guarantee
being removed rather than passing incidentally. Reverted before commit.

Matrix regenerated with `python -m tools.parity.generate_parity`; the cell
flips to a linked ✅.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fu351

fu351 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Merged via a landing branch, thanks @harshitagrawal2O! Asserting the AUTH tier in the reason rather than only the deny is what stops an unrelated hard block from proving this cell by accident, and redacting the basename separately from the full operand is one check more than the Claude Code sibling does. Two mechanical things on the landing branch: docs/PARITY.md regenerated on top of current main (the drift row landed after you branched) and a changelog.d/664.docs.md fragment, nothing else touched. #406 (level-3) is the same pattern on the proxy if you want another parity cell, no rush with #653 and #663 open.

@fu351 fu351 closed this Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants