Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bae76c4
fix(storage): persist typed raw failure authority evidence
Sinity Aug 8, 2026
3e176b6
fix(status): preserve typed raw failure projections
Sinity Aug 8, 2026
51e9029
fix(storage): align raw repair failure projection
Sinity Aug 8, 2026
4e005cd
test(ingest): exercise append stream parse failures
Sinity Aug 8, 2026
60e5b5f
fix(storage): close raw failure authority review gaps
Sinity Aug 8, 2026
44d99e4
fix(storage): validate raw failure support carriers
Sinity Aug 8, 2026
d9adce7
fix(sources): retain unknown JSONL decode failures
Sinity Aug 8, 2026
34a5213
fix(status): sample all typed raw failure evidence
Sinity Aug 8, 2026
755d79c
fix(pipeline): preserve worker failure diagnostics
Sinity Aug 8, 2026
08248bc
fix: bind raw repair evidence to exact coordinates
Sinity Aug 8, 2026
7809f92
fix: fail closed on malformed raw evidence
Sinity Aug 8, 2026
a5ae2f8
fix: expire resolved deferred CAS evidence
Sinity Aug 9, 2026
f0eb5d7
fix(sources): count final JSONL decode failures
Sinity Aug 9, 2026
7dca149
fix(storage): keep hot captures out of retained replay
Sinity Aug 9, 2026
6ca5804
fix(storage): expire CAS evidence in batched success
Sinity Aug 9, 2026
5871c41
fix(storage): retain raw failure evidence per retained raw
Sinity Aug 9, 2026
f51226d
fix(parser): report physical JSONL failure lines
Sinity Aug 9, 2026
61f4a34
fix(storage): persist batch raw failure authority
Sinity Aug 9, 2026
727e703
fix(storage): close raw failure lifecycle gaps
Sinity Aug 9, 2026
4271c4d
fix(storage): align raw failure lifecycle authority
Sinity Aug 9, 2026
0819249
test(storage): cover raw failure authority routes
Sinity Aug 9, 2026
1fee118
fix(storage): retire stale raw failure evidence
Sinity Aug 9, 2026
7bdfd36
fix(ingest): preserve typed live failure evidence
Sinity Aug 9, 2026
6497866
test(storage): type raw failure status samples
Sinity Aug 9, 2026
bca470a
chore(devtools): ratchet stale layering inventory
Sinity Aug 9, 2026
54cd4e6
fix: retire stale raw failure authority
Sinity Aug 9, 2026
8749ff4
test: keep historical source fixtures valid
Sinity Aug 9, 2026
c92c4c4
test: model pre-v30 raw failure indexes
Sinity Aug 9, 2026
c9b9d42
fix: preserve raw failure authority across repair paths
Sinity Aug 9, 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
15 changes: 0 additions & 15 deletions docs/plans/layering-surface-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,6 @@
"file": "polylogue/cli/archive_query.py",
"import": "polylogue.storage.sqlite.archive_tiers.write"
},
{
"target": "polylogue/cli",
"file": "polylogue/cli/click_app.py",
"import": "polylogue.storage.archive_identity"
},
{
"target": "polylogue/cli",
"file": "polylogue/cli/click_app.py",
Expand Down Expand Up @@ -1444,11 +1439,6 @@
"file": "polylogue/daemon/similarity.py",
"import": "polylogue.storage.archive_identity"
},
{
"target": "polylogue/daemon",
"file": "polylogue/daemon/similarity.py",
"import": "polylogue.storage.sqlite.sqlite_vec_extension"
},
{
"target": "polylogue/daemon",
"file": "polylogue/daemon/status.py",
Expand Down Expand Up @@ -1554,11 +1544,6 @@
"file": "polylogue/mcp/server_prompts.py",
"import": "polylogue.storage.sqlite.archive_tiers.archive"
},
{
"target": "polylogue/mcp",
"file": "polylogue/mcp/server_prompts.py",
"import": "polylogue.storage.sqlite.archive_tiers.write"
},
{
"target": "polylogue/mcp",
"file": "polylogue/mcp/server_resources.py",
Expand Down
2 changes: 2 additions & 0 deletions docs/plans/layering.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ writer_modules:
[admit_raw_and_parsed_result, apply_raw_membership_classification, apply_raw_revision_replay,
classify_raw_revision_cohort_for_frozen_candidate,
classify_raw_revision_cohort_for_live_watch, classify_raw_revision_cohort_for_rebuild_repair,
finalize_raw_parse_state,
mark_raw_parse_failed,
release_provisional_full_revisions, replace_raw_membership_census,
write_parsed_for_retained_raw, write_parsed_for_retained_raw_result, write_raw_and_parsed,
write_raw_and_parsed_result, write_raw_blob_and_parsed, write_raw_blob_and_parsed_result]
Expand Down
7 changes: 7 additions & 0 deletions polylogue/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ class PolylogueError(Exception):
http_status_code: int = HTTPStatus.INTERNAL_SERVER_ERROR


class RawCASFrontierError(PolylogueError):
"""Retryable compare-and-swap conflict while advancing raw authority."""

is_transient = True


class DatabaseError(PolylogueError):
"""Base class for database errors."""

Expand Down Expand Up @@ -93,5 +99,6 @@ def __init__(self, message: str, *, readiness_status: str) -> None:
"DatabaseError",
"EmbeddingRetrievalNotReadyError",
"PolylogueError",
"RawCASFrontierError",
"SchemaVersionMismatchError",
]
171 changes: 167 additions & 4 deletions polylogue/core/raw_failure_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from __future__ import annotations

import json
from enum import StrEnum

from polylogue.core.enums import ArtifactSupportStatus
Expand All @@ -16,35 +17,197 @@ class RawFailureEvidenceKind(StrEnum):
"""Durable lifecycle evidence attached to a retained raw artifact."""

DEFERRED_HOT_JSONL_CAPTURE = "deferred_hot_jsonl_capture"
DEFERRED_CLAUDE_CODE_PARTIAL_JSONL = "deferred_claude_code_partial_jsonl"
DEFERRED_CAS_FRONTIER = "deferred_cas_frontier"
# Historical rows written before CAS evidence was made provider-neutral.
# Keep this token readable until a backup-gated migration or re-observation
# receipt has converted every retained row.
DEFERRED_CODEX_CAS_FRONTIER = "deferred_codex_cas_frontier"
TERMINAL_CORRUPT_INPUT = "terminal_corrupt_input"
TERMINAL_SUPERSEDED_DEFERRED_CAS_FRONTIER = "terminal_superseded_deferred_cas_frontier"
TERMINAL_UNKNOWN_JSON_DECODE = "terminal_unknown_json_decode"
TERMINAL_UNKNOWN_EXPORT_NO_SESSION = "terminal_unknown_export_no_session"
Comment on lines +20 to +29

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 Prioritize every new typed kind in failure samples

When more than the 10 sampled raw failures exist, the lifecycle reader's ORDER BY CASE still recognizes only the three pre-existing kind/status pairs. The newly added Claude partial, CAS-frontier, unknown-decode, and unknown-no-session kinds therefore fall into the same lowest-priority bucket as unexplained failures and can be omitted from daemon status even when they are newer, while older recognized failures occupy the sample; update the sample predicate from the closed evidence vocabulary so these typed failures remain inspectable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Preserved. Lifecycle sampling is driven by the complete closed evidence vocabulary and support pairs, so every typed kind remains inspectable.

TERMINAL_UNSUPPORTED_SHAPE = "terminal_unsupported_shape"

@property
def support_status(self) -> ArtifactSupportStatus:
if self is RawFailureEvidenceKind.DEFERRED_HOT_JSONL_CAPTURE:
if self is RawFailureEvidenceKind.TERMINAL_SUPERSEDED_DEFERRED_CAS_FRONTIER:
return ArtifactSupportStatus.UNKNOWN
if self in {
RawFailureEvidenceKind.DEFERRED_HOT_JSONL_CAPTURE,
RawFailureEvidenceKind.DEFERRED_CLAUDE_CODE_PARTIAL_JSONL,
RawFailureEvidenceKind.DEFERRED_CAS_FRONTIER,
RawFailureEvidenceKind.DEFERRED_CODEX_CAS_FRONTIER,
}:
return ArtifactSupportStatus.PARTIAL_DECODE
if self is RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT:
if self in {
RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT,
RawFailureEvidenceKind.TERMINAL_UNKNOWN_JSON_DECODE,
}:
return ArtifactSupportStatus.DECODE_FAILED
return ArtifactSupportStatus.UNSUPPORTED_PARSEABLE

@property
def lifecycle(self) -> str:
return "deferred" if self is RawFailureEvidenceKind.DEFERRED_HOT_JSONL_CAPTURE else "terminal"
if self is RawFailureEvidenceKind.TERMINAL_SUPERSEDED_DEFERRED_CAS_FRONTIER:
return "resolution"
return "deferred" if self.value in RAW_FAILURE_DEFERRED_EVIDENCE_KINDS else "terminal"


RAW_FAILURE_TRUSTED_PROVENANCE = "worker-disposition-v1"
RAW_FAILURE_VALIDATION_FAILURE_KINDS = frozenset(
{
RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT.value,
RawFailureEvidenceKind.TERMINAL_UNKNOWN_JSON_DECODE.value,
}
)


def raw_failure_classification_reason(
*,
diagnostic: str | None,
evidence_ref: str | None,
outcome_code: str,
remediation: str | None,
retryable: bool | None,
trusted_validation_failure: bool,
) -> str:
"""Encode the typed carrier, including proof for a validation failure."""
payload: dict[str, object] = {
"diagnostic": diagnostic,
"evidence_ref": evidence_ref,
"outcome_code": outcome_code,
"remediation": remediation,
"retryable": retryable,
}
if trusted_validation_failure:
payload["provenance"] = RAW_FAILURE_TRUSTED_PROVENANCE
return json.dumps(payload, sort_keys=True, separators=(",", ":"))

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 | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the repository JSON helper contract and existing usage conventions.
fd -t f 'json.py' polylogue/core --exec sed -n '380,440p'
rg -nP --type=py '\bjson\.dumps\s*\(' polylogue | head -50
rg -nP --type=py 'from polylogue\.core\.json import' polylogue | head -30

Repository: Sinity/polylogue

Length of output: 11237


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file outline =="
ast-grep outline polylogue/core/raw_failure_evidence.py || true

echo "== relevant file contents =="
cat -n polylogue/core/raw_failure_evidence.py | sed -n '1,140p'

echo "== json.py relevant exports/import helpers =="
cat -n polylogue/core/json.py | sed -n '320,455p'
grep -n '^def ' polylogue/core/json.py | sed -n '1,80p'

echo "== calls to raw_failure helpers around json =="
rg -n 'raw_failure_outcome_code|has_trusted_raw_failure_provenance|load_json_json|dump_json_json|json.dumps|json.loads|from polylogue\.core\.import json|import .*json' polylogue/core raw_failure_evidence.py polylogue/core/raw_failure_evidence.py 2>/dev/null || true

Repository: Sinity/polylogue

Length of output: 18231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== backend defaults and config =="
grep -nE '_BACKEND|BACKEND|core\.json|dumps_bytes|load_json_json|dump_json_json|canonical_json|to_payload|from_dict|read.*json|write.*json' polylogue/core/raw_failure_evidence.py polylogue/core/raw_failure_evidence.py 2>/dev/null || true
rg -nE 'class RawFailure|raw_failure_classification_reason|validated_raw_failure_evidence_kind|load_json_json|dump_json_json|to_payload|read.*json|write.*json' polylogue --python -g '*.py' | head -120

echo "== deterministic json contract probe =="
python3 - <<'PY'
import ast
from pathlib import Path

json_path = Path("polylogue/core/json.py")
tree = ast.parse(json_path.read_text())
for func_name in ("_BACKEND", "dumps", "dumps_bytes", "loads"):
    nodes = [n for n in tree.body if isinstance(n, (ast.Assign, ast.AnnAssign)) and any(getattr(t, "id", None) == func_name for t in ast.walk(n.target)) if isinstance(n, ast.Assign)] or [n for n in tree.body if isinstance(n, ast.Assign)]
print("__BACKEND assignment candidates")
for node in [n for n in ast.walk(tree) if isinstance(n, ast.Assign)]:
    for target in node.targets:
        if isinstance(target, ast.Name) and target.id == "_BACKGET":
            print(ast.get_source_segment(json_path.read_text(), node))
for node in tree.body:
    if isinstance(node, ast.Assign):
        for target in node.targets:
            if isinstance(target, ast.Name) and target.id == "_BACKEND":
                print(f"_BACKEND line={node.lineno} value={ast.unparse(node.value)}")
print("dumps signature:", ast.get_source_segment(json_path.read_text(), next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == "dumps")))
PY

Repository: Sinity/polylogue

Length of output: 403


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant searches without -E flag =="
rg -n -g '*.py' 'class RawFailure|raw_failure_classification_reason|validated_raw_failure_evidence_kind|load_json_json|dump_json_json|to_payload|read.*json|write.*json|_BACKEND|_BACKGET' polylogue | head -200

echo "== json.py top imports and backend assignments =="
sed -n '1,120p' polylogue/core/json.py | cat -n

echo "== deterministic json contract probe =="
python3 - <<'PY'
import ast
from pathlib import Path

json_path = Path("polylogue/core/json.py")
src = json_path.read_text()
tree = ast.parse(src)

for node in tree.body:
    if isinstance(node, ast.Assign):
        for target in node.targets:
            if isinstance(target, ast.Name) and target.id == "_BACKEND":
                print(f"line={node.lineno} value={ast.unparse(node.value)}")
    if isinstance(node, ast.FunctionDef):
        if node.name == "dumps":
            print("dumps_function=")
            print(ast.get_source_segment(src, node))
        elif node.name == "dumps_bytes":
            print("dumps_bytes_function_signature=")
            print(src.splitlines()[node.lineno-1])

with open("temp_json_check.py", "w", encoding="utf-8") as f:
    f.write('''
def stdlib_compact_sort(payload):
    import json
    return json.dumps(payload, sort_keys=True, separators=(",", ":"))

def core_json_string_via_dumps_bytes(payload):
    import json as _stdlib_json
    separators = (",", ":")
    return _stdlib_json.dumps(
        payload, default=None, sort_keys=True, indent=None, separators=separators, ensure_ascii=False,
    ).encode("utf-8").decode("utf-8")

payload = {"a": 1, "b": 2}
print(stdlib_compact_sort(payload) == core_json_string_via_dumps_bytes(payload))
''')
PY
python3 temp_json_check.py
rm temp_json_check.py

Repository: Sinity/polylogue

Length of output: 28364


Use polylogue.core.json.dumps for the failure carrier.

raw_failure_classification_reason and raw_failure_outcome_code both use JSON for a shared, persisted artifact. This carrier needs consistent encoding/decoding behavior through the repository JSON facade so the comparison stays invariant.

🤖 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/core/raw_failure_evidence.py` at line 85, Update the JSON
serialization in the failure carrier’s encoding function to call
polylogue.core.json.dumps instead of the standard json.dumps, preserving the
existing payload ordering and compact separator options so
raw_failure_classification_reason and raw_failure_outcome_code remain
consistently comparable.

Source: Linters/SAST tools



def has_trusted_raw_failure_provenance(
classification_reason: object,
*,
artifact_kind: RawFailureEvidenceKind,
outcome_code: object,
) -> bool:
"""Check the structural worker receipt required for corrupt evidence."""
if artifact_kind.value not in RAW_FAILURE_VALIDATION_FAILURE_KINDS:
return False
if str(outcome_code) != "corrupt_input":
return False
if not isinstance(classification_reason, str):
return False
try:
payload = json.loads(classification_reason)
except (TypeError, ValueError):
return False
return isinstance(payload, dict) and payload.get("provenance") == RAW_FAILURE_TRUSTED_PROVENANCE


def raw_failure_outcome_code(classification_reason: object) -> object:
"""Read the typed outcome code from a structured failure carrier."""
if not isinstance(classification_reason, str):
return None
try:
payload = json.loads(classification_reason)
except (TypeError, ValueError):
return None
return payload.get("outcome_code") if isinstance(payload, dict) else None


def validated_raw_failure_evidence_kind(
artifact_kind: object,
support_status: object,
*,
validation_failed: bool,
classification_reason: object = None,
outcome_code: object = None,
) -> RawFailureEvidenceKind | None:
"""Return a typed kind only for a complete, self-consistent carrier.

Decode failures are reported by the worker as validation failures because
the payload cannot satisfy the input contract. A matching terminal
corrupt-input/decode carrier explains that state; deferred evidence still
requires validation to have passed or been skipped.
"""
if artifact_kind is None or support_status is None:
return None
try:
evidence_kind = RawFailureEvidenceKind(str(artifact_kind))
except ValueError:
return None
if evidence_kind.support_status.value != str(support_status):
return None
if validation_failed and not has_trusted_raw_failure_provenance(
classification_reason,
artifact_kind=evidence_kind,
outcome_code=outcome_code,
):
Comment on lines +142 to +146

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 Preserve authority from reviewed corrupt-input dispositions

When an operator applies terminal_corrupt_input to a historical raw whose validation_status is failed, maintenance/raw_failure_disposition_apply.py::_apply_candidate writes the plain kind string as classification_reason; this new check requires a JSON worker-disposition-v1 provenance receipt and a corrupt_input outcome, so the reviewed, backup-gated disposition is still classified as unexplained and bulk-rebuild preflight remains blocked. Make the actuator emit an authoritative structured reason (or recognize its immutable disposition receipt) so its documented recovery workflow can actually close these historical failures.

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

Useful? React with 👍 / 👎.

return None
return evidence_kind


RAW_FAILURE_EVIDENCE_KINDS = frozenset(kind.value for kind in RawFailureEvidenceKind)
RAW_FAILURE_DEFERRED_EVIDENCE_KINDS = frozenset({RawFailureEvidenceKind.DEFERRED_HOT_JSONL_CAPTURE.value})
RAW_FAILURE_DEFERRED_EVIDENCE_KINDS = frozenset(
{
RawFailureEvidenceKind.DEFERRED_HOT_JSONL_CAPTURE.value,
RawFailureEvidenceKind.DEFERRED_CLAUDE_CODE_PARTIAL_JSONL.value,
RawFailureEvidenceKind.DEFERRED_CAS_FRONTIER.value,
RawFailureEvidenceKind.DEFERRED_CODEX_CAS_FRONTIER.value,
}
)
# Only frontier conflicts authorize retained-raw replay. Hot captures remain
# deferred until a complete source observation arrives; replaying their
# truncated blob would advance the cursor past the record that later bytes
# complete.
RAW_FAILURE_REPLAY_AUTHORITY_EVIDENCE_KINDS = frozenset(
{
RawFailureEvidenceKind.DEFERRED_CAS_FRONTIER.value,
RawFailureEvidenceKind.DEFERRED_CODEX_CAS_FRONTIER.value,
}
)
# Every deferred raw-failure carrier represents a partial decode. Consumers
# selecting retry authority must validate this companion field as well as the
# closed kind, or contradictory rows can authorize replay.
RAW_FAILURE_DEFERRED_SUPPORT_STATUS = ArtifactSupportStatus.PARTIAL_DECODE.value
RAW_FAILURE_EVIDENCE_SUPPORT_STATUS_PAIRS = tuple(
sorted((kind.value, kind.support_status.value) for kind in RawFailureEvidenceKind)
)
RAW_FAILURE_LIFECYCLE_EVIDENCE_SUPPORT_STATUS_PAIRS = tuple(
sorted(
(kind.value, kind.support_status.value)
for kind in RawFailureEvidenceKind
if kind.lifecycle in {"deferred", "terminal"}
)
)
RAW_FAILURE_TERMINAL_EVIDENCE_KINDS = frozenset(
{
RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT.value,
RawFailureEvidenceKind.TERMINAL_UNKNOWN_JSON_DECODE.value,
RawFailureEvidenceKind.TERMINAL_UNKNOWN_EXPORT_NO_SESSION.value,
RawFailureEvidenceKind.TERMINAL_UNSUPPORTED_SHAPE.value,
}
)
RAW_FAILURE_TERMINAL_EVIDENCE_SUPPORT_STATUS_PAIRS = tuple(
sorted((kind.value, kind.support_status.value) for kind in RawFailureEvidenceKind if kind.lifecycle == "terminal")
)
Comment on lines 184 to +194

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 | 🔵 Trivial | ⚡ Quick win

Derive RAW_FAILURE_TERMINAL_EVIDENCE_KINDS from the lifecycle property.

Lines 184-191 hand-list the terminal kinds. Lines 192-194 derive the terminal pairs from kind.lifecycle == "terminal". Two sources for one partition can drift when a new kind is added. Derive the set from the same property.

♻️ Proposed refactor
-RAW_FAILURE_TERMINAL_EVIDENCE_KINDS = frozenset(
-    {
-        RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT.value,
-        RawFailureEvidenceKind.TERMINAL_UNKNOWN_JSON_DECODE.value,
-        RawFailureEvidenceKind.TERMINAL_UNKNOWN_EXPORT_NO_SESSION.value,
-        RawFailureEvidenceKind.TERMINAL_UNSUPPORTED_SHAPE.value,
-    }
-)
+RAW_FAILURE_TERMINAL_EVIDENCE_KINDS = frozenset(
+    kind.value for kind in RawFailureEvidenceKind if kind.lifecycle == "terminal"
+)
📝 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
RAW_FAILURE_TERMINAL_EVIDENCE_KINDS = frozenset(
{
RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT.value,
RawFailureEvidenceKind.TERMINAL_UNKNOWN_JSON_DECODE.value,
RawFailureEvidenceKind.TERMINAL_UNKNOWN_EXPORT_NO_SESSION.value,
RawFailureEvidenceKind.TERMINAL_UNSUPPORTED_SHAPE.value,
}
)
RAW_FAILURE_TERMINAL_EVIDENCE_SUPPORT_STATUS_PAIRS = tuple(
sorted((kind.value, kind.support_status.value) for kind in RawFailureEvidenceKind if kind.lifecycle == "terminal")
)
RAW_FAILURE_TERMINAL_EVIDENCE_KINDS = frozenset(
kind.value for kind in RawFailureEvidenceKind if kind.lifecycle == "terminal"
)
RAW_FAILURE_TERMINAL_EVIDENCE_SUPPORT_STATUS_PAIRS = tuple(
sorted((kind.value, kind.support_status.value) for kind in RawFailureEvidenceKind if kind.lifecycle == "terminal")
)
🤖 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/core/raw_failure_evidence.py` around lines 184 - 194, Update
RAW_FAILURE_TERMINAL_EVIDENCE_KINDS to derive its values by filtering
RawFailureEvidenceKind for kind.lifecycle == "terminal", matching the derivation
used by RAW_FAILURE_TERMINAL_EVIDENCE_SUPPORT_STATUS_PAIRS. Remove the
hand-maintained terminal-kind list while preserving the resulting frozenset
type.



__all__ = [
"RAW_FAILURE_DEFERRED_EVIDENCE_KINDS",
"RAW_FAILURE_DEFERRED_SUPPORT_STATUS",
"RAW_FAILURE_EVIDENCE_KINDS",
"RAW_FAILURE_EVIDENCE_SUPPORT_STATUS_PAIRS",
"RAW_FAILURE_LIFECYCLE_EVIDENCE_SUPPORT_STATUS_PAIRS",
"RAW_FAILURE_REPLAY_AUTHORITY_EVIDENCE_KINDS",
"RAW_FAILURE_TERMINAL_EVIDENCE_KINDS",
"RAW_FAILURE_TERMINAL_EVIDENCE_SUPPORT_STATUS_PAIRS",
"RAW_FAILURE_TRUSTED_PROVENANCE",
"RAW_FAILURE_VALIDATION_FAILURE_KINDS",
"RawFailureEvidenceKind",
"has_trusted_raw_failure_provenance",
"raw_failure_classification_reason",
"raw_failure_outcome_code",
"validated_raw_failure_evidence_kind",
]
47 changes: 43 additions & 4 deletions polylogue/daemon/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sqlite3
import sys
import threading
from collections.abc import Callable
from collections.abc import Callable, Mapping
from datetime import UTC, datetime
from pathlib import Path
from typing import Any, Literal, cast
Expand All @@ -21,6 +21,7 @@
from polylogue.core.payload_coercion import required_str as _required_str
from polylogue.core.payload_coercion import row_float as _row_float
from polylogue.core.payload_coercion import row_int as _row_int
from polylogue.core.raw_failure_evidence import raw_failure_outcome_code, validated_raw_failure_evidence_kind
from polylogue.core.stats import percentile
from polylogue.daemon.catchup_status import (
CatchupStatus as CatchupStatus,
Expand Down Expand Up @@ -81,6 +82,21 @@

logger = get_logger(__name__)


def _authoritative_lifecycle_artifact_kind(sample: Mapping[str, object]) -> str | None:
"""Project a typed kind only after validating the complete lifecycle row."""
evidence_kind = validated_raw_failure_evidence_kind(
sample.get("artifact_kind"),
sample.get("support_status"),
validation_failed=str(sample.get("validation_status") or "") == "failed",
classification_reason=sample.get("classification_reason"),
outcome_code=raw_failure_outcome_code(sample.get("classification_reason")),
)
if evidence_kind is None or sample.get("lifecycle") != evidence_kind.lifecycle:
return None
return evidence_kind.value


# Backwards-compatible alias for the stuck threshold (#1246). The "stale"
# rollup field stays in the typed status payload to avoid breaking
# downstream consumers, but the threshold itself is owned by
Expand Down Expand Up @@ -399,7 +415,21 @@ class RawFailureSample(BaseModel):
:attr:`operation_id` and the typed planner :attr:`locator`.
"""

failure_kind: Literal["decode_error", "parse_error", "schema_violation", "maintenance", "unknown"]
failure_kind: Literal[
"decode_error",
"parse_error",
"schema_violation",
"maintenance",
"unknown",
"deferred_hot_jsonl_capture",
"deferred_claude_code_partial_jsonl",
"deferred_cas_frontier",
"deferred_codex_cas_frontier",
"terminal_corrupt_input",
"terminal_unknown_json_decode",
"terminal_unknown_export_no_session",
"terminal_unsupported_shape",
]
provider_hint: str | None = None
redacted_error: str = ""
source: Literal["ingest", "maintenance"] = "ingest"
Expand Down Expand Up @@ -933,6 +963,12 @@ def _archive_raw_failure_info(
for sample in lifecycle_snapshot.samples
if sample.get("raw_id") is not None and sample.get("lifecycle") is not None
}
validated_artifact_kind_by_raw_id = {
str(sample["raw_id"]): artifact_kind
for sample in lifecycle_snapshot.samples
if sample.get("raw_id") is not None
and (artifact_kind := _authoritative_lifecycle_artifact_kind(sample)) is not None
}
samples: list[RawFailureSample] = []
rows_by_raw_id: dict[str, sqlite3.Row | tuple[object, ...]] = {}
if sample_ids:
Expand All @@ -955,8 +991,11 @@ def _archive_raw_failure_info(
val_status = str(row[3] or "") if row[3] else ""
val_err = str(row[4] or "") if row[4] else ""
origin = str(row[1]) if row[1] else None
if "JSONDecodeError" in parse_err or "decode error" in parse_err.lower():
kind: Literal["decode_error", "parse_error", "schema_violation", "unknown"] = "decode_error"
artifact_kind = validated_artifact_kind_by_raw_id.get(raw_id)
if artifact_kind is not None:
kind = cast(Any, artifact_kind)
elif "JSONDecodeError" in parse_err or "decode error" in parse_err.lower():
kind = "decode_error"
elif val_status == "failed":
kind = "schema_violation"
elif parse_err:
Expand Down
17 changes: 15 additions & 2 deletions polylogue/maintenance/raw_failure_disposition_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from typing import cast

from polylogue.config import Config
from polylogue.core.raw_failure_evidence import RawFailureEvidenceKind
from polylogue.core.raw_failure_evidence import RawFailureEvidenceKind, raw_failure_classification_reason
from polylogue.maintenance.offline_guard import offline_maintenance_block_reason
from polylogue.paths import render_root
from polylogue.storage.sqlite.archive_tiers.types import ArchiveTier
Expand Down Expand Up @@ -166,7 +166,20 @@ def _apply_candidate(
(
candidate.disposition_kind.value,
candidate.disposition_kind.support_status.value,
candidate.disposition_kind.value,
raw_failure_classification_reason(
diagnostic=candidate.detail,
evidence_ref=f"raw-failure-disposition:{manifest_sha256}",
outcome_code=(
"corrupt_input"
if candidate.disposition_kind is RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT
else "unsupported_shape"
),
remediation="retain the reviewed terminal disposition until a forced reparse is authorized",
retryable=False,
trusted_validation_failure=(
candidate.disposition_kind is RawFailureEvidenceKind.TERMINAL_CORRUPT_INPUT
),
),
disposed_at_ms,
row["artifact_id"],
),
Expand Down
Loading