Project::Setup owns project.yaml creation policy #580

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

Summary

Batch 3 on project.rb, ticket 3 of 4. Capability: project.yaml creation policy. Audit run 15: .sdlc/tmp/responsibility-audit/run-15/analysis.yaml, finding 1.

Project.upsert holds the create-time preconditions as private class methods on a Data.define value type. One of its two refusals is dead: ensure_creatable! (project.rb:43) runs only under unless yaml.exist? (project.rb:64), so raise_yaml_already_exists! (project.rb:45) never fires. Decision: delete the overwrite refusal; upsert means create-or-update.

Proposed owner (from the report; class renamed Project::Setup by user decision)

class Project::Setup                 # project/setup.rb
  def initialize(root, yaml: Project::Yaml.new(root), repo: Git::Repo.new(root))
    @root = root
    @yaml = yaml
    @repo = repo
  end

  def upsert(name:, **attrs)
    ensure_creatable! unless @yaml.exist?
    @yaml.merge_keys(attrs.merge(name: name))
  end

  private

  def ensure_creatable!
    raise HarnessError, "#{@root} is not inside a git repo ..." unless @repo.repo?
  end
end

Keep the not-a-git-repo message text identical to project.rb today. project.rb must stay free of gem requires (ADR-0181 pre-boot chain); the new file needs a manual require_relative where project.rb requires its siblings.

Callers that delegate

  • Project.upsert (project.rb:61): builds Project::Setup.new(root).upsert(name:, **attrs) then loads and returns the Project as today.
  • ProjectConfigCommand#create (project_config_command.rb:38): through Project.upsert, unchanged call.

Tasks

  • Project::Setup#upsert refuses a root outside a git repo with HarnessError.
  • Project::Setup#upsert creates .sdlc/project.yaml with the given name and attrs when none exists.
  • Project::Setup#upsert merges only the given keys into an existing file and leaves tracker untouched.
  • Project.upsert delegates and still returns a Project (existing tests/project_test.rb:107-129 hold).

Acceptance criteria

  • Given a directory with no .git, Project::Setup.new(dir).upsert(name: "widgets") raises HarnessError containing not inside a git repo.
  • Given a git repo with no project.yaml, the call writes the file with name: widgets.
  • Given an existing project.yaml with tracker: forgejo:a/b, upsert(name: "widgets", code_path: "src") leaves tracker unchanged and writes code_path.
  • Project.upsert(dir, name: "widgets") returns a Project whose name is widgets.

Structural follow-through

  • Delete from project.rb: ensure_creatable!, raise_not_a_git_repo!, raise_yaml_already_exists!, git_repo?. Grep after: rg "ensure_creatable|raise_yaml_already_exists|raise_not_a_git_repo|git_repo\?" plugins/os-sdlc/lib returns nothing.
  • Git::Repo leaves project.rb's collaborator list. Grep after: rg "Git::Repo" plugins/os-sdlc/lib/os_sdlc/project.rb returns nothing.
  • Run TimingBootSafetyTest after the split.

ADR case

Amends ADR-0178 (upsert stays the entry point; Project::Setup holds the preconditions; the overwrite refusal is withdrawn as unreachable). Create the amending ADR when the ticket lands.

Order

Position 3 of 4. Follows ticket 2 (RootSearch message). Next: ticket 4 (Project.load_shared).

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 3 of 4. Capability: project.yaml creation policy. Audit run 15: `.sdlc/tmp/responsibility-audit/run-15/analysis.yaml`, finding 1. `Project.upsert` holds the create-time preconditions as private class methods on a `Data.define` value type. One of its two refusals is dead: `ensure_creatable!` (project.rb:43) runs only under `unless yaml.exist?` (project.rb:64), so `raise_yaml_already_exists!` (project.rb:45) never fires. Decision: delete the overwrite refusal; upsert means create-or-update. ## Proposed owner (from the report; class renamed Project::Setup by user decision) ```ruby class Project::Setup # project/setup.rb def initialize(root, yaml: Project::Yaml.new(root), repo: Git::Repo.new(root)) @root = root @yaml = yaml @repo = repo end def upsert(name:, **attrs) ensure_creatable! unless @yaml.exist? @yaml.merge_keys(attrs.merge(name: name)) end private def ensure_creatable! raise HarnessError, "#{@root} is not inside a git repo ..." unless @repo.repo? end end ``` Keep the not-a-git-repo message text identical to project.rb today. `project.rb` must stay free of gem requires (ADR-0181 pre-boot chain); the new file needs a manual `require_relative` where `project.rb` requires its siblings. ## Callers that delegate - `Project.upsert` (project.rb:61): builds `Project::Setup.new(root).upsert(name:, **attrs)` then loads and returns the Project as today. - `ProjectConfigCommand#create` (project_config_command.rb:38): through `Project.upsert`, unchanged call. ## Tasks - [ ] `Project::Setup#upsert` refuses a root outside a git repo with `HarnessError`. - [ ] `Project::Setup#upsert` creates `.sdlc/project.yaml` with the given name and attrs when none exists. - [ ] `Project::Setup#upsert` merges only the given keys into an existing file and leaves `tracker` untouched. - [ ] `Project.upsert` delegates and still returns a `Project` (existing `tests/project_test.rb:107-129` hold). ## Acceptance criteria - Given a directory with no `.git`, `Project::Setup.new(dir).upsert(name: "widgets")` raises `HarnessError` containing `not inside a git repo`. - Given a git repo with no project.yaml, the call writes the file with `name: widgets`. - Given an existing project.yaml with `tracker: forgejo:a/b`, `upsert(name: "widgets", code_path: "src")` leaves `tracker` unchanged and writes `code_path`. - `Project.upsert(dir, name: "widgets")` returns a `Project` whose name is `widgets`. ## Structural follow-through - Delete from `project.rb`: `ensure_creatable!`, `raise_not_a_git_repo!`, `raise_yaml_already_exists!`, `git_repo?`. Grep after: `rg "ensure_creatable|raise_yaml_already_exists|raise_not_a_git_repo|git_repo\?" plugins/os-sdlc/lib` returns nothing. - `Git::Repo` leaves project.rb's collaborator list. Grep after: `rg "Git::Repo" plugins/os-sdlc/lib/os_sdlc/project.rb` returns nothing. - Run `TimingBootSafetyTest` after the split. ## ADR case Amends ADR-0178 (upsert stays the entry point; `Project::Setup` holds the preconditions; the overwrite refusal is withdrawn as unreachable). Create the amending ADR when the ticket lands. ## Order Position 3 of 4. Follows ticket 2 (RootSearch message). Next: ticket 4 (Project.load_shared). ## 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-580, map poodr-ticket-implementation.yaml, target os-sdlc.

Work started via /os-sdlc:responsibility-refactor batch 3 (map #582). Branch: ticket-580, map poodr-ticket-implementation.yaml, target os-sdlc.
Author
Owner

Resolution

Done: Project::Setup (lib/os_sdlc/project/setup.rb) owns the git-repo precondition and merge-then-write; Project.upsert delegates; ensure_creatable!, raise_not_a_git_repo!, raise_yaml_already_exists!, git_repo? deleted from Project; Git::Repo leaves project.rb; overwrite refusal withdrawn (unreachable). Class named Project::Setup per user decision.

Evidence: main 51c40d1, ADR-0185 (amends 0178), suite 1169 runs 0 failures, rubocop clean. Pipeline: 11 dispatches, escalated unexpected_pass:test-writer/diff-test after Setup was built (the remaining task, upsert delegation, is structural and has no red test). Deviations: (1) hand-finished the delegation and deletions after the escalation; (2) programmer applied the git check on every upsert; restored the sketch's create-time-only check (tests/project_test.rb:378 caught it); (3) programmer relayed the prior dispatch id again, hand-settled row 153; (4) removed the descriptive comment the programmer added to setup.rb.

Follow-ups: none

## Resolution **Done:** Project::Setup (lib/os_sdlc/project/setup.rb) owns the git-repo precondition and merge-then-write; Project.upsert delegates; ensure_creatable!, raise_not_a_git_repo!, raise_yaml_already_exists!, git_repo? deleted from Project; Git::Repo leaves project.rb; overwrite refusal withdrawn (unreachable). Class named Project::Setup per user decision. **Evidence:** main 51c40d1, ADR-0185 (amends 0178), suite 1169 runs 0 failures, rubocop clean. Pipeline: 11 dispatches, escalated unexpected_pass:test-writer/diff-test after Setup was built (the remaining task, upsert delegation, is structural and has no red test). Deviations: (1) hand-finished the delegation and deletions after the escalation; (2) programmer applied the git check on every upsert; restored the sketch's create-time-only check (tests/project_test.rb:378 caught it); (3) programmer relayed the prior dispatch id again, hand-settled row 153; (4) removed the descriptive comment the programmer added to setup.rb. **Follow-ups:** none
jared closed this issue 2026-09-14 18:34:36 +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#580
No description provided.