Phase 0 Research — Design Decisions
Most analysis was front-loaded into the post-spec adversarial squad; the full cited evidence is in research/post-spec-squad-findings.md. This file records the resulting decisions.
D-1 — Resolution seam at OrgPackConfig.effective_root, not resolve_org_roots
- Decision: Compute the effective pack root once, as an
OrgPackConfigproperty/helpereffective_root(repo_root)that normalizeslocal_pathrelative to repo root and joinssubdir. Every consumer reads through it. - Rationale: The squad (alphonso+debbie+priti, convergent) proved
resolve_org_rootsis a thin[pack.local_path …]map that ≥6 consumers bypass — including thedoctor doctrinehealth path (load_org_drg→load_org_pack). A single property is inherited by all and retires the pre-existing raw-vs-relative inconsistency (C-007). - Alternatives rejected: (a) patch only
resolve_org_roots— leavesdoctor doctrineRED (debbie's trace); (b) patch each consumer independently — N fragile sites, regression-prone.
D-2 — Containment via ensure_within_directory, validation timing split
- Decision: Reuse
specify_cli/core/utils.py:ensure_within_directory(strict.resolve(), symlink-safe). String-level escapes (absolute incl. Windows/UNC,..) rejected at config-load with a structured, operator-visible error; symlink-escape checked at effective-root resolution time../empty → "no subdir". - Rationale:
OrgPackConfigis validated before the clone exists, so a symlink the clone plants can only be caught at resolution. The error must not be swallowed byload_pack_registry's warning-and-degrade path (org_pack_config.py:128), else Scenario A1's "structured error" contract breaks. - Alternatives rejected:
is_relative_towithout.resolve()(misses symlink); validate-time-only check (can't see a not-yet-cloned symlink).
D-3 — Thread D: validate-time fail-loud guard (preferred over query-time wildcard)
- Decision:
spec-kitty doctrine validaterejectsapplies_to_languages: [any]/[all]with an actionable message ("omit the field to mean always-applicable").scoping.py:applies_to_languages_matchmay optionally treat them as wildcard as defense-in-depth, but the guard is canonical. - Rationale: The bug is silent drop with no authoring signal (
scoping.py:24—any/allare literal tokens, never overlap a concrete active set). Failing at validate time surfaces it where the author writes it (issue #2092 stated preference; C-006). - Alternatives rejected: query-time wildcard only — still silent at authoring; the next author re-trips it.
D-4 — Thread C: styleguide kind + mandatory inbound DRG edge
- Decision: Ship one
*.styleguide.yamlundersrc/doctrine/styleguides/built-in/; add ≥1 inboundsuggests/requiresedge from an existing directive/paradigm; regenerategraph.yamlvia the generator; add a non-orphan test. - Rationale: The generator auto-discovers styleguides + directives; orphan nodes are permitted (graph accepts bare nodes), so FR-009-original "resolvable" was satisfiable by an inert stub. The inbound edge + test make it real — still doctrine-only (a
suggestsedge is not CI/agent-effort). - Alternatives rejected: directive kind (styleguide is the right kind for standards); leave orphan (doctrine theater, renata+priti).
D-5 — Thread B: document current-vs-aspirational, name the mixed-usage sites
- Decision: The YAML-library doc declares whether it states current or aspirational practice, is verified against ≥3 named sites, and explicitly names the mixed-usage sites (same
config.yamlruamel-round-tripped inorg_pack_config.pyandsafe_load-ed inorg_pack_loader.py:38; 3 dual-use modules). - Rationale: Usage is genuinely ad-hoc; a single "rule" sentence would be provably false (renata). Honesty beats a fabricated invariant.
- Alternatives rejected: assert a clean rule (false); silently ignore the contradictions (papers over the real state).