Context
The Spec Kitty product surface includes three Python packages:
spec-kitty-cli, spec-kitty-events, spec-kitty-tracker, plus the
retiring spec-kitty-runtime. Pre-cutover, the CLI consumed a hybrid
mix:
- vendored events code under
src/specify_cli/spec_kitty_events/in the CLI's production paths (~23 kLoC); - production imports of
spec_kitty_runtimefrom CLI code, even thoughpyproject.tomldid not listspec-kitty-runtimeas a dependency; - an editable
[tool.uv.sources]entry pointing at a sibling../spec-kitty-eventscheckout, which masked the missing dependency in local development; - a
constraints.txtfile that papered over the transitivespec-kitty-events<4.0pin inspec-kitty-runtime0.4.x.
This state failed in two predictable ways. (1) Clean-install CI did not
exist, so a fresh pip install spec-kitty-cli would ImportError on
the first spec-kitty next invocation. (2) Cross-package releases had to
land in lockstep, because any contract change in events or tracker
required a coordinated CLI release.
PR #779 attempted a
partial cutover that moved runtime-shaped code into the CLI tree but kept
spec_kitty_runtime production imports alive. That PR was rejected
because the resulting hybrid state was structurally identical to the
pre-cutover state from a clean-install perspective and re-imposed
cross-package release lockstep.
Decision
The mission shared-package-boundary-cutover-01KQ22DS lands the cutover
in 10 work packages. Concretely:
- The CLI internalizes the runtime surface it needs from
spec-kitty-runtimeintosrc/specify_cli/next/_internal_runtime/. Production code paths import only from the internalized module. The standalonespec-kitty-runtimePyPI package is retired and is not a CLI dependency. (WP01, WP02) - The architectural test
tests/architectural/test_shared_package_boundary.pyenforces R1 / C-001: no production module undersrc/may importspec_kitty_runtime(top-level, sub-module, or lazy). The rule is enforced bypytestarch(import-graph) and an AST-walk fallback. (WP03) - CLI consumes events through the public PyPI package
(
spec_kitty_events); the vendored copy atsrc/specify_cli/spec_kitty_events/is deleted in its entirety. (WP04, WP05) - The wheel-shape and filesystem assertions in
tests/contract/test_packaging_no_vendored_events.pylock the deletion: PRs that re-introduce the directory or ship vendored paths in the wheel fail CI. (WP06) - CLI consumes tracker through the public PyPI package
(
spec_kitty_tracker); CLI-internalspecify_cli.tracker.*adapters do not re-export tracker public surface. Consumer-test contracts undertests/contract/spec_kitty_events_consumer/andtests/contract/spec_kitty_tracker_consumer/pin the subset of the public surfaces CLI uses; upstream contract changes break these tests explicitly. (WP07) pyproject.tomllists events / tracker via compatibility ranges (>=4.0.0,<5.0.0and>=0.4,<0.5respectively); exact pins live only inuv.lock. (WP08)[tool.uv.sources]does not contain editable / path entries for any shared package on the committed configuration path. Developer overrides live in dev-only configuration documented indocs/guides/local-overrides.md. (WP08)constraints.txtis removed; its only purpose (papering over thespec-kitty-runtimetransitivespec-kitty-events<4.0pin conflict) is gone. (WP08)- CI runs a
clean-install-verificationjob that provesspec-kitty nextworks in a fresh venv after onlypip install spec-kitty-cli. The job builds the wheel, creates a clean Python venv, installs the wheel, assertsspec-kitty-runtimeis not installed, assertsimport specify_clidoes not pullspec_kitty_runtimeintosys.modules, and runsspec-kitty next --jsonagainst a fixture mission. (WP09) - Operator-facing documentation (
CHANGELOG.md,README.md,CLAUDE.md,docs/development/*,docs/migration/*,docs/host-surface-parity.md) is updated to describe the new boundary, and the closing PR formally supersedes PR #779. (WP10)
Consequences
- Cross-package release lockstep is dissolved: events / tracker can ship within their compatibility windows without forcing a CLI release; the consumer-test contracts in WP07 catch breaking changes explicitly.
- Operators install only
spec-kitty-clifrom PyPI. The retiredspec-kitty-runtimepackage is unused; existing installs can leave it installed (it is harmless) or remove it viapip uninstall. - The CLI codebase grows by ~3 kLoC (the internalized runtime).
- The CLI codebase shrinks by ~23 kLoC (the deleted vendored events tree).
- Cross-package contract changes (events / tracker public surface) break the CLI's consumer-test suite explicitly, forcing CLI to react in a controlled PR rather than fail at runtime in a customer environment.
- The hybrid state (runtime-shaped code in the CLI tree alongside live
spec_kitty_runtimeproduction imports) is mechanically forbidden by the architectural and packaging tests.
Alternatives considered
- Re-publish
spec-kitty-runtimeas a stable library: rejected. The runtime API is CLI-specific; it has no other consumers. Maintaining a standalone PyPI package added cross-package release coordination cost without delivering external value. - Keep events vendored: rejected. Vendoring forks the contract;
consumers see two events surfaces that may diverge. Worse, the
vendored copy was already drifting from PyPI's
spec-kitty-events==4.0.0. - Land the cutover in two PRs (runtime first, events second): rejected. Constraint C-007 of the mission spec explicitly forbids partial cutovers; PR #779 was the cautionary example.
References
- Mission spec:
kitty-specs/shared-package-boundary-cutover-01KQ22DS/spec.md - Migration runbook:
docs/migration/shared-package-boundary-cutover.md - Local-overrides dev doc:
docs/guides/local-overrides.md - Architectural enforcement:
- Consumer contracts:
- Packaging assertions:
- Clean-install verification:
.github/workflows/ci-quality.yml(clean-install-verificationjob)tests/integration/test_clean_install_next.py
- PR #779 (rejected, superseded): https://github.com/Priivacy-ai/spec-kitty/pull/779