Contracts
events_consumer_surface.md
Contract: spec-kitty-events Consumer Surface
Mission: shared-package-boundary-cutover-01KQ22DS Upstream contract: spec-kitty-events mission events-pypi-contract-hardening-01KQ1ZK7, merged at sha 81d5ccd4. Authoritative public surface listed in spec-kitty-events/docs/public-surface.md. CLI version range: spec-kitty-events>=4.0.0,<5.0.0
This contract pins the subset of the events public surface the CLI actually imports post-cutover. The consumer test at tests/contract/spec_kitty_events_consumer/test_consumer_contract.py asserts:
1. Each pinned symbol exists at the documented import path. 2. Each pinned symbol has the structural shape (callable signature / class attributes) the CLI relies on.
Upstream may add new symbols freely (MINOR bump). Upstream removing or renaming a pinned symbol below MUST be a MAJOR bump and MUST break this consumer test, forcing the CLI to update before merging.
Pinned imports
Top-level package surface
| Import | Used by |
|---|---|
from spec_kitty_events import Event | src/specify_cli/sync/diagnose.py, sync emitter, glossary events |
from spec_kitty_events import ErrorEntry | sync emitter |
from spec_kitty_events import ConflictResolution | sync conflict path |
from spec_kitty_events import normalize_event_id | sync emitter, decisions emit |
from spec_kitty_events import LamportClock | sync emitter |
from spec_kitty_events import EventStore | sync persistence |
from spec_kitty_events import InMemoryEventStore | tests |
spec_kitty_events.decisionpoint
| Import | Used by |
|---|---|
from spec_kitty_events.decisionpoint import DecisionPointOpened | src/specify_cli/decisions/emit.py |
from spec_kitty_events.decisionpoint import DecisionPointResolved | src/specify_cli/decisions/emit.py |
from spec_kitty_events.decisionpoint import InterviewPayload (or current 4.0.0 name) | src/specify_cli/decisions/emit.py |
spec_kitty_events.decision_moment
| Import | Used by |
|---|---|
from spec_kitty_events.decision_moment import DecisionMomentOpened (or current 4.0.0 name) | src/specify_cli/decisions/emit.py |
from spec_kitty_events.decision_moment import Widened | src/specify_cli/decisions/emit.py |
spec_kitty_events.cutover
| Import | Used by |
|---|---|
from spec_kitty_events import CUTOVER_ARTIFACT | optional cutover-signal validation |
from spec_kitty_events import assert_canonical_cutover_signal | optional cutover-signal validation |
(The exact decisionpoint / decision_moment symbol names are confirmed against the events package on rebase; the CLI's current decisions/emit.py imports fixed names that may need to be re-checked against the 4.0.0 surface during WP04.)
Consumer-test assertions
For each import above, the consumer test:
1. import statement succeeds against the installed events package. 2. Callable symbols have the parameter names CLI passes (verified via inspect.signature). 3. Class symbols expose the attributes CLI reads (verified via hasattr). 4. Exception symbols are subclasses of BaseException.
The consumer test is not a behavior test; it is a shape test. Behavior tests live where the behavior is exercised (in CLI integration tests).
SemVer interaction
Per spec-kitty-events/docs/public-surface.md:
range <5.0.0 accepts it without action.
range REJECTS the new release at install time. CI catches the mismatch via the consumer test running against a pinned newer version, OR via the clean-install verification job failing to install.
- Adding a new symbol to events
__all__→ MINOR bump → CLI's compatibility - Removing or renaming a pinned symbol → MAJOR bump → CLI's compatibility
This is the entire point of the consumer-test contract: upstream contract changes break CLI explicitly, never silently.
Out of scope
subset CLI imports.
concurrency, or storage backends — those are upstream's tests.
events MUST extend this contract in the same PR that adds the new import.
- This contract does not enumerate the full events public surface. Only the
- This contract does not assert anything about events behavior under load,
- This contract does not bind future CLI-side imports; new CLI consumers of
internal_runtime_surface.md
Contract: Internalized Runtime Public Surface
Mission: shared-package-boundary-cutover-01KQ22DS Module: specify_cli.next._internal_runtime Status: New module; surface frozen for this cutover.
This contract defines the exact public symbols the new internalized runtime exposes. The surface mirrors what the CLI currently imports from spec_kitty_runtime 0.4.3. WP01 builds this surface; WP02 cuts every CLI production import over to it; WP03 architecturally enforces that no production code path reaches into spec_kitty_runtime afterward.
Public symbols (re-exported by specify_cli.next._internal_runtime.__init__)
from specify_cli.next._internal_runtime import (
DiscoveryContext,
MissionPolicySnapshot,
MissionRunRef,
NextDecision,
NullEmitter,
next_step,
provide_decision_answer,
start_mission_run,
)
| Symbol | Kind | Replaces |
|---|---|---|
DiscoveryContext | dataclass | spec_kitty_runtime.DiscoveryContext |
MissionPolicySnapshot | dataclass | spec_kitty_runtime.MissionPolicySnapshot |
MissionRunRef | dataclass | spec_kitty_runtime.MissionRunRef |
NextDecision | dataclass | spec_kitty_runtime.NextDecision |
NullEmitter | class | spec_kitty_runtime.NullEmitter |
next_step | callable | spec_kitty_runtime.next_step (callers usually alias as runtime_next_step) |
provide_decision_answer | callable | spec_kitty_runtime.provide_decision_answer |
start_mission_run | callable | spec_kitty_runtime.start_mission_run |
Sub-module symbols
specify_cli.next._internal_runtime.schema
| Symbol | Kind | Replaces |
|---|---|---|
ActorIdentity | dataclass | spec_kitty_runtime.schema.ActorIdentity |
load_mission_template_file | callable | spec_kitty_runtime.schema.load_mission_template_file |
MissionRuntimeError | exception | spec_kitty_runtime.schema.MissionRuntimeError |
specify_cli.next._internal_runtime.engine
| Symbol | Kind | Replaces |
|---|---|---|
_read_snapshot | callable | spec_kitty_runtime.engine._read_snapshot |
(module reference; runtime_bridge.py imports engine as a module) | module | spec_kitty_runtime.engine |
The _read_snapshot underscore prefix is preserved because every existing caller already imports it by that name. Renaming would expand the diff surface without architectural benefit.
specify_cli.next._internal_runtime.planner
| Symbol | Kind | Replaces |
|---|---|---|
plan_next | callable | spec_kitty_runtime.planner.plan_next |
Behavior contract
For every symbol above, the post-cutover behavior MUST be identical to the pre-cutover behavior of spec_kitty_runtime 0.4.3 against the reference fixture mission, byte-for-byte where output is JSON, and structurally where output is Python objects.
WP01 captures golden snapshots:
next_step(...) against the reference mission at three sequential steps.
start_mission_run(...) for the fixture.
output of provide_decision_answer(...) after a planted decision moment.
tests/fixtures/runtime_parity/snapshot_next_step.json— output oftests/fixtures/runtime_parity/snapshot_start_mission_run.json— output oftests/fixtures/runtime_parity/snapshot_provide_decision_answer.json—
The internalized implementation passes all three with byte-equal JSON modulo documented timestamp / path normalization (the same normalization runtime-mission-execution-extraction-01KPDYGW uses).
Internalization strategy
The internalized runtime is derived from, not copied verbatim from, the upstream spec-kitty-runtime 0.4.3 source. WP01 imports the runtime mission's public-API inventory (from spec-kitty-runtime/kitty-specs/runtime-standalone-package-retirement-01KQ20Z8/) as the authoritative list of symbols and behaviors to internalize. Behaviors not on the inventory are not internalized; if a CLI caller uses such a behavior, the inventory is wrong and a delta is filed back to the runtime mission rather than expanding scope locally.
Implementation detail freedom: the internalized code MAY restructure the sub-module layout, rename internal helpers, or simplify private code paths as long as the public surface above is preserved and the parity snapshots pass.
Forbidden patterns
(top-level, lazy, or conditional). The whole point is independence.
presentation belongs to the CLI layer (consistent with the layer rules in tests/architectural/test_layer_rules.py).
specify_cli.next._internal_runtime directly; the underscore prefix marks it internal. The public CLI surface for "next step" remains specify_cli.next.runtime_bridge and the spec-kitty next command.
- The internalized runtime MUST NOT import
spec_kitty_runtimeat any layer - The internalized runtime MUST NOT depend on
rich.ortyper.directly; - External Python importers MUST NOT reach into
tracker_consumer_surface.md
Contract: spec-kitty-tracker Consumer Surface
Mission: shared-package-boundary-cutover-01KQ22DS Upstream contract: spec-kitty-tracker mission tracker-pypi-sdk-independence-hardening-01KQ1ZKK (in implement-review at the time this plan is written; rebase before WP07 lands). CLI version range: spec-kitty-tracker>=0.4,<0.5
This contract pins the subset of the tracker public surface the CLI actually imports post-cutover. The consumer test at tests/contract/spec_kitty_tracker_consumer/test_consumer_contract.py asserts each pinned symbol exists at the documented import path with the shape CLI relies on.
Tracker's public-surface doc is finalized by the upstream mission. Until that mission merges, this contract pins what the currently-published 0.4.2 SDK exposes; WP07 rebases this contract against the upstream mission's published public-surface doc when that mission lands.
Pinned imports
Top-level package surface
| Import | Used by |
|---|---|
from spec_kitty_tracker import FieldOwner | src/specify_cli/tracker/local_service.py |
from spec_kitty_tracker import OwnershipMode | src/specify_cli/tracker/local_service.py |
from spec_kitty_tracker import OwnershipPolicy | src/specify_cli/tracker/local_service.py |
from spec_kitty_tracker import SyncEngine | src/specify_cli/tracker/local_service.py |
from spec_kitty_tracker import ... (factory entry points used by src/specify_cli/tracker/factory.py) | src/specify_cli/tracker/factory.py |
spec_kitty_tracker.models
| Import | Used by |
|---|---|
from spec_kitty_tracker.models import ExternalRef | src/specify_cli/tracker/local_service.py (line 168) |
from spec_kitty_tracker.models import ... (the model classes used by src/specify_cli/tracker/store.py line 29 — confirmed during WP07) | src/specify_cli/tracker/store.py |
(The exact factory and model symbol names are captured by enumerating the existing CLI tracker import sites during WP07; the contract module is generated from that enumeration so the tests stay in sync.)
Consumer-test assertions
For each import above, the consumer test:
1. import statement succeeds against the installed tracker package. 2. Callable symbols have the parameter names CLI passes (verified via inspect.signature). 3. Class symbols expose the attributes CLI reads (verified via hasattr). 4. Enum-like symbols (e.g. OwnershipMode) expose the enum members CLI reads.
The consumer test is a shape test, not a behavior test.
SemVer interaction
The CLI's >=0.4,<0.5 range is a conservative window pending the upstream SDK independence mission's published SemVer policy. WP08 documents the agreed range; WP07's consumer test ensures upstream contract changes break CLI explicitly.
When the upstream mission publishes a 1.0 line with a finalized SemVer policy, this CLI mission's compatibility range is bumped to follow it (typically >=1.0,<2.0).
Out of scope
contracts, or remote tracker semantics — those are upstream's tests.
adapters; their unit tests live under tests/specify_cli/tracker/.
- This contract does not enumerate the full tracker SDK surface.
- This contract does not assert tracker network protocol behavior, server
- This contract does not test the CLI-internal
specify_cli.tracker.*
Boundary invariant
CLI MUST consume tracker only via spec_kitty_tracker.. The CLI-internal specify_cli.tracker. adapters MAY remain (they own CLI-side concerns: lock management, fixture handling, local-service shims), but they MUST NOT re-export tracker public symbols under a specify_cli.tracker.* namespace. The architectural test at tests/architectural/test_shared_package_boundary.py enforces this by inspecting the public exports of specify_cli.tracker.