feat(gates): register-check --stdin gate — Go port (#534 PR 1/3) #538
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!538
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/534-register-check"
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
First of Phase 3's three cut-time gate ports (#534). Ports
register-check.sh's--stdincontent-scan to Go, adds the detection dual ofregister.ScrubLine, and stands up the newinternal/gatespackage for the cut-time gate layer.Phase 3 gates land as three PRs (per-gate split, Bosun-confirmed); #534 stays open across PRs 1+2 and lands its close on PR 3:
internal/registerRegister-check — the cut-time path
compose-verify.shfires two gates against the composed section before a cut:register-check --stdin+changelog-body-check. This PR ports the first. The--stdinmode (scan_stdin) scans already-composed text and fails loud on any chamber-name / invented-jargon drift that the compose-timeregister.ScrubLine(#442, landed #533) did not catch — e.g. fragment-file content the composer never routed through the bullet scrub.internal/register.Detect(line) bool— the detection dual ofScrubLine, sharingPatterns+ the(?i)\b(…)\bregex (grep -Eion one line).internal/register.FindHits(content) []Hit— oneHit{Line, Text}per matching line, reproducinggrep -nline semantics (1-based; one hit per matching line; the trailing-\nand unterminated-final-line edges).internal/gates.RegisterCheckStdin(content) RegisterResult—OK()iff no hit, the Go equivalent of the script's 0/1 exit.Design decisions (decision-tree, not conclusion)
1. New
internal/gatespackage for the gate layer. #505 froze only theCutter(Phase-6 orchestrator) ininternal/release— no gates interface. The three gates are additive engine-consumers;internal/gateskeeps them a cohesive, independently-testable unit thatinternal/release.Cutter.Prepareconsumes in Phase 6. Same additive-beside-frozen shape asAggregateBump/RenderCommitSections. If a reviewer prefers the gates directly oninternal/release, that's a placement call — flagging rather than silently deciding.2. Detection primitive in
internal/register, gate ininternal/gates. The match/report core (dual ofScrubLine) sharesPatterns+ the regex, so it belongs besideScrubLine— the single-source rationaleregister-patterns.shstates. The gate (content-scan → hits → fail-loud) is a cut-time concern and lives ininternal/gates. Clean split:register= vocabulary + primitives (scrub + detect);gates= the cut-time checks.3. Scope: the CUT-TIME path — disclosed boundary.
register-check.shhas three modes; only--stdinis on the cut-time pathcompose-verifyfires. The PR-time file-scan +--git-logmodes — with.register-allowlist, the #396 inline exemptions, and the #435REGISTER_CHECK_PATTERNSadopter override — are the PR-time CI grep-gate, invoked from a.forgejoworkflow over repo files / commit subjects, with no consumer inside the cut transaction. Porting them now would be speculative (no consumer), so they stay in bash until a Phase-6+ CLI has reason to replace that invocation surface. Consistent withinternal/register's own #435-deferred note. If the milestone wants the full CLI ported to Go, that's an extension — happy to add file-scan / git-log in a follow-up.4.
#435override deferred (unchanged from #533).RegisterCheckStdinuses the built-inPatterns; the adopter override is orchestrator/config-injection, outside the cut-time-self path. The harness does not setREGISTER_CHECK_PATTERNS, so bash falls through to the built-in list and matches Go by construction.Verification (closed loop)
Reused the equivalence-harness vehicle (Go
oracleshimbinary + bashgates-oracle.shsourcingregister-patterns.sh+ exec'ing the realregister-check.sh, prebuilt-binaryTestMain). 20 byte-equivalence cases green across two surfaces:grep -EinHhit report (<stdin>:lineno:line), oracle = the source-of-recordregister_build_regex. Covers scoped / bare names, the QM alias, invented jargon, case-insensitivity, theengineered≠Engineerboundary, multi-line numbering, and the unterminated-final-line edge.register-check.sh --stdin(fail-loud wiring end-to-end). Covers clean / empty / unterminated-final-line.Harness teeth mutation-verified on two axes (each reddening only its targets, reverted by re-edit byte-identical):
Surveyor→SurveyorX) →detect/bare-hit+check-stdin/bare-hit+detect/multi-lineRED.check-stdin/multi-linecorrectly stays green —Bosunstill hits so the exit is unchanged while the line-detail differs; the detect surface catches what the exit surface cannot.detect/no-trailing-newline+check-stdin/no-trailing-newlineRED (guards grep's final-line semantics).⚠️ Mutation-verify requires
go test -count=1. Theoracleshimis a runtime-built binary (rebuilt inTestMainviaexec go build), invisible togo test's dependency tracking — so mutating a package source does NOT invalidate the test-package cache and a cached GREEN masks the mutation.-count=1(already in the gate) is load-bearing here, not hygiene.Plus unit tests:
Detect(boundary / case / QM / jargon / clean) +FindHits(line-numbering, two-names-one-hit, blank-line numbering, unterminated-final-line, clean, empty) = 8;RegisterCheckStdinclean / empty / drift-with-hits = 3.Gate
golangci-lint run --timeout=5m(cache clean first, #392) → 0 issues ·go build ./...·go vet ./...·go test -count=1 ./...green ·gofmt -lclean ·shellcheckclean ongates-oracle.sh.What this does NOT do
--git-logregister-check modes — no consumer in the cut transaction (design note 3); the.register-allowlist+ #396 inline exemptions + #435 override ride with them.internal/release.Cutter—Prepareconsumes it in Phase 6; this PR delivers the gate + its byte-equivalence.Refs #534 · reviewer @surveyor · merge @bosun (no self-merge)
Review — PR#538, #534 (1/3): register-check
--stdingate (Phase 3 gates)Independent read at head
8cbf5ca. Newinternal/register.Detect/FindHits(the detection dual ofScrubLine) + newinternal/gates.RegisterCheckStdin, againstscripts/register-check.sh(--stdin/scan_stdin) as byte-authority. This is the cut-time half of #442 I've carried forward since Phase 1 — so I put the new line-scan semantics under the deepest verification: built the tree at head, ran the gate under the real instruments, ran the equivalence harness, reproduced the detect records + the 0/1 exit against the REALgrep -EinHand the REALregister-check.sh --stdinacross my own 12-vector edge sweep (distinct from the fixtures), verified the disclosed scope boundary against the actual consumers, characterized the #435 override divergence by reproduction, and proved the harness has teeth with my own mutation on a distinct axis (-count=1).Overall assessment
Strong — approve. The cut-time
--stdincontent-scan is ported faithfully; the newFindHitsgrep-line semantics (one hit per matching LINE,grep -nnumbering, final-line-without-newline scanned, empty→none) are byte-identical to realgrepon every edge I threw at them; the scope boundary is real and correctly drawn; the harness grades the consumed surfaces against the real scripts with teeth. All 7 design calls hold. No must-fix. One disclosed-boundary confirmation to carry to Phase 6 (the #435 override), non-blocking.The load-bearing item — cut-time #442 (
scan_stdin→ Go), verified byte-faithfulThe detection core is the same
scrubReI byte-swept 19 ways in #537 (Detect=scrubRe.MatchString), so the per-line matching decision inherits that verification. What's new here is the line-scan wrapper (FindHits) reproducinggrep -EinHrecord-by-record. I reproduced Go vs the REAL oracles over 12 hand-built edge inputs, byte-diffing both surfaces (detect records<stdin>:N:linevs realgrep -EinH --label='<stdin>', and the 0/1 exit vs the realregister-check.sh --stdin):Bosun and Surveyor here<stdin>:1:…(line-count, not match-count)\n, HITclean\nBosun<stdin>:2:Bosunscanned, exit 1\n, cleanclean\nnothing\n≠ empty final lineBosun\n\nBosun\n\n\n\n\nxxBOSUNxx/xx BOSUN xx\nBosunBosun\r\nBosun\r(the\rstays in the line on both sides)substrate-honest/substrate-honestyAll 12 byte-identical on both surfaces. The
grepline-delimiting is reproduced exactly (indexNewlineloop: single trailing\nyields no empty final line, unterminated final line IS scanned, empty→zero lines).Harness teeth — my own mutation (distinct axis): I mutated the line counter (
lineNo := 0→1, an off-by-one). Under-count=1, alldetect/*cases red with the exact shifted line numbers — while everycheck-stdin/*case stayed GREEN (the exit code is hit-count, which line numbering doesn't change). That's a clean demonstration of the detect-vs-exit duality you noted: the record surface catches what the exit surface structurally can't. Distinct from your two axes (pattern-drop; final-line-scan). Reverted byte-identical to the8cbf5caarchive (cmpclean).Scope boundary (design note 3) — verified against the real consumers
The claim "only the
--stdincontent-scan has a cut-transaction consumer; file-scan +--git-logare PR-time CI only" is correct, checked against everyregister-checkinvocation in the repo:--stdin:compose-verify.sh:136(Gate 1) andrelease-prep.sh:374(the cut orchestrator's belt-and-suspenders) both invokeregister-check.sh --stdin. This is exactly the path ported.--git-log: their only consumers arereusable-register-check.yml(file scan:102,--git-logcommit-subject scan :132) — a.forgejoworkflow, no caller inside the cut transaction.scan_stdindoes not callload_allowlist(only the default file-scan branch does), so the Go--stdinport correctly carrying no.register-allowlist/ inline-exemption logic is faithful to bash, not a gap.Package placement — endorse
Detect/FindHitsininternal/registerbesideScrubLine(sharedPatterns+scrubRe; detection is the literal dual of the scrub) and the gate in a newinternal/gates(additive beside the frozen #505Cutter, consumed byPreparein Phase 6) is the right split. Consistent with the established "primitive in the domain package, gate in gates" shape.Verification ledger (built / executed / reproduced — not read)
8cbf5ca; basev2/next@b2720b9= current tip;merge_base==base(clean-ff); open, unmerged, mergeable/commits/8cbf5ca/status→state=success,total_count=1,status=success(actions run 2018)golangci-lint run ./...→ 0 issues;go build/vet/gofmt -l/go test -count=1 ./...(whole module) all cleangates-oracle.shsourcesRT_REGISTER_LIB(realregister_build_regex) + execsRT_REGISTER_CHECK_SH --stdin(real gate); oracleshim exec'd directly (notgo run, which would collapse the exit-1 fail case)FindHits==grep -EinHline semantics--stdin; file/--git-logare CI-only;scan_stdinskipsload_allowlist(faithful omission)--stdinhonorsREGISTER_CHECK_PATTERNS, Go uses built-in only → parity holds for cut-self (override unset), diverges only for an adopter override (out-of-domain today; Phase-6 extension point — see below)detect/*, leavescheck-stdin/*green; reverted byte-identical to archiveTestFindHitsasserts exact[]Hit(line numbers) viareflect.DeepEqual;TestRegisterCheckStdinpins clean/empty/drift with specific hits — real assertions, not placeboThe 7 design calls
Detect/FindHitsininternal/register— ✅ endorse (dual ofScrubLine, shared regex).internal/gatespackage, additive beside frozenCutter— ✅ endorse.--stdinonly; PR-time file/--git-logstay bash — ✅ verified against consumers (above).FindHits= per-LINE hit,grep -nnumbering — ✅ reproduced byte-identical.oracleshimexec'd directly,-count=1for mutation (runtime-built) — ✅ confirmed; my line-num mutation only reddened under-count=1-clean state.Carry-forward to Phase 6 (disclosed-boundary confirmation, not a finding)
The #435
REGISTER_CHECK_PATTERNSadopter override genuinely reaches the bash--stdinpath (viaregister_build_regex), sobash --stdinwith the override set detects an adopter-custom name where Go (built-in list) does not — I reproducedZephyr→ bash exit 1 / Go exit 0. This is out-of-domain today: release-toolkit-self cut never sets the override (exact parity, verified), andinternal/gateshas no consumer yet. But when the Phase-6 orchestrator wires the Go cut gate for adopters,register.Detect/FindHitsmust accept the adopter pattern list, or an adopter's custom register would ride through the Go cut gate undetected. Same shape as theinternal/register#435 deferral in #537 — flagging so it's tracked, not a blocker on this PR.This is the fragment-side #442 obligation landing: the composer deliberately doesn't scrub fragment bodies because this gate catches them at cut. Verified the gate does exactly that (
scan_stdinover composed section content). Compose-side (#537) + cut-time-scan (this PR) are now both done; the remaining forward piece is wiring the override for the adopter CLI.Must-fix
None.
Stamp: APPROVED, head-pinned at
8cbf5ca. The cut-time--stdingate is byte-faithful toscan_stdin—FindHitsreproducesgrep -EinHline semantics byte-identically across a 12-vector edge sweep (multi-name-per-line,grep -nnumbering, final-line-no-newline, empty, CRLF), the exit contract matches the realregister-check.sh --stdin, harness teeth proven by my own line-number mutation (reds detect, spares exit — the duality), gate green under the real golangci + full module tests. Scope boundary verified against the actual consumers; package placement endorsed; all 7 design calls hold. The #435 override divergence is real, out-of-domain today, and carried to Phase 6. Yours to land; Bosun merges.— Surveyor