Skip to content

fix(embeddings): reconcile orphan rows across index rebuilds (#2755)#2755

Merged
Sinity merged 6 commits into
masterfrom
feature/fix/embedding-orphan-reconcile
Jul 12, 2026
Merged

fix(embeddings): reconcile orphan rows across index rebuilds (#2755)#2755
Sinity merged 6 commits into
masterfrom
feature/fix/embedding-orphan-reconcile

Conversation

@Sinity

@Sinity Sinity commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Adds bounded, resumable reconciliation for orphan rows in embeddings.db, runs it through daemon convergence, and exposes a dry-run-first maintenance command. Mutation is atomic and writer-coordinated, preserves surviving sessions for re-embedding, accounts for asymmetric meta/vec0 populations, and refuses to use a schema-stale active index as deletion truth. Dry-run output reports the bounded meta/vector/status candidate set separately from rows actually removed.

Problem

embeddings.db is not rebuilt in lockstep with index.db. Full replacement or index rebuild can leave metadata, vec0, and status rows whose message/session identities no longer exist, inflating storage and coverage counters. A 2026-07-12 read-only census of the current active generation found 741,267 metadata rows, including 22,442 identities absent from the active index, plus 17,231 status rows with 303 absent-session rows.

That active index is schema v32 while the packaged index schema is v34 and daemon ingest is still running. Those counts are diagnostic only: the active generation is not yet authoritative deletion truth, so destructive cleanup must wait for the rebuild.

This replaces conflicting PR #2749 from fresh master after PR #2754 changed generated topology.

Solution

  • Add polylogue/storage/embeddings/reconcile.py with one BEGIN IMMEDIATE transaction covering meta/vec0 deletion, surviving-session recount + needs_reindex = 1, and absent-session status cleanup. Exceptions roll back the whole pass and retry remains idempotent.
  • Select orphan identities from the union of metadata and vec0 so meta-only and vec0-only rows are independently counted and removed. Identity absence is the only deletion trigger; identity-present content-hash mismatches remain preserved for polylogue-0k6.
  • Track the bounded candidate identity/meta/vector/status populations separately from apply-only removal counters, so plain and JSON dry-run output reports what would actually be removed without pretending a mutation occurred.
  • Revalidate the resolved active index path/device/inode before mutation and before commit.
  • Require apply mode to verify attached PRAGMA idx.user_version equals packaged INDEX_SCHEMA_VERSION. Dry-run inspection remains available on mismatched generations; daemon and CLI mutation refuse the live v32-vs-v34 state.
  • Serialize the daemon pass through DaemonWriteCoordinator; require the CLI --yes path to hold the exclusive offline rebuild lease and reject a running daemon.
  • Bound daemon and default manual-apply passes to 500 identities. Manual dry-run remains unbounded unless --max-count is supplied.
  • Run one periodic daemon pass every 15 minutes once the archive is schema-authoritative; expose polylogue ops maintenance embedding-orphan-reconcile for inspect/break-glass operation.
  • Regenerate topology projection/status against current master.

Acceptance-criteria disposition

Criterion Disposition
Synthetic rebuild retains deleted-message and absent-session embedding rows Satisfied by real SQLite/vec0 fixtures.
Automatic bounded convergence removes only orphan rows and updates counters Satisfied; daemon coordinator route is covered.
Interruption/retry is atomic and idempotent Satisfied by a real trigger-injected failure across delete/recount/status cleanup followed by successful retry.
Active vectors survive and affected surviving sessions re-enter backlog Satisfied; the test invokes the production pending-session selector after reconciliation.
Generation/identity/content-hash guards are mutation-sensitive Identity/content-hash/path+inode/schema guards are covered. Generation-pointer lifecycle remains tracked by polylogue-b5l; this slice refuses schema-stale generations now.
Superseded changed-text rows Deferred to the existing focused lifecycle bead polylogue-0k6; this reconciler deliberately preserves identity-present rows.
Live before/after cleanup proof Deferred until the authoritative v34 index rebuild. The bead must remain open until apply records exact before/after counts.

Verification

devtools test tests/unit/storage/test_embedding_orphan_reconcile.py tests/unit/daemon/test_embedding_orphan_reconcile_daemon.py tests/unit/cli/test_archive_maintenance_cli.py
66 passed in 123.66s

devtools verify --quick
15/15 steps exit 0; total 24.7s

The focused run exercises real SQLite/vec0 rows, the production pending-session selector, the daemon coordinator route, and plain/JSON CLI routes. The rollback test fails if the transaction boundary is removed; the schema/path guards and offline lease tests fail if their production checks are removed. The manual-bound regression seeds 501 real candidates and requires the default apply pass to remove exactly 500 while leaving resumable debt.

Adversarial review notes

  • Iteration 1 found one real gap: dry-run rendered apply-only zero removal counters despite nonzero candidates. Commit ba5d8820b adds separate bounded candidate counters and production plain/JSON CLI regressions.
  • Automated Codex review found that manual apply overrode the storage default with an unbounded None. Commit a1df0b4ab restores the 500-identity apply default and adds a 501-candidate production-route regression; the review thread is answered and resolved.
  • No further legitimate code or closure gaps remained after the corrected diff was reviewed against every acceptance criterion.
  • GitHub-hosted jobs currently fail before allocation with zero steps/no logs under the repository billing lock, so local gates above are the executable verification evidence.

No destructive live apply was run. Apply and bead closure remain deferred until the active index is rebuilt from v32 to packaged v34 and the resulting before/after census is recorded.

Ref polylogue-1dk1

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

Summary by CodeRabbit

  • New Features
    • Added tools to inspect and reconcile orphaned embedding records.
    • Added a safe, read-only dry-run mode with JSON and human-readable reports.
    • Added optional cleanup mode with safeguards against concurrent or stale index changes.
    • Added automatic background maintenance to gradually process orphaned records.
  • Bug Fixes
    • Removed obsolete embedding records and refreshed related embedding status entries while preserving active data.

Sinity and others added 4 commits July 12, 2026 11:16
Problem: embeddings.db (message_embeddings_meta / message_embeddings vec0 /
embedding_status) is not rebuilt in lockstep with index.db. A full
re-ingest, ops reset --index, or a provider full-replace parse can leave
embedding rows pointing at message/session identities that no longer
exist in the rebuilt index. A live 2026-07-10 audit found 675,825
message_embeddings_meta rows vs 675,725 status-summed embedded messages:
11,348 orphan message ids and 6 orphan embedding_status rows, inflating
coverage counters past 100%.

Solution: add polylogue/storage/embeddings/reconcile.py
(reconcile_embedding_orphans / inspect_embedding_orphans) — a bounded,
resumable, idempotent reconciler over an attached index.db, guarded by:
identity (NOT EXISTS join on message_id/session_id is the sole deletion
trigger), content-hash (an identity-present row is never deleted for a
stale content_hash — that's 0k6's re-embed territory), and quiet-window
(skip rows embedded within the last 5 minutes, in case index
materialization for a full-replace session is mid-write). Recomputes
message_count_embedded for sessions with removed message rows.

Wired into daemon convergence as periodic_embedding_orphan_reconcile_check
(polylogue/daemon/embedding_backlog.py, 15 minute interval, 500-row
bounded batches) alongside the existing embedding backlog drain loop, and
exposed as the manual break-glass/inspect path via
`polylogue ops maintenance embedding-orphan-reconcile` (--yes to apply,
defaults to dry-run/inspect), mirroring the existing
blob-reference-prune-orphans command shape.

Verification:
- devtools test tests/unit/storage/test_embedding_orphan_reconcile.py
  tests/unit/daemon/test_embedding_orphan_reconcile_daemon.py
  tests/unit/cli/test_archive_maintenance_cli.py -> 55 passed
- mypy --strict on all touched files -> Success: no issues found in 8
  source files
- ruff check / format --check on all touched files -> clean
- Manual end-to-end smoke against a real `polylogue demo seed` archive:
  deleted a live embedded message from index.db, confirmed
  `ops maintenance embedding-orphan-reconcile` (dry-run) reports the
  orphan, `--yes` removes exactly it and recounts message_count_embedded,
  and a follow-up dry-run reports a clean, idempotent archive
- devtools render topology-projection && devtools render topology-status
  (new module regenerates docs/plans/topology-target.yaml +
  docs/topology-status.md per repo convention)

Remaining: acceptance criteria call for "live inspect-before/after
evidence reporting the 11,348/6 baseline and resulting exact counts" —
this worktree has no access to the real user archive, so that run is a
follow-up the operator (or a session with archive access) needs to
execute via `polylogue ops maintenance embedding-orphan-reconcile --yes`
before the bead is closed. Bead left open per instructions.

Ref polylogue-1dk1

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: orphan embedding deletion, vector recount, and status cleanup committed separately, while surviving sessions were not returned to the ambient embedding backlog. Manual apply could also race the daemon or an index generation replacement.\n\nMake the reconciliation pass one rollback-safe transaction, revalidate the active index identity before commit, and require daemon-coordinator or exclusive offline authority for mutation. Count meta-only and vec0-only populations independently and mark surviving affected sessions for reindex.\n\nAdd production-route tests for rollback and retry, bounded selector eligibility, daemon coordination, offline lease exclusion, index identity drift, and asymmetric embedding rows.\n\nRef polylogue-1dk1\n\nCo-Authored-By: Claude <noreply@anthropic.com>
Problem: orphan reconciliation could treat a schema-stale active index as deletion truth. The live archive currently has index v32 while the packaged schema is v34, so automatic daemon cleanup could remove embeddings before the authoritative rebuild completes.\n\nRequire apply mode to verify the attached index user_version matches the packaged index schema before beginning its transaction. Keep dry-run census available, and pin refusal through substrate, daemon, and CLI production routes with mutation-sensitive v32 regressions.\n\nRef polylogue-1dk1\n\nCo-Authored-By: Claude <noreply@anthropic.com>
Regenerate the topology projection and status after carrying the orphan reconciliation module onto current master.\n\nRef polylogue-1dk1\n\nCo-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds cross-tier orphan embedding reconciliation with bounded, transactional cleanup; exposes daemon and offline CLI execution paths; adds safety and behavior tests; and updates topology records.

Changes

Embedding orphan reconciliation

Layer / File(s) Summary
Reconciliation storage core
polylogue/storage/embeddings/reconcile.py, polylogue/storage/embeddings/__init__.py, tests/unit/storage/test_embedding_orphan_reconcile.py
Adds inspection and reconciliation reports, identity-guarded orphan detection, quiet-window filtering, bounded cleanup, status updates, transaction rollback, and storage-level tests.
Daemon maintenance scheduling
polylogue/daemon/embedding_backlog.py, polylogue/daemon/cli.py, tests/unit/daemon/test_embedding_orphan_reconcile_daemon.py
Schedules periodic orphan reconciliation through DaemonWriteCoordinator, with embedding/index gating, bounded execution, and daemon integration tests.
Offline CLI reconciliation
polylogue/cli/commands/maintenance.py, tests/unit/cli/test_archive_maintenance_cli.py
Adds dry-run and --yes modes, lease and daemon checks, JSON/plain output, and CLI coverage for limits and refusal conditions.
Topology projection records
docs/plans/topology-target.yaml, docs/topology-status.md
Records the new reconciliation module and updates generated topology counts and metadata.

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

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant MaintenanceCLI as maintenance embedding-orphan-reconcile
  participant Reconcile as reconcile_embedding_orphans
  participant Databases as index.db and embeddings.db
  Operator->>MaintenanceCLI: invoke dry-run or --yes
  MaintenanceCLI->>Reconcile: request inspection or mutation
  Reconcile->>Databases: scan and validate orphan rows
  Reconcile-->>MaintenanceCLI: return reconciliation report
  MaintenanceCLI-->>Operator: render JSON or plain summary
Loading

Possibly related PRs

  • Sinity/polylogue#2534: Refactored the daemon periodic-loop scheduling path used here for embedding maintenance.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% 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 summarizes the main change: orphan-row reconciliation across index rebuilds.
Description check ✅ Passed The description covers Summary, Problem, Solution, and Verification well; only the optional Changelog and explicit Risks sections are missing.
✨ Finishing Touches
📝 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/embedding-orphan-reconcile

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.

@Sinity Sinity changed the title fix(embeddings): reconcile orphan rows safely across index rebuilds fix(embeddings): reconcile orphan rows across index rebuilds (#2755) Jul 12, 2026
@Sinity
Sinity marked this pull request as ready for review July 12, 2026 09:23

@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: 8d2c1f3d47

ℹ️ 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/cli/commands/maintenance.py Outdated
Sinity and others added 2 commits July 12, 2026 11:36
Problem: dry-run inspection counted orphan rows but rendered apply-only removal counters, telling operators it would remove zero rows even when candidates were present.

Track the bounded candidate identity, metadata, vector, and status populations separately from rows actually removed. Render candidate counts in dry-run mode while preserving zero mutation counters, and cover both JSON and plain CLI routes against real SQLite/vec0 fixtures.

Ref polylogue-1dk1

Co-Authored-By: Claude <noreply@anthropic.com>
Problem: omitting --max-count in apply mode passed None through the CLI, overriding the storage reconciler's 500-identity default and holding one unbounded writer transaction.

Keep dry-run inspection unbounded, but use DEFAULT_MAX_COUNT for apply unless the operator explicitly supplies a limit. A production CLI regression seeds 501 candidates and proves the default pass removes exactly 500 and leaves resumable debt.

Ref polylogue-1dk1

Co-Authored-By: Claude <noreply@anthropic.com>
@Sinity
Sinity merged commit 7fd5b6b into master Jul 12, 2026
3 of 17 checks passed
@Sinity
Sinity deleted the feature/fix/embedding-orphan-reconcile branch July 12, 2026 09:50

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

🤖 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.py`:
- Around line 47-52: Export DEFAULT_MAX_COUNT and DEFAULT_QUIET_WINDOW_MS from
polylogue.storage.embeddings alongside EmbeddingOrphanReconcileReport and
reconcile_embedding_orphans, including both the re-export and __all__ entries.
Update the maintenance CLI imports to use the package public API rather than
polylogue.storage.embeddings.reconcile.
- Around line 2268-2299: The authoritative reconcile path around
reconcile_embedding_orphans must catch its RuntimeError and present it as a
click.ClickException, preserving the original error as the cause. Keep the
existing RebuildLeaseUnavailableError handling intact and ensure schema-refusal
failures no longer expose a raw traceback to the CLI operator.

In `@tests/unit/daemon/test_embedding_orphan_reconcile_daemon.py`:
- Around line 104-113: Add a unit test for reconcile_embedding_orphans_once that
creates index.db while leaving embeddings.db absent, enables embeddings with a
configured voyage_api_key, and asserts the function returns None. Keep this
distinct from the existing missing-index test and from
reconcile_embedding_orphans’s empty-report behavior.
🪄 Autofix (Beta)

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

Run ID: eb388eb7-e96e-4d1d-88d5-9a6711119705

📥 Commits

Reviewing files that changed from the base of the PR and between 25bea6f and a1df0b4.

📒 Files selected for processing (10)
  • docs/plans/topology-target.yaml
  • docs/topology-status.md
  • polylogue/cli/commands/maintenance.py
  • polylogue/daemon/cli.py
  • polylogue/daemon/embedding_backlog.py
  • polylogue/storage/embeddings/__init__.py
  • polylogue/storage/embeddings/reconcile.py
  • tests/unit/cli/test_archive_maintenance_cli.py
  • tests/unit/daemon/test_embedding_orphan_reconcile_daemon.py
  • tests/unit/storage/test_embedding_orphan_reconcile.py

Comment on lines +47 to +52
from polylogue.storage.embeddings.reconcile import (
DEFAULT_MAX_COUNT,
DEFAULT_QUIET_WINDOW_MS,
EmbeddingOrphanReconcileReport,
reconcile_embedding_orphans,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Import through the package public API, not the internal submodule.

This surface reaches directly into polylogue.storage.embeddings.reconcile. The package __init__ already curates the public API (EmbeddingOrphanReconcileReport, reconcile_embedding_orphans, …); it just doesn't export DEFAULT_MAX_COUNT/DEFAULT_QUIET_WINDOW_MS, which is what forces the internal import here. Export those two constants and import from the package instead.

As per coding guidelines: "surfaces should remain leaf adapters and must not import substrate internals directly."

♻️ Proposed fix (CLI + package exports)

In polylogue/cli/commands/maintenance.py:

-from polylogue.storage.embeddings.reconcile import (
-    DEFAULT_MAX_COUNT,
-    DEFAULT_QUIET_WINDOW_MS,
-    EmbeddingOrphanReconcileReport,
-    reconcile_embedding_orphans,
-)
+from polylogue.storage.embeddings import (
+    DEFAULT_MAX_COUNT,
+    DEFAULT_QUIET_WINDOW_MS,
+    EmbeddingOrphanReconcileReport,
+    reconcile_embedding_orphans,
+)

In polylogue/storage/embeddings/__init__.py, add the constants to the re-export and __all__:

 from polylogue.storage.embeddings.reconcile import (
+    DEFAULT_MAX_COUNT,
+    DEFAULT_QUIET_WINDOW_MS,
     EmbeddingOrphanReconcileReport,
     EmbeddingOrphanSample,
     inspect_embedding_orphans,
     reconcile_embedding_orphans,
 )
 __all__ = [
+    "DEFAULT_MAX_COUNT",
+    "DEFAULT_QUIET_WINDOW_MS",
     "EmbedSessionOutcome",
     "EmbedSingleStatus",
     "EmbeddingOrphanReconcileReport",
📝 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
from polylogue.storage.embeddings.reconcile import (
DEFAULT_MAX_COUNT,
DEFAULT_QUIET_WINDOW_MS,
EmbeddingOrphanReconcileReport,
reconcile_embedding_orphans,
)
from polylogue.storage.embeddings import (
DEFAULT_MAX_COUNT,
DEFAULT_QUIET_WINDOW_MS,
EmbeddingOrphanReconcileReport,
reconcile_embedding_orphans,
)
🤖 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/cli/commands/maintenance.py` around lines 47 - 52, Export
DEFAULT_MAX_COUNT and DEFAULT_QUIET_WINDOW_MS from polylogue.storage.embeddings
alongside EmbeddingOrphanReconcileReport and reconcile_embedding_orphans,
including both the re-export and __all__ entries. Update the maintenance CLI
imports to use the package public API rather than
polylogue.storage.embeddings.reconcile.

Source: Coding guidelines

Comment on lines +2268 to +2299
if yes:
from polylogue.maintenance.offline_guard import running_daemon_pid
from polylogue.storage.index_generation import RebuildLease, RebuildLeaseUnavailableError

active_config = Config(archive_root=root, render_root=render_root(), sources=[], db_path=index_db)
try:
with RebuildLease(root):
daemon_pid = running_daemon_pid(active_config)
if daemon_pid is not None:
raise click.ClickException(
f"embedding orphan reconcile refused while polylogued PID {daemon_pid} is running"
)
report = reconcile_embedding_orphans(
index_db,
embeddings_db,
dry_run=False,
max_count=DEFAULT_MAX_COUNT if max_count is None else max_count,
sample_size=sample_limit,
quiet_window_ms=quiet_window_seconds * 1000,
mutation_authority="offline-exclusive",
)
except RebuildLeaseUnavailableError as exc:
raise click.ClickException(str(exc)) from exc
else:
report = reconcile_embedding_orphans(
index_db,
embeddings_db,
dry_run=True,
max_count=max_count,
sample_size=sample_limit,
quiet_window_ms=quiet_window_seconds * 1000,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the target file and find the relevant command and tests.
ast-grep outline polylogue/cli/commands/maintenance.py --view expanded || true
printf '\n--- matches ---\n'
rg -n "reconcile_embedding_orphans|RebuildLeaseUnavailableError|ClickException|stale|schema|apply_refuses_stale_index_schema|offline-exclusive" polylogue/cli/commands/maintenance.py polylogue -g '*.py'

Repository: Sinity/polylogue

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation around the command and the tests.
sed -n '2240,2315p' polylogue/cli/commands/maintenance.py
printf '\n--- test references ---\n'
rg -n "test_apply_refuses_stale_index_schema|stale index schema|authoritative-schema|offline-exclusive|reconcile_embedding_orphans" polylogue -g '*test*.py' -g '*tests*.py'

Repository: Sinity/polylogue

Length of output: 3152


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the implementation and any tests that reference the stale/authoritative-schema refusal path.
rg -n "def reconcile_embedding_orphans|RuntimeError|stale.*schema|authoritative.*schema|schema refusal|RebuildLeaseUnavailableError|mutation_authority" polylogue -g '*.py' -A 6 -B 6
printf '\n--- tests ---\n'
rg -n "apply_refuses_stale_index_schema|stale index schema|authoritative schema|reconcile_embedding_orphans|mutation_authority" polylogue -g '*test*.py' -g '*tests*.py' -A 4 -B 4

Repository: Sinity/polylogue

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the actual implementation and tests for the embedding orphan reconcile path.
rg -n "def reconcile_embedding_orphans|class EmbeddingOrphanReconcile|stale.*schema|authoritative.*schema|mutation_authority|apply_refuses_stale_index_schema" polylogue -g '*.py' -A 8 -B 8 --max-count 80

Repository: Sinity/polylogue

Length of output: 13542


Wrap the authoritative-schema refusal in a ClickException. reconcile_embedding_orphans() raises a raw RuntimeError when the active index schema is not authoritative, and this CLI path lets it bubble to the operator as a traceback.

🤖 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/cli/commands/maintenance.py` around lines 2268 - 2299, The
authoritative reconcile path around reconcile_embedding_orphans must catch its
RuntimeError and present it as a click.ClickException, preserving the original
error as the cause. Keep the existing RebuildLeaseUnavailableError handling
intact and ensure schema-refusal failures no longer expose a raw traceback to
the CLI operator.

Comment on lines +104 to +113
def test_reconcile_embedding_orphans_once_noop_when_index_missing(tmp_path: Path) -> None:
missing_index_db = tmp_path / "index.db" # never created

with patch("polylogue.daemon.convergence_stages.load_polylogue_config") as mock_cfg:
mock_cfg.return_value.embedding_enabled = True
mock_cfg.return_value.voyage_api_key = "test-key"
result = reconcile_embedding_orphans_once(missing_index_db)

assert result is 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.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for the "embeddings.db missing" daemon gating branch.

Tests cover config-disabled and index-missing no-ops, but not reconcile_embedding_orphans_once's own embeddings_db.exists() check (embedding_backlog.py's line ~130-131), which returns None — distinct from reconcile_embedding_orphans's own handling of a missing embeddings.db (which returns an empty report rather than None). A test with index.db present but no embeddings.db would close this gap.

🤖 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/daemon/test_embedding_orphan_reconcile_daemon.py` around lines 104
- 113, Add a unit test for reconcile_embedding_orphans_once that creates
index.db while leaving embeddings.db absent, enables embeddings with a
configured voyage_api_key, and asserts the function returns None. Keep this
distinct from the existing missing-index test and from
reconcile_embedding_orphans’s empty-report behavior.

Sinity added a commit that referenced this pull request Jul 12, 2026
…2757)

## Summary

Add the typed-annotation foundation on current master: closed schema
declarations, an explicit registry, a schema-enforced assertion writer,
and
type-correct predicates over JSON annotation values. This fresh branch
replaces
unmerged PRs #2752 and #2756 without rewriting their published history
and is
based on master `7fd5b6bb9` after PR #2755.

Ref polylogue-rxdo.7

## Problem

External labels currently degrade into untyped JSON blobs. The archive
cannot
prove that a label was written under the active schema definition, and
query
predicates cannot reliably distinguish JSON strings, numbers, booleans,
and
null. The earlier branches predated merged topology changes; a fresh
branch is
required to preserve public history and regenerate derived surfaces
against
current master.

## Solution

- Add closed `AnnotationField` and `AnnotationSchema` models plus an
explicit
  `AnnotationSchemaRegistry` in `polylogue/annotations/schema.py`.
- Validate all writes against the exact active registered schema,
rejecting
  unknown, draft, deprecated, drifted, and non-finite values. Runtime
declarations reject unknown closed-vocabulary values and non-finite
bounds;
arbitrarily large finite JSON integers do not pass through float
coercion.
- Route validated external-agent rows through the existing assertion
write
  chokepoint, preserving candidate/non-injected safety semantics.
- Add `value.<path>` equality and range predicates for assertion
queries. SQL
guards JSON type before comparison, so strings, numbers, booleans, null,
and
missing paths remain distinct; equality alternation preserves each
scalar's
  identity and numeric ranges reject non-finite right-hand sides.
- Semantically carry forward the latest `polylogue-rxdo.7` record plus
open
  children `.7.1` and `.7.2`, preserving unrelated master Beads records.
- Regenerate topology projection/status against merged PR #2755 master.

## Acceptance criteria

Satisfied in this foundation phase:

- closed typed field declarations and finite numeric values/bounds
- exact active registered-schema enforcement at the public writer
- candidate/non-injected writes through the production assertion
chokepoint
- typed JSON scalar equality and numeric range predicates
- real archive write/query/judge tests

Explicitly deferred:

- `polylogue-rxdo.7.1`: durable `annotation_schemas` and
  `annotation_batches`, immutable fingerprints, a concrete
  delegation-discourse schema, and queryable batch provenance
- `polylogue-rxdo.7.2`: bounded JSONL import, live target/evidence-span
validation, CLI/MCP contracts, disagreement/adjudication roundtrips, and
  rendering
- `polylogue-kmts`: structural-target joins without row multiplication

The parent bead remains open; neither child is optional for its full
acceptance criteria. No prior PR or bead changes state through this PR
body.

## Verification

- consolidated fresh-master regression selector over schema closure,
finite
values/bounds, scalar identity/alternation, and public-writer rejection
  -> `23 passed, 14733 deselected in 25.18s`
- `devtools verify --quick` with the regenerated/merged working tree
-> all 15 steps exited 0 in 47.63s, including mypy, generated surfaces,
  topology, layering, schema roundtrip, and safety gates
- pre-push `devtools verify --quick` at `c7d1349c6`
  -> all 15 steps exited 0 in 23.53s
- `devtools render topology-projection && devtools render
topology-status`
  -> `917 rows`, `764 stable`, `6 TBD`
- `.agent/scripts/bd-graph-lint`
  -> `dup_labels=0`, `inversions=0`; two inherited missing-AC records
(`polylogue-2ilz`, `polylogue-nu2h`) are unchanged and outside this diff

GitHub-hosted jobs may still fail before startup while the repository
account
is billing-locked; that state produces no job logs or substantive CI
result.

Anti-vacuity: the focused tests exercise the real SQLite assertion
writer,
typed SQL lowering, archive query readback, and candidate judgment.
Removing
the active-registry check, finite guards, JSON-type equality guard, or
candidate chokepoint causes the corresponding production-route
regression to
fail. The equality fixture places `true` beside numeric `1`, and `false`
beside
numeric `0`, so SQLite coercion cannot pass unnoticed.

## Adversarial review notes

The independent audit of PR #2756 found four real gaps: boolean/number
equality
coercion, missing runtime enforcement of closed declaration
vocabularies,
accepted non-finite bounds/equality literals, and `OverflowError` on
huge finite
integer validation. Logical commit `ea1e75faf` carries the repair on
current
master with mutation-sensitive regressions. The replacement diff was
then
rechecked after merged PR #2755 and regenerated without carrying stale
topology
bytes.

---------

Co-authored-by: Claude <noreply@anthropic.com>
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