Skip to content

fix(sdlc): policy_decide scope-normalization + quote-strip parity unblocks 3b-cutover#3828

Merged
ryanklee merged 2 commits into
mainfrom
zeta/reform-improve-policy-decide-scope-fix-20260601
Jun 1, 2026
Merged

fix(sdlc): policy_decide scope-normalization + quote-strip parity unblocks 3b-cutover#3828
ryanklee merged 2 commits into
mainfrom
zeta/reform-improve-policy-decide-scope-fix-20260601

Conversation

@ryanklee
Copy link
Copy Markdown
Collaborator

@ryanklee ryanklee commented Jun 1, 2026

What & why

The reform 3b-cutover gate (policy-decide-shadow-eval, asymmetric_ok = tightening==0) was structurally unreachable. Replaying the real cc-task-gate decision log through policy_decide produced 178 TIGHTENINGS (~177 reason "path is outside the task's mutation_scope_refs"), so the predicate could never go clean — policy_decide could never be cut over.

This fixes both root causes so policy_decide is a strict relaxation of the legacy gate (zero tightening), as the reform design requires (master design §4.1, line 226).

Root causes (both fixed)

1. Scope normalization — 178 → 1. _scope_result string-compared an absolute worktree file_path (e.g. ~/projects/hapax-council--zeta/tests/x.py) against repo-relative scope refs (e.g. tests/), so startswith never matched → false scope:denied. The shadow replay diffs decisions logged from many worktrees in one timer process with no recorded cwd, so it cannot mirror the live gate's Path.cwd()-anchored resolve (the replay's cwd is not the decision's worktree). _repo_relative reduces both sides to cwd-independent repo-relative form (cut at the workspace /projects/ anchor) → the same verdict the live gate returned in that worktree.

2. Quote-strip parity — 1 → 0. The lone residual tightening was a python3 -c "...open('/tmp/x','w')..." verification heredoc. The legacy gate strips quoted spans before its shell-source-scope test (cc-task-gate.sh:791), so the open( inside the quoted -c payload doesn't count and it allows. policy_decide substring-matched open( on the raw command → blocked at scope:command. Fix mirrors the legacy strip at the scope:command site. Fail-closed preserved: a marker outside any quoted span (e.g. a heredoc body) still blocks.

Triage of the residual divergences (the Phase-3 deliverable)

disposition count classification
scope-mismatch tightenings 178 → 0 root cause #1 (absolute↔relative) — fixed
scope:command tightening 1 → 0 regression (missing quote-strip mirror), not justified hardening — fixed
loosenings 69 intended FM-16 false-positive removals (read-only bash the legacy substring gate over-blocked) — allowed by the asymmetry predicate

No justified-hardening allowlist is needed: the reform design mandates a strict relaxation, so every tightening is by definition a regression to fix. After the fix, evaluate_shadow_clean reports asymmetric_ok=True, tightening=0; the only remaining unmet condition is the 7-day coverage window, which accrues naturally over the shadow week.

Verification

  • Replay over the real 592-row decision log: {divergences: 69, loosening: 69, tightening: 0}.
  • New tests: absolute↔repo-relative parity (8) + worktree-prefix invariants (hypothesis property, 2) + scope:command quote-strip parity incl. the fail-closed heredoc-body case (5).
  • 377 tests green (policy_decide + shadow producer/scripts + cc_task_gate + floor + sdlc_invariants); ruff + pyright clean.

Task: reform-improve-policy-decide-scope-fix-20260601 · AuthorityCase: CASE-FORMAL-GOVERNANCE-001

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved mutation scope path resolution consistency across different development contexts.
    • Fixed bash command parsing to correctly handle scope markers in quoted strings or comments.
  • Tests

    • Added comprehensive test coverage for scope validation across various path formats and edge cases.

ryanklee and others added 2 commits June 1, 2026 08:56
…locks 3b-cutover

The 3b-cutover gate (policy-decide-shadow-eval, asymmetric_ok = tightening==0) was
structurally unreachable: replaying the real gate decision log produced 178
TIGHTENINGS, ~177 reason "path is outside the task's mutation_scope_refs". Two root
causes, both fixed so policy_decide is a strict relaxation of the legacy gate (zero
tightening), as the reform design requires.

1. Scope normalization (178 -> 1). `_scope_result` string-compared an ABSOLUTE
   worktree file_path against REPO-RELATIVE scope refs, so startswith never matched.
   The shadow replay diffs decisions logged from MANY worktrees in ONE process with
   no recorded cwd, so it cannot mirror the live gate's Path.cwd()-anchored resolve
   (replay's cwd is not the decision's worktree). `_repo_relative` reduces BOTH sides
   to cwd-independent repo-relative form (cut at the workspace /projects/ anchor),
   yielding the same verdict the live gate returned in that worktree.

2. Quote-strip parity (1 -> 0). The lone residual tightening was a
   `python3 -c "...open(...)..."` verification heredoc. The legacy gate strips quoted
   spans before its shell-source-scope test (cc-task-gate.sh:791), so the open( inside
   the quoted -c payload does not count; policy_decide substring-matched open( on the
   RAW command and blocked at scope:command. Mirror the legacy strip at the
   scope:command site. Fail-closed is preserved: a marker OUTSIDE any quoted span
   (e.g. a heredoc body) still blocks.

Triage of the 69 residual divergences: all 69 are LOOSENINGS (read-only bash the
legacy substring gate over-blocked — the FM-16 false positives), allowed by the
asymmetry predicate. Zero tightenings remain. evaluate_shadow_clean now reports
asymmetric_ok=True; only the 7-day coverage window is unmet, which accrues over the
shadow week.

Tests: absolute<->repo-relative parity + worktree-prefix property (hypothesis) +
scope:command quote-strip parity (+ fail-closed heredoc-body case). 377 tests green
(policy_decide + shadow producer/scripts + cc_task_gate + floor + sdlc_invariants);
ruff + pyright clean.

Task: reform-improve-policy-decide-scope-fix-20260601
AuthorityCase: CASE-FORMAL-GOVERNANCE-001

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…into zeta/reform-improve-policy-decide-scope-fix-20260601
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8718a638-77b6-465c-ac62-5bbc9855428f

📥 Commits

Reviewing files that changed from the base of the PR and between c1fd66b and bdc2f3a.

📒 Files selected for processing (2)
  • shared/policy_decide.py
  • tests/test_policy_decide.py

📝 Walkthrough

Walkthrough

This PR refactors mutation-scope matching to use cwd-independent repo-relative path normalization and tightens bash source-scope command evaluation via quote-strip parity, with comprehensive regression and property-based test coverage for worktree path equivalence and command parsing behavior.

Changes

Scope Resolution and Command Parsing Parity

Layer / File(s) Summary
Repo-relative path normalization for scope matching
shared/policy_decide.py
Introduces _WORKTREE_ANCHOR and _repo_relative() to reduce both target file paths and scope reference entries to a common /projects/-anchored form before comparison, replacing prior _norm() approach.
Bash source-scope quote-strip parity
shared/policy_decide.py
Updates bash "scope:command" blocking condition to evaluate _bash_is_source_scope() against _strip_quotes_and_comments(command) instead of raw command, aligning with legacy gate semantics.
Test infrastructure and hypothesis setup
tests/test_policy_decide.py
Adds Hypothesis given and strategies as st imports to enable property-based and regression testing.
Absolute worktree path scope regression tests
tests/test_policy_decide.py
TestScopeAbsoluteWorktreePaths validates multi-worktree acceptance, out-of-scope denial, prefix-confusion prevention, and directory-ref normalization equivalence via _WT worktree anchor.
Absolute vs. repo-relative path parity contract
tests/test_policy_decide.py
TestScopeAbsoluteRelativeParity ensures mutation-scope decisions for absolute worktree paths yield identical verdicts to repo-relative path inputs under the same scope configuration.
Property-based scope invariants
tests/test_policy_decide.py
TestScopePropertyInvariant uses Hypothesis to confirm randomized paths within authorized scope always allow, while randomized paths outside consistently deny with scope:denied.
Command parsing quote-strip parity and fail-closed tests
tests/test_policy_decide.py
TestScopeCommandQuoteStripParity verifies open() inside quoted python3 -c payloads does not block, confirms parity via shadow_compare, and preserves fail-closed blocking for unquoted heredoc bodies and sed -i edits.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • hapax-systems/hapax-council#3764: Modifies shared/policy_decide.py mutation-scope resolution and legacy bash source-scope logic with similar path normalization and quote/comment stripping infrastructure.
  • hapax-systems/hapax-council#3741: Tightens source mutation scope gating by stripping quotes/comments before mutation classification, using related gate mechanics.

Poem

🐰 Paths once twisted by working dirs,
Now normalized, no more blurred—
Repo-relative, crystal clear,
Through quoted commands, stripped sincere.
Tests property-check every case,
Scope decisions find their place. 🎯

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes a clear summary of what and why, detailed root cause analysis, triage of divergences, and verification details, but is missing the AuthorityCase structured fields required by the template. Add the required AuthorityCase fields (Case: CASE-FORMAL-GOVERNANCE-001, Slice: as specified) and Test plan section with checkbox items per the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main fixes: scope-normalization and quote-strip parity that enable the 3b-cutover, matching the core changes in both files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch zeta/reform-improve-policy-decide-scope-fix-20260601

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Comment @coderabbitai help to get the list of available commands and usage tips.

@ryanklee ryanklee enabled auto-merge June 1, 2026 14:00
@ryanklee ryanklee added this pull request to the merge queue Jun 1, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bdc2f3a742

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread shared/policy_decide.py
Comment on lines +282 to +284
tail = p[anchor + len(_WORKTREE_ANCHOR) :] # '<worktree>/<rel>'
slash = tail.find("/")
return tail[slash + 1 :] if slash != -1 else ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize dot-segments before scope prefix checks

When file_path is an absolute worktree path containing .., this returns the tail verbatim, so a target such as /home/me/projects/hapax-council/shared/../agents/other.py becomes shared/../agents/other.py and later passes a shared/ scope by string prefix. The live gate being mirrored resolves paths before checking scope, so this escape path would be denied there; without normalizing/collapsing dot-segments after removing the worktree prefix, replay/future enforcement can allow edits outside the declared mutation_scope_refs.

Useful? React with 👍 / 👎.

Comment thread shared/policy_decide.py
Comment on lines +501 to +506
if (
name in _BASH_TOOLS
and not path
and not is_runtime
and _bash_is_source_scope(_strip_quotes_and_comments(command))
):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not strip executable Python payloads before scope checks

For normal python -c usage the code being executed is inside quotes, so stripping quoted spans here removes the actual writer before _bash_is_source_scope runs. With a task scoped only to tests/, a command like python3 -c "open('shared/policy_decide.py','w').write('x')" now reaches authorized with no path scope check, allowing source edits outside mutation_scope_refs once this policy is enforced; the false-positive exemption needs to distinguish temp/output verification writes from quoted Python code that writes repo files.

Useful? React with 👍 / 👎.

Comment thread shared/policy_decide.py
#: cannot mirror the live gate's ``Path.cwd()``-anchored ``resolve`` because the
#: replay's cwd is not the decision's worktree. Reducing BOTH sides to repo-
#: relative form yields the same verdict the live gate returned in that worktree.
_WORKTREE_ANCHOR = "/projects/"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle absolute worktree paths outside /projects/

This hard-coded anchor only normalizes absolute paths whose checkout lives under a literal /projects/ directory. In environments such as the current /workspace/hapax-council checkout or CI/build clones under other roots, an in-scope absolute path like /workspace/hapax-council/shared/policy_decide.py is compared against shared/ as an absolute string and is reported scope:denied, while the live gate resolves the same path against the worktree and allows it; those sessions will keep producing false tightening divergences and block cutover.

Useful? React with 👍 / 👎.

Comment thread shared/policy_decide.py
target = _repo_relative(path)
for ref in real_refs:
rn = _norm(ref)
rn = _repo_relative(ref)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve absolute scope boundaries across worktrees

Normalizing the scope ref as well as the target drops the checkout segment from absolute refs, so a task scoped to /home/me/projects/hapax-council/shared/policy_decide.py will also authorize /home/me/projects/hapax-council--epsilon/shared/policy_decide.py. The live gate resolves and compares those absolute paths directly, so absolute mutation_scope_refs can intentionally pin one checkout/path; this change widens that scope to every worktree with the same repo-relative name.

Useful? React with 👍 / 👎.

Merged via the queue into main with commit 414db01 Jun 1, 2026
34 of 36 checks passed
@ryanklee ryanklee deleted the zeta/reform-improve-policy-decide-scope-fix-20260601 branch June 1, 2026 14:10
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.

1 participant