Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0440de2
fix(storage): attest source mutations across train continuity
Sinity Aug 7, 2026
7914a38
chore(beads): track source continuity refresh blocker
Sinity Aug 7, 2026
9a084aa
chore(beads): gate source remediation on continuity refresh
Sinity Aug 7, 2026
478a360
fix(storage): preserve continuity failures after source cleanup
Sinity Aug 7, 2026
68a0591
fix(storage): recover continuity after committed cleanup
Sinity Aug 7, 2026
01cb0dd
fix(storage): verify continuity receipts at startup
Sinity Aug 7, 2026
0af174f
fix(storage): recover pending liveness states safely
Sinity Aug 7, 2026
6e483ad
test(storage): cover pending cleanup without a source train
Sinity Aug 7, 2026
035db4e
fix(storage): recover terminal source continuity intents
Sinity Aug 7, 2026
d0215d7
fix(storage): guard pending source continuity applies
Sinity Aug 7, 2026
87ca06f
fix(storage): harden source continuity recovery
Sinity Aug 7, 2026
d7e30ce
fix(storage): close source continuity recovery gaps
Sinity Aug 7, 2026
9afa3de
fix(storage): preserve source train recovery boundaries
Sinity Aug 7, 2026
04ab75c
chore: refresh source continuity gate metadata
Sinity Aug 7, 2026
f940e71
chore: finalize source continuity gate metadata
Sinity Aug 7, 2026
5e4ffd4
fix(storage): reject orphaned continuity recovery
Sinity Aug 7, 2026
b33ecc2
fix(storage): preserve source continuity recovery outcomes
Sinity Aug 7, 2026
be9a84f
fix(storage): persist pending-intent directory entries
Sinity Aug 7, 2026
e7c2d31
test(storage): type pending-intent fsync probe
Sinity Aug 7, 2026
dcde113
fix(storage): preserve continuity recovery durability
Sinity Aug 7, 2026
ceb4098
fix(storage): block unsafe source continuity cleanup
Sinity Aug 7, 2026
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
3 changes: 2 additions & 1 deletion .beads/issues.jsonl

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions polylogue/cli/commands/maintenance/_blob_integrity.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,10 @@ def blob_reference_liveness_command(
}
if output_format == "json":
click.echo(json.dumps(payload, indent=2, sort_keys=True))
return
_render_blob_reference_liveness_plain(report, sample_limit=sample_limit)
else:
_render_blob_reference_liveness_plain(report, sample_limit=sample_limit)
if report.continuity_refresh_pending:
raise click.exceptions.Exit(1)


def _render_blob_reference_liveness_plain(report: BlobRefLivenessReconciliationReport, *, sample_limit: int) -> None:
Expand All @@ -120,6 +122,10 @@ def _render_blob_reference_liveness_plain(report: BlobRefLivenessReconciliationR
)
if report.receipt_path is not None:
click.echo(f"Receipt: {report.receipt_path}")
if report.continuity_refresh_receipt is not None:
click.echo(f"Train proof: {report.continuity_refresh_receipt}")
if report.continuity_refresh_error is not None:
click.echo(f"Train proof refresh failed: {report.continuity_refresh_error}")
Comment thread
Sinity marked this conversation as resolved.
for candidate in report.classification.candidates[: max(0, sample_limit)]:
click.echo(
f" orphan {candidate.ref_type} ref_id={candidate.ref_id} "
Expand Down
217 changes: 177 additions & 40 deletions polylogue/maintenance/blob_ref_liveness_reconciliation.py

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion polylogue/storage/blob_ref_liveness.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

from __future__ import annotations

import hashlib
import json
import sqlite3
from collections.abc import Iterator
from collections.abc import Iterable, Iterator
from dataclasses import dataclass
from typing import cast

Expand Down Expand Up @@ -48,6 +50,33 @@ def to_dict(self) -> dict[str, object]:
}


class BlobRefLivenessCandidateDigest:
"""Incrementally encode the canonical candidate stream used by receipts."""

def __init__(self) -> None:
self._digest = hashlib.sha256(b"[")
self._first = True

def update(self, candidate: BlobRefLivenessCandidate) -> None:
if not self._first:
self._digest.update(b",")
self._digest.update(json.dumps(candidate.to_dict(), sort_keys=True, separators=(",", ":")).encode("utf-8"))
self._first = False

def hexdigest(self) -> str:
digest = self._digest.copy()
digest.update(b"]")
return digest.hexdigest()


def digest_blob_ref_liveness_candidates(candidates: Iterable[BlobRefLivenessCandidate]) -> str:
"""Hash candidates with the receipt's canonical JSON array framing."""
digest = BlobRefLivenessCandidateDigest()
for candidate in candidates:
digest.update(candidate)
return digest.hexdigest()


@dataclass(frozen=True, slots=True)
class BlobRefLivenessClassification:
"""Complete read-only classification of source-tier blob references."""
Expand Down
Loading