Diff-test gate loops when a lib file has only scenario-split tests and no mirrored _test.rb #535
Labels
No labels
P0
P1
P2
P3
bug
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#535
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Context
Live run of
/os-sdlc:implement #525(session f3ac4424, 2026-09-08): the programmer changedplugins/os/lib/worktree_cli_finish.rb; the diff-test gate mapped it toplugins/os/tests/worktree_cli_finish_test.rb, which did not exist, and raised LoadError through three programmer-repair rounds. The finish suite is scenario-split (worktree_cli_finish_*_test.rb, 12 files). An entry file was added by hand mid-run as a workaround; that workaround stays, this ticket fixes the gate.Root cause:
CandidateTestPaths#resolve(plugins/os-sdlc/lib/os_sdlc/runner/diffed_test_paths.rb:57-62) returnscandidates.firstwithout an existence check whenever there is exactly one candidate. The candidate list is two literal strings (mirrored and unnamespaced, line 50-55); there is no glob anywhere. This is the only lib-to-test mapping site in the runner (code-probe 2026-09-08).Consumers:
gate_commands.rb:32-40builds onerequire_relativeper path;brief_facts.rb:119-129splats paths intogit diff HEAD --. Both expect a flat list of strings, so the fallback must be flattened insideDiffedTestPaths#paths, never leak arrays out.VERDICT: CREATE (ticket-skeptic, claude-sonnet-5, 2026-09-08): real observed failure, root cause named, chain ends in ADR-0160's mirrored-path contract.
Tasks
CandidateTestPaths#resolve(file)returns an Array: the first existing literal candidate as a one-element array; else every file matching the scenario glob for each candidate (<dir>/<name>_*_test.rb, relative to project_root, sorted); else raiseHarnessErrornaming both the literal candidates and the globs tried.return candidates.first if candidates.one?shortcut; existence is checked in every case.DiffedTestPaths#pathsusesflat_mapovertest_path_forso the result stays a flat, unique list of strings;gate_commands.rbandbrief_facts.rbare unchanged.plugins/os-sdlc/tests/runner/diffed_test_paths_test.rbusingDiffedProjectFixture: add a fixture that writeslib/foo.rbwith onlytest/foo_alpha_test.rbandtest/foo_beta_test.rb.Pseudo-Ruby:
Acceptance criteria
foo_alpha_test.rbandfoo_beta_test.rbyields both paths, sorted, and the diff-test command requires both.foo_test.rbyields exactly that one path, even when scenario files also exist.HarnessErrorwhose message lists the two literal candidates and the two globs.DiffedTestPaths#pathsnever contains a nested array; existing tests indiffed_test_paths_test.rbandgate_commands_test.rbstay green.Out of scope
worktree_cli_finish_test.rbentry file in plugins/os.Blocking edges
Origin
/os-sdlc:implement #525, programmer-repair dispatches 18-20plugins/os/tests/worktree_cli_finish_test.rb, an entry file that requires the scenario files, on branch ticket-525CandidateTestPaths#resolvereturns a single candidate unconditionally (source: plugins/os-sdlc/lib/os_sdlc/runner/diffed_test_paths.rb) ← mirrored-path contract from ADR-0160. End:DESIGN(ADR-0160).Work started via /os-sdlc:implement on branch ticket-535 (session 1a3b7fd0-0319-465b-8414-0ab70560de3d). Deviation: main's brief_facts.rb hot-patched with target: (#534) for the run, to be restored before finish.
Pipeline run implementation 3 (session 1a3b7fd0-0319-465b-8414-0ab70560de3d, branch ticket-535) ended implementation_failed: bound_exhausted:suite-check/fail after three red suite-checks (dispatches 62, 72, 76). Failed step: suite. Gate detail: OsSdlc::Runner::GateCommandsTest#test_lint_present_and_test_lint_nil_for_a_production_only_diff raises HarnessError "no test file found for lib/foo.rb; tried: test/foo_test.rb; and scenario globs: test/foo_*_test.rb" at diffed_test_paths.rb:72 via gate_commands.rb:33. Cause: that pre-existing test's fixture has a production-only diff with no test file at all; the old code returned the nonexistent path silently (the very behavior that caused the #525 LoadError loop), and the new code raises as this ticket specifies. Resolving it needs a test-side change (fixture gains a test file, or the assertion changes), which programmer-repair is forbidden to make, so the repair loop could not converge. Worktree ticket-535 kept with uncommitted changes to diffed_test_paths.rb and diffed_test_paths_test.rb; diff-test and lint gates were green. Decision needed from the user: keep raise (edit the fixture) or return no paths on no-match.
Resolution
Done: CandidateTestPaths#resolve now returns the existing mirrored test, else sorted _*_test.rb scenario matches, else raises HarnessError naming the paths and globs tried. Two gate-commands tests that encoded the old return-a-missing-path contract now give their fixture a test file; a new test asserts the raise. Decision: option A (keep the raise, fix the tests), taken by the agent as the memo default.
Evidence: merged to main
717d8a8(fix) +8eb1ee5(history row) on 2026-09-09; os-sdlc suite 1087 runs, 0 failures, 0 errors; rubocop clean on the three changed files; worktree ticket-535 finished and removed; main's brief hot-patch was already restored.Follow-ups: see follow-up issue #544: Pipeline cannot converge when a red suite-check needs a test-side edit