Skip to content

feat(maintenance): add immutable live-proof receipts - #3879

Merged
Sinity merged 12 commits into
masterfrom
feature/maintenance/live-proof-protocol
Aug 7, 2026
Merged

feat(maintenance): add immutable live-proof receipts#3879
Sinity merged 12 commits into
masterfrom
feature/maintenance/live-proof-protocol

Conversation

@Sinity

@Sinity Sinity commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

Add a fixed, read-only live-proof receipt protocol for the reindex campaign. The protocol emits immutable evidence for archive, inactive-candidate, and existing-apply-receipt routes and exposes validators for aggregate, candidate-acceptance, and final-proof consumers.

Problem

The phase graph requires source and candidate evidence to be bound to the exact code, archive, schema, parser, lowering, and generation state that produced it. The existing maintenance surfaces did not provide one typed receipt contract with fail-closed stale-evidence checks, private-path redaction, or a CLI route that could not execute archive mutations.

Solution

Add polylogue.maintenance.live_proof with a closed registry, immutable self-hashed receipts, current-binding validation, candidate generation checks, typed residuals, and consumer validators. Add polylogue ops maintenance live-proof, which accepts only registered proof ids and writes a new receipt outside archive-owned storage with exclusive creation, including externally pointed active-generation and rebuild roots. The binding includes all six archive-tier schema versions, including ops.db and audit.db. Normalize the archive root once before binding and verification so path aliases cannot produce mismatched evidence. Validate the aggregate contract for not_applicable residues directly.

Verification

  • direnv exec . devtools test tests/unit/maintenance/test_live_proof.py tests/unit/cli/test_maintenance_live_proof_cli.py: 41 passed.
  • direnv exec . devtools verify --quick: all 24 steps exited 0 at 02e3ac986884b7123aebf5b40af635fc62f1421a.
  • Mutation coverage changes source bindings and mutates the candidate through SQLite after receipt creation; both are rejected by the validators. CLI coverage also rejects receipt output under externally pointed active-generation and rebuild roots. The aggregate test accepts typed not_applicable residue and rejects the same status without its residue. The alias test proves symlinked archive roots bind to the canonical private path reference. Freshness, tier-content, WAL, ready-transaction, path-redaction, and failure-atomicity regressions are included.

Scope disposition

The implementation is partial because the phase-specific source-remediation, candidate-acceptance, and final-proof orchestration consumers do not yet exist on the current master. The typed validator seams are present and the remaining wiring is carried by polylogue-live-operation-receipts. No production archive mutation, candidate generation, promotion, restart, or postflight proof was performed.

Ref polylogue-x97cf.

Summary by CodeRabbit

  • New Features

    • Added the read-only maintenance live-proof command for generating tamper-evident evidence receipts.
    • Supports archive verification, candidate-generation verification, and existing apply-receipt validation.
    • Displays receipts as formatted JSON and prevents unsupported or unsafe input combinations.
    • Detects stale, altered, malformed, blocked, or unsuccessful evidence.
  • Documentation

    • Documented proof routes, required options, receipt contents, and validation behavior.

Add the fixed read-only, candidate, and existing-apply receipt protocol for
the reindex campaign. Receipts bind current archive, semantic, candidate,
and input evidence while consumer seams reject stale or failed proof results.

The maintenance command is deliberately evidence-only: it accepts only static
proof ids and writes a new receipt outside the archive.

Ref polylogue-x97cf.

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

Sinity commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

@codex review
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Sinity, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c8813503-cb32-4cbe-b473-f2918cabf266

📥 Commits

Reviewing files that changed from the base of the PR and between 1fbd133 and 02e3ac9.

📒 Files selected for processing (4)
  • docs/maintenance.md
  • polylogue/maintenance/live_proof.py
  • tests/unit/cli/test_maintenance_live_proof_cli.py
  • tests/unit/maintenance/test_live_proof.py
📝 Walkthrough

Walkthrough

The change adds a read-only maintenance live-proof command and receipt protocol. It supports archive verification, candidate-generation verification, and existing apply-receipt validation with self-hashed, binding-aware receipts.

Changes

Live-proof evidence

Layer / File(s) Summary
Proof contracts and registry
polylogue/maintenance/live_proof.py, tests/unit/maintenance/test_live_proof.py
Defines proof modes, identifiers, statuses, residues, immutable receipt models, serialization, and the fixed three-route registry.
Binding capture and proof collection
polylogue/maintenance/live_proof.py, tests/unit/maintenance/test_live_proof.py
Captures archive, schema, parser, lowering, candidate, and private-path bindings. It collects route-specific evidence and validates existing apply receipts.
Receipt validation and persistence
polylogue/maintenance/live_proof.py, tests/unit/maintenance/test_live_proof.py
Validates hashes, bindings, statuses, residues, digests, and aggregate completeness. Receipt files use exclusive durable creation.
Maintenance command integration
polylogue/cli/commands/maintenance/*, tests/unit/cli/test_maintenance_live_proof_cli.py, docs/maintenance.md
Registers the command, validates inputs and output paths, writes formatted JSON receipts, tests dispatch and failure handling, and documents the routes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant live_proof_command
  participant collect_live_proof
  participant Archive
  participant ReceiptFile

  Operator->>live_proof_command: invoke maintenance live-proof
  live_proof_command->>collect_live_proof: pass proof route and inputs
  collect_live_proof->>Archive: capture bindings and run read-only checks
  Archive-->>collect_live_proof: return proof evidence
  collect_live_proof-->>live_proof_command: return self-hashed receipt
  live_proof_command->>ReceiptFile: write receipt as JSON
  ReceiptFile-->>Operator: print formatted receipt
Loading

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • Sinity/polylogue#3850: Adds related validation of topology and candidate evidence against live-proof receipts.
  • Sinity/polylogue#3862: Strengthens rebuild and apply receipt evidence that this protocol validates.
  • Sinity/polylogue#2902: Introduces the maintenance command registration structure extended by this change.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 identifies the main change: adding immutable live-proof receipts.
Description check ✅ Passed The description is mostly complete and covers the problem, solution, verification, scope disposition, and successor work; it omits an explicit Changelog entry.
✨ 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/maintenance/live-proof-protocol

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 commented Aug 7, 2026

Copy link
Copy Markdown

@Sinity I will review the changes in #3879.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: dec47d1350

ℹ️ 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/live_proof.py Outdated
Comment on lines +422 to +423
apply_result, digest = _validated_existing_apply_receipt(apply_receipt_path, bindings)
result = {"status": LiveProofStatus.PASSED.value, "apply_receipt": apply_result}

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 Reject unsuccessful existing-apply receipts

When a binding-current, self-hashed apply receipt reports a failed or blocked operation—or even has an empty result—this branch unconditionally wraps it with status: passed. Because the aggregate validators inspect only that outer status, an operation that never succeeded becomes acceptable terminal evidence; validate the operation-specific result and propagate unsuccessful outcomes instead.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +278 to +282
metadata.get("generation_id") != generation_id
or metadata.get("state") != "inactive"
or metadata.get("archive_root") != str(root)
or metadata.get("index_path") != str(index_path)
or not index_path.is_file()

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 Match candidates to their recorded source snapshot

If the durable source changes after an inactive candidate was created, generation.json still names the source snapshot from which that candidate was built, but this validation never compares that field with the current snapshot later placed in the receipt bindings. The command can therefore issue and subsequently validate a receipt that pairs an old candidate with the new source state, especially for semantic metadata changes that corpus-absences cannot detect; require the generation's recorded source_snapshot to equal the captured snapshot.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +324 to +325
candidate_generation_id=candidate_generation_id,
candidate_index_sha256=hash_file(candidate_index) if candidate_index is not None else None,

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 Bind active proofs to active index content

For the active read-only route, candidate_index_sha256 is always None, while the archive identity digest identifies the index file by inode rather than contents. If the daemon ingests or converges more data after collection, the same index.db inode and schema remain valid, so validation accepts stale count/check evidence without noticing the changed parsed tree; capture a coherent logical index snapshot or content binding for active proofs as well.

AGENTS.md reference: AGENTS.md:L163-L169

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +388 to +391
if not is_json_document(payload.get("result")):
raise LiveProofError("existing apply receipt result is malformed")
_validate_private_path_references(receipt_bindings.get("private_paths"))
return require_json_document(payload["result"], context="existing apply receipt result"), digest

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 Redact paths copied from apply results

When an existing apply receipt's result contains a normal absolute artifact path, such as a backup, manifest, or quarantine location, this returns the result unchanged and it is embedded in the durable live-proof receipt. Only bindings.private_paths is validated, so paths elsewhere in the result bypass the advertised digest-plus-basename policy; recursively reject or redact private paths before returning the result.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +268 to +269
generation_root = root / ".index-generations" / generation_id
metadata_path = generation_root / "generation.json"

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 Resolve candidates from the canonical generation store

For archives whose configured index lives outside the archive root, IndexGenerationStore places .index-generations beside the canonical active-index pointer, but this code always searches under root/.index-generations. Consequently a valid inactive generation in a supported split-root or symlink topology is reported as unavailable; derive the generation root through ArchiveLocation/IndexGenerationStore rather than hard-coding it beneath the archive root.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +245 to +250
paths = {
"source": root / "source.db",
"index": candidate_index or root / "index.db",
"embeddings": root / "embeddings.db",
"user": root / "user.db",
}

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 Bind the ops-tier schema version

When ops.db is recreated or its schema version changes after receipt creation, validation still accepts the old receipt because this schema binding enumerates only four tiers. The archive-verification tier-schema check does inspect the ops tier, so omitting it allows an old successful result to remain current after one of its checked schemas changed; derive these bindings from the canonical tier registry and include ops.db.

AGENTS.md reference: AGENTS.md:L114-L122

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +573 to +577
with os.fdopen(descriptor, "wb", closefd=False) as stream:
stream.write(encoded)
stream.flush()
os.fsync(stream.fileno())
finally:

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 Make exclusive receipt writes failure-atomic

If writing or fsyncing the receipt fails—for example because of ENOSPC or process interruption—the final path has already been created and can contain truncated JSON. A retry then hits O_EXCL and refuses to replace that corrupt artifact, leaving the operator unable to produce the promised immutable receipt at that path; write and fsync a temporary sibling, then publish it atomically with exclusive-create semantics.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +492 to +494
parsed_residues.append(
LiveProofResidue(LiveProofResidueKind(cast(str, residue["kind"])), cast(str, residue["code"]))
)

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 Validate residue codes as strings

When a self-hashed receipt contains a residue whose code is a JSON object, list, number, or null, cast(str, ...) performs no runtime validation and this parser accepts it into a supposedly typed LiveProofResidue. A malformed not_applicable residue can consequently pass _require_acceptable_result; explicitly require a nonempty string code before constructing the residue.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +481 to +486
if not is_json_document(result) or not isinstance(residues, list) or not isinstance(input_digests, list):
raise LiveProofError("live-proof receipt evidence is malformed")
try:
LiveProofStatus(cast(str, result["status"]))
except (KeyError, TypeError, ValueError) as exc:
raise LiveProofError("live-proof receipt result status is malformed") from exc

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 Require route-specific evidence before validation

A self-hashed receipt with current bindings, proof_id: archive-verification, and only result: {"status":"passed"} is accepted here even though it contains no archive-verification result and need not have run any registered check. The same generic validation permits mode-incompatible input-digest and evidence shapes, so aggregates cannot distinguish a producer-issued proof from a structurally incomplete document; validate the exact result, residue, and input-digest schema declared by each LiveProofSpec.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +518 to +522
if status is LiveProofStatus.PASSED:
return
if status is LiveProofStatus.NOT_APPLICABLE and any(
residue.kind is LiveProofResidueKind.NOT_APPLICABLE for residue in receipt.residues
):

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 Reject passed receipts with blocking residues

When a receipt claims passed, this returns without checking whether its typed residues include blocked, unverified, or check_failed; similarly, not_applicable is accepted as soon as any one residue has that kind even if another residue is blocking. Such internally contradictory evidence therefore passes every aggregate boundary, so acceptance must enforce status/residue consistency and reject any blocking or unverified residue.

Useful? React with 👍 / 👎.

Problem: live-proof bindings omitted the canonical ops and audit tiers, so a receipt could remain valid while one of those schema files changed or was absent.\n\nWhat changed: include all six archive-tier schema versions in the binding and document the complete contract. Add a regression assertion for the tier vocabulary.\n\nCompatibility/migration: receipts produced before this change are stale because the binding shape is intentionally stronger.
@Sinity

Sinity commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: 7ec146b783

ℹ️ 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/live_proof.py Outdated
Comment on lines +231 to +234
repository = Path(__file__).resolve().parents[2]
completed = subprocess.run(
("git", "-C", str(repository), "rev-parse", "--verify", "HEAD"),
check=False,

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 Reject proofs collected from dirty Git worktrees

When collection runs from a checkout with tracked modifications, git rev-parse HEAD still returns the clean commit SHA, so the receipt claims that commit even though modified collector or verification code produced its evidence; a later clean checkout at the same HEAD then accepts the false binding. Refuse dirty worktrees or bind the receipt to a digest of the code actually executed.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
def validate_final_proof_receipts(receipts: Sequence[object], archive_root: Path) -> tuple[LiveProofReceipt, ...]:
"""Consumer seam for final-proof aggregation, without emitting a terminal proof."""

return validate_live_operation_aggregate(receipts, archive_root)

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 Require complete proof coverage at final aggregation

When a final consumer supplies only one otherwise valid receipt, this delegates to validate_live_operation_aggregate, whose only set-level requirement is that the sequence be nonempty. A lone archive-verification receipt therefore validates while candidate or apply-route evidence is omitted entirely; require the expected unique proof IDs, using typed not-applicable evidence for routes that legitimately do not apply.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
bead_id="polylogue-x97cf",
mode=LiveProofMode.CANDIDATE,
producer="archive_verification",
archive_checks=("corpus-absences",),

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 Run the canonical candidate acceptance profiles

When replay has populated every session but terminal derived-state work has not finished, corpus-absences can pass even though FTS, action pairs, delegation facts, planner statistics, or other required candidate invariants remain incomplete. The production rebuild runs the full REINDEX_ACCEPTANCE_CHECKS and REINDEX_CROSS_TIER_ACCEPTANCE_CHECKS profiles before promotion, but this advertised candidate-acceptance receipt runs only one cross-tier check and is then accepted by validate_candidate_proof_receipts; derive this route from the canonical profiles and apply strict acceptance.

Useful? React with 👍 / 👎.

or metadata.get("state") != "inactive"
or metadata.get("archive_root") != str(root)
or metadata.get("index_path") != str(index_path)
or not index_path.is_file()

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 Reject symlinked candidate index files

When a corrupted or replaced generation contains index.db as a symlink, is_file() follows that link, so metadata that names the lexical candidate path still passes even if the link targets the active index or another generation. Collection and validation then hash and verify the target while issuing a receipt for the named inactive candidate; require a regular non-symlink file whose resolved path is owned by the generation and is distinct from the active index.

Useful? React with 👍 / 👎.

Comment on lines +40 to +44
target.relative_to(root)
except ValueError:
pass
else:
raise click.BadParameter("receipt output must be outside the archive root", param_hint="--output")

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 Protect external generation roots from receipt output

When .index-active-pointer selects an index generation outside the configured archive root, this containment check protects only root; an output path inside the external active generation or its .index-generations store is therefore accepted. The supposedly read-only command then mutates archive-owned storage, and the receipt can disappear when that generation is retired or reclaimed; derive every protected archive path from ArchiveLocation/IndexGenerationStore, not just the configured root.

AGENTS.md reference: AGENTS.md:L163-L166

Useful? React with 👍 / 👎.

Problem: the existing-apply live-proof route wrapped any input result as a passed proof, including blocked, failed, or unknown mutation outcomes.\n\nWhat changed: accept only applied and already_satisfied input outcomes and add a regression test for unknown results.\n\nCompatibility/migration: previously collected live-proof receipts remain bound to their original protocol version; new existing-apply inputs fail closed unless their mutation succeeded.
@Sinity

Sinity commented Aug 7, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: 12

🤖 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/cli/commands/maintenance/_live_proof.py`:
- Around line 52-54: Update the exception handler around
write_live_proof_receipt to also catch OSError alongside LiveProofError,
translating both into click.ClickException while preserving exception chaining
and the existing error message behavior.

In `@polylogue/maintenance/live_proof.py`:
- Around line 566-584: Update write_live_proof_receipt to fsync the parent
directory after the receipt file is successfully written and closed, using
target.parent and an appropriate directory descriptor. Preserve exclusive
creation and ensure the directory descriptor is closed reliably, including when
writing or syncing the file fails.
- Around line 256-263: Unescaped filesystem paths are used in SQLite read-only
URI connections. Add a shared _read_only_uri helper that percent-encodes paths
safely, then use it in _schema_versions at
polylogue/maintenance/live_proof.py:256-263 and capture_live_proof_bindings at
polylogue/maintenance/live_proof.py:307-308, replacing both raw
f"file:{...}?mode=ro" constructions.
- Around line 338-341: Update collect_live_proof to resolve archive_root once
before invoking capture_live_proof_bindings, _candidate_index, or
verify_archive. Reuse that resolved path for candidate binding validation and
verification so relative, home-relative, and symlinked roots match the captured
metadata.
- Around line 574-584: Update the receipt-writing flow around os.open and the
descriptor-backed stream: translate all OSError failures from os.open, while
preserving the existing LiveProofError for FileExistsError, into LiveProofError
so the CLI’s existing handler can present a ClickException. Track
write/flush/fsync success and, when any stream operation fails, close the
descriptor and unlink target before propagating the translated error; preserve
the completed receipt and existing cleanup behavior on success.
- Around line 233-238: Update the subprocess.run call used for the git rev-parse
verification to include an explicit timeout, and catch subprocess.TimeoutExpired
in the surrounding live-proof flow, converting it into LiveProofError. Preserve
the existing successful-result handling and fixed command arguments.

In `@tests/unit/maintenance/test_live_proof.py`:
- Around line 171-182: Add direct unit coverage for _require_acceptable_result
through validate_live_operation_aggregate: verify a self-hashed not_applicable
result is accepted when a residue with kind not_applicable is present, and
raises LiveProofError with “not acceptable” when that residue is absent. Reuse
the existing receipt construction and hashing pattern from
test_aggregate_rejects_a_self_hashed_failed_proof_result.
- Around line 164-168: Update the test mutation around
validate_candidate_proof_receipts to modify the candidate index through a
supported SQLite connection and UPDATE statement rather than appending raw bytes
to index.db. Preserve the intended digest change while ensuring
capture_live_proof_bindings and _schema_versions can read the database, so the
assertion continues to target the “bindings are stale” LiveProofError.
- Line 80: Replace the non-callability assertion for spec.producer in the
LIVE_PROOF_SPECS test with an assertion that every producer matches one of the
allowed literal values: "archive_verification" or "existing_apply_receipt".
- Around line 35-40: Update the archive_root fixture to resolve the temporary
archive path once before initializing and returning it, ensuring the path
recorded by _candidate matches the resolved root used by
capture_live_proof_bindings and _candidate_index.
- Around line 140-151: Extend
test_existing_apply_receipt_rejects_non_successful_result to verify both
statuses in _ACCEPTED_APPLY_STATUSES: retain the existing applied-success
coverage and add an already_satisfied receipt that collect_live_proof accepts
without raising. Keep the unknown-status case asserting LiveProofError with the
existing failure message.
- Around line 57-58: Update the candidate index setup in the live-proof test to
preserve WAL-backed state: stop active writers and checkpoint the database
before copying index.db, or consistently copy index.db together with its -wal
and -shm sidecars. Ensure the candidate digest fully represents the database
state.
🪄 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: c36160aa-ac53-4113-b9b2-19cfeb4d2fd4

📥 Commits

Reviewing files that changed from the base of the PR and between 6ce0f92 and ecdc196.

📒 Files selected for processing (6)
  • docs/maintenance.md
  • polylogue/cli/commands/maintenance/__init__.py
  • polylogue/cli/commands/maintenance/_live_proof.py
  • polylogue/maintenance/live_proof.py
  • tests/unit/cli/test_maintenance_live_proof_cli.py
  • tests/unit/maintenance/test_live_proof.py

Comment thread polylogue/cli/commands/maintenance/_live_proof.py
Comment thread polylogue/maintenance/live_proof.py Outdated
Comment thread polylogue/maintenance/live_proof.py Outdated
Comment thread polylogue/maintenance/live_proof.py Outdated
Comment thread polylogue/maintenance/live_proof.py
Comment thread tests/unit/maintenance/test_live_proof.py Outdated
Comment thread tests/unit/maintenance/test_live_proof.py Outdated
Comment thread tests/unit/maintenance/test_live_proof.py Outdated
Comment thread tests/unit/maintenance/test_live_proof.py Outdated
Comment thread tests/unit/maintenance/test_live_proof.py Outdated

@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: ecdc1966a0

ℹ️ 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/live_proof.py Outdated
parser_fingerprints=parser_fingerprints,
lowering_fingerprint=lowering,
candidate_generation_id=candidate_generation_id,
candidate_index_sha256=hash_file(candidate_index) if candidate_index is not None else None,

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 Bind candidate receipts to WAL-visible content

When an inactive candidate has committed but uncheckpointed WAL frames—normal while a rebuild writer is active—verify_archive reads the logical database including those frames, but this binding hashes only index.db. Further commits can append to index.db-wal without changing that hash, so validation can accept a receipt whose corpus evidence predates the candidate's current contents. Require a quiescent completed generation with a clean checkpoint, or bind a coherent SQLite snapshot including WAL-visible state.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
def validate_live_operation_aggregate(receipts: Sequence[object], archive_root: Path) -> tuple[LiveProofReceipt, ...]:
"""Consumer seam for the live-operation aggregate, without scheduling work."""

validated = tuple(validate_live_proof_receipt(receipt, archive_root) for receipt in receipts)

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 Capture one binding snapshot for the whole aggregate

When the daemon ingests between these sequential validations, an older receipt can match the pre-ingest snapshot and a newer receipt can match the post-ingest snapshot, so both are accepted even though the returned aggregate combines evidence for different source states. Capture one coherent set of current bindings for the aggregate and require every receipt's shared bindings to match it rather than recapturing independently per item.

AGENTS.md reference: AGENTS.md:L163-L166

Useful? React with 👍 / 👎.

Problem: The live-proof protocol accepted incomplete archive evidence and could bind receipts to stale code, candidate metadata, or mutable SQLite files. Existing apply results also lost typed terminal status when converted into proof outcomes.\n\nWhat changed: Bind installed or clean-checkout code identity, canonical archive and generation paths, source snapshots, all tier schemas, active and candidate SQLite file sets, complete verification profiles, status/residue pairs, route coverage, and embedded apply evidence. Add atomic exclusive receipt publication and CLI write-error handling. Extend focused route tests and documentation.\n\nAlternatives rejected: The protocol remains a read-only evidence collector. It does not acquire, repair, promote, restart, or schedule archive work.\n\nCompatibility/migration: Existing receipts are intentionally stale unless they match the stricter bindings. No production archive was mutated.

Ref polylogue-x97cf

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

@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: 93794158d1

ℹ️ 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 on lines +475 to +477
active_index_sha256 = _sqlite_file_set_digest(location.active_index_path, allow_symlink=True)
candidate_index_sha256 = _sqlite_file_set_digest(candidate_index) if candidate_index is not None else None
schema_versions = _schema_versions(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 Bind every verified tier's contents

When a completed write changes user.db assertions, embedding references, or ops cursor/debt rows in place after collection, the inode-based archive identity and schema versions remain unchanged, as do the source snapshot and active-index digest. Validation therefore accepts the old receipt even though registered checks such as user-tier-refs, embeddings-refs-liveness, and convergence-freshness would now produce different evidence or fail; capture coherent content digests for every tier read by the selected profiles.

AGENTS.md reference: AGENTS.md:L120-L122

Useful? React with 👍 / 👎.

return value
raise LiveProofError("archive verification emitted non-JSON evidence")

evidence = {key: value for key, value in document.items() if key not in {"archive_root", "generated_at"}}

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 Expire time-sensitive verification evidence

When an ingest cursor is behind but was updated just under the one-hour stall threshold, stalled-append-cursor-freshness reports ok; after the threshold passes with no database changes, the canonical check would report a warning. Removing generated_at and enforcing no age limit lets the earlier passed receipt retain identical bindings and remain acceptable indefinitely, so preserve collection time and reject or refresh receipts when a time-dependent predicate can have changed.

Useful? React with 👍 / 👎.

Problem: the live-proof CLI rejected receipt paths inside the configured archive root but accepted paths inside an externally pointed active generation or rebuild directory. Candidate proof mutation tests also changed SQLite bytes outside a supported database operation.\n\nWhat changed: move archive-owned root discovery into the maintenance layer, reject receipt paths under configured and externally selected lifecycle roots, replace raw candidate-byte mutation with a SQLite schema mutation, and add CLI coverage for an external active generation.\n\nCompatibility/migration: receipt output remains allowed in unrelated private directories. No archive files are mutated by the proof command.\n\nRef polylogue-x97cf\n\nCo-Authored-By: Codex <noreply@openai.com>

@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: 5b8d02e6c8

ℹ️ 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/live_proof.py Outdated
Comment on lines +313 to +315
if any(
path.with_name(path.name + "-wal").exists() or path.with_name(path.name + "-wal").is_symlink() for path in paths
):

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 Permit checkpointed WAL files during live collection

When polylogued has opened any archive tier, SQLite's normal WAL mode leaves a *-wal file present even when all frames are checkpointed, but this predicate rejects the file solely for existing (including zero-length WALs that _sqlite_file_state explicitly treats as absent). Because daemon connections are cached and configured for WAL, the advertised live-proof command cannot collect evidence from the normal running-daemon environment; distinguish nonempty/uncheckpointed WAL content from a harmless quiescent sidecar or acquire a coherent read snapshot instead.

AGENTS.md reference: AGENTS.md:L163-L166

Useful? React with 👍 / 👎.

Comment on lines +456 to +458
generation.generation_id != generation_id
or generation.state != "inactive"
or Path(generation.archive_root).resolve() != location.configured_root.resolve()

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 Require a ready rebuild transaction for candidate proofs

When a rebuild is running, paused, deferred, or has crashed before its terminal stages, its generation remains inactive; readiness is recorded separately by IndexRebuildTransaction.status == "ready". Accepting every inactive generation therefore allows a candidate proof to attest an in-progress snapshot, and bulk rebuilds use a non-WAL write profile so the WAL guard does not reliably exclude this case. Resolve the owning transaction and require its ready/completed state before treating the generation as a proof candidate.

Useful? React with 👍 / 👎.

root = archive_root().resolve()
target = output.expanduser().resolve()
try:
owned_roots = archive_owned_storage_roots(root)

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 Translate invalid archive pointers into a CLI error

When .index-active-pointer is malformed, ArchiveLocation.resolve() raises ArchiveLocationError, which is a RuntimeError; this call occurs inside a handler that catches only LiveProofError and OSError. The maintenance command therefore escapes through Click with an uncaught exception instead of reporting a normal actionable failure, even though the later binding collector already normalizes archive-location failures. Catch and translate ArchiveLocationError here or have archive_owned_storage_roots() wrap it as LiveProofError.

Useful? React with 👍 / 👎.

Problem: live-proof collection accepted path aliases without normalizing the archive root once, and the aggregate path lacked a direct not-applicable residue test.

What changed: normalize the archive root before binding and verification, and add an aggregate test proving not-applicable status requires its typed residue.

Verification: devtools test tests/unit/maintenance/test_live_proof.py tests/unit/cli/test_maintenance_live_proof_cli.py; devtools verify --quick

Co-Authored-By: Claude <noreply@anthropic.com>

@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: 6068926946

ℹ️ 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 on lines +1029 to +1031
_fsync_directory(target.parent)
temporary.unlink()
temporary = None

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 Remove the published target when directory sync fails

If either directory fsync fails after os.link publishes the receipt—for example because the filesystem reports an I/O error—the exception cleanup removes only the temporary name and leaves target in place. The command then reports that the write failed, but a retry is permanently rejected because the supposedly failed output already exists; track whether publication succeeded and unlink the target on this failure path.

Useful? React with 👍 / 👎.

Comment on lines +538 to +540
if _ABSOLUTE_PATH_RE.search(value):
return f"[private-text:{hash_text(value)}]"
return value

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 Redact colon-prefixed absolute paths

When archive evidence embeds an absolute path immediately after a colon, this regex deliberately fails to recognize it. The raw-quarantine-group-dedup check emits exactly this shape as group:{group.source_path} in details, so a quarantined group whose source path is /home/... persists that private path verbatim as group:/home/... in the durable receipt despite the documented redaction guarantee.

Useful? React with 👍 / 👎.

Sinity and others added 3 commits August 7, 2026 15:33
Problem: the live-proof root-normalization fix lacked a regression test for a symlinked archive path.

What changed: collect a read-only receipt through an archive-root alias and compare its private binding with the canonical root reference.

Verification: devtools test tests/unit/maintenance/test_live_proof.py tests/unit/cli/test_maintenance_live_proof_cli.py; devtools verify --quick

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: the archive-root alias regression covered capture bindings but did not exercise validation through the same alias.\n\nWhat changed: validate the captured receipt with the symlinked archive root so the capture and verification paths must normalize to the same canonical root.\n\nVerification: devtools test tests/unit/maintenance/test_live_proof.py tests/unit/cli/test_maintenance_live_proof_cli.py passed with 32 tests.\n\nRef polylogue-x97cf.\n\nCo-Authored-By: Codex <noreply@openai.com>
Problem: the archive-root alias test did not assert that the alias itself stayed out of serialized evidence.\n\nWhat changed: require the live-proof document to redact the symlink alias in addition to validating through that alias.\n\nVerification: devtools test tests/unit/maintenance/test_live_proof.py tests/unit/cli/test_maintenance_live_proof_cli.py passed with 32 tests.\n\nRef polylogue-x97cf.\n\nCo-Authored-By: Codex <noreply@openai.com>

@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: 5

🤖 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/maintenance/live_proof.py`:
- Around line 637-639: Update the error message raised by the apply-status
validation in the live proof receipt handling to state that the status is not
recognized, rather than claiming it is not successful; update the corresponding
assertion in test_live_proof.py to expect the new wording.
- Around line 394-417: Close every SQLite connection explicitly in
_sqlite_file_set_digest, _schema_version, _schema_versions, and
capture_live_proof_bindings instead of relying on the connection context
manager; wrap each _open_readonly(path) result with contextlib.closing or close
it in finally, while preserving the existing transaction and read behavior.
- Around line 304-316: Update _require_quiescent_sqlite to reject paths with
either a SQLite -wal or -journal sidecar, checking both regular files and
symlinks. Keep the existing LiveProofError behavior and align the quiescence
gate with the sidecars already represented by _SQLITE_SIDECARS.
- Around line 1028-1044: Update the exception handling around the link operation
so the FileExistsError branch performs the same temporary-file cleanup as the
OSError branch, including unlinking temporary and syncing target.parent when
applicable. Preserve the existing LiveProofError messages and ensure cleanup
failures remain suppressed.

In `@tests/unit/maintenance/test_live_proof.py`:
- Around line 436-444: Extend test_readonly_uri_encodes_sqlite_metacharacters to
use a filename containing a literal percent sequence, such as “%2F”, alongside
the existing “?” and “#” cases. Assert that _readonly_uri preserves the stdlib
Path.as_uri escaping contract by encoding the percent character as “%25”, and
retain the SQLite read-only connection verification.
🪄 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: b1edc049-df08-4965-a4f9-3b52e3ed746b

📥 Commits

Reviewing files that changed from the base of the PR and between ecdc196 and 1fbd133.

📒 Files selected for processing (5)
  • docs/maintenance.md
  • polylogue/cli/commands/maintenance/_live_proof.py
  • polylogue/maintenance/live_proof.py
  • tests/unit/cli/test_maintenance_live_proof_cli.py
  • tests/unit/maintenance/test_live_proof.py

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +304 to +316
def _readonly_uri(path: Path) -> str:
return f"{path.resolve(strict=True).as_uri()}?mode=ro&immutable=1"


def _open_readonly(path: Path) -> sqlite3.Connection:
return sqlite3.connect(_readonly_uri(path), uri=True, timeout=2)


def _require_quiescent_sqlite(paths: Sequence[Path]) -> None:
if any(
path.with_name(path.name + "-wal").exists() or path.with_name(path.name + "-wal").is_symlink() for path in paths
):
raise LiveProofError("live-proof requires a quiescent archive without SQLite WAL files")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject a hot rollback journal, not only a WAL file.

_readonly_uri sets immutable=1. SQLite then assumes the database never changes, skips locking, and does not run hot-journal rollback or WAL recovery. _require_quiescent_sqlite only rejects a -wal file. A -journal file left by an interrupted writer therefore passes the quiescence gate, and every read in _sqlite_file_set_digest, _schema_version, and the raw_sessions query returns the un-rolled-back image. The receipt then binds evidence that does not match the committed database state. _SQLITE_SIDECARS already treats -journal as part of the bound file set, so the gate and the digest disagree.

Check both sidecars in _require_quiescent_sqlite.

🛡️ Proposed fix
 def _require_quiescent_sqlite(paths: Sequence[Path]) -> None:
-    if any(
-        path.with_name(path.name + "-wal").exists() or path.with_name(path.name + "-wal").is_symlink() for path in paths
-    ):
-        raise LiveProofError("live-proof requires a quiescent archive without SQLite WAL files")
+    sidecars = (
+        path.with_name(path.name + suffix) for path in paths for suffix in _SQLITE_SIDECARS
+    )
+    if any(sidecar.exists() or sidecar.is_symlink() for sidecar in sidecars):
+        raise LiveProofError("live-proof requires a quiescent archive without SQLite WAL or journal files")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _readonly_uri(path: Path) -> str:
return f"{path.resolve(strict=True).as_uri()}?mode=ro&immutable=1"
def _open_readonly(path: Path) -> sqlite3.Connection:
return sqlite3.connect(_readonly_uri(path), uri=True, timeout=2)
def _require_quiescent_sqlite(paths: Sequence[Path]) -> None:
if any(
path.with_name(path.name + "-wal").exists() or path.with_name(path.name + "-wal").is_symlink() for path in paths
):
raise LiveProofError("live-proof requires a quiescent archive without SQLite WAL files")
def _readonly_uri(path: Path) -> str:
return f"{path.resolve(strict=True).as_uri()}?mode=ro&immutable=1"
def _open_readonly(path: Path) -> sqlite3.Connection:
return sqlite3.connect(_readonly_uri(path), uri=True, timeout=2)
def _require_quiescent_sqlite(paths: Sequence[Path]) -> None:
sidecars = (
path.with_name(path.name + suffix) for path in paths for suffix in _SQLITE_SIDECARS
)
if any(sidecar.exists() or sidecar.is_symlink() for sidecar in sidecars):
raise LiveProofError("live-proof requires a quiescent archive without SQLite WAL or journal files")
🤖 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/maintenance/live_proof.py` around lines 304 - 316, Update
_require_quiescent_sqlite to reject paths with either a SQLite -wal or -journal
sidecar, checking both regular files and symlinks. Keep the existing
LiveProofError behavior and align the quiescence gate with the sidecars already
represented by _SQLITE_SIDECARS.

Comment thread polylogue/maintenance/live_proof.py
Comment thread polylogue/maintenance/live_proof.py Outdated
Comment thread polylogue/maintenance/live_proof.py
Comment thread tests/unit/maintenance/test_live_proof.py

@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: 1fbd1331b7

ℹ️ 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/live_proof.py Outdated
Comment on lines +771 to +774
name = check.get("name")
status = check.get("status")
if not isinstance(name, str) or not isinstance(status, str):
raise LiveProofError("live-proof receipt archive verification evidence is malformed")

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 complete archive-check evidence

When a failed receipt is rewritten so every canonical check contains only {"name": ..., "status": "ok"} and is then self-hashed again, this validator accepts it and aggregate validation treats it as passing because no summary, count, breakdown, evidence, check class, waiver, or report-level fields are required. Fresh evidence for the earlier route-schema concern is that the new _validate_archive_result implementation validates only these two fields, so require the complete canonical check/report shape or producer-authenticated evidence before accepting the receipt.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +629 to +630
if not isinstance(payload.get("operation_id"), str) or not payload["operation_id"]:
raise LiveProofError("existing apply receipt operation binding is invalid")

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 Bind apply evidence to a registered operation

When an unrelated or invented apply receipt has current bindings and a terminal status, any nonempty operation_id passes here; validate_final_proof_receipts subsequently checks only that the generic existing-apply-receipt proof ID occurs once, so that unrelated receipt can satisfy the campaign's sole apply-evidence slot. Validate the operation ID against a closed route-specific operation/Bead mapping rather than accepting arbitrary text.

Useful? React with 👍 / 👎.

Comment on lines +285 to +290
def _code_sha() -> str:
configured = os.environ.get("POLYLOGUE_CODE_SHA", "").strip().lower()
if configured:
if not _CODE_SHA_RE.fullmatch(configured):
raise LiveProofError("POLYLOGUE_CODE_SHA must be an exact git commit SHA")
return configured

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 Check checkout cleanliness before honoring the SHA override

When POLYLOGUE_CODE_SHA is present while this runs from a Git checkout, the function returns before locating the repository or running git status, so modified collector or verification code is still attested as the configured clean commit. Fresh evidence for the earlier dirty-worktree concern is that the newly added cleanliness guard exists only in the fallback branch; check checkout cleanliness first, or permit this override only when no checkout is present.

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +312 to +316
def _require_quiescent_sqlite(paths: Sequence[Path]) -> None:
if any(
path.with_name(path.name + "-wal").exists() or path.with_name(path.name + "-wal").is_symlink() for path in paths
):
raise LiveProofError("live-proof requires a quiescent archive without SQLite WAL files")

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 Resolve symlinks before checking SQLite sidecars

After a normal index-generation promotion, the canonical active index.db is a symlink, but this checks for index.db-wal beside the symlink instead of beside its resolved target, where SQLite actually places the WAL. The later _sqlite_file_state resolves the symlink and merely hashes that target WAL, while immutable binding reads ignore it, so an actively changing promoted archive bypasses the advertised quiescence guard and can produce evidence from a different logical state than the schema binding; resolve each database path before testing its sidecars.

Useful? React with 👍 / 👎.

Comment on lines +993 to +996
validated = validate_live_operation_aggregate(receipts, archive_root)
proof_ids = [receipt.proof_id for receipt in validated]
if len(proof_ids) != len(LiveProofId) or set(proof_ids) != set(LiveProofId):
raise LiveProofError("final proof requires complete route coverage exactly once")

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 Preserve candidate evidence across promotion

When terminal proof validation runs after the accepted candidate has been promoted, this aggregate validation recaptures the candidate binding through _candidate_index, which requires that generation to remain inactive and distinct from the active index. Promotion makes it active, points the active index at it, and changes the active-index binding, so the required pre-promotion candidate receipt can no longer validate and a replacement candidate receipt cannot be collected; validate that historical evidence through the promotion receipt and retained generation identity instead of requiring a currently inactive candidate.

Useful? React with 👍 / 👎.

Problem: live-proof receipts could outlive time-sensitive checks, omit content changes in verified tiers, reject harmless checkpointed WAL files, attest candidates without a ready rebuild transaction, leak colon-prefixed paths, or leave a published receipt after directory sync failure.\n\nWhat changed: bind all active tier file sets, timestamp and expire receipts, read WAL-visible SQLite state, require the owning rebuild transaction to be ready, tighten path redaction, normalize archive pointer failures at the maintenance boundary, and remove published output on post-publication errors. Add focused regressions for each safety boundary.\n\nVerification: devtools test tests/unit/maintenance/test_live_proof.py tests/unit/cli/test_maintenance_live_proof_cli.py passed with 36 tests. devtools verify --quick passed all 24 steps.\n\nRef polylogue-x97cf.\n\nCo-Authored-By: Codex <noreply@openai.com>

@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: f9089016bb

ℹ️ 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".

commit = (VERSION_INFO.commit or "").lower()
if not _CODE_SHA_RE.fullmatch(commit):
raise LiveProofError("installed package has no exact build commit")
return commit

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 Reject dirty installed builds before returning their commit

For an installed wheel or container built from a dirty tree, this returns only the base commit even though polylogue/version.py:_get_embedded_build_info preserves BUILD_DIRTY in VERSION_INFO.dirty; a clean build of the same commit can therefore validate evidence produced by modified collector or verification code. Fresh evidence beyond the earlier checkout and SHA-override cases is that the installed-package branch has its own explicit dirty bit, so reject dirty builds or include that state in the code identity.

Useful? React with 👍 / 👎.

location = ArchiveLocation.resolve(archive_root)
except RuntimeError as exc:
raise LiveProofError("archive-owned storage roots are unavailable") from exc
bases = {location.configured_root.resolve(), location.active_index_path.parent.resolve()}

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 Resolve the active index before protecting its parent

In the supported pointerless symlink-farm case (root/index.db -> /external/index.db), active_index_path.parent.resolve() resolves the configured /root, not /external, so an output such as /external/index.db-wal passes the CLI containment check; the receipt writer then puts JSON at SQLite's WAL pathname, and reopening the WAL-mode archive can fail with a disk I/O error. Fresh evidence beyond the earlier pointer-generation case is that this occurs specifically when no .index-active-pointer exists; include location.active_index.resolved_path.parent among the protected roots.

AGENTS.md reference: AGENTS.md:L163-L166

Useful? React with 👍 / 👎.

Comment thread polylogue/maintenance/live_proof.py Outdated
Comment on lines +1068 to +1069
except FileExistsError as exc:
raise LiveProofError("live-proof receipt output already exists") from exc

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 Remove the temporary receipt when the target exists

When the requested output already exists, os.link raises only after the temporary sibling has been fully written and fsynced, but this specialized FileExistsError branch bypasses the generic cleanup below. Every refused retry therefore leaves another hidden .target.* receipt in the destination directory; unlink and directory-sync the temporary file before reporting the exclusive-create conflict.

Useful? React with 👍 / 👎.



def _open_readonly(path: Path) -> sqlite3.Connection:
return sqlite3.connect(_readonly_uri(path), uri=True, timeout=2)

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 Avoid creating SQLite sidecars during proof collection

When a quiescent WAL-mode tier has no -wal or -shm files, opening it with SQLite mode=ro can still create and retain a zero-length WAL plus a 32 KiB SHM file when the directory is writable. _sqlite_file_state normalizes the new empty WAL as absent and does not track SHM, so collection succeeds while this advertised read-only command has mutated every affected archive directory; use the existing immutable read path for a verified sidecar-free snapshot or otherwise prevent sidecar creation.

AGENTS.md reference: AGENTS.md:L163-L166

Useful? React with 👍 / 👎.

else None
)
identity = ArchiveIdentity.resolve_location(location)
active_index_sha256 = _sqlite_file_set_digest(location.active_index_path, allow_symlink=True)

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 Reuse the active index file-set digest

Every proof route hashes the complete active index.db file set here, then immediately hashes the same path again when _active_tier_paths(location) supplies the index entry for tier_file_set_digests. On the repository's tens-of-gigabytes production indexes this doubles the dominant binding I/O and lengthens the window in which live collection becomes stale; capture the index digest once and reuse it for both binding fields.

Useful? React with 👍 / 👎.

Comment on lines +511 to +514
tier_file_set_digests = tuple(
(name, _sqlite_file_set_digest(path, allow_symlink=name == "index"))
for name, path in _active_tier_paths(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 Bind receipts to the physical blob namespace

The selected archive profiles run blob-integrity and attachment-acquisition-debt against blob/, but these bindings cover only SQLite file sets, while source_snapshot re-verifies only blobs referenced as raw payloads. If a non-raw acquired attachment is deleted or corrupted, or an orphan blob is added, every recorded binding remains unchanged, so validation accepts the old passed receipt even though rerunning the canonical checks would now fail; include a coherent digest of the complete physical blob namespace, preferably produced by the existing full scan.

Useful? React with 👍 / 👎.

Sinity and others added 2 commits August 7, 2026 16:54
Problem

Automated review found that live-proof validation still leaked open SQLite connections, accepted incomplete archive evidence, allowed arbitrary apply operation ids, and could not validate a candidate receipt after promotion.

What changed

Close read-only connections, validate the complete archive report shape, require the registered source-remediation operation id, keep receipt writes failure-atomic, reject dirty checkout SHA overrides, and validate retained candidate generations by their current source-side bindings and candidate file digest. Add regression coverage for each boundary and document the post-promotion route.

Compatibility/migration

Existing apply receipts must use the registered source-remediation operation id. No archive mutation or production operation was performed.

Ref polylogue-x97cf.

Co-Authored-By: Claude <noreply@anthropic.com>
Problem

The live-proof protocol now rejects unregistered apply operation ids, but the maintenance guide described only a generic registered route.

What changed

Name the current source-remediation operation id in the route documentation so operators can construct an accepted apply receipt without guessing.

Compatibility/migration

Apply receipts using other operation ids remain rejected until a route is added to the closed registry.

Ref polylogue-x97cf.

Co-Authored-By: Claude <noreply@anthropic.com>
@Sinity
Sinity merged commit b1dd5ca into master Aug 7, 2026
3 checks passed
@Sinity
Sinity deleted the feature/maintenance/live-proof-protocol branch August 7, 2026 15:09

@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: 02e3ac9868

ℹ️ 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 on lines +301 to +304
if configured:
if not _CODE_SHA_RE.fullmatch(configured):
raise LiveProofError("POLYLOGUE_CODE_SHA must be an exact git commit SHA")
return configured

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 Match the SHA override to the checked-out HEAD

When POLYLOGUE_CODE_SHA is set in a clean checkout, this returns any syntactically valid SHA without comparing it to git rev-parse HEAD, so collection and validation under the same environment can attest arbitrary code identity rather than the code that executed. Fresh evidence beyond the earlier dirty-worktree concern is that the cleanliness fix still bypasses HEAD resolution in this branch; resolve HEAD first and require the override to match it, reserving the override for installations without a checkout.

Useful? React with 👍 / 👎.

Comment on lines +1029 to +1031
generation_checks = {
"state": generation.state in {"active", "retained"},
"archive_root": Path(generation.archive_root).resolve() == location.configured_root.resolve(),

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 Tie retained candidate evidence to the current promotion

After candidate A is promoted and then superseded by candidate B, A's metadata still has state == "active" while its separate retention_state becomes retained, so this predicate accepts A and _validate_aggregate combines its old candidate receipt with newly captured active/apply receipts for B. Fresh evidence beyond the earlier post-promotion preservation concern is that the new fallback accepts a superseded generation without validating a promotion receipt or linkage to the current active generation, allowing final proof to omit candidate acceptance evidence for the generation actually active.

Useful? React with 👍 / 👎.

Comment on lines +764 to +766
receipt_bindings = payload.get("bindings")
if not isinstance(receipt_bindings, Mapping) or receipt_bindings != bindings.to_document():
raise LiveProofError("existing apply receipt bindings are stale or mismatched")

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 Preserve apply evidence across candidate promotion

The registered known-source-remediation apply happens before the source snapshot is frozen and the candidate is built, but this requires its embedded receipt to carry bindings identical to the current archive, including the active-index digest. Promotion necessarily changes that digest: collecting the wrapper after promotion rejects the original apply receipt here, while collecting it before promotion makes the wrapper fail final aggregate validation against the post-promotion active bindings. Preserve this historical prerequisite through promotion-specific evidence rather than requiring the pre-candidate apply receipt to match the final active index.

Useful? React with 👍 / 👎.

Comment on lines +816 to +823
bindings = capture_live_proof_bindings(resolved_archive_root, candidate_generation_id=candidate_generation_id)
if spec.producer == "archive_verification":
result, residues = _archive_verification_result(
spec,
resolved_archive_root,
candidate_generation_id=candidate_generation_id,
bindings=bindings,
)

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 Recheck bindings after running archive verification

When the daemon commits to any bound tier after this snapshot is captured but before the full verification profile finishes, the command still writes a receipt whose evidence was read from newer state while its bindings describe older files; validating that newly emitted receipt then immediately reports stale bindings. Because collection is advertised for the live archive and the daemon is the normal writer, capture and compare a second binding snapshot after verification and refuse publication if it differs.

AGENTS.md reference: AGENTS.md:L163-L166

Useful? React with 👍 / 👎.

Comment on lines +1044 to +1048
binding_checks = {
"code_sha": recorded.code_sha == current.code_sha,
"source_snapshot": recorded.source_snapshot == current.source_snapshot,
"parser_fingerprints": recorded.parser_fingerprints == current.parser_fingerprints,
"lowering": recorded.lowering_fingerprint == current.lowering_fingerprint,

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 Revalidate archive identity after candidate promotion

When an archive is copy-restored or replaced in place with byte-identical tier files, its inode-backed archive_identity_digest changes while the source snapshot, tier digests, schemas, code fingerprints, and private root reference can all remain equal. This promoted-candidate fallback never compares the recorded archive identity with current.archive_identity_digest, so a candidate receipt issued for the prior archive instance is accepted against the replacement despite the protocol's exact-archive binding; include the authority identity in these post-promotion checks.

Useful? React with 👍 / 👎.

Comment on lines +1081 to +1084
payload = dict(document)
digest = payload.pop("receipt_sha256", None)
if not isinstance(digest, str) or not _SHA256_RE.fullmatch(digest) or hash_payload(payload) != digest:
raise LiveProofError("live-proof receipt self-hash is invalid")

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 Reject unknown receipt fields

A self-hashed document can add arbitrary top-level fields and still pass validation because the payload's key set is never checked; for example, adding "private_source": "/home/user/export.json" produces a receipt accepted by final consumers even though it violates the closed v1 schema and the private-path guarantee. Require exactly the fields emitted by LiveProofReceipt.payload() before accepting the hash rather than silently discarding extensions when constructing the typed return value.

Useful? React with 👍 / 👎.

Comment on lines +460 to +462
expected_root = store.generations_root / generation_id
index_resolved = index_path.resolve(strict=True)
expected_resolved = expected_root.resolve(strict=True) / "index.db"

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 Reject symlinked candidate generation directories

When .index-generations/gen-X itself is replaced by a symlink to an external directory, index_path.is_symlink() is false for the regular index.db beneath it and resolving both sides makes this equality succeed, so candidate collection attests storage outside the lifecycle store as a canonical owned generation. Fresh evidence beyond the earlier final-file symlink concern is that the new check protects only the leaf while explicitly following a symlinked generation ancestor; require the generation directory itself to be a real directory contained beneath the resolved generations root.

Useful? React with 👍 / 👎.

@Sinity

Sinity commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Residual scope for Ref polylogue-x97cf: harden the existing real CLI route and typed receipt validation for the three fixed modes (read_only, candidate, existing_apply_receipt), typed residues, immutable self-hashed bindings, exact candidate/source/package/code/schema fingerprints, input receipt ownership, private-path digest handling, fail-closed non-mutation, and registered-ID dispatch. Add anti-vacuity coverage against the production route. Exclusions: consumer orchestration, campaign redesign, production operations, local Beads mutation, and Bead closure.

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