Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
04299d5
fix(storage): harden durable change-train admission
Sinity Aug 7, 2026
4d27f47
chore: refresh PR scope authority
Sinity Aug 7, 2026
e41daa7
fix(storage): bind forward train admission to schema objects
Sinity Aug 7, 2026
0057a87
fix(storage): read archive DDL through typed module boundary
Sinity Aug 7, 2026
5524b84
chore(ci): refresh durable train scope carrier
Sinity Aug 7, 2026
59c21ed
fix(storage): harden durable forward admission
Sinity Aug 7, 2026
4353c45
fix(storage): admit legacy durable identity evidence
Sinity Aug 7, 2026
541a8f2
fix(storage): bound raw failure lifecycle queries
Sinity Aug 7, 2026
f809a53
fix(storage): cache durable train schema checks
Sinity Aug 7, 2026
65eab44
fix(storage): use exported durable DDL registry
Sinity Aug 7, 2026
cbfa55b
fix(storage): defer durable train admission scans
Sinity Aug 7, 2026
b0ed05d
chore(ci): refresh durable train carrier
Sinity Aug 7, 2026
3b03522
fix(storage): expose and reuse forward receipts
Sinity Aug 7, 2026
86213e4
test(storage): use canonical evidence helper
Sinity Aug 7, 2026
5779036
fix(storage): bound durable admission evidence
Sinity Aug 7, 2026
3e2bff6
fix(storage): bind continuity checks to archive roots
Sinity Aug 7, 2026
7ce12cb
fix(storage): scope durable train identities
Sinity Aug 7, 2026
d3c994c
fix(storage): enforce train chains during startup
Sinity Aug 7, 2026
857bd1c
test(storage): pin startup chain rejection detail
Sinity Aug 7, 2026
48f005b
test(storage): assert startup rejection is non-mutating
Sinity Aug 7, 2026
079f434
fix(storage): close durable train admission gaps
Sinity Aug 7, 2026
927c958
refactor(storage): remove stale train-chain argument
Sinity Aug 7, 2026
ba754f5
fix(storage): close durable train review gaps
Sinity Aug 7, 2026
d9ff240
fix(storage): validate current durable train chain
Sinity Aug 7, 2026
c0ab48a
fix(storage): reject manifestless durable tiers
Sinity Aug 7, 2026
733e98e
fix(storage): distinguish fresh durable bootstrap
Sinity Aug 7, 2026
bc8fb78
test(storage): cover bootstrap receipt identity
Sinity Aug 7, 2026
ec37aec
fix(storage): authenticate bootstrap adoption
Sinity Aug 7, 2026
06788ed
fix(storage): close durable train review gaps
Sinity Aug 7, 2026
5b324a8
fix(storage): publish bootstrap marker atomically
Sinity Aug 7, 2026
424c773
fix(storage): fail closed on incomplete pre-marker archives
Sinity Aug 7, 2026
06e9cdb
fix(storage): recover interrupted fresh bootstrap
Sinity Aug 7, 2026
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
21 changes: 21 additions & 0 deletions polylogue/cli/commands/maintenance/_migrate_tier.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def migrate_tier_command(
raise SystemExit(1) from exc

result = execution.migration_result if execution is not None else None
receipt = execution.forward_version_receipt if execution is not None else None
payload = {
"ok": True,
"tier": tier,
Expand All @@ -138,6 +139,19 @@ def migrate_tier_command(
"from_version": result.from_version if result is not None else 0 if initialized else None,
"to_version": result.to_version if result is not None else initialized_version,
"applied_versions": list(result.applied_versions) if result is not None else [],
"forward_version_receipt": (
{
"tier": receipt.tier.value,
"historical_train_id": receipt.historical_train_id,
"historical_target_version": receipt.historical_target_version,
"current_target_version": receipt.current_target_version,
"observed_live_version": receipt.observed_live_version,
"historical_schema_inventory_sha256": receipt.historical_schema_inventory_sha256,
"archive_identity_digest": receipt.archive_identity_digest,
}
if receipt is not None
else None
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
if output_format == "json":
click.echo(json.dumps(payload, indent=2, sort_keys=True))
Expand All @@ -147,6 +161,13 @@ def migrate_tier_command(
click.echo(f"Initialized missing {tier} tier at schema version {initialized_version}.")
return
if result is None:
if receipt is not None:
click.echo(
f"No pending durable migration for {tier}; historical train {receipt.historical_train_id} "
f"is admitted at live schema v{receipt.observed_live_version} "
f"(target v{receipt.current_target_version})."
)
return
click.echo(f"No pending durable migration for {tier}.")
return
applied = ", ".join(str(version) for version in result.applied_versions) or "none"
Expand Down
31 changes: 21 additions & 10 deletions polylogue/daemon/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,21 +925,32 @@ def _archive_raw_failure_info(
).fetchone()[0]
or 0
)
sample_ids = [
str(sample["raw_id"]) for sample in lifecycle_snapshot.samples if sample.get("raw_id") is not None
]
lifecycle_by_raw_id = {
str(sample["raw_id"]): str(sample["lifecycle"])
for sample in lifecycle_snapshot.samples
if sample.get("raw_id") is not None and sample.get("lifecycle") is not None
}
samples: list[RawFailureSample] = []
for row in conn.execute(
"""
SELECT r.raw_id, r.origin, r.parse_error, r.validation_status, r.validation_error
FROM raw_sessions AS r
WHERE (parse_error IS NOT NULL AND TRIM(parse_error) != '') OR validation_status = 'failed'
ORDER BY acquired_at_ms DESC, raw_id DESC
LIMIT 50
"""
):
rows_by_raw_id: dict[str, sqlite3.Row | tuple[object, ...]] = {}
if sample_ids:
placeholders = ",".join("?" for _ in sample_ids)
rows = conn.execute(
f"""
SELECT r.raw_id, r.origin, r.parse_error, r.validation_status, r.validation_error
FROM raw_sessions AS r
WHERE r.raw_id IN ({placeholders})
AND ((r.parse_error IS NOT NULL AND TRIM(r.parse_error) != '') OR r.validation_status = 'failed')
""",
sample_ids,
)
rows_by_raw_id = {str(row[0]): row for row in rows}
for raw_id in sample_ids:
row = rows_by_raw_id.get(raw_id)
if row is None:
continue
parse_err = str(row[2] or "") if row[2] else ""
val_status = str(row[3] or "") if row[3] else ""
val_err = str(row[4] or "") if row[4] else ""
Expand All @@ -959,7 +970,7 @@ def _archive_raw_failure_info(
redacted_error=parse_err or val_err,
lifecycle=cast(
Literal["deferred", "terminal", "unexplained"],
lifecycle_by_raw_id.get(str(row[0]), "unexplained"),
lifecycle_by_raw_id.get(raw_id, "unexplained"),
),
)
)
Expand Down
159 changes: 110 additions & 49 deletions polylogue/storage/raw_failure_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def read_raw_failure_lifecycle(source_db: Path, *, sample_limit: int = 10) -> Ra
logger.warning("could not open source.db read-only", exc_info=exc)
return RawFailureLifecycleSnapshot(False, reason=f"could not open source.db read-only: {exc}")
try:
conn.execute("BEGIN")
raw_table = conn.execute(
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'raw_sessions'"
).fetchone()
Expand All @@ -129,43 +130,95 @@ def read_raw_failure_lifecycle(source_db: Path, *, sample_limit: int = 10) -> Ra
conn.execute("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'raw_artifacts'").fetchone()
is not None
)
if has_artifacts:
failure_query = """
SELECT r.raw_id, r.origin, r.validation_status,
(
SELECT a.artifact_kind
FROM raw_artifacts AS a
WHERE a.raw_id = r.raw_id
AND a.origin = r.origin
AND a.source_path = r.source_path
AND a.source_index = r.source_index
ORDER BY a.last_observed_at_ms DESC, a.artifact_id DESC
LIMIT 1
) AS artifact_kind
,(
SELECT a.support_status
FROM raw_artifacts AS a
WHERE a.raw_id = r.raw_id
AND a.origin = r.origin
AND a.source_path = r.source_path
AND a.source_index = r.source_index
ORDER BY a.last_observed_at_ms DESC, a.artifact_id DESC
LIMIT 1
) AS support_status
sample_limit = max(0, sample_limit)
failed_cte = """
WITH failed AS (
SELECT r.raw_id, r.origin, r.source_path, r.source_index,
r.validation_status, r.acquired_at_ms
FROM raw_sessions AS r
WHERE (r.parse_error IS NOT NULL AND TRIM(r.parse_error) != '')
OR r.validation_status = 'failed'
ORDER BY r.acquired_at_ms DESC, r.raw_id DESC
"""
)
"""
latest_artifact_join = """
LEFT JOIN raw_artifacts AS a
ON a.raw_id = f.raw_id
AND a.origin = f.origin
AND a.source_path = f.source_path
AND a.source_index = f.source_index
AND NOT EXISTS (
SELECT 1
FROM raw_artifacts AS newer
WHERE newer.raw_id = a.raw_id
AND newer.origin = a.origin
AND newer.source_path = a.source_path
AND newer.source_index = a.source_index
AND (newer.last_observed_at_ms > a.last_observed_at_ms
OR (newer.last_observed_at_ms = a.last_observed_at_ms
AND newer.artifact_id > a.artifact_id))
)
"""
Comment on lines +143 to +160

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect raw_artifacts DDL and indexes in the archive schema.
set -euo pipefail

fd -e sql -e py . polylogue/storage --exec rg -n -C4 'raw_artifacts' {} \
  | rg -n -C4 'CREATE (TABLE|UNIQUE )?INDEX|CREATE TABLE|PRIMARY KEY|UNIQUE'

Repository: Sinity/polylogue

Length of output: 8626


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- target file outline/section ---\n'
wc -l polylogue/storage/raw_failure_lifecycle.py
sed -n '1,220p' polylogue/storage/raw_failure_lifecycle.py

printf '\n--- raw_artifacts DDL/index occurrences ---\n'
rg -n -C6 'CREATE TABLE raw_artifacts|CREATE INDEX idx_raw_artifacts_raw_id|CREATE UNIQUE INDEX idx_raw_artifacts_source_identity|idx_raw_artifacts' polylogue/storage

Repository: Sinity/polylogue

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- target file section ---'
wc -l polylogue/storage/raw_failure_lifecycle.py
sed -n '1,220p' polylogue/storage/raw_failure_lifecycle.py

printf '%s\n' ''
printf '%s\n' '--- raw_artifacts DDL/index occurrences ---'
rg -n -C6 'CREATE TABLE raw_artifacts|CREATE INDEX idx_raw_artifacts_raw_id|CREATE UNIQUE INDEX idx_raw_artifacts_source_identity|idx_raw_artifacts' polylogue/storage

Repository: Sinity/polylogue

Length of output: 18383


🌐 Web query:

SQLite left join ON column NOT EXISTS correlated subquery index raw_id origin last_observed_at_ms artifact_id performance

💡 Result:

In SQLite, optimizing a query involving a LEFT JOIN or a NOT EXISTS correlated subquery relies heavily on indexing the columns used in the join or correlation condition [1][2][3][4]. Performance considerations: 1. Indexing: For a LEFT JOIN, the column on the right-hand table must be indexed to allow the query planner to efficiently search for matches [3]. Similarly, for a correlated NOT EXISTS subquery, the columns in the subquery's WHERE clause that link to the outer query must be indexed to allow the subquery to perform a search rather than a full table scan [2][4]. 2. LEFT JOIN vs. NOT EXISTS: While often functionally equivalent (especially when checking for non-existence via IS NULL), they are handled differently by the query planner [1][2]. A LEFT JOIN forces the database to construct a join result, which may be less efficient than a NOT EXISTS clause if the query can be short-circuited [1]. A NOT EXISTS clause, when properly indexed, allows the database to stop searching the subquery as soon as a single match is found [1]. 3. Correlated Subquery Overhead: In older or specific configurations, correlated subqueries were sometimes routed to a slower in-memory interpreter, leading to significant performance degradation [5]. However, in modern, standard SQLite, a correlated subquery that references an indexed column in the inner table should be executed efficiently using SEARCH operations [2][4]. Ensure your column affinities match, as mismatched types can sometimes prevent the optimizer from using an index [4]. 4. Optimizer Improvements: Recent versions of SQLite (as of 2026) have introduced enhanced optimizations, including EXISTS-to-JOIN transformations, which attempt to automatically convert EXISTS clauses into JOINs when the query planner determines it is more efficient [6]. Recommendations for your schema: - Ensure that the foreign key or join columns (such as artifact_id or raw_id) are explicitly indexed in the target tables [4]. - If you are frequently filtering by last_observed_at_ms, consider a composite index that includes the join/correlation column and the observation timestamp to further speed up the lookups [7]. - Use EXPLAIN QUERY PLAN to verify that your query is using SEARCH TABLE rather than SCAN TABLE for your subqueries or join operations [2][3][4]. If you see SCAN TABLE, the database is likely performing a full table scan, which indicates a missing or ineffective index [2][3].

Citations:


Add a composite index for the latest-artifact probe.

idx_raw_artifacts_raw_id supports the LEFT JOIN, but the NOT EXISTS probe also filters by origin, source_path, and source_index before using last_observed_at_ms/artifact_id. Add an index covering (raw_id, origin, source_path, source_index, last_observed_at_ms) for the latest-artifact probe, or use an indexed windowed/lateral query with the same index coverage.

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

In `@polylogue/storage/raw_failure_lifecycle.py` around lines 143 - 160, Add a
composite index covering raw_id, origin, source_path, source_index, and
last_observed_at_ms for the latest-artifact lookup used by latest_artifact_join,
and ensure it is created through the existing schema/index initialization path.

if has_artifacts:
summary_sql = (
failed_cte
+ """
SELECT f.origin, f.validation_status, a.artifact_kind, a.support_status,
COUNT(*) AS failure_count
FROM failed AS f
"""
+ latest_artifact_join
+ """
GROUP BY f.origin, f.validation_status, a.artifact_kind, a.support_status
ORDER BY f.origin, f.validation_status, a.artifact_kind, a.support_status
"""
)
sample_sql = (
failed_cte
+ """
, sampled AS (
SELECT f.raw_id, f.origin, f.validation_status, f.acquired_at_ms,
a.artifact_kind, a.support_status
FROM failed AS f
"""
+ latest_artifact_join
+ """
ORDER BY CASE
WHEN f.validation_status = 'failed' THEN 0
WHEN (a.artifact_kind, a.support_status) IN (
('deferred_hot_jsonl_capture', 'partial_decode'),
('terminal_corrupt_input', 'decode_failed'),
('terminal_unsupported_shape', 'unsupported_parseable')
) THEN 1
ELSE 2
END,
f.acquired_at_ms DESC, f.raw_id DESC
LIMIT ?
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
SELECT raw_id, origin, validation_status, artifact_kind, support_status
FROM sampled
"""
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
else:
failure_query = """
SELECT r.raw_id, r.origin, r.validation_status, NULL AS artifact_kind, NULL AS support_status
FROM raw_sessions AS r
WHERE (r.parse_error IS NOT NULL AND TRIM(r.parse_error) != '')
OR r.validation_status = 'failed'
ORDER BY r.acquired_at_ms DESC, r.raw_id DESC
"""
failed_rows = conn.execute(failure_query).fetchall()
summary_sql = (
failed_cte
+ """
SELECT f.origin, f.validation_status, NULL, NULL, COUNT(*) AS failure_count
FROM failed AS f
GROUP BY f.origin, f.validation_status
ORDER BY f.origin, f.validation_status
"""
)
sample_sql = (
failed_cte
+ """
SELECT raw_id, origin, validation_status, NULL, NULL
FROM failed
ORDER BY acquired_at_ms DESC, raw_id DESC
LIMIT ?
"""
)
summary_rows = conn.execute(summary_sql).fetchall()
sample_rows = conn.execute(sample_sql, (sample_limit,)).fetchall()
except sqlite3.Error as exc:
logger.warning("could not read raw failure lifecycle", exc_info=exc)
return RawFailureLifecycleSnapshot(False, reason=f"could not read raw failure lifecycle: {exc}")
Expand All @@ -176,25 +229,33 @@ def read_raw_failure_lifecycle(source_db: Path, *, sample_limit: int = 10) -> Ra
by_artifact_kind: Counter[str] = Counter()
counts: Counter[str] = Counter()
samples: list[dict[str, str | None]] = []
for row in failed_rows:
for row in summary_rows:
origin = str(row[0] or "unknown")
artifact_kind = str(row[2]) if row[2] is not None else None
support_status = str(row[3]) if row[3] is not None else None
validation_failed = str(row[1] or "") == "failed"
lifecycle = _lifecycle(artifact_kind, support_status, validation_failed=validation_failed)
count = int(row[4])
counts[lifecycle] += count
by_origin[origin] += count
by_artifact_kind[artifact_kind or "<none>"] += count
for row in sample_rows:
origin = str(row[1] or "unknown")
artifact_kind = str(row[3]) if row[3] is not None else None
support_status = str(row[4]) if row[4] is not None else None
validation_failed = str(row[2] or "") == "failed"
lifecycle = _lifecycle(artifact_kind, support_status, validation_failed=validation_failed)
counts[lifecycle] += 1
by_origin[origin] += 1
by_artifact_kind[artifact_kind or "<none>"] += 1
if len(samples) < max(0, sample_limit):
samples.append(
{
"raw_id": str(row[0]),
"origin": origin,
"artifact_kind": artifact_kind,
"support_status": support_status,
"lifecycle": lifecycle,
}
)
samples.append(
{
"raw_id": str(row[0]),
"origin": origin,
"artifact_kind": artifact_kind,
"support_status": support_status,
"lifecycle": _lifecycle(
artifact_kind,
support_status,
validation_failed=str(row[2] or "") == "failed",
),
}
)
return RawFailureLifecycleSnapshot(
available=True,
parse_failures=parse_failures,
Expand Down
56 changes: 55 additions & 1 deletion polylogue/storage/sqlite/archive_tiers/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,15 +312,69 @@ def initialize_archive_database(
def initialize_active_archive_root(root: Path) -> None:
"""Create or initialize every tier database in an archive root."""
from polylogue.storage.archive_identity import ArchiveLocation, OwnedArchiveLocation
from polylogue.storage.sqlite.durable_change_train import (
_record_fresh_durable_bootstrap,
_record_fresh_durable_bootstrap_intent,
_validate_fresh_durable_bootstrap_intent,
)

with OwnedArchiveLocation.acquire(
ArchiveLocation.resolve(root),
owner_id=f"bootstrap:{os.getpid()}",
allow_reentrant=True,
):
reconcile_durable_change_trains_on_startup(root)
# Classify the archive after acquiring ownership. Another process may
# publish a marker or durable train while the probe is in flight.
durable_tier_exists = any(
(root / archive_tier_spec(tier).filename).exists() for tier in DURABLE_MIGRATION_TIERS
)
manifest_root = root / ".maintenance-state" / "durable-change-trains"
has_durable_train_state = any(manifest_root.glob("*.json"))
has_bootstrap_marker = (manifest_root / ".bootstrap").is_file()
pending_bootstrap_path = manifest_root / ".bootstrap.pending"
has_pending_bootstrap = pending_bootstrap_path.is_file()
if has_pending_bootstrap:
_validate_fresh_durable_bootstrap_intent(root)
Comment on lines +336 to +337

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 Ignore stale intent after completed bootstrap

If the process or host stops after the completed .bootstrap marker is published but before .bootstrap.pending is durably removed, both files remain. After upgrading to a runtime with newer durable schema versions, this unconditional intent validation compares the pending receipt's old target versions with the new ARCHIVE_VERSION_BY_TIER values and raises before the completed marker can establish the legitimate migration baseline, blocking both startup and the numbered upgrade. When a completed marker exists, validate it first and treat a matching pending file as crash residue rather than requiring the old intent to target the current runtime.

Useful? React with 👍 / 👎.

if has_durable_train_state:
raise RuntimeError(
"fresh durable bootstrap intent conflicts with durable train state; "
"refusing to guess which authority is current"
)
fresh_durable_bootstrap = (
not durable_tier_exists
and not has_durable_train_state
and not has_bootstrap_marker
and not has_pending_bootstrap
)
recovering_fresh_durable_bootstrap = fresh_durable_bootstrap or (
has_pending_bootstrap and not has_bootstrap_marker
)
pre_marker_adoption = (
(root / archive_tier_spec(ArchiveTier.SOURCE).filename).is_file()
and all((root / archive_tier_spec(tier).filename).is_file() for tier in DURABLE_MIGRATION_TIERS)
and manifest_root.is_dir()
and not has_durable_train_state
and not has_bootstrap_marker
Comment on lines +352 to +357

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 every durable tier before adoption

When a pre-marker archive still has source.db but user.db is missing, this source-only condition selects adoption, and the subsequent initialization loop silently creates an empty user.db before the marker is recorded. Startup then treats that replacement as the authenticated baseline instead of reporting loss of the irreplaceable assertions tier; require all durable tiers to exist and be safe before adoption, leaving missing-tier recovery to the explicit maintenance path.

AGENTS.md reference: AGENTS.md:L118-L121

Useful? React with 👍 / 👎.

and not has_pending_bootstrap
)
Comment on lines +353 to +359

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 Refuse unauthenticated pre-marker adoption

When a post-adoption archive loses its durable-change-train directory, these absence checks classify it as a legacy pre-marker archive and skip reconciliation; _adopt_pre_marker_durable_bootstrap() then checks only the current schema and writes a new marker whose floor is the live version. Deleting the manifests therefore erases the required released-train chain and admits unreceipted durable contents without the verified migration/backup evidence this change otherwise enforces. Fresh evidence in this revision is that legacy status is inferred solely from missing state; require independently authenticated legacy provenance rather than absence alone.

AGENTS.md reference: AGENTS.md:L189-L192

Useful? React with 👍 / 👎.

if fresh_durable_bootstrap:
_record_fresh_durable_bootstrap_intent(root)
if not recovering_fresh_durable_bootstrap and not pre_marker_adoption:
reconcile_durable_change_trains_on_startup(root)
for spec in ARCHIVE_TIER_SPECS.values():
initialize_archive_database(root / spec.filename, spec.tier)
if recovering_fresh_durable_bootstrap:
_record_fresh_durable_bootstrap(root)
Comment on lines 365 to +367

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 Record bootstrap intent before initializing tier files

On a brand-new archive, if a later initialize_archive_database call raises after source.db has been created—for example, embeddings initialization cannot load sqlite-vec—or the process exits during this loop, _record_fresh_durable_bootstrap is never reached. The next invocation sees an existing durable tier but no marker or train state, enters startup reconciliation, and rejects the current source schema for lacking its released-train chain, so even fixing the original initialization failure cannot resume bootstrap without manually deleting files. Persist an in-progress bootstrap receipt before creating the first durable file, or otherwise recognize and safely recover this partial-fresh-bootstrap state.

Useful? React with 👍 / 👎.

elif pre_marker_adoption:
from polylogue.storage.sqlite.durable_change_train import _adopt_pre_marker_durable_bootstrap

_adopt_pre_marker_durable_bootstrap(root)
reconcile_durable_change_trains_on_startup(root)
elif has_pending_bootstrap:
# A crash after publishing the completed marker but before
# removing the intent is harmless. Keep the intent until the
# completed marker has passed normal startup reconciliation.
pending_bootstrap_path.unlink(missing_ok=True)


def reconcile_durable_change_trains_on_startup(root: Path) -> tuple[Path, ...]:
Expand Down
Loading