The implementation snapshot persists the whole Project; Project::Config becomes Project::Snapshot; Implementation#target #792
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#792
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
implementations.project_configstores one flattened target, fromProject#to_config_h. The keys are name, tracker, commit_on_complete, path, test_path, test_command, test_framework, lint_config, and lint_paths. Every other target, the target's name, andmapare dropped. The class that parses it isProject::Config, but R2 needs that name for the yaml. Two readers parse the same column:Implementation#projectand#commit_on_complete?. No object answers "the target this implementation is bound to".Reproduce
n/a: new behavior. Failing test: open an implementation with
--target bon a yaml with targetsaandb, then rewrite the yaml so thatb's test_command changes.implementation.target.nameisb, its test command is the open-time value, andimplementation.project(root).targets.map(&:name)is["a", "b"]. TodayImplementation#targetis the String column andtargetsis not persisted.Approach
Keep the
project_configtext column (no SCHEMA_VERSION bump; ADR-0129/0155). Store the whole Project without root:{name, tracker, commit_on_complete, targets: {<name> => {path, test_path, test_command, test_framework, lint_config, lint_paths, tracker}}}.Project::Snapshotis the one JSON codec (dump and parse).Implementation#snapshotparses the column once.#project(root),#commit_on_complete?, and#targetask the snapshot.Files (8): parser/project/config.rb -> parser/project/snapshot.rb, model/project.rb (
to_json/from_jsoncall Snapshot), model/implementation/instance_methods.rb, model/project/target.rb (to_hfor the blob), command/implementation/open.rb (no change to the call; confirm), tests/parser/project/config_name_guard_test.rb -> tests/parser/project/snapshot_name_guard_test.rb, tests/model/project_json_round_trip_test.rb, tests/model/implementation/target_test.rb (new).ADRs cited: ADR-0169 (binds at open, frozen for the life of the implementation, root never stored), ADR-0055, ADR-0139 (flat projects store a null target), ADR-0129/0155 (no bump), ADR-0167, ADR-0227.
ADR to write before open (amends ADR-0169 D1-D2, ADR-0192 items 1/3/4, ADR-0210 rules 1-2, ADR-0088's member-list consequence and its rejected alternative): "Project holds every target; the implementation snapshot persists the whole Project as JSON in implementations.project_config; the column and the schema version do not change; a blob without targets: is legacy and Implementation#target refuses it, so the implementation must be reopened."
Required work:
Project::ConfigtoProject::Snapshot(parser/project/snapshot.rb).Snapshot.dump(project)writes the whole-Project shape above.Snapshot.new(json).project(root)rebuildstargets. The selected-target members are rebuilt from the bound target. — check:ruby plugins/os-sdlc/tests/model/implementation/target_test.rbImplementation#snapshot = associations[:snapshot] ||= Project::Snapshot.new(project_config).#project(root)and#commit_on_complete?read it. — check:ruby plugins/os-sdlc/tests/model/implementation_empty_project_config_test.rbImplementation#target = associations[:target] ||= snapshot.project(nil).targets.find { |t| t.name == (self[:target] || snapshot.name) }. When there is no match (a legacy flat blob with a named target), raise HarnessError that names the implementation and says "reopen". — check: same test as item 1self[:target], not#target. — check:grep -rn "implementation\.target\b\|implementation\[:target\]" plugins/os-sdlc/libProject::Configreferences. — check:! grep -rn "Project::Config\b" plugins/os-sdlc/libRejected: a
projectstable plus atargetstable (option ii). The Project is a 1:1 value snapshot of the implementation with no identity and no query consumer. It needs a schema bump, and ADR-0155 refuses the bump while implementations are live (3 were live on 2026-09-28).Rejected:
Project::Snapshot.find_by_implementation(implementation_id:). It is a query that re-fetches a row that the caller already holds (lens A11/A13).implementation.snapshotis the same fact.Rejected: rename the column to
project. It needs a bump for a name only.Acceptance criteria
bon a two-target yaml, when the yaml is then edited, thenimplementation.targetisbwith the open-time test command and lint paths.implementation.project(root)is read, then its targets areaandb, and its root is the root that was given.name: widgets(the target column is null), whenimplementation.targetis read, then it is the target namedwidgets.project_configis the pre-change flat shape and whose target column isos-backlog, whenimplementation.targetis read, then a HarnessError says to reopen the implementation.name, when it is parsed, then the existing HarnessError "project_config blob is missing required key: name" is raised.Owner sketch
Non-goals
Moving gate, brief, and diff-test callers to
implementation.target(T7). The yaml rename (T5).Retired tests
plugins/os-sdlc/tests/parser/project/config_name_guard_test.rb
plugins/os-sdlc/tests/model/project_json_round_trip_test.rb#test_to_json_omits_root_and_map
Blocking edges
Blocked by: T2
Origin
Project#to_config_hflattens the selected target (DESIGN: ADR-0169 D1, ADR-0192 item 4 "project_config blob stays flat")Ruling per ADR-0238: the snapshot reader accepts both the new nested blob and the old ADR-0192 flat blob, so the three live implementations survive. Do not refuse old rows. Where the body says old flat rows are refused, read: old flat rows load as a Project with one target.
Work started on branch ticket-792 via /os-sdlc:implement (session bb36e383). #791 merged to main.
Resolution
Done: Project::Config renamed Project::Snapshot; the snapshot stores the whole Project (name, tracker, commit_on_complete, target_name, targets) in implementations.project_config with no schema bump; Implementation#snapshot memoizes it, #project(root) rebuilds the Project, #target binds the row's target (or the project name) to one Project::Target; legacy flat blobs load as a one-target Project named by the row column; Implementation#target_name replaces raw row indexing in the commit presenter.
Evidence: os-sdlc implementation 57 on branch ticket-792: 24 dispatches; contract-auditor exhausted its bound on a test conflict (old project_path_test forbade Project#root, new target_test required it); hand-finished per escalation-procedure step 4: refute line retired, auditor corrections 1 and 2 applied, BoundProject deleted; fresh opus review verdict FIX -> legacy rows now load per ADR-0238 item 4; suite 1314 runs 0 failures; eager load clean; inward-dependency count 61 unchanged; rubocop clean on changed files; commit
c7cc012aFollow-ups: Move Target.from_blob into Snapshot, drop the duplicate target_name in the blob, delete Project#to_config_h/#to_flat_h, rename or remove Target#bound_as: all fold into #795 (T7, callers read the bound Target; FlatAttrs deleted). Project#root alias: absorbed by #796 (T8 path->root). Tighten snapshot_name_guard_test message match and fix implementation_project_test alignment: folded into #795 as a comment.