Replace shell Required-work checks with typed present/absent checks that Ruby evaluates #843

Closed
opened 2026-09-30 14:13:11 +00:00 by jared · 4 comments
Owner

Problem

A Required-work check is free shell text today. RequiredWorkCheck#run gives the text to ProjectShell.capture2e, and exit 0 means pass. Authors write checks that cannot run, or that pass while the work is not done:

Ticket Item Check Result
#836 Record an ADR /os-adr:find implementation-report Gate crash: Errno::ENOENT - /os-adr:find
#829 Remove CLI:: grep -rn "CLI::" plugins/os-sdlc Passes only while CLI:: is still there
#830 Remove every exit and warn grep -rn "exit|warn" …/command Same inverted result
#831 Delete evidence grep -n evidence …/os_sdlc.rb Same inverted result
#816 Count must be 0 grep -c "for_summary" … prints 0 "prints 0" is outside the backticks, so the gate does not test it

Of the checks in #815–#842, 11 were text searches in repo files, 3 ran tests that the suite gate already runs, and 1 was a slash command. No check needed a shell. The ticket rules also do not say which items a pipeline agent can own, so an ADR item (#836 item 6) can reach the gate.

Decision: ADR-0255 (amends ADR-0244 decision 4 and ADR-0209).

Observed

os-sdlc-required-work-check 4
os-sdlc-required-work-check: harness error: Errno::ENOENT: No such file or directory - /os-adr:find

Reproduce

n/a: design change; #836 implementation 4 is the recorded instance.

Approach

A check has exactly two forms. Ruby evaluates them in the process. No shell starts.

1. Remove CLI:: — check: absent `CLI::` in `plugins/os-sdlc/lib/**/*.rb`
2. Bump schema — check: present `SCHEMA_VERSION = 25` in `plugins/os-sdlc/lib/os_sdlc/adapter/db.rb`
  • The text is a literal substring. The glob is relative to the root that DispatchCommands.required_work_check receives today.
  • present passes when at least one matched file contains the text. absent passes when no matched file contains the text.
  • A glob that matches no file fails for both forms.
  • Any other check: form is refused at implementation-open (TicketShape#missing). The os-backlog validate-template refusal is #847. This includes shell text, slash commands, and free text. The refusal names the item: Required work check is not present/absent in: <item>.
  • The placement rule does not change: the first check: in an item's lines is the item's check.
  • The gate order [required-work, suite] does not change. The os-sdlc-required-work-check bin, the gate step, and the repair route also do not change.
  • A failed check reports the item, the check, and one reason: text found in <path>, text not found, or glob matched no file.
  • REQUIRED_WORK_OUTPUT_LIMIT and the shell-output cap are deleted.

Required work:

  1. Replace the backticked-command parse in plugins/os-sdlc/lib/os_sdlc/parser/ticket_shape/required_work.rb with the typed present/absent parse, and refuse every other form through TicketShape#missing.
  2. Replace ProjectShell.capture2e in plugins/os-sdlc/lib/os_sdlc/adapter/required_work_check.rb with an in-process evaluation (Dir.glob, File.read, include?).
  3. Reshape RequiredWorkReport::Result from (item, command, status, output) to item, check, and reason. Delete REQUIRED_WORK_OUTPUT_LIMIT from dispatch_commands.rb.

Orchestrator work (not for pipeline agents):

  • ADR-0255 (recorded 2026-09-30).
  • After #847 merges, add a typed-check case to plugins/os-sdlc/tests/parser/ticket_shape_parity_test.rb.
  • In plugins/os-backlog/references/templates/capture.md, change line 25 to the typed form. Change line 61 from "its own check command" to "its own check". Replace the line 68 example with a typed check. Add this rule: "Each Required-work item is a file change inside the target that a pipeline agent can make. ADR, tracker, and skill-driven work goes on a separate Orchestrator work: line, never in Required work. Test and lint runs are not checks; the suite gate runs them."
  • Add the typed-check rule to plugins/os-backlog/references/body-style.md.
  • In plugins/os-sdlc/agents/change-auditor.md:49, delete the word "command" after the check keyword.
  • After merge, rewrite the old-form checks in the open tickets #842, #812, #806, #777, #773, #748, and #741. The #748 test-run check becomes an acceptance criterion.

Rejected: refuse only slash commands and keep shell checks — this fixes #836 but not the inverted checks or the #841 crash class.
Rejected: regex patterns — no check in #815–#842 needed one.
Rejected: a transition window that accepts both forms — only 7 open tickets use the old form.

Blocks: #847 (os-backlog part, split 2026-09-30 because an implementation takes one target).

Acceptance criteria

  1. Given a ticket with the check absent `CLI::` in `lib/**/*.rb` and no matched file that contains CLI::, when the required-work gate step runs, then the step passes.
  2. Given the same check and a matched file that contains CLI::, when the step runs, then it fails and reports the item, the check, and text found in <path>.
  3. Given the check present `X` in `lib/a.rb` and lib/a.rb does not contain X, when the step runs, then it fails and reports text not found.
  4. Given a check whose glob matches no file, when the step runs, then it fails and reports glob matched no file.
  5. Given a check that is backticked shell text, for example check: `grep -q X lib/a.rb` , when implementation-open runs under the ticket map, then open refuses and names the item.
  6. Given the check check: `/os-adr:find foo` , when implementation-open runs, then open refuses and names the item.
  7. Given a check written in the middle of an item line, when the ticket is parsed, then the typed check is read as it is today.
  8. Given a required-work gate run, when the checks are evaluated, then no child process starts for any check.

Owner sketch

class OsSdlc::TicketShape::RequiredWork
  Check = Data.define(:polarity, :text, :glob) do
    def self.parse(tail) end
    def evaluate(root) end
  end

  Item = Data.define(:lines) do
    def check end
    def typed_check? end
  end
end

class OsSdlc::RequiredWorkCheck
  def report(items) end
end

class OsSdlc::RequiredWorkReport
  Result = Data.define(:item, :check, :reason) do
    def failed? end
    def lines end
  end
end

Retired tests

plugins/os-sdlc/tests/command/dispatch_commands_required_work_check_test.rb
plugins/os-sdlc/tests/parser/ticket_shape_mid_line_check_test.rb
plugins/os-sdlc/tests/query/gate/commands_required_work_test.rb#test_registry_offers_a_required_work_step_for_a_ticket_with_checks
plugins/os-sdlc/tests/command/implementation/open_ticket_shape_test.rb#test_open_raises_naming_the_item_when_check_has_no_backticked_command

Blast radius (2026-09-30)

  • Code consumers: parser/ticket_shape/required_work.rb, parser/ticket_shape.rb (required_work_items, required_work_checks, unchecked_required_work), adapter/required_work_check.rb, model/required_work_report/result.rb, command/dispatch_commands.rb:7,112-118, os-backlog required_work.rb and heading_content_issues.rb. open.rb, query/gate/commands.rb, the bin, the boot wiring, and the gate map do not change.
  • Gate::Evaluator (adapter/gate/evaluator.rb:20) still starts the os-sdlc-required-work-check bin through ProjectShell. That is in scope for #845, not this ticket.
  • Pinning tests: listed under Retired tests. ticket_shape_parity_test.rb has no check: case (Orchestrator work, after #847).
  • Data: 7 open tickets use the old form (Orchestrator work). The 4 stored implementations are terminal. Other repos' .sdlc/project.yaml files have no check: keys.
  • Risks: (1) an item that needs a proof that is not a text search. The item then needs an acceptance criterion. (2) The glob root must be the implementation worktree, not the main checkout. Criterion 1 runs against the root the gate receives. (3) Dir.glob skips dotfiles by default.
  • ADRs: ADR-0244 decision 4 is superseded; ADR-0244 decisions 1 and 5 and ADR-0209 line 22 are reworded.

Origin

  • Trigger: /os-sdlc:implement #836, implementation 4, dispatch 30 suite-check
  • Improvised this session: hand-finish of #836; the orchestrator writes the ADR
  • Chain: suite-check crash ← slash-command check run as shell (adapter/required_work_check.rb:16) ← check is free shell text ← DESIGN: ADR-0244 decision 4, ADR-0209
  • Root candidate: this ticket (the crash class is #841; the wider shell audit is #845)
  • Where: plugins/os-sdlc/lib/os_sdlc/parser/ticket_shape/required_work.rb; plugins/os-sdlc/lib/os_sdlc/adapter/required_work_check.rb; plugins/os-backlog/lib/backlog/required_work.rb
  • Session: 3ce2d004-fe35-42ec-ba9a-b9bc2d1e9097 (filed); fc9f2295-3034-4595-9344-2d94ad4e3ee1 (redesigned 2026-09-30)
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/fc9f2295-3034-4595-9344-2d94ad4e3ee1.jsonl
## Problem A Required-work check is free shell text today. `RequiredWorkCheck#run` gives the text to `ProjectShell.capture2e`, and exit 0 means pass. Authors write checks that cannot run, or that pass while the work is not done: | Ticket | Item | Check | Result | | --- | --- | --- | --- | | #836 | Record an ADR | `` /os-adr:find implementation-report `` | Gate crash: `Errno::ENOENT - /os-adr:find` | | #829 | Remove `CLI::` | `` grep -rn "CLI::" plugins/os-sdlc `` | Passes only while `CLI::` is still there | | #830 | Remove every `exit` and `warn` | `` grep -rn "exit\|warn" …/command `` | Same inverted result | | #831 | Delete `evidence` | `` grep -n evidence …/os_sdlc.rb `` | Same inverted result | | #816 | Count must be 0 | `` grep -c "for_summary" … `` prints 0 | "prints 0" is outside the backticks, so the gate does not test it | Of the checks in #815–#842, 11 were text searches in repo files, 3 ran tests that the suite gate already runs, and 1 was a slash command. No check needed a shell. The ticket rules also do not say which items a pipeline agent can own, so an ADR item (#836 item 6) can reach the gate. Decision: ADR-0255 (amends ADR-0244 decision 4 and ADR-0209). ## Observed ``` os-sdlc-required-work-check 4 ``` ``` os-sdlc-required-work-check: harness error: Errno::ENOENT: No such file or directory - /os-adr:find ``` ## Reproduce n/a: design change; #836 implementation 4 is the recorded instance. ## Approach A check has exactly two forms. Ruby evaluates them in the process. No shell starts. ```md 1. Remove CLI:: — check: absent `CLI::` in `plugins/os-sdlc/lib/**/*.rb` 2. Bump schema — check: present `SCHEMA_VERSION = 25` in `plugins/os-sdlc/lib/os_sdlc/adapter/db.rb` ``` - The text is a literal substring. The glob is relative to the root that `DispatchCommands.required_work_check` receives today. - `present` passes when at least one matched file contains the text. `absent` passes when no matched file contains the text. - A glob that matches no file fails for both forms. - Any other `check:` form is refused at implementation-open (`TicketShape#missing`). The os-backlog `validate-template` refusal is #847. This includes shell text, slash commands, and free text. The refusal names the item: `Required work check is not present/absent `<text>` in `<glob>`: <item>`. - The placement rule does not change: the first `check:` in an item's lines is the item's check. - The gate order `[required-work, suite]` does not change. The `os-sdlc-required-work-check` bin, the gate step, and the repair route also do not change. - A failed check reports the item, the check, and one reason: `text found in <path>`, `text not found`, or `glob matched no file`. - `REQUIRED_WORK_OUTPUT_LIMIT` and the shell-output cap are deleted. Required work: 1. Replace the backticked-command parse in `plugins/os-sdlc/lib/os_sdlc/parser/ticket_shape/required_work.rb` with the typed present/absent parse, and refuse every other form through `TicketShape#missing`. 2. Replace `ProjectShell.capture2e` in `plugins/os-sdlc/lib/os_sdlc/adapter/required_work_check.rb` with an in-process evaluation (`Dir.glob`, `File.read`, `include?`). 3. Reshape `RequiredWorkReport::Result` from `(item, command, status, output)` to item, check, and reason. Delete `REQUIRED_WORK_OUTPUT_LIMIT` from `dispatch_commands.rb`. Orchestrator work (not for pipeline agents): - ADR-0255 (recorded 2026-09-30). - After #847 merges, add a typed-check case to `plugins/os-sdlc/tests/parser/ticket_shape_parity_test.rb`. - In `plugins/os-backlog/references/templates/capture.md`, change line 25 to the typed form. Change line 61 from "its own check command" to "its own check". Replace the line 68 example with a typed check. Add this rule: "Each Required-work item is a file change inside the target that a pipeline agent can make. ADR, tracker, and skill-driven work goes on a separate `Orchestrator work:` line, never in Required work. Test and lint runs are not checks; the suite gate runs them." - Add the typed-check rule to `plugins/os-backlog/references/body-style.md`. - In `plugins/os-sdlc/agents/change-auditor.md:49`, delete the word "command" after the check keyword. - After merge, rewrite the old-form checks in the open tickets #842, #812, #806, #777, #773, #748, and #741. The #748 test-run check becomes an acceptance criterion. Rejected: refuse only slash commands and keep shell checks — this fixes #836 but not the inverted checks or the #841 crash class. Rejected: regex patterns — no check in #815–#842 needed one. Rejected: a transition window that accepts both forms — only 7 open tickets use the old form. Blocks: #847 (os-backlog part, split 2026-09-30 because an implementation takes one target). ## Acceptance criteria 1. Given a ticket with the check ``absent `CLI::` in `lib/**/*.rb` `` and no matched file that contains `CLI::`, when the required-work gate step runs, then the step passes. 2. Given the same check and a matched file that contains `CLI::`, when the step runs, then it fails and reports the item, the check, and `text found in <path>`. 3. Given the check ``present `X` in `lib/a.rb` `` and `lib/a.rb` does not contain `X`, when the step runs, then it fails and reports `text not found`. 4. Given a check whose glob matches no file, when the step runs, then it fails and reports `glob matched no file`. 5. Given a check that is backticked shell text, for example ``check: `grep -q X lib/a.rb` ``, when implementation-open runs under the ticket map, then open refuses and names the item. 6. Given the check ``check: `/os-adr:find foo` ``, when implementation-open runs, then open refuses and names the item. 7. Given a check written in the middle of an item line, when the ticket is parsed, then the typed check is read as it is today. 8. Given a required-work gate run, when the checks are evaluated, then no child process starts for any check. ## Owner sketch ```ruby class OsSdlc::TicketShape::RequiredWork Check = Data.define(:polarity, :text, :glob) do def self.parse(tail) end def evaluate(root) end end Item = Data.define(:lines) do def check end def typed_check? end end end class OsSdlc::RequiredWorkCheck def report(items) end end class OsSdlc::RequiredWorkReport Result = Data.define(:item, :check, :reason) do def failed? end def lines end end end ``` ## Retired tests plugins/os-sdlc/tests/command/dispatch_commands_required_work_check_test.rb plugins/os-sdlc/tests/parser/ticket_shape_mid_line_check_test.rb plugins/os-sdlc/tests/query/gate/commands_required_work_test.rb#test_registry_offers_a_required_work_step_for_a_ticket_with_checks plugins/os-sdlc/tests/command/implementation/open_ticket_shape_test.rb#test_open_raises_naming_the_item_when_check_has_no_backticked_command ## Blast radius (2026-09-30) - Code consumers: `parser/ticket_shape/required_work.rb`, `parser/ticket_shape.rb` (`required_work_items`, `required_work_checks`, `unchecked_required_work`), `adapter/required_work_check.rb`, `model/required_work_report/result.rb`, `command/dispatch_commands.rb:7,112-118`, os-backlog `required_work.rb` and `heading_content_issues.rb`. `open.rb`, `query/gate/commands.rb`, the bin, the boot wiring, and the gate map do not change. - `Gate::Evaluator` (`adapter/gate/evaluator.rb:20`) still starts the `os-sdlc-required-work-check` bin through ProjectShell. That is in scope for #845, not this ticket. - Pinning tests: listed under Retired tests. `ticket_shape_parity_test.rb` has no `check:` case (Orchestrator work, after #847). - Data: 7 open tickets use the old form (Orchestrator work). The 4 stored implementations are terminal. Other repos' `.sdlc/project.yaml` files have no `check:` keys. - Risks: (1) an item that needs a proof that is not a text search. The item then needs an acceptance criterion. (2) The glob root must be the implementation worktree, not the main checkout. Criterion 1 runs against the root the gate receives. (3) `Dir.glob` skips dotfiles by default. - ADRs: ADR-0244 decision 4 is superseded; ADR-0244 decisions 1 and 5 and ADR-0209 line 22 are reworded. ## Origin - Trigger: /os-sdlc:implement #836, implementation 4, dispatch 30 suite-check - Improvised this session: hand-finish of #836; the orchestrator writes the ADR - Chain: suite-check crash ← slash-command check run as shell (`adapter/required_work_check.rb:16`) ← check is free shell text ← DESIGN: ADR-0244 decision 4, ADR-0209 - Root candidate: this ticket (the crash class is #841; the wider shell audit is #845) - Where: plugins/os-sdlc/lib/os_sdlc/parser/ticket_shape/required_work.rb; plugins/os-sdlc/lib/os_sdlc/adapter/required_work_check.rb; plugins/os-backlog/lib/backlog/required_work.rb - Session: 3ce2d004-fe35-42ec-ba9a-b9bc2d1e9097 (filed); fc9f2295-3034-4595-9344-2d94ad4e3ee1 (redesigned 2026-09-30) - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/fc9f2295-3034-4595-9344-2d94ad4e3ee1.jsonl
jared changed title from Define which Required-work items an os-sdlc agent can own; refuse slash-command checks to Replace shell Required-work checks with typed present/absent checks that Ruby evaluates 2026-09-30 16:47:34 +00:00
Author
Owner

Work started: /os-sdlc:implement on branch ticket-843 (worktree .claude/worktrees/ticket-843). Design: ADR-0255.

Work started: /os-sdlc:implement on branch ticket-843 (worktree .claude/worktrees/ticket-843). Design: ADR-0255.
Author
Owner

Blocked by #841. Implementation 10 failed at dispatch 61 (test-writer): os-sdlc-gate crashed two times with Sequel::NotNullConstraintViolation correction_rounds.gate_type. The retired-tests gate step failed, StepOutcome#gate_type returns nil for retired-tests (only lint/test types exist), and Gate::ViolationRecorder#record_round inserts a round with a nil gate_type. This is the #841 crash; it hits every failing gate step that is not lint or test (required-work, retired-tests). #843 cannot pass its gates until #841 ships. Earlier: implementation 6 escalated on an under-decomposed ticket (prose items moved to orchestrator work); implementation 7 could not converge because the os-backlog part is outside the os-sdlc target (split to #847). Worktree ticket-843 kept; branch has ADR-0255 (8595eb61).

Blocked by #841. Implementation 10 failed at dispatch 61 (test-writer): os-sdlc-gate crashed two times with Sequel::NotNullConstraintViolation correction_rounds.gate_type. The retired-tests gate step failed, StepOutcome#gate_type returns nil for retired-tests (only lint/test types exist), and Gate::ViolationRecorder#record_round inserts a round with a nil gate_type. This is the #841 crash; it hits every failing gate step that is not lint or test (required-work, retired-tests). #843 cannot pass its gates until #841 ships. Earlier: implementation 6 escalated on an under-decomposed ticket (prose items moved to orchestrator work); implementation 7 could not converge because the os-backlog part is outside the os-sdlc target (split to #847). Worktree ticket-843 kept; branch has ADR-0255 (8595eb61).
Author
Owner

Resolution

Done: Required-work checks are typed present/absent text checks evaluated in Ruby (ADR-0255). TicketShape::RequiredWork::Check parses the form and decides the reason; RequiredWorkCheck reads matched files in process with no ProjectShell; implementation-open refuses any other check form and names the item; RequiredWorkReport::Result is item/check/reason; REQUIRED_WORK_OUTPUT_LIMIT deleted. capture.md, body-style.md and change-auditor.md updated; os-sdlc History row added.

Evidence: Branch ticket-843: 4c297ef9 (Runner commit, implementation 13), ffb44384 (retired tests), eb8ecd76 (docs), 8595eb61 (ADR-0255). Implementation 13 implementation_complete: behavior-verifier pass on all 8 criteria, suite-check pass (required-work + suite), contract-auditor pass after one refactor round. os-sdlc suite 1329 runs, 0 failures.

Follow-ups: #847 os-backlog validate-template refusal (split by target; then add the typed-check parity case). #845 shell-call audit. The old-form checks in open tickets #842 #812 #806 #777 #773 #748 #741 are rewritten by the orchestrator in this session after merge.

## Resolution **Done:** Required-work checks are typed present/absent text checks evaluated in Ruby (ADR-0255). TicketShape::RequiredWork::Check parses the form and decides the reason; RequiredWorkCheck reads matched files in process with no ProjectShell; implementation-open refuses any other check form and names the item; RequiredWorkReport::Result is item/check/reason; REQUIRED_WORK_OUTPUT_LIMIT deleted. capture.md, body-style.md and change-auditor.md updated; os-sdlc History row added. **Evidence:** Branch ticket-843: 4c297ef9 (Runner commit, implementation 13), ffb44384 (retired tests), eb8ecd76 (docs), 8595eb61 (ADR-0255). Implementation 13 implementation_complete: behavior-verifier pass on all 8 criteria, suite-check pass (required-work + suite), contract-auditor pass after one refactor round. os-sdlc suite 1329 runs, 0 failures. **Follow-ups:** #847 os-backlog validate-template refusal (split by target; then add the typed-check parity case). #845 shell-call audit. The old-form checks in open tickets #842 #812 #806 #777 #773 #748 #741 are rewritten by the orchestrator in this session after merge.
jared closed this issue 2026-09-30 17:49:33 +00:00
Author
Owner

Lens proposal A1 (dispatch 122) not filed: the challenge found a code defect, not a lens edit (Check in parser/ while RequiredWork, RequiredWorkCheck, RequiredWorkReport share a prefix with no owning model), and ticket-skeptic returned DROP (speculative hardening; no observed failure).

Lens proposal A1 (dispatch 122) not filed: the challenge found a code defect, not a lens edit (Check in parser/ while RequiredWork, RequiredWorkCheck, RequiredWorkReport share a prefix with no owning model), and ticket-skeptic returned DROP (speculative hardening; no observed failure).
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#843
No description provided.