fix(rc06): recover sealed prompts after authority expiry

This commit is contained in:
2026-10-07 01:03:21 +08:00
parent f69039b1a3
commit 2136922b33
2 changed files with 88 additions and 8 deletions
@@ -13,7 +13,7 @@ import hashlib
import hmac
import json
import re
from datetime import UTC, datetime
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any, Literal
from uuid import uuid4
@@ -45,6 +45,7 @@ IMAGE_DIGEST = r"^sha256:[a-f0-9]{64}$"
EXPECTED_MODULE_COUNT = 14
EXPECTED_RUNTIME_COUNT = 14
NEW_RUNTIME_COUNT = 9
AUTHORITY_COMMIT_MARGIN = timedelta(seconds=5)
TERMINAL_STATES = {"sealed", "compensated", "compensation_failed"}
PROMPT_ROW_KEYS = {
"id",
@@ -235,9 +236,26 @@ def _database_wall_clock(session: Any) -> datetime:
return normalized
def _assert_authority_active(session: Any, authority_expires_at: datetime) -> None:
if _database_wall_clock(session) >= authority_expires_at.astimezone(UTC):
def _assert_authority_active(session: Any, authority_expires_at: datetime) -> datetime:
boundary_at = _database_wall_clock(session)
if boundary_at + AUTHORITY_COMMIT_MARGIN >= authority_expires_at.astimezone(UTC):
raise Rc06Conflict("RC06 publication authority expired before AI persistence.")
return boundary_at
def _assert_committed_within_authority(
transaction: Rc06PromptPublicationModel,
) -> None:
committed_at = _as_utc(transaction.committed_at)
authority_expires_at = _as_utc(transaction.authority_expires_at)
if (
committed_at is None
or authority_expires_at is None
or committed_at + AUTHORITY_COMMIT_MARGIN >= authority_expires_at
):
raise Rc06PublicationError(
"RC06 Prompt durable commit is not proven within its authority window."
)
def _prompt_rows(session: Any, names: list[str]) -> list[dict[str, Any]]:
@@ -512,10 +530,15 @@ class Rc06PromptPublicationService:
transaction.after_prompts_json = _encode(after)
transaction.state = "committed"
transaction.error_message = None
transaction.committed_at = now
transaction.updated_at = now
session.flush()
_assert_authority_active(session, frozen.authority_expires_at)
boundary_at = _assert_authority_active(
session,
frozen.authority_expires_at,
)
transaction.committed_at = boundary_at
transaction.updated_at = boundary_at
session.flush()
return self._status_row(transaction)
except (KeyError, Rc06PublicationError, Rc06Conflict):
raise
@@ -546,14 +569,12 @@ class Rc06PromptPublicationService:
return self._status_row(transaction)
if transaction.state != "committed":
raise Rc06Conflict(f"Prompt transaction cannot seal from {transaction.state}.")
_assert_authority_active(session, frozen.authority_expires_at)
self._verify_committed(session, transaction, expected_runtime, expected_modules)
now = datetime.now(UTC)
transaction.state = "sealed"
transaction.sealed_at = now
transaction.updated_at = now
session.flush()
_assert_authority_active(session, frozen.authority_expires_at)
return self._status_row(transaction)
def compensate(self, transaction_id: str, capability: str) -> PromptTransactionStatus:
@@ -799,6 +820,7 @@ class Rc06PromptPublicationService:
identities: list[dict[str, Any]],
modules: list[dict[str, Any]],
) -> None:
_assert_committed_within_authority(transaction)
snapshots = self._runtime_snapshots(session, identities)
if snapshots != _decode(transaction.runtime_snapshots_json, []):
raise Rc06Conflict("Committed Prompt runtime state drifted; automatic compensation refused.")
@@ -1314,6 +1336,7 @@ class Rc06PromptPublicationService:
raise Rc06PublicationError(
"Preserved committed Prompt state markers drifted after compensation."
)
_assert_committed_within_authority(transaction)
snapshots = _decode(transaction.runtime_snapshots_json, [])
after_rows = _decode(transaction.after_prompts_json, [])
expected_by_name = {item["name"]: item for item in identities}
@@ -129,7 +129,7 @@ def test_commit_rolls_back_all_ai_writes_when_authority_expires_before_precommit
request = _request("rc06-ai-expiry-crossing")
service.prepare(request)
expiry = request.authority_expires_at
readings = iter((expiry - timedelta(seconds=1), expiry))
readings = iter((expiry - timedelta(seconds=10), expiry - timedelta(seconds=5)))
monkeypatch.setattr(
rc06_publication,
"_database_wall_clock",
@@ -154,6 +154,63 @@ def test_commit_rolls_back_all_ai_writes_when_authority_expires_before_precommit
repository._engine.dispose()
def test_commit_persists_final_database_authority_boundary(tmp_path, monkeypatch):
repository = _repository(tmp_path)
try:
_seed_domain_dependencies(repository)
service = _service(repository)
request = _request("rc06-ai-database-boundary")
service.prepare(request)
expiry = request.authority_expires_at
prewrite = expiry - timedelta(seconds=20)
precommit = expiry - timedelta(seconds=10)
readings = iter((prewrite, precommit))
monkeypatch.setattr(
rc06_publication,
"_database_wall_clock",
lambda _session: next(readings),
)
committed = service.commit(request.transaction_id)
assert committed.state == "committed"
assert committed.committed_at == precommit
with repository._session_factory() as session:
row = session.get(Rc06PromptPublicationModel, request.transaction_id)
assert row.committed_at.replace(tzinfo=UTC) == precommit
finally:
repository._engine.dispose()
def test_seal_after_expiry_recovers_proven_preexpiry_durable_commit(
tmp_path,
monkeypatch,
):
repository = _repository(tmp_path)
try:
_seed_domain_dependencies(repository)
service = _service(repository)
request = _request("rc06-ai-expired-seal-recovery")
service.prepare(request)
committed = service.commit(request.transaction_id)
monkeypatch.setattr(
rc06_publication,
"_database_wall_clock",
lambda _session: request.authority_expires_at + timedelta(days=1),
)
sealed = service.seal(request.transaction_id, COMPENSATION_CAPABILITY)
assert sealed.state == "sealed"
assert sealed.committed_at == committed.committed_at
assert service.seal(
request.transaction_id,
COMPENSATION_CAPABILITY,
) == sealed
finally:
repository._engine.dispose()
def test_sqlite_prompt_participant_is_durable_idempotent_and_compensates(tmp_path):
repository = _repository(tmp_path)
try: