Fix ScopePolicy.load so the pre-tool-use write guard is active [capture] #571

Closed
opened 2026-09-14 13:45:02 +00:00 by jared · 2 comments
Owner

Context

The responsibility audit found that the pre-tool-use write guard never
blocks a write. ScopePolicy.load calls OsSdlc::Project.load without the
required target: keyword. The rescue on the next line swallows the error.

Observed

Project.load(dir = Dir.pwd, target:) requires target:.
lib/os_sdlc/scope_policy.rb:15 calls OsSdlc::Project.load.test_path. Line 16
rescue StandardError swallows the ArgumentError: missing keyword: :target.
So test_path is always nil, out_of_scope? always returns false, and the
guard at hooks/pre_tool_use.rb:26 never blocks a test-writer from writing
production code.

Reproduce

cd plugins/os-sdlc && ruby -Ilib -e 'require "os_sdlc/project";
OsSdlc::Project.load'

Expected

A Minitest snippet test_scope_policy_load_reads_test_path_from_project that
asserts ScopePolicy.load.test_path equals the project's configured test_path
when .sdlc/project.yaml exists, and a second assertion that out_of_scope?
returns true for a production path when the role is test-writer.

Origin

• Trigger: /os-sdlc:responsibility-audit run 13 on plugins/os-
sdlc/lib/os_sdlc/project.rb, 2026-09-14
• Improvised this session: none
• Chain: guard inert ← test_path nil ← swallowed ArgumentError ← Project.
load signature changed to require target: without updating ScopePolicy.load
(DESIGN, lib/os_sdlc/scope_policy.rb:15)
• Root candidate: this ticket
• Where: lib/os_sdlc/scope_policy.rb
• Session: 23a382aa-f78e-495f-9cbd-0934e163b37d
• Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/23a382aa-
f78e-495f-9cbd-0934e163b37d.jsonl

Tasks

  • ScopePolicy.load reads test_path from implementation.project (ADR-0169), never from Project.load.
  • Remove the rescue StandardError that swallowed the load error; a missing implementation yields test_path: nil explicitly.
  • Project.upsert calls ensure_creatable! when project.yaml is absent (git-repo check and overwrite refusal in one place).
  • Delete Project.create (no sender outside lib and tests; verified 2026-09-14).
  • The missing-project error message at project.rb:42 names os-sdlc project-config NAME, not OsSdlc::Project.create.

Acceptance criteria

  • With a project whose test_path is test, a test-writer role Write to lib/foo.rb is out_of_scope? true.
  • Project.upsert on a non-git directory raises the not-a-git-repo error.
  • Project.upsert on an existing project.yaml merges keys and does not raise.
  • OsSdlc::Project.respond_to?(:create) is false.
  • The MissingProject message includes os-sdlc project-config.

Structural follow-through

  • tests/support/project_scenario.rb:90 and tests/runner/brief_facts_enrichment_facts_test.rb:173 switch from Project.create to Project.upsert.
  • Check: rg -n 'Project\.create' plugins/os-sdlc returns nothing.

ADR case

Completes ADR-0169 (scope_policy.rb reads implementation.project). Completes ADR-0156. Amends ADR-0177 (Project.upsert is the sole creator of project.yaml); create the amending ADR when this ticket lands.

Batch

Map: #570. Position 1 of 4. Followed by #573. Map: maps/poodr-ticket-implementation.yaml.

## Context The responsibility audit found that the pre-tool-use write guard never blocks a write. ScopePolicy.load calls OsSdlc::Project.load without the required target: keyword. The rescue on the next line swallows the error. ## Observed Project.load(dir = Dir.pwd, target:) requires target:. lib/os_sdlc/scope_policy.rb:15 calls OsSdlc::Project.load.test_path. Line 16 rescue StandardError swallows the ArgumentError: missing keyword: :target. So test_path is always nil, out_of_scope? always returns false, and the guard at hooks/pre_tool_use.rb:26 never blocks a test-writer from writing production code. ## Reproduce cd plugins/os-sdlc && ruby -Ilib -e 'require "os_sdlc/project"; OsSdlc::Project.load' ## Expected A Minitest snippet test_scope_policy_load_reads_test_path_from_project that asserts ScopePolicy.load.test_path equals the project's configured test_path when .sdlc/project.yaml exists, and a second assertion that out_of_scope? returns true for a production path when the role is test-writer. ## Origin • Trigger: /os-sdlc:responsibility-audit run 13 on plugins/os- sdlc/lib/os_sdlc/project.rb, 2026-09-14 • Improvised this session: none • Chain: guard inert ← test_path nil ← swallowed ArgumentError ← Project. load signature changed to require target: without updating ScopePolicy.load (DESIGN, lib/os_sdlc/scope_policy.rb:15) • Root candidate: this ticket • Where: lib/os_sdlc/scope_policy.rb • Session: 23a382aa-f78e-495f-9cbd-0934e163b37d • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/23a382aa- f78e-495f-9cbd-0934e163b37d.jsonl ## Tasks - [ ] `ScopePolicy.load` reads `test_path` from `implementation.project` (ADR-0169), never from `Project.load`. - [ ] Remove the `rescue StandardError` that swallowed the load error; a missing implementation yields `test_path: nil` explicitly. - [ ] `Project.upsert` calls `ensure_creatable!` when `project.yaml` is absent (git-repo check and overwrite refusal in one place). - [ ] Delete `Project.create` (no sender outside lib and tests; verified 2026-09-14). - [ ] The missing-project error message at project.rb:42 names `os-sdlc project-config NAME`, not `OsSdlc::Project.create`. ## Acceptance criteria - With a project whose `test_path` is `test`, a test-writer role Write to `lib/foo.rb` is `out_of_scope?` true. - `Project.upsert` on a non-git directory raises the not-a-git-repo error. - `Project.upsert` on an existing `project.yaml` merges keys and does not raise. - `OsSdlc::Project.respond_to?(:create)` is false. - The `MissingProject` message includes `os-sdlc project-config`. ## Structural follow-through - `tests/support/project_scenario.rb:90` and `tests/runner/brief_facts_enrichment_facts_test.rb:173` switch from `Project.create` to `Project.upsert`. - Check: `rg -n 'Project\.create' plugins/os-sdlc` returns nothing. ## ADR case Completes ADR-0169 (scope_policy.rb reads `implementation.project`). Completes ADR-0156. Amends ADR-0177 (`Project.upsert` is the sole creator of project.yaml); create the amending ADR when this ticket lands. ## Batch Map: #570. Position 1 of 4. Followed by #573. Map: maps/poodr-ticket-implementation.yaml.
Author
Owner

Work started via /os-sdlc:responsibility-refactor batch 2 (map #570). Branch: ticket-571, worktree .claude/worktrees/ticket-571, map poodr-ticket-implementation.yaml.

Work started via /os-sdlc:responsibility-refactor batch 2 (map #570). Branch: ticket-571, worktree .claude/worktrees/ticket-571, map poodr-ticket-implementation.yaml.
Author
Owner

Resolution

Done: ScopePolicy.load resolves the project from cwd (Project::Yaml.containing + Project.load with OS_SDLC_TARGET), rescues only TargetSelectionError; scope_policy.rb requires what the hook process needs. Project.create deleted; Project.upsert calls ensure_creatable! on the create branch; missing-project message names os-sdlc project-config. ADR-0178 records it.

Evidence: Merged to main 8daffca (code 9b41175). Suite: 1220 runs, 0 failures. Rubocop clean on 7 touched files. Hand-verified: judge-role Edit denied; test-writer Write to lib denied and to tests allowed with OS_SDLC_TARGET=os-sdlc. Pipeline: 8 dispatches, 901 s, bound_exhausted after 3 programmer-repair rounds; deviations: hand-moved test-side Project.create callers to upsert, removed the obsolete clobber-refusal test, added six require_relative lines to scope_policy.rb (hook has no Zeitwerk). Position 1 of 4 on map #570.

Follow-ups: #575 hook cannot learn the target at a multi-target root (guard permissive there; skeptic CREATE). Structural follow-through: done (rg 'Project.create' returns only the removal test).

## Resolution **Done:** ScopePolicy.load resolves the project from cwd (Project::Yaml.containing + Project.load with OS_SDLC_TARGET), rescues only TargetSelectionError; scope_policy.rb requires what the hook process needs. Project.create deleted; Project.upsert calls ensure_creatable! on the create branch; missing-project message names os-sdlc project-config. ADR-0178 records it. **Evidence:** Merged to main 8daffca (code 9b41175). Suite: 1220 runs, 0 failures. Rubocop clean on 7 touched files. Hand-verified: judge-role Edit denied; test-writer Write to lib denied and to tests allowed with OS_SDLC_TARGET=os-sdlc. Pipeline: 8 dispatches, 901 s, bound_exhausted after 3 programmer-repair rounds; deviations: hand-moved test-side Project.create callers to upsert, removed the obsolete clobber-refusal test, added six require_relative lines to scope_policy.rb (hook has no Zeitwerk). Position 1 of 4 on map #570. **Follow-ups:** #575 hook cannot learn the target at a multi-target root (guard permissive there; skeptic CREATE). Structural follow-through: done (rg 'Project\.create' returns only the removal test).
jared closed this issue 2026-09-14 14:16:17 +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#571
No description provided.