kaldamus entity reference · EvalReport · QualityReport · ConfigSnapshot
One nightly run scores the pipeline against itself and writes three different kinds of row — one keeps a bounded history, one keeps none of its own, and one can never legally change.
| Entity | id | Permanence model |
|---|---|---|
| EvalReport | entity_type_run_id | bounded append-only history — the raw per-entity scoring output |
| QualityReport | "current" | singleton, no history of its own — a distilled, human-friendly view |
| ConfigSnapshot | sha256(payload)[:16] | immutable, content-addressed — the config-trio receipt |
EvalReport is the raw per-entity scoring, QualityReport a distillation of it, ConfigSnapshot a receipt for the config the scoring ran against.QualityReport never scores anything itself — it re-reads whatever EvalReport history is on disk and re-renders it.ConfigSnapshot belongs to no single entity — captured once per run, then referenced by every EvalReport row that run writes.
flowchart TD
classDef srcNode fill:#16233b,stroke:#5a8fd4,stroke-width:1.5px,color:#dfe7f5;
classDef reportNode fill:#241d0c,stroke:#c9a84c,stroke-width:1.5px,color:#f3e6bd;
classDef decisionNode fill:#12151f,stroke:#5a6178,stroke-width:1px,color:#c9cbd6;
classDef llmNode fill:#161822,stroke:#e0e0e0,stroke-width:2px,color:#f0f0f0;
A["make update-reports"]:::srcNode --> B["capture ConfigSnapshot
id = sha256(canonical_json)[:16]"]:::reportNode
B --> C["config_snapshots.json
never pruned"]:::reportNode
B --> D{"for each ENTITY_TYPE"}:::decisionNode
D --> E["sample rows
stratified"]:::srcNode
E --> F["deterministic checks + gates
$0"]:::srcNode
F --> G{"judge covers
this entity?"}:::decisionNode
G -- "yes (narratives, factors,
forecasts, deps, news…)" --> H["Claude rubric judge
soft-fail per row"]:::llmNode
G -- "no (events, history,
classifier, geo, gates)" --> I
H --> I["EvalReport row
id = entity_type_run_id"]:::reportNode
I --> J["eval_reports.json
append, then prune_to_runs()"]:::reportNode
J -.->|"next entity_type"| D
J --> K["_build_quality() — re-reads ALL
retained rows per entity_type"]:::srcNode
K --> L["QualityReport singleton
id='current', full overwrite"]:::reportNode
Gold = a persisted row (report or snapshot); blue = in-flight work. The judge branch is the only step that costs money; everything else in the loop is $0.
gates samples nothingerror: judge_failed, run continuesconfig_snapshot_idprune_to_runs() after the batch| Row | Written by | Changes again when… |
|---|---|---|
| EvalReport row | one run, per entity_type | never — a new run mints a brand-new row; the old one sits until pruned |
| eval_reports.json window | prune_to_runs(), after every write batch | drops the oldest run_id's rows once more than retain_runs distinct runs are on file |
| QualityReport (id="current") | every run, via _build_quality() | overwritten in full, same id, every run — never incremented |
| ConfigSnapshot row | the first run scored against a given trio | never — the id is a hash of the row's own content |
config_snapshots.json, so it accumulates independently — a snapshot can outlive every EvalReport that ever cited it.| EvalReport | QualityReport | ConfigSnapshot | |
|---|---|---|---|
| Deleted by | prune_to_runs() — automatic | nothing — overwritten, not deleted | nothing routine — no prune exists |
| History kept | bounded append-only: a retain window of runs × entity types | none of its own — recomputed live from EvalReport's window | full history since first capture — unbounded |
| Mutability | append-only while retained, then hard-deleted in a batch | mutable in place — same id, new content | can't change without becoming a different id |
EvalReport remembers, but only as long as retain_runs says; QualityReport is a view recomputed wholesale every run, with no memory of its own.ConfigSnapshot is the only one where "permanent" isn't a policy someone could quietly change — it's a property of what a sha256 hash is.eval_reports.json is the largest file any scored, generated entity writes — because it's the only entity family in the pipeline that's genuinely append-heavy.ENTITY_TYPES × retain_runsfactors row alone is a large share, because it samples at the full cap, carries a multi-dimension judge verdict, and adds corpus-level blocks no other entity does.