Data Model: Glossary DRG Surfaces and Charter Lint

Mission: glossary-drg-surfaces-and-charter-lint-01KPTY5Y


New Types

InlineNotice (WP5.3)

Ephemeral value object rendered to terminal output by the CLI observation surface. Never persisted.

FieldTypeDescription
termstrCanonical term name
term_idstrGlossary term ID
conflicting_senseslist[str]Two or more sense texts that are in conflict
severityLiteral["high", "critical"]Conflict severity (only high/critical trigger inline display)
suggested_actionstrShort human-readable suggestion (e.g., "run spec-kitty glossary resolve deployment-target")
conflict_typestrConflict category from classify_conflict()

Invariants: severity in {"high", "critical"} always; only emitted when there is at least one conflict.


GlossaryHealthData (WP5.4, WP5.4 dashboard API)

class GlossaryHealthResponse(TypedDict):
    total_terms: int
    high_severity_drift_count: int
    orphaned_term_count: int       # terms with no vocabulary edge in the DRG
    entity_pages_generated: bool   # whether compiled/glossary/ dir exists and is non-empty
    entity_pages_path: str | None  # relative path to compiled/glossary/ dir
    last_conflict_at: str | None   # ISO timestamp of most recent high-severity conflict event

Source of truth: glossary store (src/specify_cli/glossary/store.py) + event log at .kittify/events/glossary/_cli.events.jsonl.


EntityPage (WP5.5)

Markdown file at .kittify/charter/compiled/glossary/<term-id>.md. Never committed (gitignored).

Structure:

# <canonical-term-name>

**ID**: glossary:<term-id>  **Scope**: <scope>  **Status**: <active|deprecated|provisional>

## Definition

<current canonical definition>

## Provenance

First introduced by: <synthesizer-run-id> at <ISO timestamp>
Source corpus snapshot: <snapshot-id>

## References

### Work Packages
- [WP01 — <title>](<relative-path-to-tasks/WP01.md>) — cited in <context excerpt>
- ...

### ADRs
- [ADR-3 — <title>](<relative-path-to-adrs/ADR-3.md>) — defined / invalidated / referenced

### Mission Steps
- [Step N of mission <slug>](<path>) — observed at <timestamp>

### Retrospective Findings
- <finding excerpt> — [Retro <id>](<path>)

### Charter Sections
- <section name> — introduced this term in charter interview at <timestamp>

## Conflict History

| Timestamp | Severity | Type | Senses | Resolution | Actor |
|-----------|----------|------|--------|------------|-------|
| ...       | high     | ...  | ...    | ...        | ...   |

## Related Terms

- **Sibling**: [<term>](glossary:<id>.md) — via shared parent scope
- **Generalization**: [<term>](glossary:<id>.md)

LintFinding (WP5.6)

@dataclass
class LintFinding:
    category: Literal["orphan", "contradiction", "staleness", "reference_integrity"]
    type: str          # e.g. "wp", "adr", "glossary_term", "synthesized_artifact", "procedure"
    id: str            # node ID or URN of the offending artifact
    severity: Literal["low", "medium", "high", "critical"]
    message: str       # human-readable description of the decay
    feature_id: str | None   # mission slug / feature ID if scoped, else None
    remediation_hint: str | None  # optional suggested fix

JSON serialization (for --json output):

{
  "category": "orphan",
  "type": "adr",
  "id": "ADR-7",
  "severity": "medium",
  "message": "ADR-7 is not referenced by any WP, charter section, or other ADR in feature 042",
  "feature_id": "042-my-feature",
  "remediation_hint": "Reference ADR-7 from at least one WP or mark it as superseded"
}

DecayReport (WP5.6)

@dataclass
class DecayReport:
    findings: list[LintFinding]
    scanned_at: str           # ISO 8601 timestamp
    feature_scope: str | None # mission slug if --feature was passed, else None
    duration_seconds: float
    drg_node_count: int
    drg_edge_count: int

Persisted to: .kittify/lint-report.json after each run (machine-readable, keyed by scanned_at). The dashboard reads the most recent entry.

JSON envelope:

{
  "findings": [...],
  "scanned_at": "2026-04-22T15:00:00Z",
  "feature_scope": null,
  "duration_seconds": 1.24,
  "drg_node_count": 312,
  "drg_edge_count": 1048
}

DecayWatchTileResponse (WP5.6 dashboard, TypedDict)

class DecayWatchTileResponse(TypedDict):
    has_data: bool
    scanned_at: str | None
    orphan_count: int
    contradiction_count: int
    staleness_count: int
    reference_integrity_count: int
    high_severity_count: int
    total_count: int
    feature_scope: str | None

State Transitions

Entity Page Lifecycle (WP5.5)

[not generated]  →  [generated]  →  [stale]  →  [regenerated]
       ↑                                               ↓
       └─────────── ensure_charter_bundle_fresh() ─────┘

Entity pages are regenerated atomically: write to a temp file, rename into place. A stale page is never served partially.

Lint Report Lifecycle (WP5.6)

[none]  →  [current]  →  [stale (next run)]  →  [current (overwritten)]

Only one lint report file exists at a time. Dashboard always shows the most recent completed run.


File Layout

.kittify/
  charter/
    compiled/
      glossary/           ← gitignored, entity pages
        <term-id>.md
        <term-id>.md
        ...
  events/
    glossary/
      _cli.events.jsonl   ← CLI invocation conflict events (WP5.3, WP5.4)
      <mission-id>.events.jsonl
  lint-report.json        ← last DecayReport JSON (WP5.6 → WP5.6 dashboard)

src/specify_cli/
  glossary/
    entity_pages.py       ← GlossaryEntityPageRenderer (WP5.5)
  charter_lint/
    __init__.py
    engine.py             ← LintEngine (WP5.6)
    findings.py           ← LintFinding, DecayReport dataclasses (WP5.6)
    checks/
      orphan.py           ← OrphanChecker (WP5.6)
      contradiction.py    ← ContradictionChecker (WP5.6)
      staleness.py        ← StalenessChecker (WP5.6)
      reference_integrity.py ← ReferenceIntegrityChecker (WP5.6)
  dashboard/
    handlers/
      glossary.py         ← GlossaryHandler (WP5.4)
      lint.py             ← LintTileHandler (WP5.6 dashboard)
    api_types.py          ← add GlossaryHealthResponse, DecayWatchTileResponse