os-sdlc map: own the edit of existing tests a ticket deliberately invalidates #610

Closed
opened 2026-09-17 19:29:44 +00:00 by jared · 2 comments
Owner

Context

Ticket #606 changes behavior that existing tests assert. The poodr-ticket-implementation map lets test-writer add tests and lets programmer edit only production code. No node may retire or rewrite an existing test. The programmer's gate cannot go green when the ticket invalidates an existing test.

Observed

Implementation 1 of #606 (2026-09-17, worktree ticket-606) ended implementation_failed with reason verdict:error at the programmer node, dispatch 9.
The programmer's handoff disclosed the red as a test-mapping gap only. The full suite told a different story.

ruby plugins/os-sdlc/tests/all.rb
1341 runs, 2998 assertions, 14 failures, 3 errors, 0 skips

Unmodified main in the same session: 1331 runs, 3039 assertions, 0 failures, 0 errors, 0 skips.
Failing existing tests that assert the old behavior the ticket retires:

OsSdlcLspProbeDaemonTest#test_connect_with_a_stale_pid_file_removes_it_and_spawns_fresh
OsSdlcLspProbeDaemonTest#test_stop_sends_only_term_to_a_daemon_that_exits_promptly
OsSdlcLspProbeDaemonTest#test_stop_falls_back_to_the_single_pid_when_the_process_group_is_already_gone
OsSdlcLspProbeDaemonTest#test_idle_daemon_past_the_timeout_is_no_longer_alive_and_removes_its_files
OsSdlcLspProbeDaemonTest#test_open_spawns_and_returns_a_client_like_connect  (ArgumentError: unknown keywords: :spawner, :clock)

The verdict:error route went straight to implementation_failed. The suite-check repair route never ran.

Reproduce

os-sdlc-runner implementation-open SESSION 606 /home/jared/.claude/plugins/os-sdlc/maps/poodr-ticket-implementation.yaml --target os-sdlc

Then drive the map to the programmer node. Any ticket whose ## Cases contradict an existing test reproduces the shape.

Expected

When a ticket's cases invalidate an existing test, one node owns the edit to that test, and the programmer's gate can reach green. Two candidate shapes; the ticket decides which:
(a) The agent-ready ticket template gains a ## Retired tests section. test-writer owns those edits in the red round. implementation-open refuses a ticket whose cases contradict a listed test that is not in the section.
(b) The map gains an edge from programmer verdict:error back to test-writer, with the failing existing tests as the brief.
Separately: a programmer handoff that names a mapping gap while the suite is red must be classified as a red gate, not an error. The error route pre-empted the suite-check repair route here.

Origin

  • Trigger: /os-sdlc:implement #606, implementation 1, dispatch 9 (programmer), 2026-09-17
  • Improvised this session: none; run marked implementation_failed, #606 labelled waiting, worktree ticket-606 kept with the increment uncommitted
  • Chain: programmer gate red ← existing tests assert retired behavior ← no map node may edit existing tests ← DESIGN (maps/poodr-ticket-implementation.yaml; test-writer and programmer agent charters)
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/maps/poodr-ticket-implementation.yaml, plugins/os-sdlc/agents/test-writer.md, plugins/os-sdlc/agents/programmer.md
  • Session: 67967f79-5ade-4957-bd03-0a46b0f88a03
  • Transcript: /home/jared/dev/cc-os/.claude/projects/-home-jared-dev-cc-os/67967f79-5ade-4957-bd03-0a46b0f88a03.jsonl

Skeptic verdict

VERDICT: CREATE — complete in-policy pipeline run hit a reproducible design gap; Origin chain ends in DESIGN; root candidate is this ticket.

Decision (2026-09-17)

Option A chosen by the user: a ## Retired tests section owned by test-writer in the red round, plus the unmapped-file gate fallback.

Owner sketch

module OsSdlc::Pipeline
  class TicketShape
    def retired_tests
    def missing
  end
  class RetiredTest
    def exists?(root)
  end
  class BriefFacts
    def retired_tests
  end
  class OpenImplementation
    def check_ticket_shape!
  end
end

module OsSdlc::Model::Project
  class Codebase
    def tests_for(code_file)
  end
end

Cases

  1. Given a ticket-map ticket whose ## Retired tests lists path#method entries that exist under the project root, when implementation-open runs, then it opens.
  2. Given a ticket-map ticket whose ## Retired tests lists an entry whose file or method does not exist, when implementation-open runs, then it refuses with ticket body missing: Retired tests (not found: <entry>).
  3. Given a ticket-map ticket with no ## Retired tests section, when implementation-open runs, then it opens and retired_tests is empty.
  4. Given a ticket with retired tests, when the brief facts for test-writer or test-reviewer are composed, then retired_tests carries every listed entry.
  5. Given a changed code file with no mirror test and no scenario test, when the diff-test gate selects tests, then it runs the target's full test command instead of raising.

Structural follow-through

  • test-writer charter: rewrite or delete each listed retired test in the red round; never retire a test not listed.
  • test-reviewer charter: the set to check is cases plus retired tests, nothing more and nothing less.
  • capture template agent-ready section: document ## Retired tests as optional, path#method one per line.
## Context Ticket #606 changes behavior that existing tests assert. The poodr-ticket-implementation map lets test-writer add tests and lets programmer edit only production code. No node may retire or rewrite an existing test. The programmer's gate cannot go green when the ticket invalidates an existing test. ## Observed Implementation 1 of #606 (2026-09-17, worktree ticket-606) ended `implementation_failed` with reason `verdict:error` at the programmer node, dispatch 9. The programmer's handoff disclosed the red as a test-mapping gap only. The full suite told a different story. ``` ruby plugins/os-sdlc/tests/all.rb ``` ``` 1341 runs, 2998 assertions, 14 failures, 3 errors, 0 skips ``` Unmodified main in the same session: `1331 runs, 3039 assertions, 0 failures, 0 errors, 0 skips`. Failing existing tests that assert the old behavior the ticket retires: ``` OsSdlcLspProbeDaemonTest#test_connect_with_a_stale_pid_file_removes_it_and_spawns_fresh OsSdlcLspProbeDaemonTest#test_stop_sends_only_term_to_a_daemon_that_exits_promptly OsSdlcLspProbeDaemonTest#test_stop_falls_back_to_the_single_pid_when_the_process_group_is_already_gone OsSdlcLspProbeDaemonTest#test_idle_daemon_past_the_timeout_is_no_longer_alive_and_removes_its_files OsSdlcLspProbeDaemonTest#test_open_spawns_and_returns_a_client_like_connect (ArgumentError: unknown keywords: :spawner, :clock) ``` The `verdict:error` route went straight to `implementation_failed`. The suite-check repair route never ran. ## Reproduce ``` os-sdlc-runner implementation-open SESSION 606 /home/jared/.claude/plugins/os-sdlc/maps/poodr-ticket-implementation.yaml --target os-sdlc ``` Then drive the map to the programmer node. Any ticket whose `## Cases` contradict an existing test reproduces the shape. ## Expected When a ticket's cases invalidate an existing test, one node owns the edit to that test, and the programmer's gate can reach green. Two candidate shapes; the ticket decides which: (a) The agent-ready ticket template gains a `## Retired tests` section. test-writer owns those edits in the red round. `implementation-open` refuses a ticket whose cases contradict a listed test that is not in the section. (b) The map gains an edge from programmer `verdict:error` back to test-writer, with the failing existing tests as the brief. Separately: a programmer handoff that names a mapping gap while the suite is red must be classified as a red gate, not an error. The error route pre-empted the suite-check repair route here. ## Origin - Trigger: `/os-sdlc:implement #606`, implementation 1, dispatch 9 (programmer), 2026-09-17 - Improvised this session: none; run marked `implementation_failed`, #606 labelled waiting, worktree ticket-606 kept with the increment uncommitted - Chain: programmer gate red ← existing tests assert retired behavior ← no map node may edit existing tests ← `DESIGN` (maps/poodr-ticket-implementation.yaml; test-writer and programmer agent charters) - Root candidate: this ticket - Where: plugins/os-sdlc/maps/poodr-ticket-implementation.yaml, plugins/os-sdlc/agents/test-writer.md, plugins/os-sdlc/agents/programmer.md - Session: 67967f79-5ade-4957-bd03-0a46b0f88a03 - Transcript: /home/jared/dev/cc-os/.claude/projects/-home-jared-dev-cc-os/67967f79-5ade-4957-bd03-0a46b0f88a03.jsonl ## Skeptic verdict VERDICT: CREATE — complete in-policy pipeline run hit a reproducible design gap; Origin chain ends in DESIGN; root candidate is this ticket. ## Decision (2026-09-17) Option A chosen by the user: a `## Retired tests` section owned by test-writer in the red round, plus the unmapped-file gate fallback. ## Owner sketch ```ruby module OsSdlc::Pipeline class TicketShape def retired_tests def missing end class RetiredTest def exists?(root) end class BriefFacts def retired_tests end class OpenImplementation def check_ticket_shape! end end module OsSdlc::Model::Project class Codebase def tests_for(code_file) end end ``` ## Cases 1. Given a ticket-map ticket whose `## Retired tests` lists `path#method` entries that exist under the project root, when `implementation-open` runs, then it opens. 2. Given a ticket-map ticket whose `## Retired tests` lists an entry whose file or method does not exist, when `implementation-open` runs, then it refuses with `ticket body missing: Retired tests (not found: <entry>)`. 3. Given a ticket-map ticket with no `## Retired tests` section, when `implementation-open` runs, then it opens and `retired_tests` is empty. 4. Given a ticket with retired tests, when the brief facts for test-writer or test-reviewer are composed, then `retired_tests` carries every listed entry. 5. Given a changed code file with no mirror test and no scenario test, when the diff-test gate selects tests, then it runs the target's full test command instead of raising. ## Structural follow-through - test-writer charter: rewrite or delete each listed retired test in the red round; never retire a test not listed. - test-reviewer charter: the set to check is cases plus retired tests, nothing more and nothing less. - capture template agent-ready section: document `## Retired tests` as optional, `path#method` one per line.
Author
Owner

Work started via /os-sdlc:implement on branch ticket-610 (worktree from main 418804d9, session 67967f79). First live run of the #606 daemon lifecycle at implementation-open.

Work started via /os-sdlc:implement on branch ticket-610 (worktree from main 418804d9, session 67967f79). First live run of the #606 daemon lifecycle at implementation-open.
Author
Owner

Resolution

Done: Optional ## Retired tests ticket section (path#test_method per line) parsed by Pipeline::TicketShape; implementation-open on the ticket map refuses a run when a listed file or method does not exist (Pipeline::RetiredTest); retired_tests is a BriefFacts enrichment on the test-writer and test-reviewer nodes with matching charter lines; capture template documents the section; GateCommands#diff_test_command widens to the target suite for an unmapped code file instead of raising. ADR-0202 (amends 0160 in prose). Ride-along: Daemon::Client expands root before the socket (#606 regression).

Evidence: Commit 7462a0d2 on branch ticket-610, merged to main by worktree finish. Suite: 1345 runs, 3052 assertions, 0 failures, 0 errors, twice in a row. rubocop on the 11 touched Ruby files: no offenses. Map loads through Map::Loader; 31 map tests green. Pipeline impl 2 escalated bound_exhausted:programmer-repair/fail because gate_commands_test#test_production_diff_with_no_test_file_raises asserted the retired raise; rewritten by hand to assert the suite fallback.

Follow-ups: Capture os-backlog TemplateValidator support for ## Retired tests (ADR-0095 validator does not know the section; small, agent-ready); per-user runtime dir for the daemon socket still unticketed; 34 pre-existing rubocop offenses in untouched os-sdlc files unticketed by prior decision.

## Resolution **Done:** Optional ## Retired tests ticket section (path#test_method per line) parsed by Pipeline::TicketShape; implementation-open on the ticket map refuses a run when a listed file or method does not exist (Pipeline::RetiredTest); retired_tests is a BriefFacts enrichment on the test-writer and test-reviewer nodes with matching charter lines; capture template documents the section; GateCommands#diff_test_command widens to the target suite for an unmapped code file instead of raising. ADR-0202 (amends 0160 in prose). Ride-along: Daemon::Client expands root before the socket (#606 regression). **Evidence:** Commit 7462a0d2 on branch ticket-610, merged to main by worktree finish. Suite: 1345 runs, 3052 assertions, 0 failures, 0 errors, twice in a row. rubocop on the 11 touched Ruby files: no offenses. Map loads through Map::Loader; 31 map tests green. Pipeline impl 2 escalated bound_exhausted:programmer-repair/fail because gate_commands_test#test_production_diff_with_no_test_file_raises asserted the retired raise; rewritten by hand to assert the suite fallback. **Follow-ups:** Capture os-backlog TemplateValidator support for ## Retired tests (ADR-0095 validator does not know the section; small, agent-ready); per-user runtime dir for the daemon socket still unticketed; 34 pre-existing rubocop offenses in untouched os-sdlc files unticketed by prior decision.
jared closed this issue 2026-09-17 21:27:49 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#610
No description provided.