Contracts

packaging-parity.md

Contract: Packaging parity (wheel + sdist)

Requirement

packs/built-in/ ships completely in BOTH the monolith wheel and the sdist; the moved content's src/doctrine/** package-data globs are removed (no duplication).

Root pyproject.toml changes

src/doctrine/** artifacts.

excludes a top-level packs/).

  • [tool.hatch.build.targets.wheel]: force-include = { "packs" = "packs" }; drop moved-content
  • [tool.hatch.build.targets.sdist]: include gains "packs/" (today it is src/, which

Acceptance (NFR-002 — non-fakeable)

1. Build wheel + sdist. For each, the set of relative paths under packs/built-in/ equals the pre-move file manifest (exact set-equality — would pass on duplication; a partial move fails). 2. Install the wheel into a clean venv (declared deps only, no repo src/ on path); import doctrine, load_built_in_graph() (identity fixture), and spec-kitty doctor doctrine all succeed with 0 missing-file errors. 3. The moved content is absent under src/doctrine/** (FR-009) — no dual-home duplication.

Rationale

Pre-spec squad proved a build can succeed while shipping an empty/partial artifact; gate on the built artifacts' contents and a live import, never on "build exited 0."

resolve-pack-root.md

Contract: resolve_pack_root(tier)

Module: src/doctrine/pack_paths.py (doctrine layer — must not import charter/specify_cli, C-004).

Signature

def resolve_pack_root(tier: Literal["built-in", "org", "project"], *, org_root: Path | None = None,
                      project_root: Path | None = None) -> Path: ...

Resolution order (built-in)

1. SPEC_KITTY_PACKS_ROOT env → <env>/built-in if it exists. 2. Editable: nearest ancestor of __file__ containing packs/built-in/. 3. Installed: files("doctrine").parent / packs / built-in if it exists. 4. Else raise PackRootNotFound("built-in").

org/project: return the caller-supplied org_root / project_root (unchanged semantics — the seam is shared, the tier inputs differ).

Guarantees (tested — FR-006 two-layout matrix)

packs/built-in/.

  • Editable: from a repo checkout, returns repo-root packs/built-in/.
  • Installed: from a clean-venv wheel install (no repo src/ on path), returns the site-packages
  • Fail-closed: never returns a path inside src/doctrine/; never falls open to an arbitrary tree.
  • Idempotent / pure: no mutation; same inputs → same path.

Consumers repointed (FR-003/FR-004)

  • built_in_graph_source()resolve_pack_root("built-in").
  • each repository built_in_dir default → resolve_pack_root("built-in") / <kind>.
  • the enumerated moved-tree readers + the specify_cli hardcoded string path.

Non-goals (C-002)

stays OrgDRGFragment). This contract unifies the path, not the load mechanism.

  • Does not unify the loader or schema (built-in stays a DRGGraph/14-fragment reader; org