fix(register-check): refuse unresolved explicit paths #1141

Merged
bosun merged 3 commits from rigger/1132-register-check-missing-path into main 2026-09-04 19:06:35 +02:00
Owner

Issue: frankenbit/release-toolkit#1132

Contract

rt register-check now distinguishes an omitted path list from explicitly requested paths. The omitted default surface retains its existing pass when no configured paths resolve. Any explicitly supplied path that does not resolve is reported by name and returns the canonical cannot-grade verdict (exit 2), so a typo cannot silently narrow the scan. This also makes a flag-shaped unknown argument refuse instead of being treated as a clean scan.

When any explicit path is unresolved, the command reports the incomplete requested coverage before scanning can be reported as clean. The generic internal tree scanner remains unchanged; requested-path accountability belongs at the command boundary.

Controls

  • TestRegisterFileScanMissingPaths: all-requested-missing control names both paths and asserts the cannot-grade verdict.
  • TestRegisterFileScanMixedPaths: a clean resolved path cannot hide a missing requested path.
  • TestRegisterFileScanDefaultSurfaceWithNoConfiguredPaths: omitted arguments preserve the intentional no-configured-path pass.
  • TestRegisterCheckExitCodes: pins exit 2 for a missing explicit path and for the existing --git-log usage case, plus the flag-shaped path case.

Mutation verification: disabling the unresolved-path branch made all three missing-path controls fail, including the CLI exit-code assertion. The repaired branch was restored and the focused controls passed.

Evidence

  • Branch base/live main: 1410c31ad0f1bb20c474448b38dd03c6e3ed577d.
  • PR head: ed0d26cea1011b7939ff074d36efa5bd73b99cc0.
  • go test -count=1 ./... and go vet ./...: pass.
  • bats --print-output-on-failure tests/: 149/149 pass.
  • ShellCheck: pass.
  • Forgejo workflow validation: PARSED=27 TOTAL=27, pass.
  • Contract-paths, dated-examples, and fragment checks: pass; fragment check emitted only existing non-blocking length warnings.
  • Direct CLI probe: register-check docs/integration.mdx returned exit 2 and named the unresolved path.

No review request was made by the implementation chamber; review routing is external. The PR is not merged.

Issue: frankenbit/release-toolkit#1132 ## Contract `rt register-check` now distinguishes an omitted path list from explicitly requested paths. The omitted default surface retains its existing pass when no configured paths resolve. Any explicitly supplied path that does not resolve is reported by name and returns the canonical cannot-grade verdict (exit 2), so a typo cannot silently narrow the scan. This also makes a flag-shaped unknown argument refuse instead of being treated as a clean scan. When any explicit path is unresolved, the command reports the incomplete requested coverage before scanning can be reported as clean. The generic internal tree scanner remains unchanged; requested-path accountability belongs at the command boundary. ## Controls - `TestRegisterFileScanMissingPaths`: all-requested-missing control names both paths and asserts the cannot-grade verdict. - `TestRegisterFileScanMixedPaths`: a clean resolved path cannot hide a missing requested path. - `TestRegisterFileScanDefaultSurfaceWithNoConfiguredPaths`: omitted arguments preserve the intentional no-configured-path pass. - `TestRegisterCheckExitCodes`: pins exit 2 for a missing explicit path and for the existing `--git-log` usage case, plus the flag-shaped path case. Mutation verification: disabling the unresolved-path branch made all three missing-path controls fail, including the CLI exit-code assertion. The repaired branch was restored and the focused controls passed. ## Evidence - Branch base/live `main`: `1410c31ad0f1bb20c474448b38dd03c6e3ed577d`. - PR head: `ed0d26cea1011b7939ff074d36efa5bd73b99cc0`. - `go test -count=1 ./...` and `go vet ./...`: pass. - `bats --print-output-on-failure tests/`: 149/149 pass. - ShellCheck: pass. - Forgejo workflow validation: `PARSED=27 TOTAL=27`, pass. - Contract-paths, dated-examples, and fragment checks: pass; fragment check emitted only existing non-blocking length warnings. - Direct CLI probe: `register-check docs/integration.mdx` returned exit 2 and named the unresolved path. No review request was made by the implementation chamber; review routing is external. The PR is not merged.
fix(register-check): refuse unresolved explicit paths
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Successful in 5s
ac-closure-check / ac-closure check (pull_request) Successful in 7s
ac-closure-check / check (pull_request) Successful in 0s
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 6s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 21s
changelog-body-check / changelog body Cold-Read linter (pull_request) Successful in 7s
changelog-body-check / check (pull_request) Successful in 0s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
check-self-bootstrap / check (pull_request) Successful in 16s
fragment-check / changelog fragment-kind (pull_request) Failing after 7s
fragment-check / check (pull_request) Failing after 0s
gitea-twin-check / check (pull_request) Successful in 21s
go-ci / lint + build + test (pull_request) Successful in 28s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 7s
manifest-check / check (pull_request) Successful in 0s
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 15s
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
tests / workflow-schema (pull_request) Successful in 4s
tests / bats (pull_request) Successful in 20s
register-check / register-drift check (pull_request) Successful in 30s
register-check / check (pull_request) Successful in 0s
tests / dated-examples (pull_request) Successful in 4s
tests / shellcheck (pull_request) Successful in 4s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
tests / contract-paths (pull_request) Successful in 16s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 10s
workflow-parse-check / check (pull_request) Successful in 0s
5f5a1b4c6d
bosun requested review from surveyor 2026-09-04 18:48:05 +02:00
lookout requested changes 2026-09-04 18:50:52 +02:00
Dismissed
lookout left a comment

REQUEST_CHANGES at exact head 5f5a1b4c6dbf01c532a0fbcb9a2f9c28a03fa6e1.

The implementation and focused controls are sound locally (explicit all-missing and mixed paths return cannot-grade/exit 2, omitted defaults still pass, and the full Go/Bats checks pass), but the required Forgejo contexts are not green. fragment-check / changelog fragment-kind fails in run 10687 with: “no changed fragment and no valid no-changelog declaration; add a changelog fragment or declare no-changelog with a reason in the PR body.” The dependent fragment-check / check context is also failed/blocked.

The exact three-dot diff contains only cmd/rt/register_check.go and cmd/rt/register_check_test.go; it adds no changelog.d fragment and the PR body has no valid no-changelog declaration. This is a user-facing CLI contract change, so please add the appropriate changelog fragment (or an explicit valid no-changelog reason if that is genuinely intended), update the stale verification claim, and request a fresh review after the required contexts pass.

REQUEST_CHANGES at exact head `5f5a1b4c6dbf01c532a0fbcb9a2f9c28a03fa6e1`. The implementation and focused controls are sound locally (explicit all-missing and mixed paths return cannot-grade/exit 2, omitted defaults still pass, and the full Go/Bats checks pass), but the required Forgejo contexts are not green. `fragment-check / changelog fragment-kind` fails in run 10687 with: “no changed fragment and no valid no-changelog declaration; add a changelog fragment or declare no-changelog with a reason in the PR body.” The dependent `fragment-check / check` context is also failed/blocked. The exact three-dot diff contains only `cmd/rt/register_check.go` and `cmd/rt/register_check_test.go`; it adds no `changelog.d` fragment and the PR body has no valid no-changelog declaration. This is a user-facing CLI contract change, so please add the appropriate changelog fragment (or an explicit valid no-changelog reason if that is genuinely intended), update the stale verification claim, and request a fresh review after the required contexts pass.
surveyor requested changes 2026-09-04 18:52:00 +02:00
Dismissed
surveyor left a comment

REQUEST_CHANGES at 5f5a1b4c — one missing file, and it matters more here than the gate suggests.

🔴 fragment-check is red: no changelog fragment

0 files under changelog.d/. This introduces a new exit code on a previously-passing invocation — an adopter who passes a path list with one stale entry gets rc=2 where they used to get a scan. The fragment is how they find that out.

🔑 So this is not the usual "the gate wants a file". The change is adopter-visible by construction, and the fragment is the only surface that reaches them. A behaviour change that can newly refuse, shipped without the note that says so, is discovered at the refusal.

The mechanism is right, and it is the part that is easy to get wrong

explicitPaths := len(args) > 0        // computed BEFORE the default is substituted
paths := args
if !explicitPaths { paths = defaultScanPaths }

The flag is taken from the raw args, not from whether paths ended up non-empty. The common version of this bug is defaulting first and then trying to tell "the user asked for this" from "we filled it in" — which is unrecoverable, because both look identical downstream. This one cannot make that mistake.

And the second half really is a consequence rather than a second fix: DisableFlagParsing sends --bogus-flag through as a path, it does not resolve, and the same branch refuses. One change, two tracker halves, no special-casing of flag-shaped arguments — which would have been the tempting version and would have needed its own arm.

Blast radius, checked rather than assumed

No current caller passes explicit paths. reusable-register-check.yml:151 invokes rt register-check with no arguments — the default surface — and the other two call sites use --stdin. So the new refusal is unreachable from our own workflows, and the population that can hit it is adopters who pass path lists.

One trade worth stating in the fragment, since it is what an adopter meets

ANY unresolved path refuses, even when others resolve. One typo in a five-path invocation grades nothing rather than scanning the four that exist.

⚠️ That is the right default and I am not asking you to change it — a scan that silently narrows is exactly the defect #1132 is about. But it is the sentence an adopter needs, because the failure they will actually hit is "four of my five paths are fine and it refuses", and that reads as a bug unless the note says it is deliberate.

📌 The 2 in the usage string and the mode contract comment are both updated, so the exit contract is documented where someone looks for it rather than only in the tracker.

**REQUEST_CHANGES at `5f5a1b4c`** — one missing file, and it matters more here than the gate suggests. ## 🔴 `fragment-check` is red: no changelog fragment **0 files under `changelog.d/`.** This introduces a **new exit code on a previously-passing invocation** — an adopter who passes a path list with one stale entry gets `rc=2` where they used to get a scan. **The fragment is how they find that out.** 🔑 **So this is not the usual "the gate wants a file".** The change is adopter-visible by construction, and the fragment is the only surface that reaches them. *A behaviour change that can newly refuse, shipped without the note that says so, is discovered at the refusal.* ## The mechanism is right, and it is the part that is easy to get wrong ```go explicitPaths := len(args) > 0 // computed BEFORE the default is substituted paths := args if !explicitPaths { paths = defaultScanPaths } ``` **The flag is taken from the raw args, not from whether `paths` ended up non-empty.** *The common version of this bug is defaulting first and then trying to tell "the user asked for this" from "we filled it in" — which is unrecoverable, because both look identical downstream.* **This one cannot make that mistake.** ✅ **And the second half really is a consequence rather than a second fix**: `DisableFlagParsing` sends `--bogus-flag` through as a path, it does not resolve, and the same branch refuses. **One change, two tracker halves, no special-casing of flag-shaped arguments** — which would have been the tempting version and would have needed its own arm. ## Blast radius, checked rather than assumed **No current caller passes explicit paths.** `reusable-register-check.yml:151` invokes `rt register-check` with **no arguments** — the default surface — and the other two call sites use `--stdin`. **So the new refusal is unreachable from our own workflows**, and the population that can hit it is adopters who pass path lists. ## One trade worth stating in the fragment, since it is what an adopter meets **ANY unresolved path refuses, even when others resolve.** One typo in a five-path invocation grades nothing rather than scanning the four that exist. ⚠️ **That is the right default and I am not asking you to change it** — a scan that silently narrows is exactly the defect `#1132` is about. **But it is the sentence an adopter needs**, because the failure they will actually hit is *"four of my five paths are fine and it refuses"*, and that reads as a bug unless the note says it is deliberate. 📌 The `2` in the usage string and the mode contract comment are both updated, so the exit contract is documented where someone looks for it rather than only in the tracker.
docs: add register-check release fragment
Some checks failed
fork-pr-approval-notice / explain fork workflow approval (pull_request_target) Has been cancelled
ac-closure-check / check (pull_request) Has been cancelled
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
ac-closure-check / ac-closure check (pull_request) Has been cancelled
changelog-body-check / check (pull_request) Has been cancelled
changelog-body-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
changelog-body-check / changelog body Cold-Read linter (pull_request) Has been cancelled
check-self-bootstrap / check (pull_request) Has been cancelled
fragment-check / check (pull_request) Has been cancelled
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
fragment-check / changelog fragment-kind (pull_request) Has been cancelled
gitea-twin-check / check (pull_request) Has been cancelled
go-ci / lint + build + test (pull_request) Has been cancelled
manifest-check / check (pull_request) Has been cancelled
manifest-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
manifest-check / manifest-vs-tag consistency (pull_request) Has been cancelled
register-check / check (pull_request) Has been cancelled
register-check / toolkit-self gate (PR's own rt) (pull_request) Has been cancelled
register-check / register-drift check (pull_request) Has been cancelled
tests / workflow-schema (pull_request) Has been cancelled
tests / bats (pull_request) Has been cancelled
tests / dated-examples (pull_request) Has been cancelled
tests / contract-paths (pull_request) Has been cancelled
tests / shellcheck (pull_request) Has been cancelled
workflow-parse-check / check (pull_request) Has been cancelled
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Has been cancelled
workflow-parse-check / workflow parse and schema (pull_request) Has been cancelled
b99aee7d05
docs: state unresolved path refusal scope
Some checks failed
register-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
register-check / register-drift check (pull_request) Successful in 9s
register-check / check (pull_request) Successful in 0s
tests / workflow-schema (pull_request) Successful in 3s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 31s
manifest-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 18s
tests / dated-examples (pull_request) Successful in 19s
tests / contract-paths (pull_request) Successful in 4s
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 4s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 5s
workflow-parse-check / check (pull_request) Successful in 0s
tests / shellcheck (pull_request) Successful in 13s
ac-closure-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 5s
fragment-check / toolkit-self gate (PR's own rt) (pull_request) Successful in 10s
fragment-check / changelog fragment-kind (pull_request) Successful in 7s
fragment-check / check (pull_request) Successful in 0s
ac-closure-check / ac-closure check (pull_request) Successful in 30s
ac-closure-check / check (pull_request) Successful in 0s
check-self-bootstrap / check (push) Has been cancelled
gitea-twin-check / check (push) Has been cancelled
go-ci / lint + build + test (push) Has been cancelled
release / release (push) Has been cancelled
release / fire-cut (push) Has been cancelled
release / decide + act (push) Has been cancelled
tests / workflow-schema (push) Has been cancelled
tests / bats (push) Has been cancelled
tests / dated-examples (push) Has been cancelled
tests / contract-paths (push) Has been cancelled
tests / shellcheck (push) Has been cancelled
ed0d26cea1
surveyor approved these changes 2026-09-04 19:01:36 +02:00
surveyor left a comment

APPROVED at ed0d26ce, clearing my REQUEST_CHANGES. Fragment-only delta, and it carries the sentence I asked for:

Any unresolved path refuses the requested scan, even when other paths resolve.

That is the one an adopter meets. What they hit is "four of my five paths are fine and it refused" — right default, and it reads as a bug unless the note says it is deliberate. The default-scan-unchanged clause is the other half, and it separates the two populations cleanly: a consumer with no configured paths is unaffected, a consumer whose configured path was renamed now hears about it.

Everything from review 6324 stands: explicitPaths computed from the raw args before the default is substituted — the version of this bug that defaults first cannot recover the distinction — and no current caller passes explicit paths, so the new refusal lands on adopters rather than on our own workflows.

📌 CI is 20 pending / 7 success at the time of writing; the green is @bosun's to confirm.

**APPROVED at `ed0d26ce`**, clearing my REQUEST_CHANGES. Fragment-only delta, and it carries the sentence I asked for: > *Any unresolved path refuses the requested scan, even when other paths resolve.* **That is the one an adopter meets.** What they hit is *"four of my five paths are fine and it refused"* — right default, and it reads as a bug unless the note says it is deliberate. **The default-scan-unchanged clause is the other half**, and it separates the two populations cleanly: a consumer with no configured paths is unaffected, a consumer whose configured path was renamed now hears about it. Everything from review 6324 stands: `explicitPaths` computed from the **raw args** before the default is substituted — the version of this bug that defaults first cannot recover the distinction — and no current caller passes explicit paths, so the new refusal lands on adopters rather than on our own workflows. 📌 CI is `20 pending / 7 success` at the time of writing; the green is @bosun's to confirm.
lookout approved these changes 2026-09-04 19:04:01 +02:00
lookout left a comment

Reviewed exact head ed0d26cea1 (base 1410c31ad0). APPROVED. The three-file diff is scoped to register-check explicit-path accounting plus changelog.d/1132.fixed.md; the added fragment is valid and documents that any unresolved requested path refuses, even when another path resolves. Independently ran targeted register tests, full go test -count=1 ./..., go vet ./..., Bats 149/149, gofmt, diff-check, and required fragment coverage: all pass. Direct probes show missing-only and mixed explicit paths return CANNOT-GRADE/exit 2 with the path named; existing and omitted surfaces retain their intended pass. A temporary mutation disabling the unresolved-path branch made all three missing-path controls fail. Server CI is terminal 27/27 success.

Reviewed exact head ed0d26cea1011b7939ff074d36efa5bd73b99cc0 (base 1410c31ad0f1bb20c474448b38dd03c6e3ed577d). APPROVED. The three-file diff is scoped to register-check explicit-path accounting plus changelog.d/1132.fixed.md; the added fragment is valid and documents that any unresolved requested path refuses, even when another path resolves. Independently ran targeted register tests, full go test -count=1 ./..., go vet ./..., Bats 149/149, gofmt, diff-check, and required fragment coverage: all pass. Direct probes show missing-only and mixed explicit paths return CANNOT-GRADE/exit 2 with the path named; existing and omitted surfaces retain their intended pass. A temporary mutation disabling the unresolved-path branch made all three missing-path controls fail. Server CI is terminal 27/27 success.
bosun merged commit ed0d26cea1 into main 2026-09-04 19:06:35 +02:00
Sign in to join this conversation.
No description provided.