pub const CREATE_CONCEPTS_TABLE: &str = "\nCREATE TABLE IF NOT EXISTS concepts (\n rowid_pk INTEGER PRIMARY KEY,\n id TEXT NOT NULL UNIQUE,\n title TEXT NOT NULL,\n content TEXT NOT NULL DEFAULT \'\',\n embedding_model TEXT,\n valid_from TEXT NOT NULL,\n valid_to TEXT NOT NULL DEFAULT \'9999-12-31T23:59:59.999999Z\',\n recorded_at TEXT NOT NULL,\n retired INTEGER NOT NULL DEFAULT 0,\n CHECK (valid_from GLOB \'[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9]Z\' AND valid_to GLOB \'[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9]Z\' AND recorded_at GLOB \'[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9]Z\' AND 1)\n);\n";Expand description
The concepts ledger table (§4.1).
§rowid_pk is explicit, and that is the whole point (v8, D-119)
Through v7 this table declared id TEXT PRIMARY KEY, which left its rowid
implicit — and concepts_fts is external-content keyed on that rowid.
VACUUM renumbers implicit rowids, which would silently decouple the search
index from the rows it indexes: no error, no integrity-check failure, just
results that stop matching.
D-071 proved the hazard
unreachable by consequence rather than by design — trg_concepts_guard_delete
is unconditional, so rowids are dense 1..n and VACUUM’s renumbering is
the identity map. 0.9.0’s archival makes them sparse and makes the hazard
real, so v8 replaces the accident with a column: an INTEGER PRIMARY KEY is
a stored value, and VACUUM preserves it whether the numbering is dense or
not (measured in examples/concepts_rebuild_probe.rs §5).
SQLite permits one primary key per table, so id becomes NOT NULL UNIQUE.
That keeps it a valid foreign-key parent for links.source_id /
links.target_id and keeps ON CONFLICT(id) working, but it is a
primary-key change — which D-036
forbids outright after 1.0. Taken pre-1.0 on purpose, or never.