Status: Superseded (2026-10-19) by spec-kitty-charter ADR-0008 and ADR-0010. Accepted
2026-10-12.
Superseded (2026-10-19, operator ruling). The charter does not move to its own repository as a Python package. Per
spec-kitty-charterADR-0008 (rewrite and replace) and ADR-0010 (cutover scope and seam boundary; recorded in spec-kitty-charter#88): the Python charter stays in core until it is retired, and nothing Python moves tospec-kitty-charter. Offering reads cut over to thespec-kitty-charterservice throughcharter.offering.api;charter.activationstays in core until its post-GA rework (ADR 2026-10-16-1, Amendment 2026-10-19). The 4.0.0 leg is a frozen seam and a green conformance corpus, not an extraction. Epic #6099 is re-scoped accordingly. The body below is kept unchanged as the record of the earlier decision.
Date: 2026-10-12
Deciders: Stijn Dejongh (operator).
Technical Story: #645 (move the ~129 specify_cli → charter imports onto the stable
charter API — now GA-gating); #3101 (the wheel/repo split design spike). Extends ADR
2026-08-02-1 (charter-wheel assessment) and
ADR 2026-04-25-1 (shared-package boundary).
Companion: ADR 2026-10-01-2
(the open-core producer surface). Durable plans:
Packs Extraction §3.1 and
Doctrine & Charter §3.6.
Reader: a maintainer planning the charter extraction, or changing the enforced layer chain / shared-package-boundary gates.
Target state, not shipped state. This record fixes the direction the 2026-10-12 October direction update ratified. The repository does not contain a
charter-repo split today, and the diagram below shows the ratified target topology — for exampleservices/mission-status-api/does not exist yet (onlycontracts/mission-status/and the Python reducer ship). The extraction mechanics ride with a dedicated epic (to be filed); this ADR records the decision and its boundary, and changes no gate, layer rule orpyproject.tomlpackage set.
Context and Problem Statement
spec-kitty-events and spec-kitty-tracker are already external contract packages: the CLI
consumes them as ordinary versioned dependencies through their public import surface, never
vendoring their source (charter, "Architecture: Shared Package Boundaries"; ADR 2026-04-25-1).
The charter layer — the governance authority at src/charter/offering/ (or all of
src/charter/) — is not yet such a unit. It already has a curated public surface
(charter.offering.api, #3179) and depends only on kernel, but specify_cli still reaches
charter internals directly across ~129 files, and the charter ships only inside the CLI wheel.
ADR 2026-08-02-1 assessed the kernel → doctrine → charter wheel split and deferred it; the
dormant spec-kitty-doctrine wheel groundwork was later deleted. The October direction update
(2026-10-12) changes the priority and the target: the charter is to become an
independently-released external contract package in its own repository, and its import-
decoupling precondition (#645) is now part of the 4.0.0 GA gate.
Decision Drivers
- Single canonical authority / architectural alignment (charter governing principles): the charter is consumed through one public surface, like the other external contract packages, not through deep internal imports.
- Open-core shape. The charter ("Brain Kitty" governance/doctrine) is open-core producer material; releasing it as its own unit lets open-core consumers depend on it directly.
- The GA gate expands (2026-10-12). Charter modular extraction joins local integrity and the mission-status API backend as a GA gate leg; #645 is its precondition.
Considered Options
- Keep the charter in-tree indefinitely. Rejected: it leaves ~129 internal reaches in place and blocks open-core consumers from depending on the charter without the whole CLI.
- Ship the charter as a wheel built from this repo, but keep it in-tree. A partial step; it does not give the charter its own release cadence or repository.
- Extract the charter into its own repository as an external contract package (chosen).
Released and versioned on its own, consumed through
charter.offering.api, exactly asspec-kitty-events/spec-kitty-trackerare today.
Decision Outcome
Chosen option: Option 3. The charter becomes an external contract package in its own repository.
- D-1 (Accepted): the charter is an external contract package. Callers consume it only
through
charter.offering.api. The enforced module chain (kernel <- charter <- {glossary, runtime, mission_runtime} <- specify_cli) and the shared-package-boundary gates (test_pyproject_shape.py,test_shared_package_boundary.py,test_layer_rules.py, theclean-install-verificationjob) will gaincharteras an external contract package when the extraction lands. No gate, layer rule or package set changes in this ADR — this records the direction. - D-2 (Accepted): #645 is the GA-gating precondition. The ~129
specify_cli → charterinternal imports move ontocharter.offering.apibefore the charter can be packaged separately. Per ADR 2026-09-30-1 (allowlist ratchets are priced debt), drain them rather than holding them in an allowlist. - D-3 (Accepted): its own repository. The released unit lives in its own repository
(extending, not contradicting, 2026-08-02-1's wheel assessment: the wheel question —
charter.offeringalone or all ofsrc/charter/, and whetherkernelships first — is settled by the extraction epic). The extraction mechanics ride with that epic (to be filed). - D-4 (Accepted): relationship to the open-core producer boundary. The charter is a producer-side open-core unit. It is distinct from the Mission Status Read API producer surface (ADR 2026-10-01-2) and from the charter backend service (a future runtime resolution daemon, charter-backend-service-future.md): this ADR is about packaging/repository topology, not a runtime service.
Consequences
Positive
- Open-core consumers can depend on the charter directly, at its own version, without the CLI.
- The ~129 internal reaches are drained, hardening the single-public-surface invariant (#3179).
Negative
- Cross-repo release coordination (version ranges, compatibility fixtures) like the other external contract packages already carry.
- Until the extraction lands, the charter still ships only inside the CLI wheel.
Neutral
- The pack source-home move to
spec-kitty-open-packs(both tiers; source-location only, the no-bundle invariant unchanged, ADR 2026-08-16-3 not reversed) is a sibling packaging track, not part of this code extraction (see the Packs Extraction plan).
Confirmation
- An architectural test pins
charteras an external contract package once the extraction lands; a consumer pinned tocharter.offering.apisurvives the split. - #645 reaches zero
specify_cli → charterinternal reaches.
Target topology
The ratified producer/consumer topology (target state; services/mission-status-api/ does not
exist yet — the diagram shows where units are headed, not what is on disk now):
Alt-text: Build Kitty and Brain Kitty open-core producer subgraphs versus an opaque Team Kitty consumer subgraph; units tagged exists / extracting / planned; dotted arrows for charter → its own repository and mission-status-api → its own open-core repository; the Mission-Status API contract is the producer → consumer boundary.
%%{init: {'theme':'base','themeVariables': {
'fontFamily': 'Trebuchet MS, Verdana, Arial, sans-serif',
'fontSize': '14px',
'primaryColor': '#f5f5f5',
'primaryTextColor': '#103a82',
'primaryBorderColor': '#1c75cc',
'lineColor': '#0a0721'
}}}%%
flowchart LR
subgraph Build["Build Kitty — open core (producer)"]
direction TB
cliRepo["<b>spec-kitty</b> (this repo)<br/>CLI + event ledger + Lamport reducer<br/><i>exists</i>"]
apiRepo["<b>mission-status-api</b><br/>Java service · services/mission-status-api/<br/><i>planned · to start in-repo</i>"]
apiRepoFuture["mission-status-api<br/>own open-core repo<br/><i>planned extraction</i>"]
end
subgraph Brain["Brain Kitty — open core (producer)"]
direction TB
charterNow["<b>charter</b> · src/charter/offering/<br/>governance / doctrine<br/><i>extracting</i>"]
charterRepo["charter<br/>own repository<br/><i>planned extraction target</i>"]
openpacks["<b>spec-kitty-open-packs</b><br/>open doctrine packs<br/><i>exists</i>"]
dockitty["<b>doc-kitty</b><br/>docs-site toolkit<br/><i>exists</i>"]
end
subgraph Contracts["Extracted contract packages — open core (producer)"]
direction TB
events["spec-kitty-events<br/><i>exists (wheel)</i>"]
tracker["spec-kitty-tracker<br/><i>exists (wheel)</i>"]
end
seam(["<b>Mission-Status API contract</b><br/>contracts/mission-status/openapi.yaml<br/><i>producer → consumer boundary</i>"])
subgraph Team["Team Kitty — consumer / 'Multiplayer Mode' (opaque)"]
direction TB
tkbox["mission-ui · pack composer<br/><i>consumer · internals not opened</i>"]
end
cliRepo -->|serves| apiRepo
events -->|consumed by| cliRepo
tracker -->|consumed by| cliRepo
charterNow -->|governs| cliRepo
openpacks -->|doctrine for| cliRepo
apiRepo -->|publishes| seam
seam -->|consumed across contract| tkbox
charterNow -. extract to own repo .-> charterRepo
apiRepo -. extract to own open-core repo .-> apiRepoFuture
classDef coreProducer fill:#dceafd,stroke:#1168bd,color:#103a82,stroke-width:2px;
classDef extracting fill:#fff4d6,stroke:#d9a400,color:#5a4600,stroke-width:2px,stroke-dasharray:5 3;
classDef planned fill:#eceff3,stroke:#8c9cab,color:#384b5f,stroke-width:1.5px,stroke-dasharray:3 3;
classDef pkg fill:#e3f3e8,stroke:#2f8f52,color:#1d5a34,stroke-width:1.5px;
classDef seam fill:#fde8c8,stroke:#d9731a,color:#6e3a05,stroke-width:2.5px;
classDef consumer fill:#f7e6ee,stroke:#b5457f,color:#6e2650,stroke-width:1.5px;
class cliRepo,openpacks coreProducer;
class charterNow extracting;
class charterRepo,apiRepo,apiRepoFuture planned;
class events,tracker,dockitty pkg;
class seam seam;
class tkbox consumer;
More Information
- Charter, "Architecture: Shared Package Boundaries" (charter as a third external contract package, direction).
- Packs Extraction domain plan §3.1 (the code boundary, #3101) and §3.2 (the open-packs source home).
- 4.0.0 milestone roadmap — the three-leg GA gate.
- The docsite→doc-kitty and QA test-absorption tracks ride with their own epics/ADRs (to be filed); this ADR references them only.
Amendment 2026-10-13: two sibling entry-point seams, and a shrink-only activation baseline (#6188, #6099)
D-1 and D-2 name one seam, charter.offering.api. The offering / activation split (ADR 2026-10-06-2 §1) means the charter has two independently extractable layers, so there are two sibling seams. The decisions above are unchanged; this note says how they apply.
- Two seams.
charter.offering.apiis the seam for the offering layer.charter.activation.apiis the seam for the activation layer. Each re-exports only its own layer's symbols, by object identity.tests/architectural/test_charter_offering_does_not_import_activation.pykeeps the layers apart. - The
charter.*facades stay the in-tree door for offering.specify_climust not importcharter.offering.*,charter.offering.apiincluded (C-001, stated in thecharter.offering.apidocstring and ratcheted bytests/architectural/test_runtime_charter_doctrine_boundary.py). It reaches offering through thecharter.*facades, which re-export the offering objects.src/glossaryandsrc/mission_runtimeare scanned by the same gate. - A sanctioned exception to D-2. 236
(file, module)reaches fromspecify_cli,runtime,glossaryandmission_runtimeintocharter.activationinternals existed when the activation seam was added. They are held as a shrink-only baseline (tests/architectural/charter_activation_boundary_baseline.yaml, count intests/architectural/_baselines.yaml), owner #6099, drained ontocharter.activation.apibefore the 4.0.0 GA gate(superseded — see Amendment 2026-10-16: the activation drain is now post-GA).tests/architectural/test_runtime_charter_activation_boundary.pyfails on a new reach and on a stale row, and it also covers the lazycharter.<name>root facade. This is a temporary, priced exception to "drain rather than allowlist", not a precedent.
Amendment 2026-10-16: extraction splits by seam — offering first (GA), activation rework post-GA (#6099)
Superseded by and recorded in ADR 2026-10-16-1. The decisions above stand; this note narrows what the GA leg is and when the activation drain lands.
- The charter extracts in two seam-scoped steps, not one.
charter.offeringhas zero code-level dependency oncharter.activation(verified: offering → activation runtime imports = 0; activation → offering ≈ 360), so offering is the foundational layer and is independently liftable. The charter modular-extraction GA leg (D-2, and the roadmap's GA gate) is now scoped to offering. - The activation drain moves off the GA gate. The Amendment 2026-10-13 clause "drained onto
charter.activation.apibefore the 4.0.0 GA gate" is superseded:charter.activationstays in core as-is for GA, and its 236-reach drain and any activation extraction ride the post-GA CLI 4.x stable line. The shrink-only baseline remains priced debt (ADR 2026-09-30-1); only its deadline moves. - D-2's precondition citation is corrected. The "~129
specify_cli → charterimports" move completed as #6188 (closed 2026-10-13, PR #6200); #645 is closednot_planned. Neither is an open precondition. The live extraction epic is #6099, which now carries four offering-extraction phases on milestone "4.0.0 release scope".