Project.load_shared: target-agnostic read for tracker and lint_config #581

Closed
opened 2026-09-14 17:26:02 +00:00 by jared · 3 comments
Owner

Summary

Batch 3 on project.rb, ticket 4 of 4. Capability: target-agnostic project read. Audit run 15: .sdlc/tmp/responsibility-audit/run-15/analysis.yaml, finding 2.

TargetSelectionError exists so a misselected target fails loudly (harness_error.rb:17). Two readers need only shared keys (tracker, lint_config), pass target: nil, and rescue HarnessError, which swallows it. On a multi-target project.yaml the tracker slug and the root lint config silently become nil.

Proposed owner (from the report, verbatim)

class ProjectTargets
  def shared_attrs = sliced(data)          # becomes public; no validate! call
end

class Project
  def self.load_shared(dir = Dir.pwd)      # no target, no TargetSelectionError
    yaml = Project::Yaml.containing(dir) or raise_missing_root!(File.expand_path(dir))
    new(name: yaml.read.fetch("name"), root: yaml.root,
        **ProjectTargets.new(yaml.read, nil, ATTR_DEFAULTS.keys).shared_attrs)
  end
end

After ticket 2, the missing-root raise goes through Project::RootSearch#root!, not raise_missing_root!.

Callers that delegate

  • Runner::TeaTracker#resolve_slug (runner/tea_tracker.rb:148): Project.load_shared(@root).tracker, no rescue of HarnessError. The yaml read at tea_tracker.rb:125 goes the same way.
  • AutocorrectPrepass#root_lint_config (autocorrect_prepass.rb:95): Project.load_shared(@root).resolved_lint_config, no rescue of HarnessError.

Tasks

  • Project.load_shared(dir) returns a Project with the top-level shared keys and defaults from a targets-shaped project.yaml without raising TargetSelectionError.
  • Project.load_shared(dir) on a flat project.yaml returns the same attrs as Project.load(dir, target: nil).
  • TeaTracker resolves the forgejo slug from a targets-shaped project.yaml with a top-level tracker.
  • AutocorrectPrepass resolves the root lint config from a targets-shaped project.yaml with a top-level lint_config.

Acceptance criteria

  • Given targets: {a: {code_path: lib}} plus top-level tracker: forgejo:j/r, Project.load_shared(dir).tracker == "forgejo:j/r" and no error is raised.
  • Given the same file, Project.load(dir, target: nil) still raises TargetSelectionError (unchanged).
  • Given the same file, TeaTracker built on that root resolves slug j/r.
  • Given the same file with top-level lint_config: .rubocop.yml, the autocorrect pre-pass's generated config inherits <root>/.rubocop.yml.

Structural follow-through

  • Remove the rescue HarnessError at autocorrect_prepass.rb:96 and tea_tracker.rb:153 and the ADR-0169 exemption comment at tea_tracker.rb:145-146. Grep after: rg "target: nil" plugins/os-sdlc/lib returns nothing.
  • Remove the two exemptions in tests/runner_project_load_guard_test.rb:10; the guard now allows load_shared and forbids Project.load outside implementation-open as before.

ADR case

Amends ADR-0088 (a shared-key read is a named constructor, not a nil target) and ADR-0169 item 3 (the two pre-open readers use Project.load_shared, so the exemption is withdrawn). Create the amending ADR when the ticket lands.

Order

Position 4 of 4. Follows ticket 3 (Project::Setup). Last in batch; re-audit follows.

Origin

  • Filed by: agent, on user approval via /os-sdlc:responsibility-refactor
  • Source: responsibility-audit run 15, analysis .sdlc/tmp/responsibility-audit/run-15/analysis.yaml
  • Where: plugins/os-sdlc/lib/os_sdlc/project.rb
  • Session: 932cc3fb-b634-45ac-a990-737178341a12
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/932cc3fb-b634-45ac-a990-737178341a12.jsonl
  • Skeptic: not run (user-approved batch)
## Summary Batch 3 on project.rb, ticket 4 of 4. Capability: target-agnostic project read. Audit run 15: `.sdlc/tmp/responsibility-audit/run-15/analysis.yaml`, finding 2. `TargetSelectionError` exists so a misselected target fails loudly (harness_error.rb:17). Two readers need only shared keys (`tracker`, `lint_config`), pass `target: nil`, and rescue `HarnessError`, which swallows it. On a multi-target project.yaml the tracker slug and the root lint config silently become nil. ## Proposed owner (from the report, verbatim) ```ruby class ProjectTargets def shared_attrs = sliced(data) # becomes public; no validate! call end class Project def self.load_shared(dir = Dir.pwd) # no target, no TargetSelectionError yaml = Project::Yaml.containing(dir) or raise_missing_root!(File.expand_path(dir)) new(name: yaml.read.fetch("name"), root: yaml.root, **ProjectTargets.new(yaml.read, nil, ATTR_DEFAULTS.keys).shared_attrs) end end ``` After ticket 2, the missing-root raise goes through `Project::RootSearch#root!`, not `raise_missing_root!`. ## Callers that delegate - `Runner::TeaTracker#resolve_slug` (runner/tea_tracker.rb:148): `Project.load_shared(@root).tracker`, no rescue of `HarnessError`. The yaml read at tea_tracker.rb:125 goes the same way. - `AutocorrectPrepass#root_lint_config` (autocorrect_prepass.rb:95): `Project.load_shared(@root).resolved_lint_config`, no rescue of `HarnessError`. ## Tasks - [ ] `Project.load_shared(dir)` returns a `Project` with the top-level shared keys and defaults from a targets-shaped project.yaml without raising `TargetSelectionError`. - [ ] `Project.load_shared(dir)` on a flat project.yaml returns the same attrs as `Project.load(dir, target: nil)`. - [ ] `TeaTracker` resolves the forgejo slug from a targets-shaped project.yaml with a top-level `tracker`. - [ ] `AutocorrectPrepass` resolves the root lint config from a targets-shaped project.yaml with a top-level `lint_config`. ## Acceptance criteria - Given `targets: {a: {code_path: lib}}` plus top-level `tracker: forgejo:j/r`, `Project.load_shared(dir).tracker == "forgejo:j/r"` and no error is raised. - Given the same file, `Project.load(dir, target: nil)` still raises `TargetSelectionError` (unchanged). - Given the same file, `TeaTracker` built on that root resolves slug `j/r`. - Given the same file with top-level `lint_config: .rubocop.yml`, the autocorrect pre-pass's generated config inherits `<root>/.rubocop.yml`. ## Structural follow-through - Remove the `rescue HarnessError` at autocorrect_prepass.rb:96 and tea_tracker.rb:153 and the ADR-0169 exemption comment at tea_tracker.rb:145-146. Grep after: `rg "target: nil" plugins/os-sdlc/lib` returns nothing. - Remove the two exemptions in `tests/runner_project_load_guard_test.rb:10`; the guard now allows `load_shared` and forbids `Project.load` outside implementation-open as before. ## ADR case Amends ADR-0088 (a shared-key read is a named constructor, not a `nil` target) and ADR-0169 item 3 (the two pre-open readers use `Project.load_shared`, so the exemption is withdrawn). Create the amending ADR when the ticket lands. ## Order Position 4 of 4. Follows ticket 3 (Project::Setup). Last in batch; re-audit follows. ## Origin - Filed by: agent, on user approval via /os-sdlc:responsibility-refactor - Source: responsibility-audit run 15, analysis `.sdlc/tmp/responsibility-audit/run-15/analysis.yaml` - Where: plugins/os-sdlc/lib/os_sdlc/project.rb - Session: 932cc3fb-b634-45ac-a990-737178341a12 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/932cc3fb-b634-45ac-a990-737178341a12.jsonl - Skeptic: not run (user-approved batch)
Author
Owner

Map: #582

Map: #582
Author
Owner

Work started via /os-sdlc:responsibility-refactor batch 3 (map #582). Branch: ticket-581, map poodr-ticket-implementation.yaml, target os-sdlc. Note: after #579 the missing-root raise goes through Project::RootSearch#root!.

Work started via /os-sdlc:responsibility-refactor batch 3 (map #582). Branch: ticket-581, map poodr-ticket-implementation.yaml, target os-sdlc. Note: after #579 the missing-root raise goes through Project::RootSearch#root!.
Author
Owner

Resolution

Done: Project.load_shared (target-agnostic read) built from ProjectTargets#shared_attrs, now public; TeaTracker#resolve_slug and AutocorrectPrepass#root_lint_config use it and no longer rescue HarnessError; ADR-0169 item 3 exemption withdrawn; guard test exempt list reduced to open_implementation.rb.

Evidence: main 31e6759, ADR-0186 (amends 0088/0169), suite 1176 runs 0 failures, rubocop clean. Pipeline: 12 dispatches, 2 retries. Deviations: (1) the new autocorrect test asserted a single inherit path where the chain is always plugin config then custom (ADR-0091); hand-corrected to assert_includes. (2) implementation-report crashes with record_usage! for nil when run from plugins/os-sdlc instead of the worktree root; rerun from root. (3) One order-dependent failure in tests/autocorrect_prepass_test.rb (trailing whitespace assertion) appeared once in a full run and not in isolation or later runs; not investigated.

Follow-ups: none

## Resolution **Done:** Project.load_shared (target-agnostic read) built from ProjectTargets#shared_attrs, now public; TeaTracker#resolve_slug and AutocorrectPrepass#root_lint_config use it and no longer rescue HarnessError; ADR-0169 item 3 exemption withdrawn; guard test exempt list reduced to open_implementation.rb. **Evidence:** main 31e6759, ADR-0186 (amends 0088/0169), suite 1176 runs 0 failures, rubocop clean. Pipeline: 12 dispatches, 2 retries. Deviations: (1) the new autocorrect test asserted a single inherit path where the chain is always plugin config then custom (ADR-0091); hand-corrected to assert_includes. (2) implementation-report crashes with record_usage! for nil when run from plugins/os-sdlc instead of the worktree root; rerun from root. (3) One order-dependent failure in tests/autocorrect_prepass_test.rb (trailing whitespace assertion) appeared once in a full run and not in isolation or later runs; not investigated. **Follow-ups:** none
jared closed this issue 2026-09-14 19:04:59 +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#581
No description provided.