Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions polylogue/storage/repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ def _verify_browser_origin_copy_forward_source_stage(
len(projection.message_hashes),
0,
RawRevisionAuthority.QUARANTINED.value,
"applied",
None,
)
or census is None
or tuple(census) != ("complete", 1)
Expand Down Expand Up @@ -1336,6 +1336,7 @@ def _inspect_browser_capture_origin_mismatch(
or str(raw["revision_authority"]) != RawRevisionAuthority.QUARANTINED.value
or raw["source_revision"] is None
or raw["acquisition_generation"] is None
or int(raw["acquisition_generation"]) != 0
or any(
raw[name] is not None
for name in (
Expand Down Expand Up @@ -1417,6 +1418,17 @@ def _inspect_browser_capture_origin_mismatch(
"SELECT session_id, origin, native_id, content_hash FROM sessions WHERE session_id = ?",
(session_id,),
).fetchone()
old_applications = conn.execute(
"""
SELECT session_id, logical_source_key, source_revision, acquisition_generation,
decision, accepted_raw_id, accepted_source_revision,
accepted_content_hash, baseline_raw_id, decided_at_ms
Comment on lines +1423 to +1425

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the application frontier before accepting repair

This new application-witness check still omits accepted_frontier_kind and accepted_frontier from the receipt comparison. If a historical selected_baseline receipt is partially corrupted or manually repaired with the same raw/hash fields but a semantic or wrong frontier, this branch remains eligible because the separate head row is checked, so the repair can copy-forward without an exact immutable application receipt. Include the frontier fields in this tuple and require byte plus the blob size so application mutations fail closed.

Useful? React with 👍 / 👎.

FROM raw_revision_applications
WHERE raw_id = ? AND logical_source_key = ? AND decision = 'selected_baseline'
ORDER BY decision_id
""",
(raw_id, old_key),
).fetchall()
if (
head is None
or indexed is None
Expand All @@ -1428,6 +1440,21 @@ def _inspect_browser_capture_origin_mismatch(
or int(head["accepted_frontier"]) != blob_size
or int(head["acquisition_generation"]) != int(raw["acquisition_generation"])
or _bytes_value(indexed["content_hash"]) != accepted_hash
or len(old_applications) != 1
or tuple(old_applications[0])[:-1]
!= (
session_id,
old_key,
blob_hash_hex,
int(raw["acquisition_generation"]),
ApplicationDecision.SELECTED_BASELINE.value,
raw_id,
blob_hash_hex,
accepted_hash,
raw_id,
)
or not isinstance(old_applications[0][-1], int)
or int(old_applications[0][-1]) < 0
):
return _browser_origin_ineligible(raw_id, "current accepted head does not exactly prove the normalized session")
membership = conn.execute(
Expand All @@ -1453,7 +1480,12 @@ def _inspect_browser_capture_origin_mismatch(
or int(membership["message_count"]) != len(projection.message_hashes)
or int(membership["acquisition_generation"]) != int(raw["acquisition_generation"])
or str(membership["revision_authority"]) != RawRevisionAuthority.QUARANTINED.value
or str(membership["decision"]) != "applied"
# A quarantined source raw has not been admitted to replay, so its
# singleton census witness must remain undecided. The old
# selected-baseline receipt above is the separate, immutable authority
# witness for the current unknown-key head. A non-null membership
# decision would be incompatible with this narrow recovery shape.
or membership["decision"] is not None
or str(census["status"]) != "complete"
or int(census["member_count"]) != 1
):
Expand Down
47 changes: 44 additions & 3 deletions tests/unit/storage/test_browser_capture_origin_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ def _seed_mismatched_browser_head(root: Path) -> str:
accepted_content_hash=accepted_hash,
accepted_frontier_kind="byte",
accepted_frontier=len(payload),
baseline_raw_id=raw_id,
detail="historical mismatched browser head",
),
decided_at_ms=2,
Expand All @@ -139,7 +140,7 @@ def _seed_mismatched_browser_head(root: Path) -> str:
normalized_content_hash, message_count, acquisition_generation,
revision_authority, decision, decided_at_ms
) VALUES (?, 'chatgpt:browser-origin-one', 'browser-origin-one', ?, ?, 1, 0,
'quarantined', 'applied', 2)
'quarantined', NULL, NULL)
""",
(raw_id, accepted_hash.hex(), accepted_hash),
)
Expand Down Expand Up @@ -319,6 +320,24 @@ def test_browser_capture_origin_copy_forward_preserves_old_evidence_and_is_idemp
assert receipt.read_text().count("\n") == 2


def test_browser_capture_origin_rejects_decided_quarantined_membership(tmp_path: Path) -> None:
raw_id = _seed_mismatched_browser_head(tmp_path)
with sqlite3.connect(tmp_path / "source.db") as source:
source.execute(
"""
UPDATE raw_session_memberships
SET decision = 'ambiguous', decided_at_ms = 2
WHERE raw_id = ?
""",
(raw_id,),
)

report = repair_browser_capture_origin_mismatches(_config(tmp_path), [raw_id])

assert report.ineligible_count == 1
assert report.items[0].reason == "membership census does not exactly reproduce the accepted session"


def test_browser_capture_origin_rebuild_keeps_copy_forward_head(tmp_path: Path) -> None:
raw_id = _seed_mismatched_browser_head(tmp_path)
dry_run = repair_browser_capture_origin_mismatches(_config(tmp_path), [raw_id])
Expand All @@ -340,7 +359,9 @@ def test_browser_capture_origin_rebuild_keeps_copy_forward_head(tmp_path: Path)
).fetchone() == (copy_raw_id,)


@pytest.mark.parametrize("mutation", ["blob", "head", "origin", "canonical_head"])
@pytest.mark.parametrize(
"mutation", ["blob", "head", "origin", "application", "generation", "authority", "canonical_head"]
)
def test_browser_capture_origin_copy_forward_mutations_fail_closed(tmp_path: Path, mutation: str) -> None:
raw_id = _seed_mismatched_browser_head(tmp_path)
with sqlite3.connect(tmp_path / "source.db") as source, sqlite3.connect(tmp_path / "index.db") as index:
Expand All @@ -350,6 +371,23 @@ def test_browser_capture_origin_copy_forward_mutations_fail_closed(tmp_path: Pat
index.execute("UPDATE raw_revision_heads SET accepted_frontier = accepted_frontier + 1")
elif mutation == "origin":
source.execute("UPDATE raw_sessions SET origin = 'chatgpt-export' WHERE raw_id = ?", (raw_id,))
elif mutation == "application":
index.execute(
"DELETE FROM raw_revision_applications WHERE raw_id = ? AND decision = 'selected_baseline'",
(raw_id,),
)
elif mutation == "generation":
source.execute("UPDATE raw_sessions SET acquisition_generation = 1 WHERE raw_id = ?", (raw_id,))
source.execute("UPDATE raw_session_memberships SET acquisition_generation = 1 WHERE raw_id = ?", (raw_id,))
index.execute(
"UPDATE raw_revision_heads SET acquisition_generation = 1 WHERE accepted_raw_id = ?", (raw_id,)
)
index.execute(
"UPDATE raw_revision_applications SET acquisition_generation = 1 WHERE raw_id = ?",
(raw_id,),
)
elif mutation == "authority":
source.execute("UPDATE raw_sessions SET revision_authority = 'byte_proven' WHERE raw_id = ?", (raw_id,))
else:
index.execute(
"""
Expand All @@ -371,7 +409,10 @@ def test_browser_capture_origin_copy_forward_mutations_fail_closed(tmp_path: Pat
def test_browser_capture_origin_rejects_unresolved_source_membership(tmp_path: Path) -> None:
raw_id = _seed_mismatched_browser_head(tmp_path)
with sqlite3.connect(tmp_path / "source.db") as source:
source.execute("UPDATE raw_session_memberships SET decision = 'ambiguous' WHERE raw_id = ?", (raw_id,))
source.execute(
"UPDATE raw_session_memberships SET decision = 'ambiguous', decided_at_ms = 2 WHERE raw_id = ?",
(raw_id,),
)

report = repair_browser_capture_origin_mismatches(_config(tmp_path), [raw_id])

Expand Down
Loading