Writ Architecture.
Chapter 1 of 4

Graph data model

What lives in the graph, how it is identified, and how a piece of knowledge earns its place. The schema is one Pydantic module (writ/graph/schema.py, 757 lines) plus the edge registry (writ/graph/db/_common.py).

This chapter answers

1.1Node types come in four tiers

Thirteen curated types are defined in one enum (schema.py:112-129). Only six enter the ranked candidate pool that build_pipeline loads (pipeline.py:565-621). Five more never rank: they surface only when graph traversal pulls them in next to a ranked result. Categories and Abstractions are structure. Decision-memory records are deliberately kept out of every retrieval and reconcile path (schema.py:577-586).

Figure 1.1 The four node tiers Click to enlarge
flowchart TB
  subgraph RANK["Tier 1: ranked candidates"]
    RULE["Rule<br/>the coding-rule corpus"]
    SKL["Skill"]
    PBK["Playbook"]
    TEC["Technique"]
    ANT["AntiPattern"]
    FRB["ForbiddenResponse"]
  end
  subgraph EXPAND["Tier 2: adjacency-surfaced"]
    PHA["Phase"]
    RAT["Rationalization"]
    PSC["PressureScenario"]
    EXM["WorkedExample"]
    ROL["SubagentRole"]
  end
  subgraph STRUCT["Tier 3: structure"]
    CAT["Category<br/>routing declarations,<br/>BELONGS_TO hub"]
    ABS["Abstraction<br/>summary-mode substitute<br/>for clustered rules"]
  end
  subgraph REC["Tier 4: records"]
    DEC["Decision"]
    FCH["FileChange"]
    CMT["Commit"]
    PRJ["Project registry"]
  end
  RANK ~~~ EXPAND
  STRUCT ~~~ REC

Every curated type has exactly one primary-key field, registered once so ingest, the database layer, and reconcile all derive from the same map (NODE_ID_FIELDS, schema.py:701-715). Ids share one grammar (RULE_ID_PATTERN, schema.py:13): examples are SEC-INJ-SQL-001 (Rule), SKL-PROC-MODE-001 (Skill), CAT-CODE-SECURITY-001 (Category).

TierTypesPrimary keyWhy this tier exists
Ranked candidatesRule, Skill, Playbook, Technique, AntiPattern, ForbiddenResponserule_id, skill_id, playbook_id, technique_id, antipattern_id, forbidden_idContent that should be findable by meaning or keywords.
Adjacency-surfacedPhase, Rationalization, PressureScenario, WorkedExample, SubagentRolephase_id, rationalization_id, scenario_id, example_id, role_idSupporting material that only makes sense next to the node it belongs to.
StructureCategory, Abstractioncategory_id, abstraction_idCategories declare routing; Abstractions stand in for many rules when token budget is tight.
RecordsDecision, FileChange, Commit, Projectdecision_id, change_id, commit_hash, nameRuntime history. Absent from every retrieval and reconcile enumeration by design.

1.2Edges: 24 types, three origins

ALLOWED_EDGE_TYPES is the single source of truth (_common.py:32-49); the corpus subset is derived, never hand-copied (CORPUS_EDGE_TYPES = ALLOWED - RECORD, _common.py:64). The split that matters operationally is declared versus derived: declared edges are authored knowledge and survive the export round-trip; derived edges are recomputed on every ingest and are deliberately excluded from export (writ/export.py:53-65) so they never freeze into source.

GroupTypesOrigin
Declared corpus (14) DEPENDS_ON, PRECEDES, CONFLICTS_WITH, SUPPLEMENTS, SUPERSEDES, TEACHES, COUNTERS, DEMONSTRATES, DISPATCHES, GATES, PRESSURE_TESTS, CONTAINS, ATTACHED_TO, INVOKES Hand-authored in front-matter edges: lists or RULE-START ### Edges sections. Each type documents its complete valid source and target sets (schema.py:637-679).
Derived (3) RELATED_TO, BELONGS_TO, ABSTRACTS RELATED_TO from rule-id mentions in prose (ingest.py:41); BELONGS_TO from every node's category field plus the category tree; ABSTRACTS from the compression run (chapter 4).
Record (7) HAS_DECISION, HAS_CHANGE, HAS_COMMIT, MOTIVATED_BY, GOVERNED_BY, INCLUDES, REALIZES Written at runtime by decision-memory capture. Endpoint labels are validated against an allowlist before any label reaches a Cypher string (_common.py:73), closing the label-interpolation injection surface.

1.3Identity, multi-project isolation, and race safety

1.4Provenance: how knowledge earns canon

Every node carries a provenance state (VALID_PROVENANCE, schema.py:32). The design principle: a rule can be born inside the graph, but it only becomes canonical markdown source through a human gate. Records are permanent history and are excluded from promotion by construction.

Figure 1.2 Provenance lifecycle Click to enlarge
stateDiagram-v2
  direction LR
  state "hand-authored<br/>(the corpus at rest)" as HA
  state "proposed<br/>(graph-first, no markdown home)" as PR
  state "graduation_pending<br/>(evidence threshold crossed)" as GP
  state "graduated<br/>(exported to source)" as GR
  state "record<br/>(permanent runtime history)" as RC
  [*] --> HA: ingest from bible/
  [*] --> PR: POST /propose or writ add
  PR --> GP: frequency crossing
  GP --> GR: human promotion gate
  [*] --> RC: decision-memory capture
Why this matters

The provenance machinery is the schema-level encoding of Writ's governance stance: the system may propose knowledge and gather evidence for it, but promotion into canon is always a human act. There is no code path that graduates a rule without the gate.