fix(rd21): retain draft revisions and qualify conflict recovery

This commit is contained in:
cesnimda
2026-10-23 12:34:34 +02:00
parent 12f342c09c
commit 05a738693e
53 changed files with 813 additions and 192 deletions
@@ -0,0 +1,106 @@
"""Read only the explicitly named synthetic owner's committed SQLite rows."""
import json
from contextlib import closing
from pathlib import Path
import sqlite3
import sys
import tempfile
CHILDREN = ("CareerExperiences", "CareerEducations", "CareerSkills", "CareerProjects",
"CareerCertifications", "CareerLanguages")
def connect_readonly(filename):
path = Path(filename).resolve(strict=True)
connection = sqlite3.connect(path.as_uri() + "?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
return connection
def snapshot(connection, owner, job_id=None):
def rows(sql, parameters):
return [dict(row) for row in connection.execute(sql, parameters)]
identities = rows('SELECT "Id", "ProfileCvText", "ProfileCvStructureJson", '
'"CurrentCvProfileVersion" FROM "AspNetUsers" WHERE "Id" = ?', (owner,))
if len(identities) != 1:
raise ValueError("Expected exactly one explicitly owned synthetic Identity row")
careers = rows('SELECT * FROM "CareerProfiles" WHERE "OwnerUserId" = ?', (owner,))
if len(careers) > 1:
raise ValueError("More than one canonical Career Profile for owner")
result = {
"identity": identities[0],
"career": careers[0] if careers else None,
"careerHistory": rows('SELECT * FROM "CareerProfileVersions" WHERE "OwnerUserId" = ? ORDER BY "Version", "Id"', (owner,)),
"children": {table: rows(f'SELECT * FROM "{table}" WHERE "OwnerUserId" = ? ORDER BY "SortOrder", "Id"', (owner,)) for table in CHILDREN},
"job": None, "opportunity": None, "events": [],
}
if job_id is not None:
jobs = rows('SELECT * FROM "JobApplications" WHERE "OwnerUserId" = ? AND "Id" = ?', (owner, job_id))
if len(jobs) != 1:
raise ValueError("Expected exactly one explicitly owned synthetic job")
result["job"] = jobs[0]
opportunities = rows('SELECT * FROM "Jobs" WHERE "OwnerUserId" = ? AND "Id" = ?', (owner, jobs[0]["JobId"]))
if len(opportunities) != 1:
raise ValueError("Expected the job's explicitly owned opportunity")
result["opportunity"] = opportunities[0]
result["events"] = rows('SELECT e.* FROM "JobEvents" e JOIN "JobApplications" j ON e."JobApplicationId" = j."Id" '
'WHERE j."OwnerUserId" = ? AND j."Id" = ? ORDER BY e."Id"', (owner, job_id))
return result
def self_test():
with tempfile.TemporaryDirectory(prefix="rd21-readonly-proof-") as directory:
filename = Path(directory) / "jobtracker.db"
with closing(sqlite3.connect(filename)) as writer, writer:
writer.executescript('''
CREATE TABLE AspNetUsers (Id TEXT, ProfileCvText TEXT, ProfileCvStructureJson TEXT, CurrentCvProfileVersion INTEGER);
INSERT INTO AspNetUsers VALUES ('owner', 'raw', '{}', 1), ('other', 'private', '{}', 1);
CREATE TABLE CareerProfiles (Id INTEGER, OwnerUserId TEXT, Version INTEGER, ProfileJson TEXT);
INSERT INTO CareerProfiles VALUES (1, 'owner', 1, '{}'), (2, 'other', 1, '{}');
CREATE TABLE CareerProfileVersions (Id INTEGER, OwnerUserId TEXT, Version INTEGER);
INSERT INTO CareerProfileVersions VALUES (1, 'owner', 1), (2, 'other', 1);
CREATE TABLE JobApplications (Id INTEGER, OwnerUserId TEXT, JobId INTEGER, Version INTEGER);
INSERT INTO JobApplications VALUES (1, 'owner', 10, 2), (2, 'other', 20, 3);
CREATE TABLE Jobs (Id INTEGER, OwnerUserId TEXT);
INSERT INTO Jobs VALUES (10, 'owner'), (20, 'other');
CREATE TABLE JobEvents (Id INTEGER, JobApplicationId INTEGER);
INSERT INTO JobEvents VALUES (1, 1), (2, 2);
''')
for table in CHILDREN:
writer.execute(f'CREATE TABLE "{table}" (Id INTEGER, OwnerUserId TEXT, SortOrder INTEGER)')
writer.executemany(f'INSERT INTO "{table}" VALUES (?, ?, ?)', [(1, "owner", 0), (2, "other", 0)])
with closing(connect_readonly(filename)) as reader:
before = snapshot(reader, "owner", 1)
assert before["career"]["Version"] == 1
assert before["job"]["Version"] == 2
assert before["opportunity"]["Id"] == 10
assert before["events"] == [{"Id": 1, "JobApplicationId": 1}]
assert all(len(rows) == 1 for rows in before["children"].values())
assert "private" not in json.dumps(before)
try:
reader.execute('UPDATE CareerProfiles SET Version = 99')
except sqlite3.OperationalError as error:
assert "readonly" in str(error).lower()
else:
raise AssertionError("Read-only URI allowed mutation")
assert snapshot(reader, "owner", 1) == before
try:
snapshot(reader, "owner", 2)
except ValueError:
pass
else:
raise AssertionError("Cross-owner job was returned")
print("readonly-db-proof self-test passed")
if __name__ == "__main__":
if sys.argv[1:] == ["--self-test"]:
self_test()
else:
if len(sys.argv) not in (3, 4):
raise SystemExit("Usage: readonly-db-proof.py DATABASE OWNER_ID [JOB_ID] | --self-test")
with closing(connect_readonly(sys.argv[1])) as connection:
connection.execute("BEGIN")
print(json.dumps(snapshot(connection, sys.argv[2], int(sys.argv[3]) if len(sys.argv) == 4 else None)))