Skip to content

ADR-0077 — Schema-driven page-type routing and an open page-type set (v1.7.0)

  • Status: Accepted
  • Date: 2026-07-14
  • Depends on: ADR-0076 (block-based ingest pipeline)
  • Invariants touched: I5, I6, I8
  • Reference: docs/reference/LLMWIKI-CORE-LOGIC-v0.6.3.md §5.1, §1.3

Context

Synapse fixed the six user-content page types in a PageType StrEnum with a hardcoded type → subdir map (ingest/schemas.py). nashsu/llm_wiki instead treats the vault's schema.md "Page Types" table as the authoritative routing rule: a page's frontmatter type maps to a directory via that table, custom types included (goal, habit, character, thesis, methodology, finding, …), and a page whose type doesn't match the directory in its FILE-block path is dropped. The 1.7.0 scenario templates (ADR-0081) rely on this — the Research template adds thesis, methodology, finding; Reading adds character, theme, plot-thread, chapter; etc. With a closed six-type enum those pages are impossible.

Decision

1. A provider-neutral schema-routing module

app/wiki/schema.py (pure, stdlib-only) ports wiki-schema.ts: - parse_page_type_routing(schema_md) -> {type: subdir} parses the ## Page Types markdown table (dir cell must be wiki or start wiki/; malformed/header/separator rows dropped; table read only within the Page-Types section; later rows win). It returns exactly what the table declares — base types are not auto-injected. - validate_page_routing(page_type, rel_path, routing) -> (ok, error) reproduces validateWikiPageRouting's two error conditions (declared type routed to a dir the path doesn't use; path's dir routed to a different type than declared). - subdir_for_type(page_type, routing) resolves a (possibly custom) type: routing first, then the base defaults BASE_TYPE_DIRS, then the type name itself as a last-resort custom dir.

Routing values are Synapse's bare subdirs (entities, thesis), consistent with ingest/schemas.py::_TYPE_DIR and the vault bootstrap dirs; error messages render back to the wiki/… form so the ported TS assertions hold verbatim.

2. The open type set lives in the block path, not the enum

The strict PageType enum and WikiPage/WikiFrontmatter models are unchanged — they remain the JSON rollback path's contract (ADR-0076). Custom-typed pages exist only on the block-based path, which persists pages.page_type as the raw string (the column is already nullable text, no migration). The block writer places a page at its FILE-block path, validates it against the parsed routing, and drops a mis-routed or app-managed (index/overview) block — matching llm_wiki.

3. The generation prompt embeds the table as authoritative

The block generation prompt (ADR-0076, ingest/prompts.py) includes the vault's schema.md under "Project Schema and Routing (AUTHORITATIVE)" and instructs the model that every page's frontmatter type must match the schema directory in its FILE path — so routing is steered at generation and enforced at write.

Consequences

  • Template parity (ADR-0081) is possible: custom types route and persist correctly (verified end-to-end — the routing parser reads every 1.7.0 template table and its dirs match the scaffolded extra_dirs).
  • The JSON path and the six-type enum are untouched, so the change is additive and low-risk (I6: no provider branching; routing is data, not code).
  • I5 preserved: types are ordinary frontmatter strings; wiki/ stays a valid Obsidian vault.
  • I8: this ADR + test_wiki_schema_routing.py (31 cases) satisfy the docs/test gate.