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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docs/maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,22 @@ Exit code is non-zero when any check reports `error` (or, with `--strict`,
temporarily busy under a concurrent rebuild — never aborts the rest; each
check independently reports its own outcome.

### `polylogue ops maintenance live-proof` — immutable campaign evidence

Read-only evidence collection for the reindex campaign. The command accepts a fixed registered proof id and writes one new self-hashed JSON receipt outside the archive. It has no command-execution option and cannot apply a mutation, control the daemon, migrate a tier, or promote a generation.

```bash
polylogue ops maintenance live-proof \
--proof-id archive-verification \
--output /path/to/new/live-proof.json
```

The registry currently has exactly three routes: `archive-verification` for a fixed read-only archive-check profile, `candidate-archive-verification` for that profile against one named inactive generation, and `existing-apply-receipt` for a pre-existing `polylogue.apply-receipt.v1` input. Candidate mode requires `--candidate-generation`; existing-apply mode requires `--apply-receipt` and the registered `known-source-remediation` operation id; every other combination is rejected. An arbitrary nonempty operation id is not accepted.

Each `polylogue.live-proof-receipt.v1` binds the proof and Bead id, exact code SHA, archive identity, source snapshot, all six active archive-tier schema versions, parser and lowering fingerprints, the active SQLite file set, and the candidate generation, schema, and SQLite file set where applicable. SQLite bindings include the database and WAL/journal sidecars and refuse a file set that changes while it is captured. Candidate metadata must name the canonical inactive generation and the same source snapshot. Archives whose configured paths contain SQLite URI query characters are rejected before proof dependencies open them. Private local paths are represented only as a SHA-256 digest plus basename.

The receipt keeps complete structured archive-verification evidence after redacting archive paths and any absolute paths emitted by checks. `archive-verification` runs the entire live archive profile. `candidate-archive-verification` runs both canonical candidate acceptance profiles: the index-candidate checks against the inactive generation and the cross-tier checks against that generation plus the durable archive. Existing-apply evidence embeds the validated input receipt, so consumers revalidate its self-hash, bindings, registered operation id, and match to the recorded input digest. Verification consumers validate the fixed profile membership, check outcomes, typed status/residue relationship, bindings, and input hashes again. Aggregate validation binds pre-promotion candidate evidence to the inactive generation. After promotion, it validates the retained generation and candidate file set directly while recapturing the current active binding for post-promotion receipts. Final proof consumption requires every registered route exactly once; `not_applicable` is accepted only with its typed residue. Output creation is exclusive and failure-atomic: a failed write removes its partial file and syncs the destination directory.

### `--operation-id` and `--resume`: worked example

Replay execution writes a small JSON state file under
Expand Down
6 changes: 6 additions & 0 deletions polylogue/cli/commands/maintenance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@
"verify_archive_command",
"Prove the archive is coherent after a rebuild, restore, or promotion. Read-only.",
),
(
"live-proof",
"_live_proof",
"live_proof_command",
"Collect one fixed, immutable live-proof receipt. Read-only.",
),
(
"cursor-authority-reconcile",
"_cursor_authority",
Expand Down
63 changes: 63 additions & 0 deletions polylogue/cli/commands/maintenance/_live_proof.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""``maintenance live-proof``: collect one fixed, immutable evidence receipt."""

from __future__ import annotations

import json
from pathlib import Path

import click

from polylogue.paths import archive_root


def _is_under_any(path: Path, roots: tuple[Path, ...]) -> bool:
return any(path == root or root in path.parents for root in roots)


@click.command("live-proof")
@click.option("--proof-id", required=True, help="One registered live-proof id.")
@click.option("--candidate-generation", type=str, help="Inactive generation id for the candidate proof route only.")
@click.option(
"--apply-receipt",
type=click.Path(path_type=Path, file_okay=True, dir_okay=False, readable=True),
help="Existing immutable apply receipt for the existing-apply route only.",
)
@click.option(
"--output",
type=click.Path(path_type=Path, file_okay=True, dir_okay=False, writable=True),
required=True,
help="New receipt path outside the archive. Existing files are refused.",
)
def live_proof_command(
proof_id: str,
candidate_generation: str | None,
apply_receipt: Path | None,
output: Path,
) -> None:
"""Collect one registered proof without mutating archive or daemon state."""

from polylogue.maintenance.live_proof import (
LiveProofError,
archive_owned_storage_roots,
collect_live_proof,
write_live_proof_receipt,
)

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 👍 / 👎.

if _is_under_any(target, owned_roots):
raise click.BadParameter("receipt output must be outside archive-owned storage", param_hint="--output")
receipt = collect_live_proof(
proof_id,
root,
candidate_generation_id=candidate_generation,
apply_receipt_path=apply_receipt,
)
write_live_proof_receipt(target, receipt)
except LiveProofError as exc:
raise click.ClickException(str(exc)) from exc
Comment thread
coderabbitai[bot] marked this conversation as resolved.
except OSError as exc:
raise click.ClickException("live-proof receipt output could not be written") from exc
click.echo(json.dumps(receipt.to_document(), indent=2, sort_keys=True))
Loading