Přeskočit na obsah

ADR: JSON-first kanonický datový model

Rozhodnutí, kterým se jediným zdrojem pravdy stala kanonická JSON/JSON-LD data a Markdown pod content/ se změnil v generovaný adaptér. Jádro dnešní architektury webu.

Text níže je vykreslený přímo ze zdrojového souboru docs/adr/json-first-canonical-data-model.md v repozitáři při buildu (Zola, build-time render — žádný klientský JavaScript) — jediná kanonická kopie textu, stejný soubor, jaký vykresluje i GitHub. historie změn · kdo co napsal

ADR: JSON/JSON-LD-first kanonický datový model

  • Stav: accepted (mise T-028, vlastník zadal 2026-08-01)
  • Kontext úlohy: docs/coop/TASKS.md T-028; baseline docs/migrations/json-first-baseline.md
  • Fáze: A–I hotové (baseline, schémata + context, kompilátor, lossless migrátor, adaptéry, view modely, přepojení generátorů, ODSTRANĚNÍ STARÝCH ZDROJŮ PRAVDY, contributor tooling + dokumentace); J (finální parity gate) navazuje. Tento dokument se po každé fázi aktualizuje — sekce „Stav implementace" na konci.

Rozhodnutí

Kanonickým zdrojem pravdy pro všechny dossierové doménové záznamy se stává výhradně data/dossiers/**/*.json (JSON validovaný JSON Schematem, současně deterministicky interpretovatelný jako JSON-LD přes verzovaný lokální context). Markdown v content/dossiers/** a content/entities/ se stává generovaným Zola routing adaptérem, Tera zůstává čistě prezentační vrstvou a static/data/ generovaným veřejným exportem.

Proč Markdown front matter přestává být zdrojem pravdy

Dnešní tok (front matter → regex parsování record-tables.mjs → odvozené exporty) má tři strukturální vady, které baseline změřil, ne odhadl:

  1. Composite-key problém: 1 649 identifikátorů typu CLM-01/SRC-01 je dossier-scoped; CLM-01 existuje nezávisle ve všech 22 dossierech. Historicky už způsobil chybné spojování SRC-## napříč dossiery. Řešení: globální @id https://vomaste.cz/id/dossiers/<slug>/claims/CLM-01; lokální identifier zůstává jen pro UI.
  2. Dvě reprezentace, jeden ručně synchronizovaný zdroj: tabulka v _index.md + generované detailní stránky drží konzistenci jen díky validátorům a regeneračním skriptům; každé redakční kolo riskuje drift (viz nález duplicitních kotev z 2026-07-31).
  3. Regex parsování TOML front matter je křehké vůči víceřádkovým hodnotám a pořadí klíčů; JSON + AJV dává tvrdé chyby s cestou.

Proč je JSON/JSON-LD kanonické (a ne jen výstupní dekorace)

  • Každý záznam nese @context, stabilní @id a @type už na vstupu; JSON-LD expanze je součástí validace (fáze C), takže exporty nemohou tvrdit nic, co ve vstupních datech není.
  • Context data/dossiers/_shared/context/vomaste-v1.jsonld je lokální a verzovaný; build nikdy nestahuje context ze sítě (lokální document loader, cizí URL = chyba). Veřejná routa /context/v1.jsonld je publikací téhož souboru.
  • Slovníky (statusy tvrzení, typy entit, typy vztahů, coverage stavy) jsou data (_shared/vocabularies/), převzatá doslovně z dosavadních enumů — žádné přejmenování, žádná „vylepšení". Statusy zůstávají kategoriální: žádné confidence/truth skóre (konstituce §6, mise §5.3).

Proč Zola stále potřebuje generované content adaptéry

Zola neumí vytvořit routu bez content souboru. Generátor (fáze E) proto vytváří minimální content/** stuby (generated = true, odkaz na view model, prázdné tělo) — deterministicky, kompletně regenerovatelné, zakázané k ruční editaci (lint fáze H/J). Alternativa „nechat fakta ve front matter" by vrátila dva zdroje pravdy, což je přesně stav, který tato změna ruší.

Autorizační hranice

Append-only log v AGENTS.md zůstává jediným lidským zdrojem autorizací; data/authorizations.toml je jeho auditně navázaný index. Kompilátor (fáze C) odmítne entity dossier bez platné reference na autorizační záznam a odmítne kontextovou osobu vydávanou za subjekt. Migrace (fáze D) nesmí měnit rozsah: texty, statusy, zdroje, procesní výhrady i vazby se přenášejí byte-verně; macinka-turek agregát se migruje beze změny významu — vlastnictví záznamů řeší dossier pole podle existujícího subjects taggingu, ne přesun souborů (náhrada za zrušený T-001).

Tok dat (cílový)

data/dossiers/**/*.json  (kanonická data, JSON Schema + JSON-LD validace)
        → jednotný kompilátor (scripts/data: discover/load/validate/compile)
        → compiled model (jediný vstup pro všechny konzumenty)
        → view modely + routy + navigace + graf + statistiky + exporty
        → generované content/** adaptéry → Zola/Tera HTML
        → static/data/* + manifest (SHA-256, verze schémat i contextu)

Rollback strategie

Fáze A–D nemění produkční výstup (nové soubory + nástroje vedle starého toku; npm run build beze změny). Do dokončení fáze H existují oba toky souběžně a parity testy (§20 mise) drží ekvivalenci; rollback = smazání data/dossiers/**/*.json a nových skriptů, staré generátory zůstávají funkční. Teprve fáze H odstraňuje staré zdroje pravdy — až po zeleném route/export parity gate.

Rozšiřování schémat

schemaVersion je explicitní (const 1); změna tvaru = nová verze + migrace. Obsahové bloky jsou rozšiřitelná unie (markdown je lossless základ; jemnější typy se zavádějí jen tam, kde je lze bezpečně odvodit).

Contributor workflow (cílový)

$EDITOR data/dossiers/<slug>/...   # úprava JSON
npm run data:validate              # shape + reference + sémantika + JSON-LD
npm run data:build                 # kompilace + generování
npm run build                      # plný web

Přidání autorizovaného dossieru nesmí vyžadovat úpravu šablon, navigace, JS ani ručního seznamu slugů.

Stav implementace

FázeStavCommit
A — baseline audithotovocb357a0 (+ refresh f2d9318 po T-034)
B — schémata, context, loader, fixtureshotovoviz git log task/T-028
C — kompilátor + sémantika + JSON-LD validacehotovoviz git log task/T-028
D — lossless migrátor + parityhotovo (v pracovním stromu)scripts/migrations/migrate-content-to-json.mjs; report docs/migrations/json-first-migration-report.md; 1 866 kanonických souborů (835 claims / 514 sources / 81 cases / 187 gaps / 101 relations / 42 updates / 22 dossiers / 84 entit); grandfathered debt v data/dossiers/_shared/semantics-baseline.json (2× S2)
E — view modely + content adaptéry (staging)hotovoscripts/data/{build-view-models,generate-zola-content,check-generated}.mjs; 1 938 view modelů, 1 936 stubů, route parity 0/0, alias parity 179/179, determinismus ověřen; timeline (225 entries) doplněna do dossier.json
G — přepojení build generátorů na compiled modelhotovo (v pracovním stromu)record-tables.mjs = tenká projekce nad compiled modelem (API beze změny); generate-stats, build-route-manifest, build-data-exports, build-search-index, build-jsonld-exports, build-graph-projections (hrany z compiled relations, uzly/pořadí z graph.toml s mirror gatem), build-navigation, build-entity-type-sections přepnuty; schémata source/dossier aditivně rozšířena o description/reviewedAt (+ context v1 term reviewedAt, migrátor, 535 kanonických souborů); scripts/build/pipeline.mjs (build|dev|check) je jediný orchestrační entrypoint s data:validate bránou; generate-authorization-candidates + generate-discovery-log zůstávají na front matter (provenienční pole entit mimo model v1 — inventář, rozhodnutí fáze H); koncepty-subtree navigace mimo kanonický model (dokumentovaná výjimka)
F — šablony na view modelech + content swaphotovo (v pracovním stromu)content/dossiers/** a content/entities/*.md jsou GENEROVANÉ adaptéry (data:sync-content kopíruje staging → content, gate data:check-generated --content vynucuje content == staging; kořenové dossiers/_index.md a entities/_index.md zůstávají ruční — SYNC_EXCLUDED). Adaptéry jsou plnohodnotná regenerace dnešních souborů: doménová pole DERIVOVANÁ z kanonického modelu (inverze migrátoru), pole mimo model v1 passthrough (aliasy, entity provenience, redakční těla registry indexů, prezentační tituly). Tělo záznamu nese adaptér (kanonický markdown blok) — runtime markdown() filtr neresolvuje @/ odkazy, proto tělo renderuje Zola standardně a {#kotvy} vznikají beze změny (verify:anchors kontrakt drží). Šablony (claim/source/case/gap/relation/entity, 5 registry indexů, entity-dossier-registry, dossier/entity-dossier overview, entities-index, dossiers-index, landing demo bloky, jsonld partial pro Claim/citation uzly) čtou load_data("data/" ~ extra.view_model). Aditivní schémata: relation.order + entity.order (redakční pořadí — bez něj view modely nereprodukují dnešní pořadí výpisů; hodnoty entity order obnoveny z původních weight v commitu 55562e0). Parita: public/ byte-identický s HEAD renderem (2 203 HTML) kromě 22 evidence stránek (whitespace-only) a graph manifest generated_at; Playwright 195 passed/17 skipped beze změny
H — odstranění starých zdrojů pravdyhotovo (v pracovním stromu)Jediný kanonický vstup = data/dossiers/**/.json. SMAZÁNO: data/dossiers.toml (registr = kanonický dataset, pořadí nese aditivní dossier.order), data/dossiers//graph.toml (kurátorovaná vrstva → dossier.json graph + relation.note; depth se POČÍTÁ BFS — scripts/data/lib/graph-depth.mjs), data/dossiers//updates.toml (šablony čtou view modely), stats.toml (generátor zrušen, počty ve view modelech/readDossierStats). Provenience entit → entity.provenance (+ entity.description); government_ vlastní data/government.toml. Zrušené validátory s předaným vlastnictvím pravidel: validate-dossier → validate-registry-table T1–T8; validate-graph → R7 (validate-references) + S7/S8 (validate-semantics) + dossier.schema graph; validate-schemas → schema brána v build:data-exports (lib/export-schemas). Content adaptéry = minimální obálka (lint-generated-content vynucuje, lint-hardcoded-records rozšířen o slug literály a hardcoded počty). Jednorázové migrátory + scaffold archivovány (scripts/migrations/archive/, README tamtéž); parity testy migrace nahrazeny golden testy compiled modelu (scripts/data/compiled-golden.test.mjs). HTML výstup byte-identický (2 258 souborů, žádná výjimka), plný build zelený.
I — contributor tooling + dokumentacehotovo (v pracovním stromu)npm run dossier:scaffold (scripts/data/scaffold-dossier.mjs — minimální validní kanonický balíček, autorizační brána nad data/authorizations.toml, e2e test proti temp stromu); expand-entity.mjs píše kanonické entity JSON (data/dossiers/_shared/entities, nikdy nepřepisuje, kolize jmenovce = report, testy nad temp adresářem); npm run data:validate -- --file <cesta> (scripts/data/validate.mjs, jednosouborová tvarová validace s cestou v hlášce); kompletní dokumentační sweep na JSON-first (AGENTS.md technické sekce, CLAUDE.md, README, PROJECT_INSTRUCTIONS, CONTRIBUTING, docs/data-contract.md, docs/contributing/add-dossier-data.md NOVÝ, schemas/README.md, entity-discovery, skills bootstrap/dossier-entry/investigate/commit/adr; historické dokumenty dostaly úvodní poznámku). Gates: npm test 238/0, plný build 37/37 kroků zelený
J — finální parity gatehotovoAbsorpce masteru přes archivované migrátory (merge b8ee240: vlna 2 Babiše byte-verně, S1 přepsáno na rodinovou nezávislost, semantics-baseline splacen a vyprázdněn); merge dokumentační větve (e4d6cb4); archivní import legacy oracle opraven; 2× po sobě jdoucí build: HTML strom byte-identický (2 291 souborů, hash d557f762…), datové exporty deterministické mimo dokumentovaný graph-manifest generated_at; npm test 248/248