Data Model: Common Docs Structural Move (Mission B)

Phase 1 output · Mission common-docs-structural-move-01KW3SBK · 2026-06-27

This mission moves files and metadata; the "data model" is the set of document-metadata entities and the invariants that must hold across the move. No database — every entity is a file or a generated artifact on disk.


Entities

1. Doc page

A single .md file under the unified docs/ root, carrying in-file YAML frontmatter as the sole metadata SSOT (A's D1, directive 042).

FieldTypeNotes
titlestringRequired; consumed by DocFX + SEO gate.
descriptionstringRequired; length 50–180 (NFR-003 SEO).
doc_statusenum`draft \
updateddate YYYY-MM-DDFreshness date.
relatedlist[repo-relative .md path]Cross-references; resolvable, validated at build time (replaces the dead citation_refs).
version_tag / divio_type / owning_workstreamstringRolled up into the lockfile.

architecture, adr, plans, api, configuration, integrations, security, guides, operations, migrations, changelog). Each section directory carries its own index.md.

  • Location: one of the 13 Common Docs sections under docs/ (index, context,
  • SSOT direction: frontmatter is authoritative; the inventory lockfile is derived from it.

2. ADR (Architecture Decision Record)

An immutable decision record under docs/adr/<era>/ (1.x / 2.x / 3.x).

FieldTypeNotes
titlestringRequired (ADR_FRONTMATTER_REQUIRED_KEYS).
statusenumBare status — `Proposed \
datedateRequired.
bodymarkdownImmutable decision content — only location + header format may change (C-002).

architecture/adrs/) migrate to adr/3.x/ by dated filename. 47 byte-identical flat mirrors are dropped (lossless) as the flat shim closes.

dialects converted by the two parsers (D-R2).

  • Census (live): 117 unique ADRs = 97 era + 20 era-less. The 20 era-less (flat
  • Header dialects: ~12 markdown-table, ~34 bold-inline, 0 with YAML frontmatter today — both

3. Page-inventory lockfile

docs/development/3-2-page-inventory.yaml — a generated rollup, regenerated FROM frontmatter via scripts/docs/inventory_lockfile.py.

AspectValue
Generationrun_generate_and_compare(docs_root, inventory, repo_root, strict)InventoryDrift(added, removed, changed)
AuthorityDerived — frontmatter is SSOT (D1); the lockfile is the asserted rollup
Rollup semantics (load-bearing, must survive)completeness ("every .md inventoried"), workstream ownership, deterministic alphabetical diff
Current state580 rows; live --strict drift 252 removed / 296 changed / 0 added (= FR-010 backfill workload)
Target statedrift = 0 (generated == committed) — only AFTER the move + backfill

4. Redirect map

A checked-in old-path → new-path mapping, consumed by scripts/docs/redirect_stub_generator.py to emit <meta http-equiv="refresh"> stub pages per old path into the DocFX _site output.

FieldTypeNotes
old_pathURL/pathThe pre-move published path (key).
new_pathURL/pathThe post-move target.
denominatorbaseline-URL inventoryThe captured pre-move published URL set — the NFR-002 "100% resolve" denominator.

resolves directly or via a stub.

  • Baseline-anchored: every entry traces to a captured baseline URL; coverage = every baseline URL

5. Occurrence map

occurrence_map.yaml — the 8-category bulk-edit classification of every reference rewrite. (Owned by a parallel task; modeled here, not authored by this mission.)

AspectValue
Unitpath-pair (old-path → new-path) — the #1815 structural-restructure extension (D-R1)
Categoriesthe 8 bulk-edit occurrence categories (exceptions, not exemptions)
Scopesrc/ (runtime-critical first), doctrine, kitty-specs/, tests, docs
Sizingregenerated from the live tree — historical ~571 files is a likely-LOW estimate

Invariants

InvariantStatementEnforced by
Content-invariance (ADR)For every moved ADR, body-minus-header bytes are identical before and after conversion; 0 decision-content mutated, 0 lost (the 47 byte-identical mirrors drop losslessly). The reconciliation-ADR self-amendment (FR-013) is the one sanctioned exception — scoped out of the check.Content-invariance check (contracts/content-invariance.md); NFR-001, C-002
URL-continuity100% of captured-baseline public URLs resolve directly or via a generated <meta refresh> stub.Redirect-stub generator + coverage check (contracts/redirect-stub.md); NFR-002
Single-rootExactly one docs/ documentation root; no second root (architecture/ removed) and no docs/<version>x shadow tree survives; every section carries index.md.Anti-sprawl ratchet (blocking, FR-011); SC-001/SC-005
Frontmatter-SSOT / lockfile-syncPer-page metadata lives in exactly one place (frontmatter); the inventory lockfile regenerates deterministically and generated == committed (drift = 0).Lockfile drift gate (blocking via code change, FR-011); NFR-006, SC-006
Link integrity0 broken internal doc links; 0 dangling related: edges.related: validator (blocking, FR-011); NFR-004
Glossary read-path (merge-blocker)The .kittify/glossaries/<scope>.yaml seed read-path and the doctrine-extraction source resolve after the context/ move; only the human markdown relocates.Manual + dashboard GlossaryHandler / load_seed_file() verification; C-006
ADR completenessAll 117 unique ADRs present post-move under adr/<era>/ with era + frontmatter.ADR census check; NFR-001, SC-002

State transitions (the serial spine)

occurrence-map (IC-01)
  → src/ runtime-critical reads rewritten + resolution-tested (IC-02)   [BEFORE any move — C-003]
    → tree move: two-root collapse + glossary (IC-03)                   [gates IC-04, IC-05]
      ├─ ADR conversion: 117 → adr/<era>/ frontmattered (IC-04)         ⎫ parallel — disjoint
      └─ refs + redirects + frontmatter backfill (IC-05)                ⎭ surfaces
        → lockfile drift closes to 0 (FR-010 — only AFTER move+backfill)
          → rulers flip to blocking (IC-06) + full-gate dry-run (C-005)
            → ADR-note amendment (FR-013) + LEAK retirement (FR-014) (IC-07)

Investigation-note lifecycle (D7, FR-008/FR-009): doc_status: draft|active (in-flight in plans/) → distil durable finding into adr/ or architecture/doc_status: deprecated → delete-stale retirement.