project.rb batch 4/3: Project::Yaml::Document owns the project.yaml document shape #585

Closed
opened 2026-09-14 20:04:01 +00:00 by jared · 2 comments
Owner

Map: #586

Batch 4 of the project.rb responsibility refactor, ticket 3 of 3. Follows ticket 2.
Audit: tuning/responsibility-audit/runs/16-project-rb-batch3-reaudit/report.md.
Capability: project-yaml-document-shape. Owner: missing. Proposed OsSdlc::Project::Yaml::Document at plugins/os-sdlc/lib/os_sdlc/project/yaml/document.rb (confidence 0.82).
Map: maps/poodr-ticket-implementation.yaml.

Cases (one red round, all listed)

  1. Project.load_shared(root) on a project.yaml without a name key raises HarnessError whose message names the missing key and the file path. Today: bare KeyError (project.rb:31).
  2. Project.load(root, target: "x") on the same file raises the same HarnessError (project.rb:36).
  3. Project.load_shared(root) on a targets-shaped file where exactly one target sets tracker: and the top level does not returns a project whose tracker is that value. Today: nil, while Runner::TeaTracker#project_tracker_present? (tea_tracker.rb:117) says a tracker is configured, so resolve_slug (tea_tracker.rb:144) runs with nil. When more than one target sets a different tracker:, load_shared returns nil tracker (ambiguous is not shared).
  4. Runner::TeaTracker#tracker_configured? gives the same answer as Project.load_shared(root).tracker.nil? negated, for the three file shapes: top-level tracker, single per-target tracker, no tracker.

Owner sketch (from the audit, verbatim intent)

class Project
  class Yaml
    class Document
      def initialize(data, path:)
      def name              # raises HarnessError "<path> is missing required key: name"
      def tracker(target = nil)   # top level, else the target's, else the one value all targets share, else nil
      def targets_shaped?
      def attrs(target, keys)     # delegates to ProjectTargets
      def shared_attrs(keys)
    end
  end
end

Project::Yaml#read returns a Document. ProjectTargets stays as the target-selection validator behind it.

Callers that delegate

  • lib/os_sdlc/project.rb:30 load_shared
  • lib/os_sdlc/project.rb:36 from_data
  • lib/os_sdlc/project/config.rb:21 (keeps its own blob guard per ADR-0176; may reuse the message)
  • lib/os_sdlc/runner/tea_tracker.rb:117-132 project_tracker_present?, top_level_tracker?, per_target_tracker?, readable_project_yaml collapse to one Document call

Tests that touch the old behavior

  • tests/runner/tea_tracker_test.rb:154
  • tests/project_load_shared_test.rb, tests/project/yaml_test.rb:39, tests/project_test.rb:376-384

ADR

Amends ADR-0177 (Yaml#read returns a Document), ADR-0186 (load_shared tracker rule), ADR-0056 (tracker precedence source). Create the amending ADR when the ticket lands.

Structural follow-through (after implementation_complete, subagent applies)

  • Move Project::Source (project.rb:84-100) to lib/os_sdlc/project/source.rb with require_relative "project/source" in project.rb; callers lib/os_sdlc/runner/brief_facts.rb:40 and gate_commands.rb:34 unchanged. rg "class Source" plugins/os-sdlc/lib/os_sdlc/project.rb returns nothing.
  • rg 'data\["tracker"\]|data.fetch\("name"\)' plugins/os-sdlc/lib returns nothing outside project/yaml/document.rb.
  • project.rb stays free of gem requires (ADR-0181 pre-boot constraint).
Map: #586 Batch 4 of the project.rb responsibility refactor, ticket 3 of 3. Follows ticket 2. Audit: tuning/responsibility-audit/runs/16-project-rb-batch3-reaudit/report.md. Capability: project-yaml-document-shape. Owner: missing. Proposed `OsSdlc::Project::Yaml::Document` at plugins/os-sdlc/lib/os_sdlc/project/yaml/document.rb (confidence 0.82). Map: maps/poodr-ticket-implementation.yaml. ## Cases (one red round, all listed) 1. `Project.load_shared(root)` on a project.yaml without a `name` key raises `HarnessError` whose message names the missing key and the file path. Today: bare `KeyError` (project.rb:31). 2. `Project.load(root, target: "x")` on the same file raises the same `HarnessError` (project.rb:36). 3. `Project.load_shared(root)` on a targets-shaped file where exactly one target sets `tracker:` and the top level does not returns a project whose `tracker` is that value. Today: nil, while `Runner::TeaTracker#project_tracker_present?` (tea_tracker.rb:117) says a tracker is configured, so `resolve_slug` (tea_tracker.rb:144) runs with nil. When more than one target sets a different `tracker:`, `load_shared` returns nil tracker (ambiguous is not shared). 4. `Runner::TeaTracker#tracker_configured?` gives the same answer as `Project.load_shared(root).tracker.nil?` negated, for the three file shapes: top-level tracker, single per-target tracker, no tracker. ## Owner sketch (from the audit, verbatim intent) ```ruby class Project class Yaml class Document def initialize(data, path:) def name # raises HarnessError "<path> is missing required key: name" def tracker(target = nil) # top level, else the target's, else the one value all targets share, else nil def targets_shaped? def attrs(target, keys) # delegates to ProjectTargets def shared_attrs(keys) end end end ``` `Project::Yaml#read` returns a `Document`. `ProjectTargets` stays as the target-selection validator behind it. ## Callers that delegate - lib/os_sdlc/project.rb:30 `load_shared` - lib/os_sdlc/project.rb:36 `from_data` - lib/os_sdlc/project/config.rb:21 (keeps its own blob guard per ADR-0176; may reuse the message) - lib/os_sdlc/runner/tea_tracker.rb:117-132 `project_tracker_present?`, `top_level_tracker?`, `per_target_tracker?`, `readable_project_yaml` collapse to one Document call ## Tests that touch the old behavior - tests/runner/tea_tracker_test.rb:154 - tests/project_load_shared_test.rb, tests/project/yaml_test.rb:39, tests/project_test.rb:376-384 ## ADR Amends ADR-0177 (Yaml#read returns a Document), ADR-0186 (load_shared tracker rule), ADR-0056 (tracker precedence source). Create the amending ADR when the ticket lands. ## Structural follow-through (after implementation_complete, subagent applies) - Move `Project::Source` (project.rb:84-100) to lib/os_sdlc/project/source.rb with `require_relative "project/source"` in project.rb; callers lib/os_sdlc/runner/brief_facts.rb:40 and gate_commands.rb:34 unchanged. `rg "class Source" plugins/os-sdlc/lib/os_sdlc/project.rb` returns nothing. - `rg 'data\["tracker"\]|data.fetch\("name"\)' plugins/os-sdlc/lib` returns nothing outside project/yaml/document.rb. - project.rb stays free of gem requires (ADR-0181 pre-boot constraint).
Author
Owner

Work started on branch ticket-585 via os-sdlc pipeline (map poodr-ticket-implementation.yaml, batch 4 map #586).

Work started on branch ticket-585 via os-sdlc pipeline (map poodr-ticket-implementation.yaml, batch 4 map #586).
Author
Owner

Resolution

Done: Batch 4/3 merged to main 39dd336, ADR-0189. Project::Yaml::Document owns the project.yaml document shape (name guard, tracker resolution incl. single per-target tracker, targets delegation); Yaml#document returns it; Project.load/load_shared and Runner::TeaTracker delegate; TeaTracker's private yaml reading deleted; Project::Source moved to project/source.rb.

Evidence: Suite 1190 runs 0 failures; rubocop clean; rg for hash indexing of name/tracker under lib returns only document.rb; pre-boot require check loads Source and Document. Pipeline impl 16: 18 dispatches, implementation_complete after contract-auditor pass. Deviations: (1) test-writer needed 3 rounds. (2) suite-check failed twice on a pre-existing cli_test fixture that omitted name and expected the unreadable-yaml message; sonnet changed the fixture to genuinely malformed YAML. (3) structural follow-through was applied before the terminal state; the diff-test gate could not map the new lib files and programmer-repair reverted them (one 94-tool-call round). Reapplied after implementation_complete by haiku. (4) contract-auditor dispatch 219 idled 27960 s before settling. (5) ADR skill wrote to the main checkout again; moved by hand.

Follow-ups: none

## Resolution **Done:** Batch 4/3 merged to main 39dd336, ADR-0189. Project::Yaml::Document owns the project.yaml document shape (name guard, tracker resolution incl. single per-target tracker, targets delegation); Yaml#document returns it; Project.load/load_shared and Runner::TeaTracker delegate; TeaTracker's private yaml reading deleted; Project::Source moved to project/source.rb. **Evidence:** Suite 1190 runs 0 failures; rubocop clean; rg for hash indexing of name/tracker under lib returns only document.rb; pre-boot require check loads Source and Document. Pipeline impl 16: 18 dispatches, implementation_complete after contract-auditor pass. Deviations: (1) test-writer needed 3 rounds. (2) suite-check failed twice on a pre-existing cli_test fixture that omitted name and expected the unreadable-yaml message; sonnet changed the fixture to genuinely malformed YAML. (3) structural follow-through was applied before the terminal state; the diff-test gate could not map the new lib files and programmer-repair reverted them (one 94-tool-call round). Reapplied after implementation_complete by haiku. (4) contract-auditor dispatch 219 idled 27960 s before settling. (5) ADR skill wrote to the main checkout again; moved by hand. **Follow-ups:** none
jared closed this issue 2026-09-15 06:10:14 +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#585
No description provided.