06 — Unified Charter Bundle

Status: v1.0.0 (introduced by mission unified-charter-bundle-chokepoint-01KP5Q2G, WP01). Canonical manifest module: src/charter/bundle.py JSON Schema: kitty-specs/unified-charter-bundle-chokepoint-01KP5Q2G/contracts/bundle-manifest.schema.yaml

Purpose

Spec Kitty's governance surface is a small bundle of files rooted at .kittify/charter/. Some are tracked (committed to git, human-authored) and some are derived (regenerated by src/charter/sync.py :: sync() from the tracked source). Before this document, the set of derived files was implicit in imperative code paths; every reader had its own idea of what the "bundle" was. The unified charter bundle contract replaces that implicit knowledge with a single typed manifest — CharterBundleManifest — that every reader, migration, and CLI surface consults.

This file is the narrative source of truth. The Pydantic model in src/charter/bundle.py is the machine-readable authority. The two MUST stay in sync; changes to either REQUIRE a SCHEMA_VERSION bump and an accompanying migration.

v1.0.0 scope

The v1.0.0 manifest covers exactly the files that sync() materializes today. These are enumerated in src/charter/sync.py:32-36 as _SYNC_OUTPUT_FILES:

Role Path Derived from
tracked .kittify/charter/charter.md — (human-authored)
derived .kittify/charter/governance.yaml .kittify/charter/charter.md
derived .kittify/charter/directives.yaml .kittify/charter/charter.md
derived .kittify/charter/metadata.yaml .kittify/charter/charter.md

Three derived files. Three derivation_sources entries. One tracked source. That is the entire v1.0.0 contract.

Out of v1.0.0 scope (C-012)

The following files live under .kittify/charter/ but are not managed by the v1.0.0 manifest. They are produced by separate pipelines with their own lifecycle and ownership:

  • .kittify/charter/references.yaml — produced by the compiler pipeline at src/charter/compiler.py, invoked by spec-kitty charter generate. The compiler owns its own staleness check and write path. Pulling references.yaml into the manifest would require a coordinated rework of the compiler's completeness contract and is deferred to a later tranche.
  • .kittify/charter/context-state.json — runtime state written by src/charter/context.py lines 385-398 inside build_charter_context(). This is lazy, per-invocation runtime state; it is not part of any reproducibility contract and is intentionally absent from the manifest.
  • .kittify/charter/interview/answers.yaml and .kittify/charter/library/*.md — optional companion inputs for charter generation, owned by the interview and catalog surfaces respectively.

The bundle validate CLI surfaces these as informational warnings when present, never as failures. Operators can leave them in place, remove them, or extend their .gitignore to match; the v1.0.0 manifest does not forbid them.

Tracked vs. derived classification

The split exists so tooling can reason about the two classes separately:

  • Tracked files MUST exist on disk, MUST be committed to git, and MUST appear in git ls-files. They are the human-authored source of truth and are therefore the input to the derivation.
  • Derived files MAY be absent in a fresh clone (before the first sync() invocation) and MUST be listed in .gitignore so they never accidentally land in a commit. Every entry in gitignore_required_entries corresponds to a derived path.

The manifest asserts the shape of this split via the Pydantic _validate hook: no path may be in both sets; every derivation_sources key must be a derived path; every value must be a tracked path.

Canonical-root contract (forward reference)

All readers resolve the canonical project root through a single helper — charter.resolution.resolve_canonical_repo_root() — which correctly maps a worktree path back to its main-checkout location. WP01 ships the manifest but leaves the resolver surface to WP02; see contracts/canonical-root-resolver.contract.md for the full contract. WP01's bundle validate CLI uses a temporary git rev-parse --show-toplevel wrapper with a TODO(WP02) marker; WP03 replaces it once WP02 lands.

Staleness semantics

The derivation is hash-driven:

  1. sync() computes a SHA-256 of charter.md.
  2. It compares that hash against the source_hash field inside metadata.yaml.
  3. If they match and --force is not set, sync() returns early with synced=False. Otherwise it re-extracts and rewrites all three derived files, updating source_hash and the timestamp in metadata.yaml.

The chokepoint (ensure_charter_bundle_fresh, introduced in a later WP) performs the same check on every read path that depends on the bundle, keeping the derivation eventually consistent with the tracked source.

Gitignore policy: MUST-INCLUDE, not exclusive

gitignore_required_entries is a MUST-INCLUDE set. The .gitignore at the project root:

  • MUST contain every entry the manifest lists on its own line.
  • MAY carry additional entries, including entries for the out-of-scope files enumerated above.

bundle validate only fails when a required entry is missing. It does not enforce exclusivity and does not warn about additional .kittify/charter/* entries.

Schema versioning policy

CANONICAL_MANIFEST.schema_version carries an independent semver (1.0.0 at introduction). The manifest version is not tied to the spec-kitty package version.

  • Major bump (2.0.0): breaking change to the manifest shape or required fields — e.g., adding a required key, renaming an existing key, removing the tracked/derived split. Requires a new migration module under src/specify_cli/upgrade/migrations/.
  • Minor bump (1.1.0): scope expansion (e.g., pulling references.yaml into the managed set) or additive optional fields. Requires a migration that extends the manifest and updates every reader site simultaneously.
  • Patch bump (1.0.1): narrative or docstring fixes that do not change the shape or scope. No migration needed.

Future manifest versions ship with their own migration; there is no fallback for older manifests at runtime (per C-001). A project that lags a manifest bump must upgrade before it can use the bundle CLI.