fix(sdlc): policy_decide scope-normalization + quote-strip parity unblocks 3b-cutover#3828
Conversation
…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
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis 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. ChangesScope Resolution and Command Parsing Parity
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped 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 Comment |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
💡 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".
| tail = p[anchor + len(_WORKTREE_ANCHOR) :] # '<worktree>/<rel>' | ||
| slash = tail.find("/") | ||
| return tail[slash + 1 :] if slash != -1 else "" |
There was a problem hiding this comment.
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 👍 / 👎.
| if ( | ||
| name in _BASH_TOOLS | ||
| and not path | ||
| and not is_runtime | ||
| and _bash_is_source_scope(_strip_quotes_and_comments(command)) | ||
| ): |
There was a problem hiding this comment.
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 👍 / 👎.
| #: 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/" |
There was a problem hiding this comment.
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 👍 / 👎.
| target = _repo_relative(path) | ||
| for ref in real_refs: | ||
| rn = _norm(ref) | ||
| rn = _repo_relative(ref) |
There was a problem hiding this comment.
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 👍 / 👎.
What & why
The reform 3b-cutover gate (
policy-decide-shadow-eval,asymmetric_ok = tightening==0) was structurally unreachable. Replaying the realcc-task-gatedecision log throughpolicy_decideproduced 178 TIGHTENINGS (~177 reason"path is outside the task's mutation_scope_refs"), so the predicate could never go clean —policy_decidecould never be cut over.This fixes both root causes so
policy_decideis 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_resultstring-compared an absolute worktreefile_path(e.g.~/projects/hapax-council--zeta/tests/x.py) against repo-relative scope refs (e.g.tests/), sostartswithnever matched → falsescope: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'sPath.cwd()-anchoredresolve(the replay's cwd is not the decision's worktree)._repo_relativereduces 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 theopen(inside the quoted-cpayload doesn't count and it allows.policy_decidesubstring-matchedopen(on the raw command → blocked atscope:command. Fix mirrors the legacy strip at thescope:commandsite. 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)
scope:commandtighteningNo 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_cleanreportsasymmetric_ok=True,tightening=0; the only remaining unmet condition is the 7-day coverage window, which accrues naturally over the shadow week.Verification
{divergences: 69, loosening: 69, tightening: 0}.scope:commandquote-strip parity incl. the fail-closed heredoc-body case (5).policy_decide+ shadow producer/scripts +cc_task_gate+ floor +sdlc_invariants);ruff+pyrightclean.Task:
reform-improve-policy-decide-scope-fix-20260601· AuthorityCase:CASE-FORMAL-GOVERNANCE-001🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests