programmer-repair gate omits the required-work step that sent it the failure #1643

Closed
opened 2026-11-08 18:28:34 +00:00 by jared · 1 comment
Owner

Problem

In maps/poodr-ticket-implementation.yaml, a suite-check fail on the required-work step routes to programmer-repair. The programmer gate runs diff-test and lint only. So the programmer reports green without running the step that failed, and the next suite-check can fail on the same step again. Each wasted loop costs one programmer, one behavior-verifier and one suite-check dispatch.

Observed

Implementation 266 (#1634): dispatch 2599 suite-check fail, gate_steps [{"step":"required-work","ms":331,"exit":1}]; dispatch 2600 programmer pass, gate_steps diff-test exit 0, lint exit 0 (14 s); dispatch 2602 suite-check fail, required-work exit 1 again; dispatch 2603 programmer pass, diff-test, lint (23 s); dispatch 2605 suite-check pass. Two loops; about 3 minutes and 4 extra dispatches.

Reproduce

Open an implementation whose ticket has a Required work item whose check fails after green. Suite-check fails on required-work, and programmer-repair passes on diff-test and lint.

Approach

The programmer-repair node in both implementation maps gets the required-work gate. suite-check sends a required-work failure to programmer-repair. With this change the programmer's own os-sdlc-gate run fails on that same step, so the programmer cannot report green until every required-work check passes.

The new gate list is [diff-test, lint, required-work]. The step goes last, so the test and lint signal stays first when the repair follows a test failure from programmer or refactorer. The check takes about 300 ms.

No Ruby change is needed. Gate::Commands#to_h maps required-work to os-sdlc-required-work-check <id> and gives nil when the ticket has no required-work checks; Gate::StepRun treats a nil command as exit 0. The gate_results brief fact already carries every step summary, so the required-work output reaches the repair brief.

The ticket map's programmer-repair instruction names only "test or lint gate failure"; it must also name required-work. The takedown map's instruction already says "the supplied gate failure".

Orchestrator work: a run on poodr-ticket-implementation cannot edit that map (map digest, #1641), so build this ticket by hand, test-first. Abandon stale implementation 219 (ticket 1510, takedown-implement, needs-orchestrator since 2026-11-02; #1510 shipped 2026-11-03) before the map edit, because it pinned the current digest of takedown-implement.yaml.

Required work:

  1. In plugins/os-sdlc/maps/poodr-ticket-implementation.yaml, set the programmer-repair node's verdict.gates to [diff-test, lint, required-work] — check: present gates: [diff-test, lint, required-work] in plugins/os-sdlc/maps/poodr-ticket-implementation.yaml
  2. In plugins/os-sdlc/maps/takedown-implement.yaml, set the programmer-repair node's verdict.gates to [diff-test, lint, required-work] — check: present gates: [diff-test, lint, required-work] in plugins/os-sdlc/maps/takedown-implement.yaml
  3. In plugins/os-sdlc/maps/poodr-ticket-implementation.yaml, change the programmer-repair instruction from "the supplied test or lint gate failure" to "the supplied test, lint, or required-work gate failure" — check: absent supplied test or lint gate in plugins/os-sdlc/maps/poodr-ticket-implementation.yaml

Rejected: required-work first, as in suite-check (a test failure from programmer or refactorer would show after the required-work result). Rejected: a test that reads the map YAML and asserts the gate list (Gate#verdict on the real node is a public seam).

Acceptance criteria

  1. Given the ticket map's programmer-repair node, a failing required-work check, and passing diff-test and lint, when the gate runs, then the verdict is fail on step required-work.
  2. Given the takedown map's programmer-repair node, a failing required-work check, and passing diff-test and lint, when the gate runs, then the verdict is fail on step required-work.
  3. Given the ticket map's programmer-repair node and a ticket with no required-work checks, when the gate runs, then the required-work step runs with no command and the verdict is pass.
  4. Given the takedown map's programmer-repair node and a ticket with no required-work checks, when the gate runs, then the required-work step runs with no command and the verdict is pass.

Owner sketch

Owner Responsibility
maps/poodr-ticket-implementation.yaml node programmer-repair Names the gates the repair dispatch must pass, and tells the programmer which failures it repairs
maps/takedown-implement.yaml node programmer-repair Names the gates the repair dispatch must pass
OsSdlc::Gate#verdict (unchanged) Runs each gate the node names, in order, and fails at the first failing step
OsSdlc::Gate::Commands#to_h (unchanged) Gives the required-work command, or nil when the ticket has no checks

Failing test at plugins/os-sdlc/tests/gate/programmer_repair_required_work_test.rb. The failing-check tests are red today because the node runs only diff-test and lint (verdict pass); the no-check tests are red because required-work is not among the recorded steps. Use the verdict constants that tests/gate_test.rb uses if Gate::Verdict::FAIL/PASS are not the names.

# frozen_string_literal: true

require_relative "../test_helper"

module OsSdlc
  class GateProgrammerRepairRequiredWorkTest < TmpdirTestCase
    TICKET_MAP = "poodr-ticket-implementation"
    TAKEDOWN_MAP = "takedown-implement"
    FAILING_CHECK = "sh -c 'echo missing 1>&2; exit 1'"
    GREEN = { Gate::Name::DIFF_TEST => "true", Gate::Name::LINT => "true" }.freeze

    def setup
      super
      use_plugin_maps
    end

    def test_a_failing_required_work_check_fails_programmer_repair_on_the_ticket_map
      verdict, = run_repair(TICKET_MAP, FAILING_CHECK)

      assert_equal Gate::Verdict::FAIL, verdict.value
      assert_equal Gate::Name::REQUIRED_WORK, verdict.step
    end

    def test_a_failing_required_work_check_fails_programmer_repair_on_the_takedown_map
      verdict, = run_repair(TAKEDOWN_MAP, FAILING_CHECK)

      assert_equal Gate::Verdict::FAIL, verdict.value
      assert_equal Gate::Name::REQUIRED_WORK, verdict.step
    end

    def test_a_ticket_without_required_work_checks_passes_programmer_repair_on_the_ticket_map
      verdict, steps = run_repair(TICKET_MAP, nil)

      assert_equal Gate::Verdict::PASS, verdict.value
      assert_includes steps, Gate::Name::REQUIRED_WORK
    end

    def test_a_ticket_without_required_work_checks_passes_programmer_repair_on_the_takedown_map
      verdict, steps = run_repair(TAKEDOWN_MAP, nil)

      assert_equal Gate::Verdict::PASS, verdict.value
      assert_includes steps, Gate::Name::REQUIRED_WORK
    end

    private

    def run_repair(map_name, required_work_command)
      steps = []
      recorder = ->(outcome, classification:) { steps << outcome.step }
      node = MapCatalog.default.load(map_name).node("programmer-repair")
      commands = GREEN.merge(Gate::Name::REQUIRED_WORK => required_work_command)
      verdict = Gate.new(node: node, commands: commands, recorder: recorder, root: @dir).verdict
      [verdict, steps]
    end
  end
end

Out of scope

  • maps/poodr-guard-implementation.yaml and maps/structure-change.yaml; neither has a programmer-repair node.
  • Adding required-work to the programmer or refactorer nodes.
  • A required-work check that only a test edit can satisfy (#1452, #954).
  • Any change to Gate::Commands, RequiredWorkCheck, or the gate_results fact.
  • Historical design records that list the old gates (docs/os-sdlc-rebuild/poodr-map-spec.md:84, poodr-implementation-map-blueprint.md:125-140).

Origin

  • Trigger: implementation 266 report (os-sdlc-implementation-report 266)
  • Improvised this session: none
  • Chain: two wasted repair loops ← programmer-repair gate set lacks required-work ← map node gates. End: CODE (map node gates or Gate::Commands)
  • Root candidate: none found; searched required-work, programmer-repair, suite-check, gates. #1452 and #954 share the loop but not the fault: their check can never pass; this check is passable but the repair gate does not run it.
  • Where: plugins/os-sdlc/maps/poodr-ticket-implementation.yaml (programmer-repair node gates)
  • Session: 87bc217f-61e2-4d4d-8470-9f3c8fd1ef43
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/87bc217f-61e2-4d4d-8470-9f3c8fd1ef43.jsonl

VERDICT: CREATE

## Problem In `maps/poodr-ticket-implementation.yaml`, a `suite-check` fail on the `required-work` step routes to `programmer-repair`. The programmer gate runs `diff-test` and `lint` only. So the programmer reports green without running the step that failed, and the next `suite-check` can fail on the same step again. Each wasted loop costs one programmer, one behavior-verifier and one suite-check dispatch. ## Observed Implementation 266 (#1634): dispatch 2599 suite-check fail, gate_steps `[{"step":"required-work","ms":331,"exit":1}]`; dispatch 2600 programmer pass, gate_steps `diff-test` exit 0, `lint` exit 0 (14 s); dispatch 2602 suite-check fail, `required-work` exit 1 again; dispatch 2603 programmer pass, `diff-test`, `lint` (23 s); dispatch 2605 suite-check pass. Two loops; about 3 minutes and 4 extra dispatches. ## Reproduce Open an implementation whose ticket has a Required work item whose check fails after green. Suite-check fails on `required-work`, and programmer-repair passes on `diff-test` and `lint`. ## Approach The `programmer-repair` node in both implementation maps gets the `required-work` gate. `suite-check` sends a `required-work` failure to `programmer-repair`. With this change the programmer's own `os-sdlc-gate` run fails on that same step, so the programmer cannot report green until every required-work check passes. The new gate list is `[diff-test, lint, required-work]`. The step goes last, so the test and lint signal stays first when the repair follows a test failure from `programmer` or `refactorer`. The check takes about 300 ms. No Ruby change is needed. `Gate::Commands#to_h` maps `required-work` to `os-sdlc-required-work-check <id>` and gives `nil` when the ticket has no required-work checks; `Gate::StepRun` treats a `nil` command as exit 0. The `gate_results` brief fact already carries every step summary, so the `required-work` output reaches the repair brief. The ticket map's `programmer-repair` instruction names only "test or lint gate failure"; it must also name required-work. The takedown map's instruction already says "the supplied gate failure". Orchestrator work: a run on `poodr-ticket-implementation` cannot edit that map (map digest, #1641), so build this ticket by hand, test-first. Abandon stale implementation 219 (ticket 1510, `takedown-implement`, `needs-orchestrator` since 2026-11-02; #1510 shipped 2026-11-03) before the map edit, because it pinned the current digest of `takedown-implement.yaml`. Required work: 1. In `plugins/os-sdlc/maps/poodr-ticket-implementation.yaml`, set the `programmer-repair` node's `verdict.gates` to `[diff-test, lint, required-work]` — check: present `gates: [diff-test, lint, required-work]` in `plugins/os-sdlc/maps/poodr-ticket-implementation.yaml` 2. In `plugins/os-sdlc/maps/takedown-implement.yaml`, set the `programmer-repair` node's `verdict.gates` to `[diff-test, lint, required-work]` — check: present `gates: [diff-test, lint, required-work]` in `plugins/os-sdlc/maps/takedown-implement.yaml` 3. In `plugins/os-sdlc/maps/poodr-ticket-implementation.yaml`, change the `programmer-repair` instruction from "the supplied test or lint gate failure" to "the supplied test, lint, or required-work gate failure" — check: absent `supplied test or lint gate` in `plugins/os-sdlc/maps/poodr-ticket-implementation.yaml` Rejected: `required-work` first, as in `suite-check` (a test failure from `programmer` or `refactorer` would show after the required-work result). Rejected: a test that reads the map YAML and asserts the gate list (`Gate#verdict` on the real node is a public seam). ## Acceptance criteria 1. Given the ticket map's `programmer-repair` node, a failing required-work check, and passing diff-test and lint, when the gate runs, then the verdict is fail on step `required-work`. 2. Given the takedown map's `programmer-repair` node, a failing required-work check, and passing diff-test and lint, when the gate runs, then the verdict is fail on step `required-work`. 3. Given the ticket map's `programmer-repair` node and a ticket with no required-work checks, when the gate runs, then the `required-work` step runs with no command and the verdict is pass. 4. Given the takedown map's `programmer-repair` node and a ticket with no required-work checks, when the gate runs, then the `required-work` step runs with no command and the verdict is pass. ## Owner sketch | Owner | Responsibility | |---|---| | `maps/poodr-ticket-implementation.yaml` node `programmer-repair` | Names the gates the repair dispatch must pass, and tells the programmer which failures it repairs | | `maps/takedown-implement.yaml` node `programmer-repair` | Names the gates the repair dispatch must pass | | `OsSdlc::Gate#verdict` (unchanged) | Runs each gate the node names, in order, and fails at the first failing step | | `OsSdlc::Gate::Commands#to_h` (unchanged) | Gives the `required-work` command, or `nil` when the ticket has no checks | Failing test at `plugins/os-sdlc/tests/gate/programmer_repair_required_work_test.rb`. The failing-check tests are red today because the node runs only `diff-test` and `lint` (verdict pass); the no-check tests are red because `required-work` is not among the recorded steps. Use the verdict constants that `tests/gate_test.rb` uses if `Gate::Verdict::FAIL`/`PASS` are not the names. ```ruby # frozen_string_literal: true require_relative "../test_helper" module OsSdlc class GateProgrammerRepairRequiredWorkTest < TmpdirTestCase TICKET_MAP = "poodr-ticket-implementation" TAKEDOWN_MAP = "takedown-implement" FAILING_CHECK = "sh -c 'echo missing 1>&2; exit 1'" GREEN = { Gate::Name::DIFF_TEST => "true", Gate::Name::LINT => "true" }.freeze def setup super use_plugin_maps end def test_a_failing_required_work_check_fails_programmer_repair_on_the_ticket_map verdict, = run_repair(TICKET_MAP, FAILING_CHECK) assert_equal Gate::Verdict::FAIL, verdict.value assert_equal Gate::Name::REQUIRED_WORK, verdict.step end def test_a_failing_required_work_check_fails_programmer_repair_on_the_takedown_map verdict, = run_repair(TAKEDOWN_MAP, FAILING_CHECK) assert_equal Gate::Verdict::FAIL, verdict.value assert_equal Gate::Name::REQUIRED_WORK, verdict.step end def test_a_ticket_without_required_work_checks_passes_programmer_repair_on_the_ticket_map verdict, steps = run_repair(TICKET_MAP, nil) assert_equal Gate::Verdict::PASS, verdict.value assert_includes steps, Gate::Name::REQUIRED_WORK end def test_a_ticket_without_required_work_checks_passes_programmer_repair_on_the_takedown_map verdict, steps = run_repair(TAKEDOWN_MAP, nil) assert_equal Gate::Verdict::PASS, verdict.value assert_includes steps, Gate::Name::REQUIRED_WORK end private def run_repair(map_name, required_work_command) steps = [] recorder = ->(outcome, classification:) { steps << outcome.step } node = MapCatalog.default.load(map_name).node("programmer-repair") commands = GREEN.merge(Gate::Name::REQUIRED_WORK => required_work_command) verdict = Gate.new(node: node, commands: commands, recorder: recorder, root: @dir).verdict [verdict, steps] end end end ``` ## Out of scope - `maps/poodr-guard-implementation.yaml` and `maps/structure-change.yaml`; neither has a `programmer-repair` node. - Adding `required-work` to the `programmer` or `refactorer` nodes. - A required-work check that only a test edit can satisfy (#1452, #954). - Any change to `Gate::Commands`, `RequiredWorkCheck`, or the `gate_results` fact. - Historical design records that list the old gates (`docs/os-sdlc-rebuild/poodr-map-spec.md:84`, `poodr-implementation-map-blueprint.md:125-140`). ## Origin - Trigger: implementation 266 report (os-sdlc-implementation-report 266) - Improvised this session: none - Chain: two wasted repair loops ← programmer-repair gate set lacks required-work ← map node gates. End: CODE (map node gates or Gate::Commands) - Root candidate: none found; searched required-work, programmer-repair, suite-check, gates. #1452 and #954 share the loop but not the fault: their check can never pass; this check is passable but the repair gate does not run it. - Where: plugins/os-sdlc/maps/poodr-ticket-implementation.yaml (programmer-repair node gates) - Session: 87bc217f-61e2-4d4d-8470-9f3c8fd1ef43 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/87bc217f-61e2-4d4d-8470-9f3c8fd1ef43.jsonl VERDICT: CREATE
Author
Owner

Resolution

Done: programmer-repair gates are [diff-test, lint, required-work] in poodr-ticket-implementation.yaml and takedown-implement.yaml; the ticket map instruction names required-work failures. All 4 acceptance criteria covered by tests/gate/programmer_repair_required_work_test.rb.

Evidence: Branch ticket-1643: 644b4100 (maps + test), b6a92109 (history row). Built by hand test-first: 4 tests red before the map edit, green after; suite 3563 runs, 0 failures, 66.4 s; rubocop 0 offenses. Stale implementation 219 (ticket 1510) abandoned first because it pinned the takedown map digest.

Follow-ups: none

## Resolution **Done:** programmer-repair gates are [diff-test, lint, required-work] in poodr-ticket-implementation.yaml and takedown-implement.yaml; the ticket map instruction names required-work failures. All 4 acceptance criteria covered by tests/gate/programmer_repair_required_work_test.rb. **Evidence:** Branch ticket-1643: 644b4100 (maps + test), b6a92109 (history row). Built by hand test-first: 4 tests red before the map edit, green after; suite 3563 runs, 0 failures, 66.4 s; rubocop 0 offenses. Stale implementation 219 (ticket 1510) abandoned first because it pinned the takedown map digest. **Follow-ups:** none <!-- os-backlog kind: resolution -->
jared closed this issue 2026-11-08 19:24: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#1643
No description provided.