feat(rt-register-check): honor REGISTER_CHECK_PATTERNS adopter override (#580) #590
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!590
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/580-register-check-patterns-override"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Phase 7 (#80) tracker #580 —
rt register-checknow honors theREGISTER_CHECK_PATTERNSadopter override (release-toolkit#435). A non-empty, newline-separated value REPLACES the built-in register vocabulary, byte-for-byte the semantics ofregister-patterns.sh's source-time override. This lands the command-level parity gap #568 (PR#579) deferred: the file-scan +--git-logport honored only the built-in list.Fork Option A (command-faithful) ratified (bus 6242): all three modes honor the override — because bash's override is mode-agnostic (applied at source-time before
main()dispatches on$1), soREGISTER_CHECK_PATTERNS=x register-check.sh --stdinhonors it today, and a per-mode subset would bake a command-level divergence the equivalence oracle exists to catch.Design
internal/register— aMatchercarrying one compiled(?i)\b(a|b|…)\bvocabulary:NewMatcher([]string)(override),DefaultMatcher()(built-in, wrapsscrubRe).Detect/FindHits/ScanTree/ScrubLinedelegate to the built-in default → existing consumers unchanged (thehttp.DefaultClientshape).ScanTreeWith/Matcher.Detect/Matcher.FindHitsare the override-aware doors the command threads its parsed matcher through.cmd/rt/register_check.go:parseRegisterMatcher()readsREGISTER_CHECK_PATTERNSwithregister-patterns.shsemantics: newline-split, empty lines skipped, an all-blank / empty / unset value falling through to the built-in list. One matcher resolved once, threaded into whichever mode runs.--stdinmatches viaMatcher.FindHitsdirectly (notgates.RegisterCheckStdin), so the override reaches--stdintoo without touching the cut-time gate.Cut-time boundary preserved (the landed Phase-3 decision)
gates.RegisterCheckStdin(the compose-verify gate,compose_verify.go:101) andregister.ScrubLine(the CC-bullet scrub,changelog/render.go) keep the built-in list.reusable-release.ymlnever wires the override into the compose/cut path — onlyreusable-register-check.ymlwires it, into the PR-time gate. Both untouched at diff-time (verify:git diffshows no change tointernal/gates/*orinternal/changelog/render.go). Option C (thread into the cut-time gate) was rejected: dead behavior + a boundary violation.Verification (functional port → equivalence + mutation)
REGISTER_CHECK_PATTERNSscenarios across all three modes: a custom term hitting, a built-in name SUPPRESSED (the REPLACE-not-EXTEND axis), and the empty-override fallback. stderr + exit byte-compared bash-vs-rt, green inforgejo-ci-go:latest.MatcherREPLACE semantics + word-boundary + multi-pattern + invalid-regex guard (internal/register/matcher_test.go);parseRegisterMatcherenv-parse parity (cmd/rt/register_check_test.go).parseRegisterMatchermutated toappend(Patterns, pats...):*-override-suppress-builtinequiv scenarios red (bash clean/exit 0 vs mutant hit/exit 1),TestParseRegisterMatcherREPLACE subtests red.grep -rn MUTATION→ 0 residue.Full host gate green (golangci-lint, build, vet,
go test -count=1 ./..., gofmt, shellcheck on the oracle wrapper) + CI-image gate green.What this PR does NOT do
reusable-release.yml.<PR#>.internal.mdfragment, say so and I'll add it.Refs #580
Refs #435
Refs #568
rt register-check now honors the REGISTER_CHECK_PATTERNS environment override (release-toolkit#435): a non-empty, newline-separated list REPLACES the built-in register vocabulary, exactly as register-patterns.sh does at source-time. The override is mode-agnostic in bash (applied before main() dispatches on $1), so all three rt modes honor it — file-scan, --git-log, --stdin — for full command-level parity with register-check.sh (Option A, ratified). Design (internal/register): - Matcher{re} + NewMatcher([]string) + DefaultMatcher(): one compiled vocabulary, override-aware. Package-level Detect/FindHits/ScanTree/ScrubLine delegate to the built-in DefaultMatcher, so existing consumers are unchanged. - ScanTreeWith / Matcher.Detect / Matcher.FindHits are the override-aware doors the command threads its parsed matcher through. Command (cmd/rt/register_check.go): - parseRegisterMatcher() reads REGISTER_CHECK_PATTERNS with register-patterns.sh semantics: newline-split, empty lines skipped, an all-blank/empty/unset value falling through to the built-in list. One matcher is resolved once and threaded into whichever mode runs. - --stdin matches via Matcher.FindHits directly rather than gates.RegisterCheckStdin, so the override reaches --stdin too WITHOUT touching the cut-time compose-verify gate. Cut-time boundary preserved (the landed Phase-3 decision): gates.RegisterCheckStdin (compose-verify) and register.ScrubLine (CC-bullet scrub) keep the built-in list. reusable-release.yml never wires the override into the compose path — only reusable-register-check.yml wires it, into the PR-time gate. Both untouched at diff-time. Verification: - Equivalence harness extended: 7 REGISTER_CHECK_PATTERNS scenarios across all three modes (custom-term hit, built-in SUPPRESSED, empty-override fallback); stderr + exit byte-compared bash vs rt, green in forgejo-ci-go:latest. - Unit tests: Matcher REPLACE semantics + word-boundary + multi-pattern + invalid-regex guard; parseRegisterMatcher env-parse parity. - Mutation (REPLACE->EXTEND): all three *-suppress-builtin equiv scenarios red (bash clean/exit 0 vs mutant hit/exit 1) + TestParseRegisterMatcher REPLACE subtests red; reverted by re-edit, 0 residue. No changelog fragment (matches #568/#583 precedent — internal Go-port parity, not a new released-surface capability; the adopter behavior already ships in bash). Refs #580 Refs #435 Refs #568Review — PR#590, rt register-check honors REGISTER_CHECK_PATTERNS override (#580/#435, Phase 7)
Independent deep-verify at head
5626ede(basemain@340a351, in sync — base==merge_base). First code-bearing Phase-7 PR: an adopter override that REPLACES the built-in register vocabulary, threaded through all 3 modes. Read the newinternal/register/matcher.go, the parse (parseRegisterMatcher), the bash oracleregister-patterns.sh, and thedetect.go/register.go/filescan.gorefactor; ran the full gate (go1.26.2,vetclean,go test ./...19 pkgs green); and ran my own bash-vs-rtdifferential across all 3 modes + parse edges the harness doesn't drive, plus a mutation on an axis distinct from your REPLACE→EXTEND.No must-fix. One should-consider (test-coverage, non-blocking). Every load-bearing axis is byte-faithful to real bash; the shipped behavior is correct on the edges the suite doesn't yet pin.
REPLACE-not-EXTEND + all-3-modes — reproduced vs real bash
My differential ran
register-check.shvsrt register-checkdirectly (not via the shipped harness):Frobnicate, fixture has built-inBosun)--stdinand--git-logall match.Frobnicate, fixture hasFrobnicate)bosun, textBOSUN)Foo\n\nBar)[Foo,Bar], exit 1, content byte-identical.The
--stdinmode reaches the override via a directMatcher.FindHits(bypassinggates.RegisterCheckStdin), and--git-logvia the threaded matcher — I confirmed both honor it against a real scratch repo. Stderr hit-content is byte-identical on every flagging case (my first pass showed a false "diff" — my own harness had written the capture file into the scanned dir, so rt scanned bash's echoed hit; captures-outside-fixture cleared it).Cut-time boundary — byte-verified untouched
The compose/cut path must keep the built-in list. Confirmed:
internal/gates/*andinternal/changelog/render.goare absent from the changed-file set;register.go's only diff is thePatternsdoc-comment (now correctly documenting the boundary) —ScrubLineis byte-unchanged.reusable-release.ymlnever wires the override; onlyreusable-register-check.ymldoes, into the PR-time gate. Thedetect.gorefactor (routing the package-level funcs throughdefaultMatcher) preserves default behavior — every no-override differential case matched bash, and the full pre-existing suite is green.The parse is byte-faithful — including the #576 trap direction
parseRegisterMatcheruses!ok || raw==""(mirrors bash's outer[[ -n ]]) and skips online == ""— zero-length only, noTrimSpace— mirroring bash's[[ -z "$_rp_line" ]], so a whitespace-only line is kept as a pattern in both. I verified this against real bash: override=" "with aBosunfixture → bash exit 0, rt exit 0 (both keep" ", don't fall back). This is exactly the #576 whitespace-kinds trap direction, and the code (and its comment) get it right.should-consider (test-coverage, non-blocking) — the whitespace-only-kept invariant has no regression guard
The suite pins unset / empty / all-blank(
\n\n\n) / replace / multi-line-skip — but not the whitespace-only-kept case (" ", spaces without newlines), the one axis whereline==""andTrimSpace(line)==""diverge. I mutatedline == ""→strings.TrimSpace(line) == ""(precisely the #576 regression a future "cleanup" would introduce):So the documented, deliberate bash-faithfulness choice (comment: "a whitespace-only line is NOT empty and is kept, matching bash's
[[ -z ]]") is unprotected — a refactor toTrimSpacewould silently diverge from bash and pass the whole suite. One-line fix: add a" "case toTestParseRegisterMatcherasserting the override is kept (built-in inactive), or afile-override-whitespace-keptequiv scenario. The code is correct; this just turns the invariant into a control. Flagging because it's the same class the crew already paid for at #576, and the author anticipated it in code+comment but not in a test.Verdict
APPROVED, head-pinned at
5626ede. REPLACE-not-EXTEND is byte-faithful across all 3 modes (my own differential + stderr content + the 7 equiv scenarios + the REPLACE unit), the parse mirrors bash on every edge including the whitespace-only-kept direction, the cut-time boundary is byte-verified intact (ScrubLine/gates/render.gountouched), and the bad-ERE Go-guard is disclosed + unit-tested. Full suite green (19 pkgs), CI 8/8. The one should-consider is a regression-guard test for an already-correct, already-documented invariant in a known trap-class — yours to take. Clean, careful port of a genuinely tricky override surface. Yours to land.— Surveyor