Status: Proposed
Date: 2026-07-18
Deciders: Operator (Stijn Dejongh); assessment by a 4-lens grounding squad + a thesis/antithesis dialectic + a neutral empirical investigation (2026-07-18).
Technical Story: epic #2519; mission #2773 (consolidate-charter-bundle); relates #2772; aligns with ADR 2026-07-15-1.
Context and Problem Statement
Charter state is compiled from a hand-authored charter.md into four separate on-disk artifacts under .kittify/charter/: governance.yaml, directives.yaml, metadata.yaml (the "derived triad", produced by sync.py::sync()), and references.yaml (produced by compiler.py, and also the ID-parity authority). Mission #2773 consolidates these four into one compiled charter.yaml.
While specifying #2773 the operator reopened a deeper question under a deterministic-first philosophy: should charter.yaml become the authoritative structured source and charter.md a curated companion (rationale write-up), inverting the current "charter.md is the authored source" model?
The proposal was assessed and de-risked. The key findings:
- Three surfaces are conflated by the phrase "what is active". (A) the activation ledger —
config.yaml activated_*; (B) the compiled resolving bundle — the four files →charter.yaml; (C) human prose —charter.md. #2773 consolidates B; the inversion targets C. answers.yamlis provenance-only (operator-binding): a creation-time record of why/when/who, with zero runtime/behavioural impact by design. Readinganswers.yaml selected_*for behaviour is a bug. Thereforeconfig.yaml activated_*is the sole activation authority; the #2519 "disjoint activation ledgers" concern is largely a mislabel.charter.mdis currently dual-owned — hand-authored prose AND regenerated from an interview template bycompiler.py:421(charter_path.write_text(compiled.markdown)), reachable viacharter generate --force. That single writer is the #2772 clobber (a 1237-line curated-prose destruction was observed live).- The "AI/hybrid extractor" is a phantom.
extractor.py:807 extract_with_aireturns{}and has zero callers; the prose→triad extraction is 100% deterministic regex/keyword scraping. Theextraction_mode: "hybrid"label is cosmetic. - Prose is display-only for governance. A neutral trace of every
charter.md-content reader found all governance/directive reads flow into agent-facing display text (context.py:274 _extract_policy_summary,context.py:1023/2754/2784 render_critical_section_bodies,compact.py:138), not decisions. Runtime governance/directive loaders (sync.py:307/356) read the triad YAML, not prose. The one behavioral prose read — doctrine language-scoping (language_scope.py:101-103) — is orthogonal: its authoritative source isreferences.yaml(structured, from the interview), withcharter.mdonly a degraded tier-3 fallback; it is not produced by the scraper and not part of the triad.
Consequence of the findings: seeding charter.yaml from the existing triad is a deterministic, lossless yaml→yaml fold (the same migration #2773 already commits to). The inversion's cost is therefore not a data-migration hazard — it is (a) a governance/authoring-UX structural change and (b) a schema-shape decision that is expensive to reverse under the bundle-manifest schema contract (a version bump + migration).
Decision Drivers
- Deterministic-first — the authoritative "what is active / what governance applies" state should be structured and deterministically read, not scraped from prose.
- Single canonical authority (charter governing principle) — one owning source per surface; eliminate the dual-owned
charter.md. - Kill the split/drift/stopgap churn — epic #2519's purpose. Shipping a
charter.yamlschema we already know we will re-cut for the inversion would recreate exactly that churn. - Cut the schema once — the bundle-manifest schema/migration is the only expensive-to-reverse artifact; deciding its end-state shape now avoids a second bump on the same brand-new file.
- Eliminate the #2772 clobber at its single write site.
- Preserve the #2732 content-identity machinery and the C-001 layer boundary (
src/charter/must not importspecify_cli).
Considered Options
- Option X — ADR now + reshape #2773 to be authoring-ready; stage the deletion. (chosen)
- Option Y — ADR-first, then shape #2773 from the outcome.
- Option Z — keep #2773 bounded (derived-from-prose), do the inversion later as a wholly separate mission.
- Option (b) — charter.md becomes fully generated from rationale fields embedded in charter.yaml. (rejected)
Decision Outcome
Chosen option: X, because the only expensive-to-reverse artifact is the charter.yaml schema, and the empirical trace removed the "lossy migration" blocker — so we commit the full end-state in one coherent mission, delivered on a single branch and PRed as a consistent whole (sequenced tidy-first within the mission rather than split across PRs).
The end-state this ADR pins:
charter.yamlis the project charter — the authoritative structured source for the project's active doctrine: governance knobs, directive declarations, the resolving/artifact-ID catalog, the project activation state and overrides. It is pack-shaped (same flatactivated_kinds/mission_type_activations/activated_*root-key vocabulary assrc/charter/packs/default.yaml). This mission relocates a single flat activation surface (default.yamlsupplies the absent-key fallback/seed); the multi-tier org⊆team⊆repo charter accumulation is the target model but is forward-intent — the currentpack_rootsoverlay applies to artifact definitions, not activation tiers — and is fenced OUT with the rest of the ADR 2026-07-15-1 restructure.charter.yamlOWNS activation. The project activation state (activated_*/activated_kinds/mission_type_activations) relocates out of.kittify/config.yamlintocharter.yaml; the activation engine (commit_plan,merge_defaults) andPackContext.from_configare re-pointed to write/readcharter.yaml;config.yamlretains only non-doctrine config (agents, tooling).answers.yamlstays provenance-only. Fenced OUT of this mission: the broader ADR 2026-07-15-1 restructure (runtime activation-gating; first-class DRG nodes formission_type/gate/asset) — this mission advances only the activation-surface/ownership axis. Local-only/personal doctrine is assumed to activate via the same pack-shaped mechanism (the local-override mechanism itself is a separately-tracked gap).charter.mdis a hand-authored curated companion — the human "why". It is never a resolving input and never clobbered by any generate/compile path.- The prose→triad regex extractor (
extractor.pySECTION_MAPPING,sync()backward scrape) is retired — governance/directive structure is authored/held structurally, not scraped from prose. - Delivery is one coherent mission on a single branch, PRed as a whole, sequenced tidy-first: (i) build
charter.yamlwith first-class authorable fields + fold the clobber guard; (ii) seed it deterministically from the triad and re-point resolving/parity/freshness consumers; (iii) re-point the display prose-consumers and retire the prose→triad extractor +sync()backward scrape; (iv)charter.mdbecomes a pure hand-authored companion the derive path never writes. No intermediate PR ships a half-inverted state.
This aligns with ADR 2026-07-15-1 ("Doctrine offers, charter activates, runtime consumes") on the activation axis (activation stays in the charter/config layer, routed via the default charter) and adds the orthogonal authoring-direction decision that ADR did not cover.
Consequences
Positive
- One canonical structured authoring source; the dual-owned
charter.mdsmell and the #2772 clobber are eliminated. - Deterministic-first resolving/governance with no brittle prose scraper.
- The
charter.yamlschema is cut once (no second C-004 bump), honoring #2519's anti-churn goal. charter.mdbecomes freely curatable rationale that no tool overwrites.
Negative
- The mission widens materially — it now carries the full inversion (schema design + clobber guard + C-001 flip + extractor retirement + display-consumer re-pointing) plus relocating the activation state and re-pointing the activation engine (
commit_plan/merge_defaults/PackContext.from_config) in one branch/PR. Expect a large WP count and review surface; the activation-engine change is the biggest new blast radius. charter.yamlis git-tracked (an authoring surface); its derivedcatalogsection produces honest tracked diffs when activation changes — acceptable, and detected by the freshness signal.- The governance authoring UX shifts from prose to structured fields — a real change delivered within the mission, needing docs + operator guidance in the same PR.
Neutral
- The doctrine language-scoping tier-3
charter.mdfallback (language_scope.py:103) is orthogonal; it is folded into this mission as FR-009/IC-08 (migrated offcharter.mdprose to the structured cataloglanguages), socharter.mdis behaviorally inert by mission end. #2772is folded (the guard) / superseded (the derive path stops writingcharter.mdonce the extractor retires).
Amendment (2026-07-19, #2800 landing pass)
The original outcome — the compiler (write_compiled_charter) never writes charter.md — is preserved. But "generate never writes charter.md at all" left a fresh project with no companion and no signal that one should exist (and stranded the FR-009 golden-path affordance). Refined: the charter generate command now seeds a minimal starter charter.md only when the file is absent (_seed_charter_md_companion_if_absent). This is create-if-absent — an existing curated charter.md is left byte-for-byte untouched, so the never-clobber invariant (#2772 / IC-03, pinned by test_generate_force_preserves_curated_charter_prose_2772) is fully preserved, and the companion is discoverable. charter.md remains display-only and never a resolving input.
Confirmation
- #2773 ships
charter.yamlwith authorable governance/directive/catalog fields; its migration is a deterministic idempotent yaml→yaml fold (second run reports 0 changes). - No
charter generate/compile path writescharter.mdwithout an explicit guard; a regression test pins that curated prose survives a refresh. - By mission end (same PR):
extractor.pySECTION_MAPPINGis deleted; governance/directive loaders readcharter.yaml; a grep shows no runtime governance decision readscharter.mdprose. - Activation is relocated:
.kittify/config.yamlno longer carriesactivated_*;commit_plan/merge_defaults/PackContext.from_configread/writecharter.yaml;charter.yamloverlaysdefault.yaml(layer-0); the activation-parity/DRG-filter behavior is preserved (existing activation tests green). - Confidence: high on the losslessness/determinism facts (traced to
src/lines); medium on authoring-UX ergonomics, which the fast-follow will validate with real operator authoring.
Pros and Cons of the Options
Option X — ADR + reshape #2773 to carry the full inversion in one branch/PR
Pros: cuts the schema once; delivers the operator's direction as a consistent, reviewable whole (no half-inverted intermediate state ships); de-risked by the empirical trace; eliminates the clobber at its single site; fully folds #2772. Cons: materially widens the mission (more WPs, larger PR); commits to the inversion direction within a single review surface.
Option Y — ADR-first, then shape #2773
Pros: fullest end-state pinned before any schema is cut; safest for a structural-boundary change. Cons: stalls a ready P2 consolidation; the empirical trace already de-risked the decision, so the extra gate buys little.
Option Z — keep #2773 bounded; invert separately
Pros: minimal #2773 now. Cons: near-certain second schema re-cut + migration on the same brand-new file — the exact split/drift/stopgap churn #2519 exists to end. Rejected as self-defeating for the epic.
Option (b) — charter.md fully generated from charter.yaml rationale fields
Pros: single-source/deterministic on paper. Cons: curated rationale (~30 KB, ~7× the structured extract) is not derivable from structured data; authoring rich prose inside YAML block scalars is diff-hostile and fragile; makes the #2772 clobber the normal path. Rejected — the viable model is a hand-authored companion, not a generated one.
More Information
- Assessment + dialectic + empirical resolution:
kitty-specs/consolidate-charter-bundle-01KXSYB9/research/charter-authority-inversion-assessment.md. - Mission spec:
kitty-specs/consolidate-charter-bundle-01KXSYB9/spec.md. - Related: ADR 2026-07-15-1 (activation axis); #2772 (charter.md non-destructive refresh); epic #2519.