Skip to content
Closed
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
32 changes: 32 additions & 0 deletions docs/devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,38 @@ Catalog bypass audit sites are machine-checked across workflow runs, CI-owned np

<!-- END GENERATED: devtools-command-catalog -->

## Cursor-authority reconciliation

`polylogue ops maintenance cursor-authority-reconcile` is a dry-run-by-default
repair route for exactly one proven cursor-ahead source. It reads the fixed
`/realm/db/polylogue` archive root, requires the daemon to be stopped, and
writes a plan containing path and raw identifiers only as digests. Apply
requires that immutable plan, a freshly verified `full_evidence` backup
manifest with blob rollback evidence, and a new receipt path. The apply route
uses the normal live full-ingest/replay path under one single-use exact path
and frontier authorization. Receipts distinguish a performed ingest from an
observed recovery, leave cursor row counts null when the before/after state did
not prove them, and record typed deferred or failed post-ingest evidence. It
never accepts a global cursor bypass or writes `ingest_cursor` or accepted-head
rows directly.

The dry-run form is:

```text
polylogue ops maintenance cursor-authority-reconcile \
--source-path-file /private/path-file \
--output-plan /private/reconciliation-plan.json
```

The apply form is:

```text
polylogue ops maintenance cursor-authority-reconcile --apply \
--plan /private/reconciliation-plan.json \
--backup-manifest /private/full-evidence-backup \
--receipt /private/reconciliation-receipt.json
```

## Validation and Evidence

When changing semantics, validation, or surfaces:
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.",
),
(
"cursor-authority-reconcile",
"_cursor_authority",
"cursor_authority_reconcile_command",
"Plan or apply one backup-gated cursor-authority reconciliation.",
),
)


Expand Down
52 changes: 52 additions & 0 deletions polylogue/cli/commands/maintenance/_cursor_authority.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""``maintenance cursor-authority-reconcile`` command."""

from __future__ import annotations

import json
from pathlib import Path

import click


@click.command("cursor-authority-reconcile")
@click.option("--source-path-file", type=click.Path(path_type=Path, dir_okay=False), default=None)
@click.option("--output-plan", type=click.Path(path_type=Path, dir_okay=False), default=None)
@click.option("--plan", "plan_path", type=click.Path(path_type=Path, dir_okay=False), default=None)
@click.option("--backup-manifest", type=click.Path(path_type=Path, file_okay=True, dir_okay=True), default=None)
@click.option("--receipt", type=click.Path(path_type=Path, dir_okay=False), default=None)
@click.option("--apply", "apply_changes", is_flag=True, help="Apply one previously written reconciliation plan.")
def cursor_authority_reconcile_command(
source_path_file: Path | None,
output_plan: Path | None,
plan_path: Path | None,
backup_manifest: Path | None,
receipt: Path | None,
apply_changes: bool,
) -> None:
"""Plan or apply one backup-gated cursor-authority reconciliation."""

from polylogue.maintenance.cursor_authority_reconcile import (
CursorAuthorityReconciliationError,
apply_reconciliation,
build_reconciliation_plan,
)

try:
if apply_changes:
if plan_path is None or backup_manifest is None or receipt is None:
raise click.UsageError("--apply requires --plan, --backup-manifest, and --receipt")
if source_path_file is not None or output_plan is not None:
raise click.UsageError("--apply does not accept --source-path-file or --output-plan")
result = apply_reconciliation(plan_path=plan_path, backup_manifest=backup_manifest, receipt=receipt)
else:
if source_path_file is None or output_plan is None:
raise click.UsageError("dry-run requires --source-path-file and --output-plan")
if plan_path is not None or backup_manifest is not None or receipt is not None:
raise click.UsageError("dry-run accepts only --source-path-file and --output-plan")
result = build_reconciliation_plan(source_path_file=source_path_file, output_plan=output_plan)
except CursorAuthorityReconciliationError as exc:
raise click.ClickException(str(exc)) from exc
click.echo(json.dumps(result, indent=2, sort_keys=True))


__all__ = ["cursor_authority_reconcile_command"]
Loading