Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f7b4f17
fix(reindex): isolate inactive candidates from durable writes
Sinity Aug 8, 2026
b095c1a
fix(reindex): seal candidate durable capability leaks
Sinity Aug 8, 2026
66b45e6
chore(architecture): inventory frozen-candidate writer entrypoint
Sinity Aug 8, 2026
3d9cfe1
fix(reindex): block explicit candidate ops telemetry routes
Sinity Aug 8, 2026
eaab4b2
fix(reindex): validate frozen authority before candidate allocation
Sinity Aug 8, 2026
78650b6
fix(reindex): validate split-root candidate authority
Sinity Aug 8, 2026
f23d620
fix(reindex): gate daemon candidate allocation on frozen source
Sinity Aug 8, 2026
f6b5d9d
refactor(reindex): route daemon admission through maintenance
Sinity Aug 8, 2026
7f137af
fix(reindex): preserve anchor during stale retirement
Sinity Aug 8, 2026
3a2116d
fix(reindex): bind candidate census identity
Sinity Aug 8, 2026
86b802b
fix(reindex): normalize census identity vocabulary
Sinity Aug 8, 2026
3124cb6
fix(reindex): require exact durable authority keys
Sinity Aug 8, 2026
8622b2f
fix(reindex): bind append and canary cleanup authority
Sinity Aug 8, 2026
92a9d4a
fix(reindex): anchor append identity to full authority
Sinity Aug 8, 2026
6bf48c0
chore(beads): close inactive candidate durable barrier
Sinity Aug 8, 2026
9fe7cc0
fix(reindex): close frozen candidate review gaps
Sinity Aug 8, 2026
8e4d6f4
fix(ingest): declare mutable Drive governance mode
Sinity Aug 8, 2026
329cdf5
fix(reindex): make source admission rebuild-safe
Sinity Aug 8, 2026
4d02ba7
test(reindex): type bounded admission probe
Sinity Aug 8, 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
1 change: 1 addition & 0 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/plans/layering.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ writer_modules:
interruption: atomic
entrypoints:
[admit_raw_and_parsed_result, apply_raw_membership_classification, apply_raw_revision_replay,
classify_raw_revision_cohort_for_frozen_candidate,
classify_raw_revision_cohort_for_live_watch, classify_raw_revision_cohort_for_rebuild_repair,
release_provisional_full_revisions, replace_raw_membership_census,
write_parsed_for_retained_raw, write_parsed_for_retained_raw_result, write_raw_and_parsed,
Expand Down
10 changes: 8 additions & 2 deletions polylogue/daemon/bulk_rebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from polylogue.maintenance.rebuild_index import (
_REBUILD_TERMINAL_NOT_RESUMABLE,
_reconcile_active_generation_transaction,
validate_rebuild_source_admission,
)
from polylogue.storage.archive_identity import ArchiveLocation, OwnedArchiveLocation, assert_owns_archive_location
from polylogue.storage.index_generation import (
Expand Down Expand Up @@ -210,7 +211,7 @@ def resolve_or_start_daemon_bulk_rebuild_transaction(
# after ownership acquisition so receipt expiry, source revision, or
# external-corpus drift cannot reach generation bookkeeping.
_validate_rebuild_provenance_receipt(root, schema_inference_receipt_path)
store = IndexGenerationStore(location)
store = IndexGenerationStore(location, repair_anchor=False)
transaction: IndexRebuildTransaction | None
try:
transaction = store.load_transaction(DAEMON_BULK_REBUILD_OPERATION_ID)
Expand All @@ -230,6 +231,11 @@ def resolve_or_start_daemon_bulk_rebuild_transaction(
_validate_rebuild_provenance_receipt(root, schema_inference_receipt_path)
return transaction

# A fresh transaction receives one archive-wide source admission.
# Resumed passes validate only their selected authority component in
# rebuild_index_from_source_sync, avoiding a full reparse per page.
validate_rebuild_source_admission(root, location)

if transaction is not None:
if transaction.status == "promoted-attestation-failed":
# The generation is already active. Preserve the terminal
Expand Down Expand Up @@ -379,7 +385,7 @@ async def run_daemon_bulk_rebuild_pass(
# Recheck before consuming the receipt or selecting source material.
await asyncio.to_thread(require_rebuild_schema_currency, root)
await asyncio.to_thread(_validate_rebuild_provenance_receipt, root, receipt_path)
store = IndexGenerationStore(location)
store = IndexGenerationStore(location, repair_anchor=False)
await asyncio.to_thread(_validate_rebuild_provenance_receipt, root, receipt_path)
page = await asyncio.to_thread(store.next_raw_page, transaction, limit=batch_size)
finally:
Expand Down
47 changes: 38 additions & 9 deletions polylogue/maintenance/rebuild_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,15 @@ def require_rebuild_schema_currency(root: Path) -> dict[str, object]:
return diagnostic


def validate_rebuild_source_admission(root: Path, location: ArchiveLocation) -> None:
"""Validate frozen source authority under the owned archive identity."""
from polylogue.sources.revision_backfill import validate_frozen_source_authority

if location.configured_root != root.absolute():
raise RuntimeError("rebuild source admission received a foreign archive location")
validate_frozen_source_authority(root)


@dataclass(slots=True)
class RebuildProvenanceContext:
"""Validated evidence shared by every mutation in one rebuild pass.
Expand Down Expand Up @@ -274,7 +283,7 @@ def _mark_rebuild_transaction_stale_after_provenance_failure(
from polylogue.storage.index_generation import IndexGenerationStore

try:
store = IndexGenerationStore.for_archive_root(root)
store = IndexGenerationStore.for_archive_root(root, repair_anchor=False)
transaction = store.load_transaction(operation_id)
transaction = _reconcile_active_generation_transaction(store, transaction)
except Exception as load_error:
Expand Down Expand Up @@ -1332,8 +1341,16 @@ async def rebuild_index_from_source(request: RebuildIndexRequest) -> RebuildInde
# route below, so a missing root/index.db must not masquerade as raw debt.
if count_source_raw_sessions(root) and location.active_index_path.parent == root:
from polylogue.readiness.capability import raw_frontier_source_selection_block_reason

if reason := raw_frontier_source_selection_block_reason(root):
from polylogue.storage.archive_readiness import raw_materialization_readiness_snapshot

materialization = raw_materialization_readiness_snapshot(root)
# An available active index must agree with source and cursor
# authority before it can seed a rebuild. A missing or unreadable
# derived tier is the recovery case this operation exists to handle;
# source-only candidate admission below remains mandatory.
if materialization.get("available") is True and (
reason := raw_frontier_source_selection_block_reason(root, materialization)
):
raise RuntimeError(f"reindex source preflight gate failed: raw frontier integrity: {reason}")
active_config = Config(
archive_root=root,
Expand Down Expand Up @@ -1413,15 +1430,14 @@ async def _rebuild_index_from_source_owned(
verify_archive,
)
from polylogue.maintenance.replay import rebuild_index_from_source as replay_source
from polylogue.sources.revision_backfill import RebuildDeadlineExceededError
from polylogue.storage.archive_readiness import archive_readiness_status
from polylogue.storage.index_generation import (
IndexGenerationStore,
rebuild_source_evidence_snapshot,
from polylogue.sources.revision_backfill import (
RebuildDeadlineExceededError,
validate_frozen_source_authority,
)
from polylogue.storage.archive_readiness import archive_readiness_status
from polylogue.storage.index_generation import IndexGenerationStore, rebuild_source_evidence_snapshot
from polylogue.storage.repair import repair_session_insights

generation_store = IndexGenerationStore(owned.location)
provenance = RebuildProvenanceContext(
root=root,
receipt_path=request.schema_inference_receipt_path,
Expand Down Expand Up @@ -1458,10 +1474,12 @@ async def _rebuild_index_from_source_owned(
page = None
pass_started_at_ms = int(time.time() * 1000)
if resumable_full_source:
generation_store = IndexGenerationStore(owned.location, repair_anchor=False)
if request.operation_id is not None:
transaction = generation_store.load_transaction(request.operation_id)
transaction = _reconcile_active_generation_transaction(generation_store, transaction)
else:
validate_frozen_source_authority(root)
transaction = _create_rebuild_transaction_after_receipt_validation(
generation_store, request, provenance
)
Expand Down Expand Up @@ -1544,11 +1562,21 @@ async def _rebuild_index_from_source_owned(
selected_raw_ids = [raw_id for raw_id, _blob_hash_hex, _blob_size in page.rows]
selected_raw_count = len(selected_raw_ids)
skipped_by_blob_limit_count = 0
if not transaction_created_here and selected_raw_ids:
validate_frozen_source_authority(
root,
selected_raw_ids=selected_raw_ids,
)
else:
selection_started_at = time.perf_counter()
raw_count, selected_raw_ids, skipped_by_blob_limit_count = select_rebuild_raw_ids(request)
selection_elapsed_s = time.perf_counter() - selection_started_at
selected_raw_count = len(selected_raw_ids)
validate_frozen_source_authority(
root,
selected_raw_ids=selected_raw_ids,
)
generation_store = IndexGenerationStore(owned.location, repair_anchor=request.promote)
provenance.validate()
generation = generation_store.create(source_snapshot=rebuild_source_evidence_snapshot(root))
try:
Expand Down Expand Up @@ -2017,6 +2045,7 @@ def _check_pass_deadline() -> None:
dry_run=False,
archive_root_override=generation_root,
owned_inactive_generation=(generation.generation_id, generation.owner_id),
resolve_convergence_debt=False,
)
terminal_timings_s["terminal.session_insights"] = time.perf_counter() - terminal_started_at
logger.info(
Expand Down
2 changes: 1 addition & 1 deletion polylogue/maintenance/reindex_canary.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def _discard_canary_candidate(archive_root: Path, receipt: object) -> list[BaseE
generation_id = generation.get("generation_id")
if not isinstance(generation_id, str) or not generation_id:
return [RuntimeError("canary receipt candidate generation id is missing")]
store = IndexGenerationStore(ArchiveLocation.resolve(archive_root))
store = IndexGenerationStore(ArchiveLocation.resolve(archive_root), repair_anchor=False)
try:
candidate = store.load(generation_id)
except BaseException as exc:
Expand Down
1 change: 1 addition & 0 deletions polylogue/pipeline/services/ingest_batch/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ class _DriveRevisionGovernanceAdapter:
def __init__(self, source_conn: sqlite3.Connection, blob_publisher: ArchiveBlobPublisher | None) -> None:
self._source_conn = source_conn
self._blob_publisher = blob_publisher
self._inactive_candidate_durable_read_only = False
# Never read by bind_raw_revision/classify_raw_revision_cohort; see
# class docstring for why this is a harmless placeholder value.
self._conn = source_conn
Expand Down
Loading