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
34 changes: 34 additions & 0 deletions polylogue/storage/raw_reconciler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,40 @@ def _apply_strategy(
duplicate_locked = _inspect_duplicate_raw_identity(
conn, root, item.raw_id, canonical_ids[0], logical_source_key
)
if duplicate_locked.status == "ineligible":

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 Classify the no-op sibling as nonblocking

When two or more fan-out plans share the canonical raw, this path records every later sibling as EXECUTED without changing its stale head. Postflight then classifies that unchanged sibling as UNRESOLVED_PROVENANCE (_classify_frontier, lines 520-524), which archive_readiness.py counts as blocking; the next daemon census persists it as an obligation but cannot select it because it is non-executable. Thus the exception is avoided, but raw-materialization readiness remains permanently false for this exact batch scenario. This no-op must also produce a nonblocking terminal frontier classification rather than merely returning success.

Useful? React with 👍 / 👎.

# polylogue-ewfp: a legitimate N:1 duplicate-alias fan-out
# terminal outcome, not a transient apply failure.
# Several sessions can share one stale raw as their
# accepted head; only ONE fold can ever land on the
# single available canonical twin. When several fan-out
# siblings are selected together from one pre-apply
# census snapshot (all looked executable before any of
# them committed), a sibling earlier in this same batch
# can already have claimed the canonical by the time
# this plan's own apply runs -- this plan is now
# permanently moot, not merely blocked pending a retry.
# The witness comparison below is skipped deliberately:
# it exists to catch data drift that would invalidate an
# ELIGIBLE fold between census and apply, which does not
# apply once the fold is no longer being attempted at
# all. Committing this empty transaction and returning
# normally (rather than raising) matches the existing
# "already_repaired" no-mutation-but-resolved path
# immediately below, so the caller records EXECUTED
# (permanently resolved) instead of RETRYABLE -- which
# previously required the postflight check to see this
# exact plan_id survive unchanged forever, crashing
# every subsequent raw-materialization pass that
# reached this fan-out group.
conn.commit()
return json_document(
{
"strategy": item.actuator.value,
"repaired_count": 0,
"already_repaired_count": 0,
"ineligible_reason": duplicate_locked.reason,
}
)
if _duplicate_strategy_witness(duplicate_locked) != item.strategy_witness:
raise RuntimeError("duplicate strategy proof changed after plan authorization")
if duplicate_locked.status == "eligible":
Expand Down
54 changes: 54 additions & 0 deletions tests/unit/storage/test_duplicate_raw_identity_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,3 +658,57 @@ def fake_inspect(
assert sibling_item.actuator is RawAuthorityActuator.NONE
assert not sibling_item.executable
assert "already an accepted head" in sibling_item.reason


def test_duplicate_alias_batch_race_does_not_crash_postflight(tmp_path: Path) -> None:
"""polylogue-ewfp regression: applying BOTH fan-out siblings together must not crash.

At a single pre-apply census snapshot, the canonical raw is still
genuinely dangling for every fan-out sibling, so a batch selection can
(and, live, did) select more than one sibling's fold together in the
SAME ``apply_raw_authority_frontier`` call. The first plan applied
commits and claims the canonical; the second plan's own re-inspection
inside ``_apply_strategy`` then legitimately finds ``status="ineligible"``
(not a transient failure). Before the fix, this raised, which the
caller's generic exception handler labeled ``RETRYABLE`` -- an outcome
the postflight check then required to remain byte-identical forever,
crashing every subsequent raw-materialization pass that reached this
fan-out group (observed live: a 405s writer-lock hold ending in
"raw authority postflight changed a retryable/carried-forward plan").
The second plan must instead resolve as a permanent, non-retryable
no-op.
"""
stale_raw_id, canonical_raw_id, heads = _seed_duplicate_raw_fanout(tmp_path)
(session_a, key_a), (session_b, key_b) = heads

preview = inspect_raw_authority_frontier(_config(tmp_path))
duplicate_items = {
item.logical_source_key: item
for item in preview.items
if item.raw_id == stale_raw_id and item.state is RawAuthorityFrontierState.DUPLICATE_ALIAS
}
assert set(duplicate_items) == {key_a, key_b}

# The regression: selecting BOTH siblings together must not raise.
report = apply_raw_authority_frontier(
_config(tmp_path),
preview_census_id=preview.census_id,
selected_plan_ids=(duplicate_items[key_a].plan_id, duplicate_items[key_b].plan_id),
)

assert report.selected_plan_count == 2
assert report.executed_plan_count == 2
assert report.retryable_plan_count == 0
assert report.success

with sqlite3.connect(tmp_path / "index.db") as conn:
heads_by_key = dict(
conn.execute(
"SELECT logical_source_key, accepted_raw_id FROM raw_revision_heads WHERE logical_source_key IN (?, ?)",
(key_a, key_b),
).fetchall()
)
# Exactly one sibling folded onto the canonical; the other's own head is
# untouched, still pointing at the (now-orphaned) stale raw -- correctly
# recognized as permanently ineligible rather than corrupted or retried.
assert sorted(heads_by_key.values()) == sorted((canonical_raw_id, stale_raw_id))