Mission Specification: Post-Convergence Governance & Enforcement

Mission Branch: tier3/governance-enforcement Created: 2026-09-06 Status: Draft Input: Post-Convergence Tier-3 — bring the governance record (ADRs), the modularity single-source-of-truth (SSOT), and the module-boundary enforcement gates back into agreement with the code the Convergence (#3881) actually shipped.

Context

The Convergence merge (#3881, landed 2026-09-05) inverted feature-ownership: this core repo (Priivacy-ai/spec-kitty) is now a CLIENT. The authoritative repositories are spec-kitty/zeitgeist and spec-kitty/saas; src/specify_cli/zeitgeist_client/ and src/specify_cli/saas_client/ are consumer code (saas_client is generated — PR #3589 / saas#300). This mirrors the shared-package-boundary model (events/tracker are external deps). Authoring/publishing the API lives upstream; this repo pulls/uses/integrates the clients.

Three governance surfaces still describe the pre-Convergence world:

1. ADRs — four Accepted ADRs govern retired subsystems (sync daemon, SaaS rollout/readiness, project-sync store, CLI-tracker-gated-by-SaaS-sync-flag). No ADR records the retirement or the client-repo inversion. 2. Modularity SSOT — the enforced authority pair (pyproject.toml [wheel].packages + conftest.landscape/test_layer_rules) agrees with reality, but the document that self-declares authority (docs/architecture/05_ownership_manifest.yaml) is stale (names deleted src/doctrine/, src/specify_cli/sync/, src/specify_cli/saas/, and never-created src/lifecycle//src/orchestrator/), and its schema gate test_ownership_manifest_schema.py pins the stale 8-key vocabulary — so correcting the manifest reds the gate (finding D7). 3. Runtime boundary gate — the layer chain places runtime below specify_cli, but ~93 runtime→specify_cli upward edges are ungated: TestRuntimeBoundary forbids only specify_cli.cli and specify_cli.next, and the runtime↔doctrine scan never examines src/runtime (#3522). New inversions land green. _PRODUCTION_ROOTS also lists the retired doctrine shim and omits mission_runtime/glossary (finding D6).

User Scenarios & Testing (mandatory)

User Story 1 - The governance record reflects the retirement (Priority: P1)

An engineer reading docs/adr/ to understand the sync/SaaS subsystems finds the four ADRs that govern them marked Superseded with a pointer to a single new convergence-retirement ADR. That new ADR records what was retired and the client-repo inversion (core = client; spec-kitty/zeitgeist and spec-kitty/saas authoritative upstream; zeitgeist_client/saas_client are consumer code). The shared-package-boundary ADR (2026-04-25-1) stays Accepted — it is the precedent the inversion extends.

Why this priority: ADRs are the binding decision record; a stale Accepted ADR for a deleted subsystem actively misleads. Pure-docs, zero code-behavior risk, unblocks the SSOT narrative.

Independent Test: An ADR-hygiene gate asserts each of the four ADRs carries status: Superseded and links the new ADR; the new ADR exists, is Accepted, supersedes the four, and names the inversion; 2026-04-25-1 remains Accepted.

Acceptance Scenarios:

1. Given the four retired-subsystem ADRs, When the ADR-hygiene test runs, Then each shows status: Superseded and references the convergence-retirement ADR. 2. Given the new convergence-retirement ADR, When the test runs, Then it is Accepted, lists the four superseded ADRs, and records the client-repo inversion. 3. Given 2026-04-25-1-shared-package-boundary.md, When the test runs, Then its status is still Accepted.


User Story 2 - One canonical, reality-agreeing modularity SSOT (Priority: P1)

A contributor asking "which source owns module boundaries?" is pointed at the enforced pair (pyproject.toml [wheel].packages for inventory + conftest.landscape/test_layer_rules for direction), documented as canonical in CLAUDE.md and docs/architecture/00_landscape. The ownership docs are demoted: 05_ownership_manifest.yaml is either deleted or regenerated as a clearly non-authoritative, code-derived artifact, and 05_ownership_map.md is reduced to narrative-only. The D7 schema gate no longer pins stale keys (deleted with the manifest, or repointed to reality-checks if regenerated). The arch docs frame zeitgeist_client/saas_client as clients of the upstream authoritative repos.

Why this priority: the de-jure authority is the least true and its gate blocks correction. Naming the enforced pair as SSOT and demoting the stale doc removes a standing contradiction (D1–D4, D7).

Independent Test: test_ownership_manifest_schema.py's hardcoded-8-keys assertion is gone/relaxed; CLAUDE.md + 00_landscape name the enforced pair as canonical SSOT; the surviving ownership artifact carries no deleted src/specify_cli/sync/+saas/ (or src/doctrine/) current_state paths; the enforced pair (test_pyproject_shape, test_layer_rules) still passes.

Acceptance Scenarios:

1. Given the demotion decision, When the ownership manifest is deleted, Then test_ownership_manifest_schema.py is deleted with it and no other gate references it. 2. Given 05_ownership_map.md, When a reader opens it, Then it is narrative-only and states the enforced pair is the authority. 3. Given CLAUDE.md / 00_landscape, When a reader looks for the modularity SSOT, Then both name the enforced pair and describe zeitgeist_client/saas_client as upstream clients.


User Story 3 - New runtime→specify_cli inversions red CI (Priority: P1)

A future contributor who adds a new runtime → specify_cli import finds CI red: runtime now has the same enforcement mission_runtime already has — a clean should_not import LayerRule or a shrink-only allowed-exception ledger pinned to the current ~93 edges. _PRODUCTION_ROOTS is refreshed (drop the doctrine shim, add mission_runtime + glossary).

Why this priority: the single highest-value enforcement fix — it makes the largest live inversion visible instead of green, closing #3522's exact gap.

Independent Test: a red-first test adds a synthetic new runtime→specify_cli edge to the ledger input and asserts the gate flags it (or, for a pure LayerRule, that a non-ledgered edge fails); the current ~93 edges pass; _PRODUCTION_ROOTS contains mission_runtime + glossary and not doctrine.

Acceptance Scenarios:

1. Given the current tree, When test_layer_rules.py runs, Then the new runtime→specify_cli guard passes (the existing edges are ledgered/allowed). 2. Given a hypothetical new un-ledgered runtime→specify_cli edge, When the guard runs, Then it fails (proven by a non-vacuity/red-first test). 3. Given _PRODUCTION_ROOTS, When test_shared_package_boundary.py runs, Then it lists mission_runtime + glossary, not the retired doctrine shim, and still passes.

Edge Cases

repointed to reality-checks (every current_state/canonical_package path exists on disk), not left pinning fixed keys. (Decision recorded in plan: default is DELETE; regenerate only if a consumer needs it.)

(allow ≤ current set) rather than an exact count, mirroring mission_runtime, so ordinary refactors that remove edges don't red CI.

out-of-scope follow-up; this mission closes the module-level hole (#3522), not #2986.

  • The ownership manifest is regenerated-as-derived instead of deleted → the D7 gate must be
  • The runtime→specify_cli edge count drifts between authoring and merge → use a shrink-only ledger
  • A runtime→specify_cli function-local import (#2986) may bypass a module-level ratchet → note as

Requirements (mandatory)

Functional Requirements

IDTitleUser StoryPriorityStatus
FR-001Supersede four retired-subsystem ADRsAs a maintainer, I want the four retired-subsystem ADRs marked Superseded with a pointer so the decision record stops asserting deleted designs.HighOpen
FR-002Author convergence-retirement ADRAs a maintainer, I want one new ADR that supersedes the four and records the client-repo inversion so the retirement + inversion are governed decisions.HighOpen
FR-003Keep shared-package-boundary ADR AcceptedAs a maintainer, I want 2026-04-25-1 left Accepted so the precedent the inversion extends is preserved.HighOpen
FR-004Name the enforced pair as canonical modularity SSOTAs a contributor, I want CLAUDE.md + 00_landscape to declare pyproject [wheel].packages + conftest.landscape/test_layer_rules the SSOT so module-boundary questions resolve to enforced sources.HighOpen
FR-005Demote the ownership docsAs a contributor, I want 05_ownership_manifest.yaml deleted (or regenerated as non-authoritative) and 05_ownership_map.md narrative-only, so no stale doc self-declares authority.HighOpen
FR-006Resolve D7 gate couplingAs a maintainer, I want test_ownership_manifest_schema.py deleted (if the manifest is deleted) or repointed to reality-checks (if regenerated), so the gate stops pinning stale keys.HighOpen
FR-007Frame client packages as upstream clientsAs a reader, I want arch docs to describe zeitgeist_client/saas_client as clients of spec-kitty/zeitgeist + spec-kitty/saas, not in-repo successor subsystems.MediumOpen
FR-008Enforce runtime→specify_cli boundaryAs a maintainer, I want a LayerRule or shrink-only ledger so a new runtime→specify_cli edge reds CI (#3522).HighOpen
FR-009Refresh _PRODUCTION_ROOTSAs a maintainer, I want _PRODUCTION_ROOTS to drop the doctrine shim and add mission_runtime+glossary so the retired-import scan covers the real roots (D6).MediumOpen

Non-Functional Requirements

IDTitleRequirementCategoryPriorityStatus
NFR-001Gate runtime costEvery new/changed architectural gate completes in ≤5 s locally (suite NFR-002 budget).PerformanceHighOpen
NFR-002No code-behavior changeThis mission changes governance docs + tests only; it introduces no runtime code-behavior change (no src/** production-logic edits beyond import-boundary conformance if any edge must move).ReliabilityHighOpen
NFR-003Non-vacuous enforcementEvery new gate carries a proof-of-teeth (red-first) test showing it fails on a synthetic violation.ReliabilityHighOpen

Constraints

IDTitleConstraintCategoryPriorityStatus
C-001Do not re-litigate the inversionThe client-repo inversion is binding architectural context; no zeitgeist/SaaS redesign or authoring epic is created in this repo.TechnicalHighOpen
C-002Enforced pair is the SSOT05_ownership_* may not be treated as authoritative; the enforced pair wins on every conflict (D2).TechnicalHighOpen
C-003Shrink-only ledgerThe runtime→specify_cli enforcement must be shrink-only (allow ≤ current edges), never an exact count, to avoid red-on-removal.TechnicalHighOpen
C-004Targeted verification onlyThe full tests/architectural/ suite is session-breaking; verify only the gates this mission touches.TechnicalMediumOpen

Key Entities

Superseded) and cross-references.

demoted, formerly self-authoritative slice-ownership doc.

tests/architectural/conftest.py landscape fixture / test_layer_rules.py (direction).

test_layer_rules.py, mirroring _MISSION_RUNTIME_ALLOWED_SPECIFY_CLI.

  • ADR: a decision record under docs/adr/{2.x,3.x}/; carries a status field (Accepted /
  • Ownership manifest / map: docs/architecture/05_ownership_manifest.yaml (+ .md mirror); the
  • Enforced SSOT pair: pyproject.toml [tool.hatch.build.targets.wheel].packages (inventory) +
  • Runtime boundary ledger: the allowed-exception set gating runtime→specify_cli edges in

Success Criteria (mandatory)

Measurable Outcomes

convergence-retirement ADR exists and is Accepted; 2026-04-25-1 remains Accepted — verified by a gate.

regenerated-as-derived), and no surviving ownership artifact names a deleted sync//saas//doctrine/ path.

edges pass; the enforced pair (test_pyproject_shape, test_layer_rules) stays green.

test_ownership_manifest_schema if kept, test_no_stale_charter_path_literals, test_no_legacy_terminology) pass; each new gate runs in ≤5 s.

  • SC-001: 100% of the four retired-subsystem ADRs are Superseded with a pointer; exactly one new
  • SC-002: The stale ownership manifest is no longer treated as authoritative (deleted or
  • SC-003: A new runtime→specify_cli import reds CI (proven by a red-first test); the current ~93
  • SC-004: The touched gates (test_layer_rules, test_shared_package_boundary,