Skip to content

fix(reindex): isolate inactive candidate durable writes - #3885

Merged
Sinity merged 19 commits into
masterfrom
feature/fix/inactive-candidate-durable-barrier
Aug 8, 2026
Merged

fix(reindex): isolate inactive candidate durable writes#3885
Sinity merged 19 commits into
masterfrom
feature/fix/inactive-candidate-durable-barrier

Conversation

@Sinity

@Sinity Sinity commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

Introduce an owned inactive-candidate access mode that permits index construction while preserving the frozen durable source, user database, blob namespace, active pointer, and live operations state. Ref polylogue-inactive-candidate-durable-barrier.

Problem

The current rebuild route represented an inactive generation with read-through symlinks, then initialized that generation as a normal writable archive. The durable change-train guard rejected the symlinked source tier. Bypassing that guard would have allowed candidate replay to mutate source, user, blob, and operations state during phase 3. Parser-census and persisted-authority decisions also needed to be validated before allocating a candidate generation.

Solution

Add explicit owned-inactive archive semantics, verify generation ownership and exact read-through targets, open durable tiers read-only, and refuse durable, blob, and live-operations mutations before side effects. Candidate admission now re-derives the frozen parser census and exact source authority before generation allocation. APPEND authority must form an acyclic predecessor chain terminating at a byte-proven FULL baseline under one canonical source key. Candidate, stale-retirement, canary-cleanup, and daemon routes no longer repair the active pointer unless promotion is explicitly authorized.

Production candidate construction, acceptance, promotion, restart, and postflight remain under their existing phase Beads. This PR supplies the safe mechanism only.

Verification

  • devtools test tests/unit/maintenance/test_inactive_candidate_durable_barrier.py tests/unit/maintenance/test_rebuild_index_provenance_gate.py tests/unit/sources/test_revision_backfill.py: 133 passed.
  • devtools test tests/unit/maintenance/test_inactive_candidate_durable_barrier.py tests/unit/maintenance/test_reindex_canary.py -k 'inactive_candidate_durable_barrier or cleans_candidate_after_comparison_failure': 20 passed, 40 deselected.
  • devtools test tests/unit/daemon/test_bulk_rebuild.py: 7 passed; the two failures are present in the exact current-master full-suite baseline under the same node IDs.
  • devtools verify --quick: all 24 steps passed in run 20260808T120215Z-quick-1146980-704f744a at head 4d02ba7e83b75049e60c65f049aa78dfc73add53.
  • Independent adversarial review: no legitimate blockers at code head 92a9d4ac3a62e81a01d2ad832acd1060d5657c02.
  • The live bd graph policy still reports the inherited 220-item AC-less database. The committed branch snapshot contains the 218 structured contracts merged in chore(beads): add guarded acceptance contracts #3880; tracker database reconciliation remains separate release-authority work.
  • No production source mutation, candidate build, promotion, restart, or postflight action was run.

Sinity and others added 15 commits August 8, 2026 10:50
Owned inactive generations previously reopened source and user tiers as writable through candidate read-through symlinks. The production bootstrap correctly rejected the layout, and bypassing that check would have let candidate replay mutate the frozen phase-2 source.

Add an explicit owned-candidate mode that writes only index.db, validates every read-through target, opens durable tiers read-only, refuses blob publication, and requires current parser census plus persisted authority before allocating or accepting a candidate.

Ref polylogue-inactive-candidate-durable-barrier.
Problem

Inactive candidates could reach live ops telemetry, convergence debt, and
several inherited blob and user-tier mutation APIs through read-through
links. Candidate allocation also preceded complete frozen-source authority.

What changed

Require current parser and settled authority evidence before allocating a
generation. Validate split-root ownership against generation metadata, block
every blob and user mutation surface, and suppress inactive-candidate writes
to the live ops ledger while retaining active-generation telemetry.

Verification

Focused candidate barrier tests pass 6/6. The wider affected batch passed
162 tests; its 15 failures exactly match the frozen current-master full-suite
ledger. Ruff and strict mypy pass for all touched source and test files.

Ref polylogue-inactive-candidate-durable-barrier.

Co-Authored-By: Codex <noreply@openai.com>
Problem

The writer-module policy discovered the new frozen-candidate classifier as a
source/index mutation entrypoint but its declared inventory did not name it.

What changed

Add the classifier to the revision-governance writer inventory so the policy
continues to audit its twin-tier reach.

Verification

`devtools verify layering --json` reports zero violations.

Ref polylogue-inactive-candidate-durable-barrier.

Co-Authored-By: Codex <noreply@openai.com>
Problem

The inactive-generation telemetry guard applied only when the sampler inferred
ops.db from the candidate index path. A caller supplying an explicit archive
root could bypass the same durable boundary.

What changed

Resolve and classify the connected index before either ops path is selected.
The candidate canary now exercises both inferred and explicit telemetry routes
and proves live ops.db bytes remain unchanged.

Verification

Strict mypy and Ruff pass. The candidate barrier and active FTS convergence
suites pass 10/10.

Ref polylogue-inactive-candidate-durable-barrier.

Co-Authored-By: Codex <noreply@openai.com>
Re-derive current byte and membership authority before creating an inactive generation or rebuild transaction. Add a non-repairing generation-store mode so candidate validation cannot rewrite a missing or poisoned active-pointer anchor. Prove refusal leaves pointer, generation, transaction, and durable-tier evidence unchanged.

Ref polylogue-inactive-candidate-durable-barrier

Co-Authored-By: Codex <noreply@openai.com>
Bind frozen source validation to the canonical active index selected by the owned archive identity. Keep the durable archive root for source, user, and blob reads while ignoring missing or poisoned generation-member anchors without repairing them.

Ref polylogue-inactive-candidate-durable-barrier

Co-Authored-By: Codex <noreply@openai.com>
Run the same current byte and membership authority validation inside the daemon bulk resolver after ownership and provenance checks, before constructing the generation store or creating a transaction. Update daemon fixtures to represent a phase-2-complete source and prove missing or poisoned anchors remain untouched on refusal.

Ref polylogue-inactive-candidate-durable-barrier

Co-Authored-By: Codex <noreply@openai.com>
Keep daemon adapters out of source-layer internals by exposing the frozen-source admission boundary from the maintenance operation layer. The wrapper retains the exact owned active-index identity and leaves generation allocation ordering unchanged.

Ref polylogue-inactive-candidate-durable-barrier

Co-Authored-By: Codex <noreply@openai.com>
A resumable rebuild that failed provenance admission marked its transaction stale through an IndexGenerationStore constructor that also repaired missing or poisoned active-pointer anchors. That lifecycle-only failure path must not alter active archive authority.

Construct the store with anchor repair disabled and cover both missing and poisoned anchors on the real operation-id route while preserving stale transaction evidence.

Ref polylogue-inactive-candidate-durable-barrier.
Inactive admission trusted a parser-census status without comparing its persisted logical-key identity to a fresh current-parser derivation. Candidate transaction setup also repaired missing or poisoned active-pointer anchors before any promotion boundary.

Decode and normalize every current parser census key set, compare parseable raws against fresh parser identity, and construct candidate admission stores without pointer repair. Promotion-authorized offline requests retain their explicit repair authority.

Ref polylogue-inactive-candidate-durable-barrier.
Frozen authority receipts can contain either transitional Provider prefixes or public Origin prefixes depending on the acquisition route. Comparing either raw spelling to freshly parsed public identity rejected valid Codex and ChatGPT archives.

Normalize both persisted aliases to Origin while preserving the native id, reject unknown or collapsing key sets, and compare that canonical identity to the current parser result.

Ref polylogue-inactive-candidate-durable-barrier.
Parser receipts were compared to fresh parses only for standalone payloads. Extra durable membership bindings and typed continuation rows could therefore carry identity outside that comparison and survive frozen-source admission.

Require each canonical census key set to equal the durable typed and membership authority bindings before replay. Add real-route regressions for an extra membership, a poisoned typed append receipt, and jointly poisoned durable plus parser identity.

Ref polylogue-inactive-candidate-durable-barrier.
Typed append rows cannot be parsed independently, so jointly poisoned durable and census keys could evade fresh full-row identity checks. Canary failure cleanup also constructed a generation store with pointer repair enabled.

Require each append key to match its linked predecessor and baseline authority, whose full rows remain fresh-parser validated. Keep canary discard bookkeeping from repairing missing or poisoned active anchors.

Ref polylogue-inactive-candidate-durable-barrier.
A self-linked or incomplete APPEND row could make its poisoned durable and census identity agree without reaching any independently parseable authority. String equality alone could not establish that the continuation belonged to a real full source.

Expand selected validation through revision links, require byte-proven acyclic predecessor chains that terminate at a distinct parseable FULL baseline, and enforce one canonical identity across the chain.

Ref polylogue-inactive-candidate-durable-barrier.
Record the exact implementation head, focused verification, quick-gate receipt, and independent no-blockers verdict. Production candidate execution and downstream phases remain open under their existing Beads.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Inactive rebuild candidates now validate frozen source authority, avoid active-pointer repair, and restrict durable-tier mutations. Frozen replay skips source writes while validating authority. Tests cover write barriers, admission failures, pointer preservation, and authority drift.

Changes

Inactive candidate durability

Layer / File(s) Summary
Admission and anchor control
polylogue/storage/index_generation.py, polylogue/maintenance/rebuild_index.py, polylogue/daemon/bulk_rebuild.py, polylogue/maintenance/reindex_canary.py
Rebuild flows validate source admission and control whether IndexGenerationStore repairs the active pointer.
Frozen source replay and authority validation
polylogue/sources/revision_backfill.py, polylogue/storage/sqlite/archive_tiers/revision_governance.py, docs/plans/layering.yaml
Inactive backfills validate parser census, revision authority, membership decisions, and append lineage without persisting source changes.
Durable-tier write guards
polylogue/storage/sqlite/archive_tiers/archive.py, polylogue/storage/repair.py, polylogue/storage/fts/drift_sampling.py
Inactive candidates use read-only durable access, reject blob and user-tier writes, verify frozen attachments, and avoid active convergence-debt or telemetry updates.
Durability barrier regression coverage
tests/unit/maintenance/test_inactive_candidate_durable_barrier.py, tests/unit/maintenance/test_rebuild_index_provenance_gate.py, tests/unit/maintenance/test_reindex_canary.py, tests/unit/daemon/test_bulk_rebuild.py, .beads/issues.jsonl
Tests cover inactive-candidate isolation, invalid admission, authority drift, pointer preservation, and real-archive rebuild flows.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Rebuild
  participant FrozenBackfill
  participant ArchiveStore
  participant CandidateIndex
  Rebuild->>FrozenBackfill: validate frozen source evidence
  FrozenBackfill->>ArchiveStore: verify durable authority and census
  ArchiveStore-->>FrozenBackfill: validation result
  FrozenBackfill->>CandidateIndex: replay index changes only
  CandidateIndex-->>Rebuild: candidate readiness
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: isolating durable writes from inactive reindex candidates.
Description check ✅ Passed The description covers the required summary, problem, solution, verification, scope disposition, risks, and changelog considerations with detailed evidence.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/fix/inactive-candidate-durable-barrier

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@polylogue/sources/revision_backfill.py`:
- Around line 897-916: Add brief comments next to the two logical-key
derivations in the surrounding backfill function, clarifying that
parsed_logical_keys use origin-prefixed keys to match recorded_logical_keys,
while membership_candidates uses provider-prefixed keys to match
raw_session_memberships.logical_source_key. Keep both derivations unchanged.
- Around line 84-107: Explicitly close the SQLite connections used by
_expand_frozen_revision_link_selection and all four source.db connections in
require_current_parser_source_census, using contextlib.closing or equivalent
finally-based cleanup while preserving the existing transaction context
behavior.

In `@polylogue/storage/sqlite/archive_tiers/archive.py`:
- Around line 2178-2200: In _preacquire_attachment_blobs, remove the
hashlib.file_digest read and stored_hash comparison for attachment blobs. Retain
the blob_path existence and size validation, then record the attachment as
acquired and continue; rely on rebuild_source_evidence_snapshot for
content-identity verification.

In `@polylogue/storage/sqlite/archive_tiers/revision_governance.py`:
- Around line 2525-2528: Move the frozen-candidate check before the
terminal_raw_ids loop at
polylogue/storage/sqlite/archive_tiers/revision_governance.py:2525-2528 so the
function returns before calling raw_revision_descriptor for any raw ID. In the
corresponding flow at
polylogue/storage/sqlite/archive_tiers/revision_governance.py:2994-3008, return
immediately after require_frozen_membership_authority succeeds, preventing
raw_membership_census from being repeated per raw.
- Around line 209-215: Declare the _inactive_candidate_durable_read_only boolean
member on the RawRevisionGovernanceHost Protocol alongside the other private
members used by this module, so host implementations are required to expose the
guard flag and type checking can detect omissions.

In `@tests/unit/maintenance/test_inactive_candidate_durable_barrier.py`:
- Around line 600-610: Update initialize_active_archive_root and its
durable-file validation path through initialize_archive_database so unsafe tier
files such as symlinks or non-files raise DurableChangeTrainError instead of
RuntimeError, using an error message that matches “unsafe file” and preserving
the existing rejection behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9926611c-af9a-4606-a7ea-f2d80ed2e7b4

📥 Commits

Reviewing files that changed from the base of the PR and between ef38228 and 6bf48c0.

📒 Files selected for processing (15)
  • .beads/issues.jsonl
  • docs/plans/layering.yaml
  • polylogue/daemon/bulk_rebuild.py
  • polylogue/maintenance/rebuild_index.py
  • polylogue/maintenance/reindex_canary.py
  • polylogue/sources/revision_backfill.py
  • polylogue/storage/fts/drift_sampling.py
  • polylogue/storage/index_generation.py
  • polylogue/storage/repair.py
  • polylogue/storage/sqlite/archive_tiers/archive.py
  • polylogue/storage/sqlite/archive_tiers/revision_governance.py
  • tests/unit/daemon/test_bulk_rebuild.py
  • tests/unit/maintenance/test_inactive_candidate_durable_barrier.py
  • tests/unit/maintenance/test_rebuild_index_provenance_gate.py
  • tests/unit/maintenance/test_reindex_canary.py

Comment thread polylogue/sources/revision_backfill.py
Comment thread polylogue/sources/revision_backfill.py
Comment on lines +2178 to +2200
if self._inactive_candidate_durable_read_only:
if attachment.inline_bytes is not None:
hash_hex = hashlib.sha256(attachment.inline_bytes).hexdigest()
size = len(attachment.inline_bytes)
elif attachment.precomputed_blob is not None:
hash_hex, size = attachment.precomputed_blob
else:
continue
blob_path = self._blob_publisher.blob_path(hash_hex)
if not blob_path.is_file() or blob_path.stat().st_size != size:
raise InactiveCandidateDurableWriteError(
"inactive candidate requires attachment bytes to be present in the frozen blob namespace: "
f"{hash_hex}"
)
with blob_path.open("rb") as handle:
stored_hash = hashlib.file_digest(handle, "sha256").hexdigest()
if stored_hash != hash_hex:
raise InactiveCandidateDurableWriteError(
"inactive candidate found attachment bytes that do not match the frozen blob identity: "
f"{hash_hex}"
)
acquired[id(attachment)] = (bytes.fromhex(hash_hex), size, "acquired")
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check where blob content identity is already verified during a rebuild.
set -euo pipefail

rg -nP -C 5 '\bdef verify\s*\(' polylogue/storage/blob_store.py
rg -nP -C 3 'blob_store\.verify|file_digest' --type=py

Repository: Sinity/polylogue

Length of output: 616


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
fd -a 'archive\.py|blob_store\.py$' . | sed 's#^\./##'

echo "== archive relevant outline =="
ast-grep outline polylogue/storage/sqlite/archive_tiers/archive.py --match Archive --view expanded || true

echo "== archive imports and verify context =="
sed -n '1,120p' polylogue/storage/sqlite/archive_tiers/archive.py
echo "== around preacquire =="
rg -n "_preacquire_attachment_blobs|inactive_candidate_durable_read_only|rebuild_source_evidence_snapshot|verify" polylogue/storage/sqlite/archive_tiers/archive.py -C 8
echo "== blob_store relevant =="
sed -n '250,350p' polylogue/storage/blob_store.py

Repository: Sinity/polylogue

Length of output: 40640


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== rewrite_raw_evidence and rebuild calls =="
rg -n "rebuild_source_evidence_snapshot|raw_replay|preacquire_attachment_blobs|_preacquire_attachment_blobs|_raw_revision_replay_plan|raw_revision_material|verify\\(" polylogue/storage/sqlite/archive_tiers/archive.py -C 8

echo "== rebuild source snapshot outline/context =="
sed -n '11700,12280p' polylogue/storage/sqlite/archive_tiers/archive.py

echo "== tests around attachment preacquire =="
rg -n "_preacquire_attachment_blobs|inactive_candidate_durable_read_only|attachment" tests/unit/storage/test_archive_tiers_archive.py -C 5

echo "== tests around rebuild/verify =="
rg -n "rebuild_source_evidence_snapshot|verify\\(" tests/unit/storage/test_archive_tiers_archive.py -C 6

Repository: Sinity/polylogue

Length of output: 18868


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== full _preacquire_attachment_blobs =="
sed -n '2162,2218p' polylogue/storage/sqlite/archive_tiers/archive.py

echo "== write_raw_and_parsed and payload blob paths =="
rg -n "def write_raw_and_parsed|write_raw_payload|write_hook_event|write_parsed_for_retained_raw|write_parsed|verify|blob_store|file_digest|precompute|precomputed" polylogue/storage/sqlite/archive_tiers/archive.py -C 12

echo "== raw revision material / replay plan helpers =="
rg -n "def open_raw_revision_material|def raw_revision_material|def raw_revision_replay_plan|def raw_revision_rebuild_selection|raw_revision_replay_plan\\(|raw_revision_rebuild_selection\\(" polylogue -g '*.py' -C 8

echo "== all verify usages outside tests if any =="
rg -n "\bverify\s*\(|file_digest" polylogue -g '*.py' -C 4 | rg -v 'tests/|\.pyc' || true

Repository: Sinity/polylogue

Length of output: 47915


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== revision backfill archive opening and selected reconstruction flow =="
sed -n '980,1060p' polylogue/sources/revision_backfill.py
sed -n '1100,1220p' polylogue/sources/revision_backfill.py
sed -n '1480,1565p' polylogue/sources/revision_backfill.py

echo "== active writer lease / write_parsed calls in revision backfill =="
rg -n "open_frozen_source_validation|archive.open\\(|write_parsed\\(|_preacquire_attachment_blobs|selected_raw_ids|accepted_raw|write_hook_event|write_raw_blob_ref|apply_raw_revision_replay" polylogue/sources/revision_backfill.py -C 6

echo "== write hook event implementation =="
rg -n "def _write_hook_event|def write_hook_event|def write_raw_payload|def write_raw_blob_ref" polylogue/storage/sqlite/archive_tiers source_write.py -C 10

echo "== source_write relevant functions =="
sed -n '1,180p' polylogue/storage/sqlite/archive_tiers/source_write.py

Repository: Sinity/polylogue

Length of output: 45947


Drop the attachment blob end-to-end digest from _preacquire_attachment_blobs.

_preacquire_attachment_blobs already checks the frozen blob path and size before this loop. Re-hashing every attachment blob on every accepted raw also duplicates the existing frozen raw payload verification in rebuild_source_evidence_snapshot. Keep the existence and size check here; let the raw payload verification cover attachment blob content identity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@polylogue/storage/sqlite/archive_tiers/archive.py` around lines 2178 - 2200,
In _preacquire_attachment_blobs, remove the hashlib.file_digest read and
stored_hash comparison for attachment blobs. Retain the blob_path existence and
size validation, then record the attachment as acquired and continue; rely on
rebuild_source_evidence_snapshot for content-identity verification.

Comment thread polylogue/storage/sqlite/archive_tiers/revision_governance.py
Comment thread polylogue/storage/sqlite/archive_tiers/revision_governance.py Outdated
Comment on lines +600 to +610
def test_active_bootstrap_still_rejects_candidate_durable_symlinks(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
root = tmp_path / "archive"
_prepare_frozen_source(root, monkeypatch)
generation_store = IndexGenerationStore.for_archive_root(root)
generation = generation_store.create(source_snapshot=source_revision_snapshot(root))

with pytest.raises(DurableChangeTrainError, match="unsafe file"):
initialize_active_archive_root(Path(generation.index_path).parent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate initialize_active_archive_root and the refusal it raises.
set -euo pipefail

rg -nP -A 40 'def initialize_active_archive_root\s*\(' polylogue/storage/sqlite/archive_tiers/bootstrap.py
rg -nP -C 3 'DurableChangeTrainError\(' --type=py
rg -nP -C 2 'unsafe file' --type=py

Repository: Sinity/polylogue

Length of output: 2285


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)(bootstrap\.py|test_inactive_candidate_durable_barrier\.py)$' || true

echo
echo "== bootstrap relevant sections =="
wc -l polylogue/storage/sqlite/archive_tiers/bootstrap.py
rg -n -C 6 'candidate|symlink|safe existing|unsafe|PermissionError|DurableChangeTrainError|RuntimeError|has_bootstrap_marker|has_pending_bootstrap|fresh_durable_bootstrap' polylogue/storage/sqlite/archive_tiers/bootstrap.py

echo
echo "== test section =="
wc -l tests/unit/maintenance/test_inactive_candidate_durable_barrier.py
sed -n '540,630p' tests/unit/maintenance/test_inactive_candidate_durable_barrier.py | cat -n

echo
echo "== durable change train error definition/usages =="
rg -n 'class DurableChangeTrainError|DurableChangeTrainError' --type=py .

Repository: Sinity/polylogue

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== test file imports and helper =="
sed -n '1,80p' tests/unit/maintenance/test_inactive_candidate_durable_barrier.py | cat -n
sed -n '1,180p' tests/unit/maintenance/test_inactive_candidate_durable_barrier.py | rg -n -C 4 '_prepare_frozen_source|DurableChangeTrainError|initialize_active_archive_root' || true

echo
echo "== bootstrap import/order around entrypoint =="
sed -n '1,40p' polylogue/storage/sqlite/archive_tiers/bootstrap.py | cat -n
sed -n '230,370p' polylogue/storage/sqlite/archive_tiers/bootstrap.py | cat -n

echo
echo "== deterministic source-text verifier =="
python3 - <<'PY'
from pathlib import Path
import re

boot = Path('polylogue/storage/sqlite/archive_tiers/bootstrap.py').read_text()
test = Path('tests/unit/maintenance/test_inactive_candidate_durable_barrier.py').read_text()

patterns = {
    'test_awaited_type': 'DurableChangeTrainError',
    'test_awaited_message': 'match="unsafe file"',
    'entrypoint': re.compile(r'def initialize_active_archive_root\s*\(', re.S),
    'runtime_unsafe_file_text': re.compile(r'raise RuntimeError\([\s\S]*?safe existing file[\s\S]*?\)', re.S),
    'dCTE_in_bootstrap': re.compile(r'DurableChangeTrainError', re.S),
    'match_expr_for_test': re.compile(r'pytest\.raises\s*\(\s*([A-Za-z0-9_]+)[\s\S]*?match=([^,\)]+)', re.S),
}

print("bootstrap contains DurableChangeTrainError:", bool(patterns['dCTE_in_bootstrap'].search(boot)))
print("bootstrap entrypoint found:", bool(patterns['entrypoint'].search(boot)))
print("runtime unsafe existing file raise found:", bool(patterns['runtime_unsafe_file_text'].search(boot)))
print("test imported/annotated exception:", patterns['test_awaited_type'] in test)
print("test contains unsafe file matcher:", patterns['test_awaited_message'] in test)
m = patterns['match_expr_for_test'].search(test)
print("parsed pytest.raises exception/message:", (m.group(1).strip(), m.group(2).strip()) if m else None)

# Check control flow path: is archive tier spec for one durable tier, and does code call initialize_archive_database after link check.
if any(t in test for t in ['source.db']) or any(line in Path('tests/unit/maintenance/test_inactive_candidate_durable_barrier.py').read_text() for line in ['source.db', '.durable']):
    print("test likely creates durable archive files.")
PY

echo
echo "== targeted test helper =="
python3 - <<'PY'
from pathlib import Path
text = Path('tests/unit/maintenance/test_inactive_candidate_durable_barrier.py').read_text()
start = text.index('def _prepare_frozen_source')
next_def = re.search(r'\ndef \w', text[start+1:])
if next_def:
    print(text[start:start+next_def.start()+1])
else:
    print(text[start:])
PY

Repository: Sinity/polylogue

Length of output: 15700


Raise the asserted exception for unsafe durable bootstrap files.

initialize_active_archive_root() validates each archive tier file through initialize_archive_database(), which raises RuntimeError("durable tier is not a safe existing file; refusing runtime initialization: ...") for symlinks/non-files. Update the bootstrap check to raise DurableChangeTrainError with a message matching "unsafe file" so this test can catch the existing candidate durable symlink case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/maintenance/test_inactive_candidate_durable_barrier.py` around
lines 600 - 610, Update initialize_active_archive_root and its durable-file
validation path through initialize_archive_database so unsafe tier files such as
symlinks or non-files raise DurableChangeTrainError instead of RuntimeError,
using an error message that matches “unsafe file” and preserving the existing
rejection behavior.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6bf48c08eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread polylogue/maintenance/rebuild_index.py Outdated
Comment on lines +1475 to +1478
validate_frozen_source_authority(
root,
active_index_path=canonical_active_index_path(owned.location),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate only the bounded rebuild page on resume

For a resumable full-source rebuild containing more raws than raw_batch_size, this call omits selected_raw_ids, so validate_frozen_source_authority() reaches _load_frozen_revision_evidence() and reparses every parseable raw before next_raw_page() selects the bounded page. The daemon path performs the same archive-wide validation during transaction resolution and then repeats it here, meaning each page can parse the entire corpus twice and an N-row rebuild with B-row pages approaches O(N²/B) parsing; on the multi-GiB archives this path targets, bounded passes can become impractical. Perform the full admission once and validate only the selected page/cohort on resumed passes, or persist reusable validation evidence.

Useful? React with 👍 / 👎.

Comment thread polylogue/sources/revision_backfill.py Outdated
Comment on lines +1140 to +1142
c.raw_id IS NULL OR c.status != 'complete'
OR m.raw_id IS NULL OR m.decision IS NULL
OR m.decision IN ('ambiguous', 'deferred')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Treat terminal non-session census as resolved

When phase 2 parses a retained raw into zero sessions, replace_raw_membership_census() records status='non_session' with no membership rows, and _record_raw_authority_parser_census() explicitly treats that status as complete. If the historical raw remains revision_authority='quarantined', this predicate nevertheless flags it because the status is not complete and m.raw_id is null. A legitimate settings/config/sidecar artifact therefore makes every full candidate rebuild fail permanently with FrozenSourceRemediationRequiredError, even though no membership can or should be created for it; exclude terminal non_session census rows from unresolved authority debt.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/rebuild_index.py Outdated
Comment on lines +139 to +142
validate_frozen_source_authority(
root,
active_index_path=canonical_active_index_path(location),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Allow frozen admission when the active index is absent

When rebuilding after a lost or explicitly reset index.db, canonical_active_index_path(location) names a path that does not exist, but frozen admission passes it to ArchiveStore.open_frozen_source_validation(), which opens the index with SQLite mode=ro before any candidate is allocated. The rebuild therefore fails with an open error instead of recreating the derived tier, despite missing_index_raw_ids() and rebuild_schema_currency_preflight() explicitly treating a missing index as a supported rebuild case. Frozen source validation must use a source-only path or tolerate an absent/corrupt active read model rather than requiring the tier being rebuilt.

AGENTS.md reference: AGENTS.md:L193-L201

Useful? React with 👍 / 👎.

Sinity added 4 commits August 8, 2026 13:41
Close read-only source census connections explicitly and document the intentional Provider-to-Origin normalization boundary. Expose frozen candidate mode through the governance protocol and stop source-tier finalization work once frozen authority has already been proven.

Focused candidate, provenance, and revision-backfill verification passes with 130 tests.
Declare the Drive revision adapter as a normal mutable governance host so the frozen-candidate protocol remains explicit and structurally type-safe.
Validate the full frozen source once when a resumable transaction is created, then revalidate only each selected authority component on resumed pages. Treat terminal non-session census rows as resolved and let source-only admission operate when the derived index is missing or unreadable.

Regression coverage proves bounded resume, non-session authority, and missing-index recovery. The 133-test candidate/provenance/revision suite passes. The daemon suite has seven passes plus two failures already present in the exact current-master baseline.
Keep the resumed-page validation spy faithful to the production validator signature so strict type checking covers the regression harness.
@Sinity
Sinity merged commit e6228af into master Aug 8, 2026
3 checks passed
@Sinity
Sinity deleted the feature/fix/inactive-candidate-durable-barrier branch August 8, 2026 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant