Bug
Our brain reads schema_version 111, but idx_timeline_dedup was still the 3-column (page_id, date, summary) shape. Migration v102 timeline_entries_source_in_dedup — whose own comment says "originally v99, bumped to v102 after master merge" — never executed: the version counter passed it via the pre-renumber chain, so it was recorded as applied without its SQL ever running.
An artifact audit of v101–v109 against the live schema showed only v102 missing (link_kind column, migration_impact_log, atom-hash idx, slug_aliases, extract_rollup_7d, generation clock, embedding_signature, newest_content_at all present).
Consequence at v0.42.x
Every addTimelineEntry / addTimelineEntriesBatch call fails:
there is no unique or exclusion constraint matching the ON CONFLICT specification
because both code paths infer on the 4-column index (page_id, date, summary, source) (src/core/postgres-engine.ts, both insert sites at eefe8b57). On our mesh this silently broke timeline writes on every node, including the hub.
Recovery (what we did)
Ran the migration's own SQL by hand:
DROP INDEX IF EXISTS idx_timeline_dedup;
CREATE UNIQUE INDEX idx_timeline_dedup ON timeline_entries(page_id, date, summary, source);
Suggested fixes
- Key applied-migration tracking by stable name, not only the version integer — renumbering during merges then can't strand a migration as recorded-but-not-executed.
- A doctor check that verifies migration artifacts (expected index/column/table per migration) rather than trusting the version counter — would have caught this immediately.
- Defensively,
addTimelineEntry could fall back to a plain insert-where-not-exists when the ON CONFLICT inference fails.
Related observation
The brain_score SQL counts soft-deleted pages in every denominator (SELECT count(*) FROM pages with no deleted_at IS NULL filter), so a brain mid-purge-window is double-penalized on timeline/orphan/link-density components. Possibly intentional, but worth a look while in this code.
Bug
Our brain reads
schema_version111, butidx_timeline_dedupwas still the 3-column(page_id, date, summary)shape. Migration v102timeline_entries_source_in_dedup— whose own comment says "originally v99, bumped to v102 after master merge" — never executed: the version counter passed it via the pre-renumber chain, so it was recorded as applied without its SQL ever running.An artifact audit of v101–v109 against the live schema showed only v102 missing (link_kind column, migration_impact_log, atom-hash idx, slug_aliases, extract_rollup_7d, generation clock, embedding_signature, newest_content_at all present).
Consequence at v0.42.x
Every
addTimelineEntry/addTimelineEntriesBatchcall fails:because both code paths infer on the 4-column index
(page_id, date, summary, source)(src/core/postgres-engine.ts, both insert sites ateefe8b57). On our mesh this silently broke timeline writes on every node, including the hub.Recovery (what we did)
Ran the migration's own SQL by hand:
Suggested fixes
addTimelineEntrycould fall back to a plain insert-where-not-exists when the ON CONFLICT inference fails.Related observation
The
brain_scoreSQL counts soft-deleted pages in every denominator (SELECT count(*) FROM pageswith nodeleted_at IS NULLfilter), so a brain mid-purge-window is double-penalized on timeline/orphan/link-density components. Possibly intentional, but worth a look while in this code.