Skip to content

fix(classifier): v1.2.37 narrow protected-ref gating to true target pushes (t_c0f25265) - #64

Open
maximalang wants to merge 3 commits into
codex/company-osfrom
fix/classifier-fp-v1237
Open

maximalang wants to merge 3 commits into
codex/company-osfrom
fix/classifier-fp-v1237

Conversation

@maximalang

Copy link
Copy Markdown
Owner

What

v1.2.37 — release_to_protected_branch classifier narrowing (card t_c0f25265).

Root cause (live repro on deployed v1.2.36)

The gated category armed on any single command line where a push/merge
token co-occurred with a protected branch name — regardless of direction or
stage boundaries:

  • git merge origin/main (trunk integrated INTO a feature branch) — denied
    with evidence_gate_missing, deadlocking rr-team merge-train and
    landing-lane workers on 2026-10-07;
  • chained lines where a protected name appeared in an earlier read-only
    stage (fetch/merge-base) next to an unrelated later push;
  • step-description prose containing both tokens.

(merge-base and pr view --json mergeable reads were already fixed by the
v1.2.34 FP corpus / v1.2.36 merge(?!-base) lookahead; probe confirms they
stay read-only.)

Fix

The category now requires an actual protected target:

  • push whose ref argument resolves to a protected branch (incl. src:dst
    and refs/heads/<branch> forms),
  • gh pr merge,
  • explicit merge ... into <protected> phrasing,

with matching scoped to a single shell stage ([^;&|\n]), so earlier stages
and prose cannot arm the gate. Serious-risk rules (force-push →
irreversible_data_loss) untouched.

Evidence

  • New regression tests: 5 FP cases (must NOT arm the gate) + 6 TP controls
    (must stay gated) in tests/test_policy.py.
  • Full suite: 924 passed (pytest, venv python, worktree at this commit).
  • Live-classifier probe before/after: git merge origin/main goes
    release_to_protected_branch → repository_change (allow);
    git push origin main, push HEAD:main, push ... refs/heads/main,
    gh pr merge all stay release_to_protected_branch.
  • Version pins bumped consistently (pyproject, both plugin.yaml, __init__,
    version-guard test, CHANGELOG).

Deploy note

After merge, roll v1.2.37 out to profile plugin dirs and verify one
previously denied command class passes live.

… pushes

Root cause (live repro on v1.2.36, card t_c0f25265): the gated category
armed on any command line where a push/merge token co-occurred with a
protected branch name — including integrating the trunk INTO a feature
branch and stage-chained commands where the protected name appeared in
an earlier read-only stage. Affected workers deadlocked in
evidence_gate_missing on legitimate integration git usage
(rr-team train + landing lane, 2026-10-07).

Fix: the category now requires an actual protected target —
a push whose ref argument resolves to a protected branch (incl.
src:dst and refs/heads forms), gh pr merge, or an explicit
"merge ... into <protected>" form — with matching scoped to a single
shell stage. Read-only probes (merge-base, pr view --json) were already
fixed in v1.2.34/36 and keep passing via the FP corpus.

Tests: +11 regression cases (5 FP, 6 TP controls) in test_policy.py;
version pin updated in test_hardening_v121.py; full suite 924 passed.
Versions bumped to 1.2.37 (pyproject, both plugin.yaml, __init__,
CHANGELOG).
tech added 2 commits October 14, 2026 13:12
…c0f25265)

Merge trunk v1.2.46 into fix/classifier-fp-v1237 and finish the FP fix:

- release_to_protected_branch now requires a push ref argument that RESOLVES
  to a protected branch, boundary-anchored ((?<![:\w./-]) / (?![\w./-]), plus
  :<branch>, :refs/heads/<branch>, refs/heads/<branch> forms). Protected names
  that are only a path component of a different branch
  (push origin feature/main-fix) no longer arm the gate.
- Matching stays scoped to one shell stage, so an earlier fetch/merge-base/
  prose stage cannot arm a later unrelated push (landed on the PR head).
- Version 1.2.37 -> 1.2.48 across pyproject/plugin.yaml x2/__init__/uv.lock/
  version-guard test + CHANGELOG (1.2.37 collided with open refs and the
  trunk already released 1.2.46).
- tests/test_policy.py: added boundary regression cases (feature/main-fix,
  mainnet, release-notes, HEAD:feature/main-fix) and TP controls
  (feature/x:release, HEAD:refs/heads/master, multi-refspec).

Evidence: probe matrix fp=0 tp=0 (only force-push stays approval_required via
irreversible_data_loss); uv run pytest 956 passed (normal + worker env);
version guard collision=false; uv lock --check, compileall, drift-check,
build/verify-bundle, uv build all green at 1.2.48.

Refs t_c0f25265. No config/evidence_gates changes; no deploy.
- Boundary character class was written as [\w./-:] whose final two members form
  the ASCII range 0x2F-0x3A ('/' and ':'), which made '-' a member and pulled
  'release-notes' back into the gate. Use an explicit alternation rather than a
  trailing ':' inside the class, and require the negative lookahead to reject
  ':' so 'push origin main:feature/x' (protected ref as SOURCE) is not read as
  a protected destination.
- tests/test_policy.py: added the release-notes regression that caught it.

Evidence: pytest 956 passed; probe matrix fp=0 (tp=1 is 'push --force main',
which stays approval_required via irreversible_data_loss, i.e. stricter).
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