Map owns the XDG drafts directory (Map::DRAFTS_DIR); Map::Catalog searches the approved and drafts roots #904
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#904
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
Drafted maps live in
maps/drafts/inside the plugin tree, and the drafts directory name"drafts"has two owners.Map#drafted?(model/map.rb:22) spells the literal.Map::Catalog::DRAFTS_DIR(adapter/map/catalog.rb:7) spells it again. A12 requires one owner for a rule. Only the #880 Required-work check R1 (present `"drafts"` in `plugins/os-sdlc/lib/os_sdlc/adapter/map/catalog.rb`) keeps the second copy. No code fact needs it.ADR-0264 moves drafted maps to an XDG data directory. So the one owner,
Map::DRAFTS_DIR, holds the XDG drafts path, andMap::Catalogsearches two roots: the approved plugin root and that drafts root. This is more than a rename: the drafts location changes, and the catalog no longer derives it from its own directory.LENS line (verbatim):
LENS: A12 broken at plugins/os-sdlc/lib/os_sdlc/model/map.rb:22 - BREAKS: the drafts directory name is spelled in Map#drafted? and again as Map::Catalog::DRAFTS_DIR (adapter/map/catalog.rb:7); JUSTIFIED: yes, one owner (Map::DRAFTS_DIR used by Catalog) removes the literal that Required-work R1 checks in catalog.rb, so the gate fails (principle 4); PROPOSAL: NONE, the tension comes from this ticket check wording and will not recur; the commit records it.
Parent: #859
Ticket: #904 (owner of this change; found in #880 implementation 5, dispatch 23)
Verdict: code defect found. The heuristic holds; no lens edit.
Evidence:
plugins/os-sdlc/lib/os_sdlc/model/map.rb:22def drafted? = File.basename(File.dirname(path)) == "drafts"plugins/os-sdlc/lib/os_sdlc/adapter/map/catalog.rb:7DRAFTS_DIR = "drafts"; line 22drafts_dir = File.join(@dir, DRAFTS_DIR)Map#drafted?; R1 checks the literal in catalog.rb. The ticket wording caused the copy.Map, used byMap::Catalog) removes the copy. Q10: the obeying design costs one constant and one reference change.Reproduce
n/a: design defect, nothing to reproduce.
Approach
SUPERSEDED (2026-10-02 second pass, steps 1-7 below replace the
Map::DRAFTS_DIRconstant that read ENV at load): DefineDRAFTS_DIRonOsSdlc::Map(model/map.rb) as the XDG drafts root of ADR-0264:File.join(ENV.fetch("XDG_DATA_HOME", File.expand_path("~/.local/share")), "os-sdlc", "maps", "drafts").First pass (
ec449ee1, kept):Map::Catalogsearches two roots, the approved plugin root first and the drafts root, with the same rule as before (raiseInvalidMapErrorwhen the name is in both or neither), and has noDRAFTS_DIRconstant ordrafts_dirmethod of its own. The plugin tree has nomaps/drafts/directory (ADR-0264).Second pass (user ruling 2026-10-02; fixes lens A6, lens-architecture SKILL.md:59-65 and lens-cases Case 8, and finishes criterion 5):
model/map.rb: replace the constant withdef self.drafts_dir(env) = File.join(env.fetch("XDG_DATA_HOME", File.join(env.fetch("HOME"), ".local/share")), "os-sdlc", "maps", "drafts"). No ENV read in lib/ model or adapter code.controller/boot.rbBoot.run(every bin enters here;ProcessIO#envalready holdsENV.to_h,adapter/process_invocation.rb:6,controller/process_io.rb:6): install the default catalog once:Map::Catalog.default ||= Map::Catalog.new(Map::Catalog::PLUGIN_MAPS_DIR, drafts_dir: Map.drafts_dir(io.env)).Map::Catalog#initialize(dir, drafts_dir:):drafts_dir:is required, with no default.Map#drafted?compares the parent directory basename with the literal"drafts"(the XDG path and test tmp dirs both end indrafts).tests/model/map_drafts_dir_test.rbcallsMap.drafts_dir(hash)in-process; no subprocess.plugins/os-sdlc/Gemfileprocman line toFile.expand_path("~/dev/ruby-gems/procman")and droprequire "etc"; it existed only for the subprocess test.tests/support/tmpdir_test_case.rb(lines 12 and 23 setCatalog.default = nil): build the test catalog with the plugin maps dir (or a tmp dir) and a tmpdrafts_dir. No test reaches the real~/.local/share.Required work:
def self.drafts_dirinplugins/os-sdlc/lib/os_sdlc/model/map.rbENVinplugins/os-sdlc/lib/os_sdlc/model/map.rb"drafts"inplugins/os-sdlc/lib/os_sdlc/adapter/map/catalog.rbEtcinplugins/os-sdlc/GemfileAcceptance criteria
x.yamlonly in the drafts root, whenCatalog#load("x")runs, then it returns the map anddrafted?is true.y.yamlonly in the approved root, whenCatalog#load("y")runs, then it returns the map anddrafted?is false.z.yamlin both roots, or in neither, whenCatalog#path_for("z")runs, then it raisesInvalidMapError(:ambiguous_map_name).Map.drafts_dir(env)returns<XDG_DATA_HOME>/os-sdlc/maps/draftswhenXDG_DATA_HOMEis set and<HOME>/.local/share/os-sdlc/maps/draftswhen it is not; the test calls it in-process.plugins/os-sdlc/lib/os_sdlc/modelorplugins/os-sdlc/lib/os_sdlc/adapter/mapreads ENV.Map::Catalog.new(dir)withoutdrafts_dir:, when it runs, then it raisesArgumentError.Boot.run, when it starts, thenMap::Catalog.defaultis a catalog whose drafts root isMap.drafts_dir(io.env).drafts) or the plugin maps dir plus a tmp drafts dir.Owner sketch
Origin
"drafts"literal ← R1 check requires the literal in catalog.rb ← DESIGN (#880 Approach step 2 and Required work 1)VERDICT: CREATE
Owner decision: the one owner of the literal "drafts" is the object that owns DRAFTS_ROOT (the drafts root path, File.join(XDG_DATA_HOME or ~/.local/share, "os-sdlc", "maps", "drafts")), per ADR-0264 (user ruling on #898, option B1). Map::Catalog looks up [PLUGIN_MAPS_DIR, DRAFTS_ROOT]. The body names Map::DRAFTS_DIR on Map as owner; reconcile this when the ticket is implemented: Map#drafted? and the catalog must both take the name from that one owner, and the owner is not a second spelling beside DRAFTS_ROOT.
Lens proposal: one owner for the drafts directory name (Map::DRAFTS_DIR)to Map owns the XDG drafts directory (Map::DRAFTS_DIR); Map::Catalog searches the approved and drafts rootsWork started on branch ticket-904 (os-sdlc pipeline, map poodr-ticket-implementation).
Skeptic DROP on follow-up: TmpdirTestCase#use_plugin_maps (plugins/os-sdlc/tests/support/tmpdir_test_case.rb:23) and teardown (line 12) set Catalog.default to nil, so the catalog falls back to the real Map::DRAFTS_DIR. Acceptance criterion 5 (two tmp dirs) is only partly met. Finish it on #904: build the Catalog with the plugin maps dir and a tmp drafts_dir.
Second pass started per user ruling 2026-10-02: A6 fix (Opus lens challenge, verdict code defect) + criterion 5 completion. First pass
ec449ee1.Resolution
Done: Drafted maps resolve from Map.drafts_dir(env) (XDG_DATA_HOME, else HOME/.local/share) per ADR-0264. Boot#run installs the default catalog from io.env; Map::Catalog requires drafts_dir: and searches [PLUGIN_MAPS_DIR, drafts_dir]. No model reads ENV (lens A6). Test support uses a tmp drafts dir, so no test reads the real user dir (criterion 5). Two pipeline passes: implementation 8 (
ec449ee1) and implementation 9 (ff16cc43), History row182f8825.Evidence: Branch ticket-904:
ec449ee1,ff16cc43,182f8825. Implementation 9 suite-check dispatch 73 green; behavior-verifier dispatch 72 pass on all 8 criteria; contract-auditor dispatch 74 pass. Gemfile identical to main.Follow-ups: #907 filed (Runner auto-commit fails on already-staged deletions; skeptic CREATE). Dropped: Catalog.default still falls back to drafts_dir: File::NULL when Boot did not install a catalog; every current bin enters through Boot, so no caller is affected; revisit only if a non-Boot entry point appears.