Skip to content

Commit f8e1d97

Browse files
feat: add evidence-backed Phase 4 completeness engine
Adds shadow per-source completeness proof from validated raw timeline structure, durable attempt evidence, observation coverage, independent watermarks, fail-closed Rust decisions, and production-image Rust packaging. Keeps legacy health/delivery authority unchanged pending real-source shadow validation.
1 parent 5f9f271 commit f8e1d97

15 files changed

Lines changed: 1137 additions & 3 deletions

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ SENTRY_DSN=
1313
# Webhook hosting. Render provides RENDER_EXTERNAL_URL automatically.
1414
# Set PUBLIC_BASE_URL only for another provider or a custom public origin.
1515
PUBLIC_BASE_URL=
16+
17+
# Phase 4 shadow truth; disabled is explicitly non-healthy, never a safety bypass.
18+
COMPLETENESS_ENGINE_MODE=shadow
19+
# Install/build the versioned Rust JSONL executable before enabling proof decisions.
20+
EDITORIAL_CORE_BINARY=jeonghan-editorial-core

.github/workflows/rust-editorial-core.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ on:
99
- "Cargo.toml"
1010
- "Cargo.lock"
1111
- "rust/**"
12+
- "app/completeness*.py"
13+
- "tests/test_completeness_engine.py"
1214
- ".github/workflows/rust-editorial-core.yml"
1315
pull_request:
1416
branches:
@@ -17,6 +19,8 @@ on:
1719
- "Cargo.toml"
1820
- "Cargo.lock"
1921
- "rust/**"
22+
- "app/completeness*.py"
23+
- "tests/test_completeness_engine.py"
2024
- ".github/workflows/rust-editorial-core.yml"
2125
workflow_dispatch:
2226

@@ -48,3 +52,16 @@ jobs:
4852

4953
- name: Test workspace
5054
run: cargo test --workspace --all-features
55+
56+
- name: Build JSONL executable
57+
run: cargo build --locked --workspace
58+
59+
- name: Python
60+
uses: actions/setup-python@v6
61+
with:
62+
python-version: "3.11"
63+
64+
- name: Python Rust completeness integration
65+
run: |
66+
python -m pip install -r requirements.txt
67+
python -m unittest discover -s tests -p test_completeness_engine.py -v

Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
FROM rust:1.98-slim AS editorial-core
2+
WORKDIR /build
3+
COPY Cargo.toml Cargo.lock ./
4+
COPY rust ./rust
5+
RUN cargo build --locked --release --workspace
6+
17
FROM python:3.11-slim
28

39
ENV PYTHONDONTWRITEBYTECODE=1 \
@@ -10,6 +16,7 @@ RUN apt-get update \
1016
&& rm -rf /var/lib/apt/lists/*
1117

1218
WORKDIR /app
19+
COPY --from=editorial-core /build/target/release/jeonghan-editorial-core /usr/local/bin/jeonghan-editorial-core
1320
COPY requirements.txt requirements-optional-media.txt ./
1421
RUN python -m pip install --no-cache-dir --upgrade "pip>=26.1.2" \
1522
&& python -m pip install --no-cache-dir -r requirements.txt \

app/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,12 @@
6363
# lazy-loaded inside that Daily-only hook so importing/running app.fic_digest does not
6464
# acquire a non-Fanfic Translation Fusion runtime dependency.
6565
from . import event_fusion_private_runtime as _event_fusion_private_runtime # noqa: F401,E402
66+
67+
# Phase 4 inspects raw top-level UserTweets instructions before the shadow runtime is
68+
# installed. This prevents pinned/self-quoted nested Tweet objects from acting as a
69+
# false lower-bound witness while leaving the compatibility collector unchanged.
70+
from . import completeness_provider_proof as _completeness_provider_proof # noqa: F401,E402
71+
72+
# Phase 4 shadow completeness wraps the final runtime bindings. Legacy production
73+
# health and delivery remain authoritative until real source evidence is reviewed.
74+
from . import completeness_runtime as _completeness_runtime # noqa: F401,E402

app/completeness_engine.py

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
"""Shadow completeness on the existing source-ledger SQLite connection."""
2+
from __future__ import annotations
3+
4+
import json
5+
import os
6+
import subprocess
7+
import uuid
8+
from datetime import datetime, timezone
9+
from typing import Any
10+
11+
from .completeness_evidence import TraversalEvidence
12+
from .source_ledger import SourceLedgerStore
13+
14+
15+
def utc(value) -> str:
16+
dt = value if isinstance(value, datetime) else datetime.fromisoformat(str(value).replace("Z", "+00:00"))
17+
if dt.tzinfo is None:
18+
raise ValueError("Completeness windows require timezone-aware timestamps")
19+
return dt.astimezone(timezone.utc).isoformat(timespec="microseconds")
20+
21+
22+
def core_request(op: str, **fields):
23+
binary = os.environ.get("EDITORIAL_CORE_BINARY", "jeonghan-editorial-core")
24+
process = subprocess.run(
25+
[binary], input=json.dumps({"contract_version": 1, "op": op, **fields}) + "\n",
26+
text=True, capture_output=True, timeout=5, check=True,
27+
)
28+
reply = json.loads(process.stdout)
29+
if reply.get("contract_version") != 1 or reply.get("ok") is not True:
30+
raise ValueError("Invalid editorial core response")
31+
return reply["result"]
32+
33+
34+
def proof_inputs(evidence: TraversalEvidence, error_class: str = "") -> tuple[dict[str, Any], dict[str, Any]]:
35+
"""Convert raw traversal facts into the conservative Rust proof contract.
36+
37+
The Rust v1 contract already understands a generic terminal bit. Python may set
38+
that bit from provider exhaustion OR a structurally proven lower boundary, but
39+
only after every top-level in-window ID exposed by those raw pages has reached
40+
the Phase 1 observation path.
41+
"""
42+
expected = {str(value) for value in evidence.expected_window_ids if str(value)}
43+
observed = {str(value) for value in evidence.observation_ids if str(value)}
44+
missing = expected - observed
45+
coverage_complete = not missing
46+
ordered_boundary = bool(
47+
evidence.lower_boundary_proven
48+
and evidence.timeline_order_valid
49+
and coverage_complete
50+
)
51+
terminal_proven = bool(
52+
evidence.timeline_order_valid
53+
and coverage_complete
54+
and (evidence.exhausted or ordered_boundary)
55+
)
56+
unresolved_boundary = bool(evidence.lower_boundary and not ordered_boundary)
57+
proof = {
58+
"pages": max(0, int(evidence.pages)),
59+
"raw_count": max(0, int(evidence.raw_count)),
60+
"valid_response": bool(evidence.valid_response),
61+
"exhausted": terminal_proven,
62+
"resumed": bool(evidence.resumed),
63+
"lower_boundary": unresolved_boundary,
64+
"failed": bool(error_class),
65+
}
66+
detail = {
67+
"provider_exhausted": bool(evidence.exhausted),
68+
"lower_boundary_observed": bool(evidence.lower_boundary),
69+
"lower_boundary_proven": bool(evidence.lower_boundary_proven),
70+
"timeline_order_valid": bool(evidence.timeline_order_valid),
71+
"expected_window_ids": sorted(expected),
72+
"expected_window_count": len(expected),
73+
"observed_expected_count": len(expected & observed),
74+
"missing_expected_ids": sorted(missing),
75+
"expected_coverage_complete": coverage_complete,
76+
"terminal_proven": terminal_proven,
77+
}
78+
return proof, detail
79+
80+
81+
class CompletenessEngine:
82+
def __init__(self, ledger: SourceLedgerStore):
83+
self.conn = ledger.conn
84+
self.conn.executescript("""
85+
CREATE TABLE IF NOT EXISTS completeness_attempts (
86+
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
87+
attempt_id TEXT NOT NULL UNIQUE,
88+
run_id TEXT NOT NULL,
89+
source TEXT NOT NULL,
90+
source_order INTEGER NOT NULL,
91+
window_start TEXT NOT NULL,
92+
window_end TEXT NOT NULL,
93+
status TEXT NOT NULL,
94+
attempted INTEGER NOT NULL DEFAULT 0,
95+
retry_count INTEGER NOT NULL DEFAULT 0,
96+
evidence TEXT NOT NULL DEFAULT '{}',
97+
error_class TEXT NOT NULL DEFAULT 'NotAttempted',
98+
retained_count INTEGER NOT NULL DEFAULT 0,
99+
legacy_status TEXT NOT NULL DEFAULT '',
100+
finalized INTEGER NOT NULL DEFAULT 0,
101+
UNIQUE(run_id, source)
102+
);
103+
CREATE TABLE IF NOT EXISTS completeness_shadow_cursors (
104+
source TEXT PRIMARY KEY,
105+
complete_through TEXT NOT NULL,
106+
attempt_id TEXT NOT NULL,
107+
sequence INTEGER NOT NULL
108+
);
109+
CREATE TABLE IF NOT EXISTS completeness_observations (
110+
attempt_id TEXT NOT NULL,
111+
post_id TEXT NOT NULL,
112+
PRIMARY KEY(attempt_id,post_id)
113+
);
114+
""")
115+
116+
def checkpoint(self, attempt_id, evidence):
117+
payload = {
118+
"pages": max(0, int(evidence.pages)),
119+
"raw_count": max(0, int(evidence.raw_count)),
120+
"provider_cursor": str(evidence.provider_cursor or "")[:4096],
121+
"valid_response": bool(evidence.valid_response),
122+
"expected_window_ids": sorted(str(value) for value in evidence.expected_window_ids)[:5000],
123+
"lower_boundary_proven": bool(evidence.lower_boundary_proven),
124+
"timeline_order_valid": bool(evidence.timeline_order_valid),
125+
}
126+
with self.conn:
127+
self.conn.execute(
128+
"UPDATE completeness_attempts SET evidence=? WHERE attempt_id=? AND finalized=0",
129+
(json.dumps(payload, sort_keys=True), attempt_id),
130+
)
131+
132+
def link_observation(self, attempt_id, post_id):
133+
with self.conn:
134+
self.conn.execute("""INSERT OR IGNORE INTO completeness_observations
135+
SELECT attempt_id,? FROM completeness_attempts WHERE attempt_id=? AND finalized=0""", (post_id, attempt_id))
136+
137+
def plan(self, sources, start, end) -> str:
138+
start, end = utc(start), utc(end)
139+
if start >= end:
140+
raise ValueError("Completeness window must be nonempty")
141+
run_id = uuid.uuid4().hex
142+
with self.conn:
143+
for index, source in enumerate(sources):
144+
if not source.get("enabled", True):
145+
continue
146+
handle = str(source.get("handle", "")).strip().lstrip("@").casefold()
147+
# Invalid enabled configuration remains visible, never disappears.
148+
handle = handle or f"invalid-source-{index}"
149+
self.conn.execute("""
150+
INSERT OR IGNORE INTO completeness_attempts
151+
(attempt_id,run_id,source,source_order,window_start,window_end,status)
152+
VALUES(?,?,?,?,?,?,'unproven')
153+
""", (uuid.uuid4().hex, run_id, handle, index, start, end))
154+
return run_id
155+
156+
def start(self, run_id, source) -> str:
157+
source = source.strip().lstrip("@").casefold()
158+
with self.conn:
159+
self.conn.execute("BEGIN IMMEDIATE")
160+
row = self.conn.execute("SELECT * FROM completeness_attempts WHERE run_id=? AND source=?", (run_id, source)).fetchone()
161+
if row is None or row["finalized"] or row["attempted"]:
162+
raise ValueError("Source attempt is missing or already started")
163+
retries = self.conn.execute("""SELECT count(*) FROM completeness_attempts
164+
WHERE source=? AND window_start=? AND window_end=? AND attempted=1""",
165+
(source, row["window_start"], row["window_end"])).fetchone()[0]
166+
self.conn.execute("""UPDATE completeness_attempts SET attempted=1,
167+
status='attempting',error_class='',retry_count=? WHERE attempt_id=?""", (retries, row["attempt_id"]))
168+
return row["attempt_id"]
169+
170+
def finish(self, attempt_id: str, evidence: TraversalEvidence, retained: int, error_class: str = ""):
171+
proof, proof_detail = proof_inputs(evidence, error_class)
172+
try:
173+
status = core_request("evaluate_completeness", proof=proof)
174+
if status not in ("complete", "partial", "unproven"):
175+
raise ValueError("Invalid completeness state")
176+
except (OSError, ValueError, KeyError, subprocess.SubprocessError):
177+
status, error_class = "unproven", "EditorialCoreUnavailable"
178+
179+
if status == "complete" and proof_detail["provider_exhausted"]:
180+
proof_kind = "validated_provider_exhaustion"
181+
elif status == "complete" and proof_detail["lower_boundary_proven"]:
182+
proof_kind = "validated_ordered_lower_boundary"
183+
else:
184+
proof_kind = "bounded_window_unproven"
185+
payload = {
186+
**proof,
187+
**proof_detail,
188+
"provider_cursor": evidence.provider_cursor,
189+
"raw_observation_count": len(evidence.observation_ids),
190+
"observation_ids": sorted(evidence.observation_ids),
191+
"proof_kind": proof_kind,
192+
}
193+
194+
# Serialize writers before reading the cursor; rollback covers both rows.
195+
with self.conn:
196+
self.conn.execute("BEGIN IMMEDIATE")
197+
row = self.conn.execute("SELECT * FROM completeness_attempts WHERE attempt_id=?", (attempt_id,)).fetchone()
198+
if row is None or not row["attempted"]:
199+
raise ValueError("Unknown attempt")
200+
if row["finalized"]:
201+
return # Immutable completed attempts; duplicate results are idempotent.
202+
if status == "complete":
203+
cursor = self.conn.execute("SELECT * FROM completeness_shadow_cursors WHERE source=?", (row["source"],)).fetchone()
204+
# Older/equal proven metadata is never replaced by a stale result.
205+
eligible = cursor is None or (row["window_end"] > cursor["complete_through"] and row["sequence"] > cursor["sequence"])
206+
# A gap cannot be silently skipped by advancing a watermark.
207+
contiguous = cursor is None or row["window_start"] <= cursor["complete_through"]
208+
if eligible and contiguous:
209+
try:
210+
through = core_request("advance_cursor", state={
211+
"source_handle": row["source"], "window_start": row["window_start"],
212+
"window_end": row["window_end"], "completeness": status,
213+
"complete_through": cursor["complete_through"] if cursor else None,
214+
}, candidate=row["window_end"])
215+
if through != row["window_end"]:
216+
raise ValueError("Invalid core cursor")
217+
except (OSError, ValueError, KeyError, subprocess.SubprocessError):
218+
status, error_class = "unproven", "EditorialCoreUnavailable"
219+
else:
220+
self.conn.execute("""INSERT INTO completeness_shadow_cursors VALUES(?,?,?,?)
221+
ON CONFLICT(source) DO UPDATE SET complete_through=excluded.complete_through,
222+
attempt_id=excluded.attempt_id,sequence=excluded.sequence""",
223+
(row["source"], through, attempt_id, row["sequence"]))
224+
elif not contiguous:
225+
payload["cursor_gap"] = True
226+
if status != "complete":
227+
payload["proof_kind"] = "bounded_window_unproven"
228+
payload["error_summary"] = error_class[:160]
229+
self.conn.execute("""UPDATE completeness_attempts SET status=?,evidence=?,
230+
error_class=?,retained_count=?,finalized=1,
231+
legacy_status=COALESCE((SELECT last_status FROM source_cursors WHERE source=?),'')
232+
WHERE attempt_id=?""",
233+
(status, json.dumps(payload, sort_keys=True), error_class[:160], max(0, retained), row["source"], attempt_id))
234+
235+
def close_run(self, run_id: str, reason: str = "Interrupted"):
236+
with self.conn:
237+
self.conn.execute("""UPDATE completeness_attempts SET status='unproven',
238+
error_class=CASE WHEN attempted=1 THEN ? ELSE 'NotAttempted' END,
239+
finalized=1 WHERE run_id=? AND finalized=0""", (reason, run_id))
240+
241+
def report(self, run_id: str) -> dict[str, Any]:
242+
rows = [dict(row) for row in self.conn.execute(
243+
"SELECT * FROM completeness_attempts WHERE run_id=? ORDER BY source_order,source", (run_id,))]
244+
for row in rows:
245+
row["evidence"] = json.loads(row["evidence"])
246+
linked = [link[0] for link in self.conn.execute(
247+
"SELECT post_id FROM completeness_observations WHERE attempt_id=? ORDER BY post_id", (row["attempt_id"],))]
248+
if linked:
249+
row["evidence"]["observation_ids"] = linked
250+
row["evidence"]["raw_observation_count"] = len(linked)
251+
return {"mode": "shadow", "run_id": run_id, "configured": len(rows),
252+
"attempted": sum(row["attempted"] for row in rows),
253+
"complete": sum(row["status"] == "complete" for row in rows),
254+
"healthy": bool(rows) and all(row["status"] == "complete" and row["finalized"] for row in rows),
255+
"sources": rows}

app/completeness_evidence.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Attempt-local raw traversal evidence; never derived from editorial filtering."""
2+
from __future__ import annotations
3+
4+
from contextvars import ContextVar
5+
from dataclasses import dataclass, field
6+
from typing import Callable
7+
8+
9+
@dataclass
10+
class TraversalEvidence:
11+
source_handle: str = ""
12+
window_start: str = ""
13+
window_end: str = ""
14+
pages: int = 0
15+
raw_count: int = 0
16+
observation_ids: set[str] = field(default_factory=set)
17+
expected_window_ids: set[str] = field(default_factory=set)
18+
provider_cursor: str = ""
19+
exhausted: bool = False
20+
valid_response: bool = False
21+
resumed: bool = False
22+
lower_boundary: bool = False
23+
lower_boundary_proven: bool = False
24+
timeline_order_valid: bool = True
25+
checkpoint: Callable | None = None
26+
link_observation: Callable | None = None
27+
28+
29+
active_evidence: ContextVar[TraversalEvidence | None] = ContextVar("completeness_evidence", default=None)
30+
31+
32+
def record_page(*, count: int, cursor: str | None, valid: bool) -> None:
33+
evidence = active_evidence.get()
34+
if evidence is not None:
35+
evidence.valid_response = valid if evidence.pages == 0 else evidence.valid_response and valid
36+
evidence.pages += 1
37+
evidence.raw_count += count
38+
evidence.provider_cursor = str(cursor or "")[:4096]
39+
if evidence.checkpoint is not None:
40+
evidence.checkpoint(evidence)
41+
42+
43+
def record_observation(post_id: str) -> None:
44+
evidence = active_evidence.get()
45+
if evidence is not None:
46+
evidence.observation_ids.add(str(post_id))
47+
if evidence.link_observation is not None:
48+
evidence.link_observation(str(post_id))

0 commit comments

Comments
 (0)