feat(rc06): add durable online publication facade

This commit is contained in:
2026-10-06 23:01:26 +08:00
parent e20fbaac7d
commit c9749b0484
10 changed files with 3650 additions and 7 deletions
@@ -0,0 +1,168 @@
"""add durable RC06 online publication facade journal
Revision ID: 20261006_0082
Revises: 20261006_0081
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
revision = "20261006_0082"
down_revision = "20261006_0081"
branch_labels = None
depends_on = None
def upgrade() -> None:
with op.batch_alter_table("rc06_publication_sagas") as batch:
batch.add_column(sa.Column("ai_compensation_status_json", sa.Text(), nullable=True))
op.create_table(
"rc06_online_publication_transactions",
sa.Column("transaction_id", sa.String(length=64), primary_key=True),
sa.Column("tenant_id", sa.String(length=64), nullable=False),
sa.Column("idempotency_key", sa.String(length=160), nullable=False),
sa.Column("request_sha256", sa.String(length=64), nullable=False),
sa.Column("environment_id", sa.String(length=256), nullable=False),
sa.Column("candidate_commit", sa.String(length=64), nullable=False),
sa.Column("contract_release_image_digest", sa.String(length=71), nullable=False),
sa.Column("ai_release_image_digest", sa.String(length=71), nullable=False),
sa.Column("batch_manifest_raw_sha256", sa.String(length=64), nullable=False),
sa.Column("batch_manifest_payload_sha256", sa.String(length=64), nullable=False),
sa.Column("approval_receipt_sha256", sa.String(length=64), nullable=False),
sa.Column("legal_source_review_binding_sha256", sa.String(length=64), nullable=False),
sa.Column("legal_source_review_expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("challenge_id", sa.String(length=64), nullable=False),
sa.Column("challenge_sha256", sa.String(length=64), nullable=False),
sa.Column("state", sa.String(length=64), nullable=False),
sa.Column("request_json", sa.Text(), nullable=False),
sa.Column("prepare_request_json", sa.Text(), nullable=False),
sa.Column("saga_status_json", sa.Text(), nullable=True),
sa.Column("participant_evidence_json", sa.Text(), nullable=True),
sa.Column("online_payload_json", sa.Text(), nullable=True),
sa.Column("online_primary_signature_json", sa.Text(), nullable=True),
sa.Column("online_attestation_json", sa.Text(), nullable=True),
sa.Column("online_primary_kms_request_id", sa.String(length=512), nullable=True),
sa.Column("online_attestation_kms_request_id", sa.String(length=512), nullable=True),
sa.Column("online_receipt_json", sa.Text(), nullable=True),
sa.Column("online_receipt_sha256", sa.String(length=64), nullable=True),
sa.Column("runtime_payload_json", sa.Text(), nullable=True),
sa.Column("runtime_primary_signature_json", sa.Text(), nullable=True),
sa.Column("runtime_attestation_json", sa.Text(), nullable=True),
sa.Column("runtime_primary_kms_request_id", sa.String(length=512), nullable=True),
sa.Column("runtime_attestation_kms_request_id", sa.String(length=512), nullable=True),
sa.Column("runtime_receipt_json", sa.Text(), nullable=True),
sa.Column("runtime_receipt_sha256", sa.String(length=64), nullable=True),
sa.Column("rollback_response_json", sa.Text(), nullable=True),
sa.Column("rollback_evidence_sha256", sa.String(length=64), nullable=True),
sa.Column("rollback_response_sha256", sa.String(length=64), nullable=True),
sa.Column("apply_receipt_json", sa.Text(), nullable=True),
sa.Column("apply_receipt_sha256", sa.String(length=64), nullable=True),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("failure_code", sa.String(length=128), nullable=True),
sa.Column("failure_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("failure_evidence_sha256", sa.String(length=64), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("contract_committed_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("pending_countersign_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("finalized_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("rolled_back_at", sa.DateTime(timezone=True), nullable=True),
sa.CheckConstraint(
"state in ('authorized', 'saga_prepared', 'saga_committing', 'ai_committed', "
"'contract_committed', 'evidence_pending', 'online_primary_signed', "
"'online_attested', 'online_receipt_persisted', 'runtime_primary_signed', "
"'runtime_attested', 'online_committed_pending_countersign', 'finalized', "
"'failed_before_commit', 'compensating', 'rolled_back', 'compensation_failed')",
name="ck_rc06_online_publication_state",
),
sa.UniqueConstraint(
"tenant_id", "idempotency_key", name="ux_rc06_online_publication_idempotency"
),
sa.UniqueConstraint("challenge_id", name="ux_rc06_online_publication_challenge_id"),
sa.UniqueConstraint("challenge_sha256", name="ux_rc06_online_publication_challenge_sha256"),
)
op.create_index(
"ix_rc06_online_publication_transactions_tenant_id",
"rc06_online_publication_transactions",
["tenant_id"],
)
op.create_index(
"ix_rc06_online_publication_transactions_candidate_commit",
"rc06_online_publication_transactions",
["candidate_commit"],
)
op.create_index(
"ix_rc06_online_publication_transactions_state",
"rc06_online_publication_transactions",
["state"],
)
op.create_table(
"rc06_apply_challenge_uses",
sa.Column("challenge_id", sa.String(length=64), primary_key=True),
sa.Column("nonce_sha256", sa.String(length=64), nullable=False),
sa.Column("challenge_sha256", sa.String(length=64), nullable=False),
sa.Column("tenant_id", sa.String(length=64), nullable=False),
sa.Column("idempotency_key", sa.String(length=160), nullable=False),
sa.Column("transaction_id", sa.String(length=64), nullable=False),
sa.Column("request_sha256", sa.String(length=64), nullable=False),
sa.Column("outcome", sa.String(length=32), nullable=False),
sa.Column("reserved_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("consumed_at", sa.DateTime(timezone=True), nullable=True),
sa.CheckConstraint(
"outcome in ('reserved', 'pending_countersign', 'finalized', 'rolled_back', "
"'compensation_failed', 'no_go')",
name="ck_rc06_apply_challenge_outcome",
),
sa.ForeignKeyConstraint(
["transaction_id"],
["rc06_online_publication_transactions.transaction_id"],
ondelete="NO ACTION",
),
sa.UniqueConstraint("nonce_sha256", name="ux_rc06_apply_challenge_nonce"),
sa.UniqueConstraint("transaction_id", name="ux_rc06_apply_challenge_transaction"),
sa.UniqueConstraint("challenge_sha256", name="ux_rc06_apply_challenge_sha256"),
)
op.create_index(
"ix_rc06_apply_challenge_uses_tenant_id",
"rc06_apply_challenge_uses",
["tenant_id"],
)
def downgrade() -> None:
bind = op.get_bind()
online_count = bind.execute(
sa.text("SELECT COUNT(*) FROM rc06_online_publication_transactions")
).scalar_one()
challenge_count = bind.execute(
sa.text("SELECT COUNT(*) FROM rc06_apply_challenge_uses")
).scalar_one()
if online_count or challenge_count:
raise RuntimeError(
"RC06 migration 0082 refuses to erase durable publication authority, "
"receipt, countersign, or challenge-consumption evidence."
)
op.drop_index(
"ix_rc06_apply_challenge_uses_tenant_id",
table_name="rc06_apply_challenge_uses",
)
op.drop_table("rc06_apply_challenge_uses")
op.drop_index(
"ix_rc06_online_publication_transactions_state",
table_name="rc06_online_publication_transactions",
)
op.drop_index(
"ix_rc06_online_publication_transactions_candidate_commit",
table_name="rc06_online_publication_transactions",
)
op.drop_index(
"ix_rc06_online_publication_transactions_tenant_id",
table_name="rc06_online_publication_transactions",
)
op.drop_table("rc06_online_publication_transactions")
with op.batch_alter_table("rc06_publication_sagas") as batch:
batch.drop_column("ai_compensation_status_json")
@@ -29,8 +29,25 @@ class ContractApiSettings(BaseSettings):
rc06_publication_shared_secret: str = ""
rc06_frozen_candidate_commit: str = ""
rc06_release_image_digest: str = ""
rc06_ai_release_image_digest: str = ""
rc06_low_level_coordinator_http_enabled: bool = False
rc06_publication_tenant_id: str = "default"
rc06_publication_facade_enabled: bool = False
rc06_publication_environment_id: str = ""
rc06_publication_bearer_token_file: str = ""
rc06_publication_max_body_bytes: int = Field(default=16 * 1024 * 1024, ge=1024, le=32 * 1024 * 1024)
rc06_approval_root_public_key_file: str = ""
rc06_approval_root_public_key_sha256: str = ""
rc06_approval_root_key_id: str = "yuqei-rc06-approval-root-v1"
rc06_approval_keyring_epoch: int = 0
rc06_apply_service_public_key_file: str = ""
rc06_apply_service_public_key_sha256: str = ""
rc06_legal_review_keyring_sha256: str = ""
rc06_online_signing_provider: str = ""
rc06_online_signing_key_reference: str = ""
rc06_online_signing_key_version: str = ""
rc06_online_signing_algorithm: str = "RSA_PSS_SHA_256"
rc06_online_signing_public_key_spki_sha256: str = ""
web_proxy_shared_secret: str = ""
require_web_proxy_auth: bool = False
playbook_direct_mutations_enabled: bool = False
@@ -160,6 +177,7 @@ def check_runtime_config(settings: ContractApiSettings) -> RuntimeConfigStatus:
_check_seed_config(settings),
_check_ai_platform_config(settings),
_check_rc06_publication_config(settings),
_check_rc06_publication_facade_config(settings),
_check_web_proxy_auth_config(settings),
_check_playbook_governance_config(settings),
_check_playbook_calendar_oauth_config(settings),
@@ -695,6 +713,70 @@ def _check_rc06_publication_config(settings: ContractApiSettings) -> RuntimeConf
)
def _check_rc06_publication_facade_config(settings: ContractApiSettings) -> RuntimeConfigCheck:
if not settings.rc06_publication_facade_enabled:
return RuntimeConfigCheck(
name="rc06_publication_facade",
status="warning",
message="RC06 online publication facade is disabled.",
details={"enabled": False},
)
errors: list[str] = []
if settings.rc06_low_level_coordinator_http_enabled:
errors.append("low-level coordinator HTTP routes must be disabled")
if not settings.rc06_publication_environment_id.strip():
errors.append("environment id is missing")
if not settings.rc06_publication_tenant_id.strip():
errors.append("tenant id is missing")
if settings.reliable_evidence_signing_backend != "kms_gateway":
errors.append("online receipt signing must use the KMS gateway")
if settings.rc06_approval_keyring_epoch < 1:
errors.append("approval keyring epoch is not frozen")
if re.fullmatch(r"(?:[a-f0-9]{40}|[a-f0-9]{64})", settings.rc06_frozen_candidate_commit) is None:
errors.append("candidate commit is not frozen")
for label, value in (
("Contract image", settings.rc06_release_image_digest),
("AI image", settings.rc06_ai_release_image_digest),
):
if re.fullmatch(r"sha256:[a-f0-9]{64}", value) is None:
errors.append(f"{label} digest is invalid")
for label, value in (
("approval root pin", settings.rc06_approval_root_public_key_sha256),
("apply service pin", settings.rc06_apply_service_public_key_sha256),
("legal keyring pin", settings.rc06_legal_review_keyring_sha256),
("online signing SPKI pin", settings.rc06_online_signing_public_key_spki_sha256),
):
if re.fullmatch(r"[a-f0-9]{64}", value) is None:
errors.append(f"{label} is invalid")
for label, value in (
("approval root public key", settings.rc06_approval_root_public_key_file),
("apply service public key", settings.rc06_apply_service_public_key_file),
("facade bearer token", settings.rc06_publication_bearer_token_file),
):
if not value or not Path(value).is_absolute():
errors.append(f"{label} path must be absolute")
if settings.rc06_online_signing_algorithm not in {
"RSA_PSS_SHA_256", "RSA_PKCS1_SHA_256", "ECDSA_SHA_256"
}:
errors.append("online signing algorithm is unsupported")
if not all((
settings.rc06_online_signing_provider.strip(),
settings.rc06_online_signing_key_reference.strip(),
settings.rc06_online_signing_key_version.strip(),
)):
errors.append("online KMS identity is incomplete")
return RuntimeConfigCheck(
name="rc06_publication_facade",
status="error" if errors else "ok",
message=(
"RC06 online publication facade configuration is invalid: " + "; ".join(errors)
if errors
else "RC06 online publication facade trust and KMS identities are frozen."
),
details={"enabled": True, "error_count": len(errors)},
)
def _check_worker_config(settings: ContractApiSettings) -> RuntimeConfigCheck:
if not settings.review_worker_name.strip():
return RuntimeConfigCheck(
@@ -34,6 +34,10 @@ from yuqei_contract_api.api.support import register_support_routes
from yuqei_contract_api.api.system import register_system_routes
from yuqei_contract_api.api.templates import register_template_routes
from yuqei_contract_api.rc06_publication import AiRc06Client, register_rc06_publication_routes
from yuqei_contract_api.rc06_publication_facade import (
is_rc06_publication_facade_path,
register_rc06_publication_facade_routes,
)
from yuqei_contract_api.config import ContractApiSettings, get_settings
from yuqei_contract_api.logging import (
clear_request_tenant_id,
@@ -126,10 +130,15 @@ def create_app(
trace_id = request.headers.get("X-Trace-Id") or f"trace-{uuid.uuid4().hex[:16]}"
set_trace_id(trace_id)
rc06_internal = (
rc06_low_level_internal = (
resolved_settings.rc06_low_level_coordinator_http_enabled
and _is_rc06_internal_path(request.url.path, resolved_settings.api_prefix)
)
rc06_facade_internal = (
resolved_settings.rc06_publication_facade_enabled
and is_rc06_publication_facade_path(request.url.path)
)
rc06_internal = rc06_low_level_internal or rc06_facade_internal
if (
resolved_settings.require_web_proxy_auth
and not rc06_internal
@@ -149,7 +158,7 @@ def create_app(
if rc06_internal
else request.headers.get("X-Tenant-Id") or "default"
)
user_id = request.headers.get("X-User-Id")
user_id = "rc06-publication-service" if rc06_facade_internal else request.headers.get("X-User-Id")
set_tenant_context(tenant_id)
set_user_context(user_id)
set_request_tenant_id(tenant_id)
@@ -351,6 +360,12 @@ def create_app(
repository=repository,
ai_client=rc06_ai_client,
)
register_rc06_publication_facade_routes(
app,
settings=resolved_settings,
repository=repository,
ai_client=rc06_ai_client,
)
register_drafting_routes(app, settings=resolved_settings, repository=repository)
register_party_profile_routes(app, settings=resolved_settings, repository=repository)
register_legal_asset_routes(app, settings=resolved_settings, repository=repository)
@@ -1756,6 +1756,7 @@ class Rc06PublicationSagaModel(Base):
item_results_json: Mapped[str | None] = mapped_column(Text, nullable=True)
runtime_snapshots_json: Mapped[str | None] = mapped_column(Text, nullable=True)
ai_status_json: Mapped[str | None] = mapped_column(Text, nullable=True)
ai_compensation_status_json: Mapped[str | None] = mapped_column(Text, nullable=True)
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
compensation_capability: Mapped[str | None] = mapped_column(String(128), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
@@ -1765,6 +1766,107 @@ class Rc06PublicationSagaModel(Base):
compensated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class Rc06OnlinePublicationTransactionModel(Base):
"""Durable facade journal for authority, receipts, and countersign state."""
__tablename__ = "rc06_online_publication_transactions"
__table_args__ = (
UniqueConstraint(
"tenant_id",
"idempotency_key",
name="ux_rc06_online_publication_idempotency",
),
CheckConstraint(
"state in ('authorized', 'saga_prepared', 'saga_committing', 'ai_committed', "
"'contract_committed', 'evidence_pending', 'online_primary_signed', "
"'online_attested', 'online_receipt_persisted', 'runtime_primary_signed', "
"'runtime_attested', 'online_committed_pending_countersign', 'finalized', "
"'failed_before_commit', 'compensating', 'rolled_back', 'compensation_failed')",
name="ck_rc06_online_publication_state",
),
)
transaction_id: Mapped[str] = mapped_column(String(64), primary_key=True)
tenant_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
idempotency_key: Mapped[str] = mapped_column(String(160), nullable=False)
request_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
environment_id: Mapped[str] = mapped_column(String(256), nullable=False)
candidate_commit: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
contract_release_image_digest: Mapped[str] = mapped_column(String(71), nullable=False)
ai_release_image_digest: Mapped[str] = mapped_column(String(71), nullable=False)
batch_manifest_raw_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
batch_manifest_payload_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
approval_receipt_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
legal_source_review_binding_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
legal_source_review_expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
challenge_id: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
challenge_sha256: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
state: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
request_json: Mapped[str] = mapped_column(Text, nullable=False)
prepare_request_json: Mapped[str] = mapped_column(Text, nullable=False)
saga_status_json: Mapped[str | None] = mapped_column(Text, nullable=True)
participant_evidence_json: Mapped[str | None] = mapped_column(Text, nullable=True)
online_payload_json: Mapped[str | None] = mapped_column(Text, nullable=True)
online_primary_signature_json: Mapped[str | None] = mapped_column(Text, nullable=True)
online_attestation_json: Mapped[str | None] = mapped_column(Text, nullable=True)
online_primary_kms_request_id: Mapped[str | None] = mapped_column(String(512), nullable=True)
online_attestation_kms_request_id: Mapped[str | None] = mapped_column(String(512), nullable=True)
online_receipt_json: Mapped[str | None] = mapped_column(Text, nullable=True)
online_receipt_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
runtime_payload_json: Mapped[str | None] = mapped_column(Text, nullable=True)
runtime_primary_signature_json: Mapped[str | None] = mapped_column(Text, nullable=True)
runtime_attestation_json: Mapped[str | None] = mapped_column(Text, nullable=True)
runtime_primary_kms_request_id: Mapped[str | None] = mapped_column(String(512), nullable=True)
runtime_attestation_kms_request_id: Mapped[str | None] = mapped_column(String(512), nullable=True)
runtime_receipt_json: Mapped[str | None] = mapped_column(Text, nullable=True)
runtime_receipt_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
rollback_response_json: Mapped[str | None] = mapped_column(Text, nullable=True)
rollback_evidence_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
rollback_response_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
apply_receipt_json: Mapped[str | None] = mapped_column(Text, nullable=True)
apply_receipt_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
failure_code: Mapped[str | None] = mapped_column(String(128), nullable=True)
failure_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
failure_evidence_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
contract_committed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
pending_countersign_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
finalized_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
rolled_back_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class Rc06ApplyChallengeUseModel(Base):
"""One-way reservation ledger preventing apply-challenge replay."""
__tablename__ = "rc06_apply_challenge_uses"
__table_args__ = (
UniqueConstraint("nonce_sha256", name="ux_rc06_apply_challenge_nonce"),
UniqueConstraint("transaction_id", name="ux_rc06_apply_challenge_transaction"),
CheckConstraint(
"outcome in ('reserved', 'pending_countersign', 'finalized', 'rolled_back', "
"'compensation_failed', 'no_go')",
name="ck_rc06_apply_challenge_outcome",
),
)
challenge_id: Mapped[str] = mapped_column(String(64), primary_key=True)
nonce_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
challenge_sha256: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
tenant_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
idempotency_key: Mapped[str] = mapped_column(String(160), nullable=False)
transaction_id: Mapped[str] = mapped_column(
String(64),
ForeignKey("rc06_online_publication_transactions.transaction_id", ondelete="NO ACTION"),
nullable=False,
)
request_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
outcome: Mapped[str] = mapped_column(String(32), nullable=False)
reserved_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
consumed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class Rc06TemplatePublicationItemModel(Base):
__tablename__ = "rc06_template_publication_items"
__table_args__ = (
@@ -94,6 +94,14 @@ class Rc06SagaStatus(BaseModel):
ai_sealed_at: datetime | None = None
contract_committed_at: datetime | None = None
committed_at: datetime | None = None
compensated_from_state: str | None = None
compensation_item_results: list[dict[str, Any]] = Field(default_factory=list)
runtime_snapshot_compensation_results: list[dict[str, Any]] = Field(default_factory=list)
restored_item_count: int = 0
removed_item_count: int = 0
unchanged_item_count: int = 0
compensated_at: datetime | None = None
compensation_evidence_sha256: str | None = None
error: str | None = None
@@ -524,19 +532,22 @@ class Rc06PublicationCoordinator:
current.state = "compensating"
current.updated_at = datetime.now(UTC)
errors: list[str] = []
ai_status: dict[str, Any] | None = None
try:
self._compensate_templates(transaction_id)
except Exception as error:
errors.append(f"contract:{type(error).__name__}:{error}")
try:
ai_status = self._ai.compensate(transaction_id, compensation_capability)
if ai_status.get("state") != "compensated":
raise Rc06PublicationError("AI participant did not confirm compensation.")
self._assert_ai_compensation_state(ai_status, transaction_id, self._load_saga(
transaction_id, tenant_id=tenant_id
))
except Exception as error:
try:
ai_status = self._ai.status(transaction_id)
if ai_status.get("state") != "compensated":
raise Rc06PublicationError("AI participant compensation outcome is not durable.")
self._assert_ai_compensation_state(ai_status, transaction_id, self._load_saga(
transaction_id, tenant_id=tenant_id
))
except Exception as status_error:
errors.append(
f"ai:{type(error).__name__}:{error};"
@@ -552,6 +563,9 @@ class Rc06PublicationCoordinator:
current.error_message = "; ".join(errors)[:4000] if errors else None
current.compensated_at = now if not errors else None
if not errors:
if ai_status is None:
raise Rc06PublicationError("AI compensation evidence is unavailable.")
current.ai_compensation_status_json = _encode(ai_status)
current.compensation_capability = None
current.updated_at = now
result = self._status_row(current)
@@ -827,6 +841,125 @@ class Rc06PublicationCoordinator:
raise Rc06PublicationError("Committed RC06 saga has incomplete or stale durable evidence.")
return self._status_row(row)
@staticmethod
def _assert_ai_compensation_state(
payload: dict[str, Any],
transaction_id: str,
saga: Rc06PublicationSagaModel,
) -> None:
request = Rc06PublicationCoordinator._frozen_request(saga)
if (
payload.get("transaction_id") != transaction_id
or payload.get("state") != "compensated"
or payload.get("candidate_commit") != request["candidate_commit"]
or payload.get("release_image_digest") != request["ai_release_image_digest"]
or payload.get("candidate_tag") != request["candidate_tag"]
or payload.get("batch_manifest_raw_sha256") != request["batch_manifest_raw_sha256"]
or payload.get("batch_manifest_payload_sha256") != request["batch_manifest_payload_sha256"]
or payload.get("approval_receipt_sha256") != request["approval_receipt_sha256"]
or payload.get("no_go") is not True
):
raise Rc06PublicationError("AI participant compensation identity drifted.")
from_state = payload.get("compensated_from_state")
item_results = payload.get("compensation_item_results")
runtime_results = payload.get("runtime_snapshot_compensation_results")
if from_state not in {"prepared", "committed"} or not isinstance(item_results, list) or not isinstance(runtime_results, list):
raise Rc06PublicationError("AI participant compensation evidence is incomplete.")
if from_state == "prepared":
if item_results or runtime_results:
raise Rc06PublicationError("Prepared AI compensation falsely claims publication writes.")
else:
expected_items = sorted(
("prompt_module", item["key"]) for item in request["prompt_modules"]
)
supplied_items = []
for index, item in enumerate(item_results):
if not isinstance(item, dict) or set(item) != {
"kind", "item_id", "pre_apply_content_sha256",
"post_compensation_content_sha256", "action", "status",
}:
raise Rc06PublicationError(f"AI compensation module row {index} drifted.")
supplied_items.append((item.get("kind"), item.get("item_id")))
Rc06PublicationCoordinator._assert_compensation_hashes(item, f"module row {index}")
if supplied_items != expected_items:
raise Rc06PublicationError("AI compensation module identities are not exact 14.")
kind_by_source = {
"composed_drafting_modules": "composed_drafting",
"new_review_module_composite": "new_review_module",
"existing_domain_prompt_asset": "existing_domain_prompt",
}
expected_runtime = sorted(
(kind_by_source[item["source_kind"]], item["name"], item["version"])
for item in request["runtime_identities"]
)
expected_by_identity = {
(kind_by_source[item["source_kind"]], item["name"], item["version"]): item
for item in request["runtime_identities"]
}
supplied_runtime = []
for index, item in enumerate(runtime_results):
if not isinstance(item, dict) or set(item) != {
"kind", "runtime_key", "version", "pre_apply_content_sha256",
"post_compensation_content_sha256", "action", "status",
}:
raise Rc06PublicationError(f"AI compensation runtime row {index} drifted.")
identity = (item.get("kind"), item.get("runtime_key"), item.get("version"))
supplied_runtime.append(identity)
expected = expected_by_identity.get(identity)
unchanged = (
expected["content_sha256"]
if expected is not None and expected["source_kind"] == "existing_domain_prompt_asset"
else None
)
Rc06PublicationCoordinator._assert_compensation_hashes(
item, f"runtime row {index}", unchanged_sha256=unchanged
)
if unchanged is not None and item["action"] != "unchanged_preexisting":
raise Rc06PublicationError("Pre-existing runtime Prompt was falsely compensated.")
if supplied_runtime != expected_runtime:
raise Rc06PublicationError("AI compensation runtime identities are not exact 14.")
actions = [item.get("action") for item in [*item_results, *runtime_results]]
counts = {
"restored_item_count": actions.count("restored_previous"),
"removed_item_count": actions.count("removed_new"),
"unchanged_item_count": actions.count("unchanged_preexisting"),
}
if any(payload.get(key) != value for key, value in counts.items()):
raise Rc06PublicationError("AI compensation counts drifted.")
compensated_at = _parse_time(payload.get("compensated_at"))
compensation_evidence_sha256 = payload.get("compensation_evidence_sha256")
if (
compensated_at is None
or not isinstance(compensation_evidence_sha256, str)
or re.fullmatch(HEX64, compensation_evidence_sha256) is None
):
raise Rc06PublicationError("AI compensation evidence hash is missing or invalid.")
# The AI participant validates this hash against additional private
# before/after Prompt snapshots before its status method returns. Those
# snapshots intentionally do not cross the service boundary. Contract
# independently validates every public compensation row/count above,
# then persists the participant's durable evidence hash verbatim.
@staticmethod
def _assert_compensation_hashes(
item: dict[str, Any],
label: str,
*,
unchanged_sha256: str | None = None,
) -> None:
if item.get("status") != "verified":
raise Rc06PublicationError(f"AI compensation {label} is unverified.")
action = item.get("action")
before = item.get("pre_apply_content_sha256")
after = item.get("post_compensation_content_sha256")
if action == "restored_previous" and isinstance(before, str) and re.fullmatch(HEX64, before) and after == before:
return
if action == "removed_new" and before is None and after is None:
return
if action == "unchanged_preexisting" and unchanged_sha256 is not None and before == after == unchanged_sha256:
return
raise Rc06PublicationError(f"AI compensation {label} hash evidence drifted.")
@staticmethod
def _assert_ai_state(
payload: dict[str, Any],
@@ -1127,6 +1260,7 @@ class Rc06PublicationCoordinator:
results = _decode(row.item_results_json, [])
snapshots = _decode(row.runtime_snapshots_json, [])
ai_status = _decode(row.ai_status_json, {})
compensation = _decode(row.ai_compensation_status_json, {})
return Rc06SagaStatus(
transaction_id=row.transaction_id,
state=row.state,
@@ -1149,6 +1283,16 @@ class Rc06PublicationCoordinator:
ai_sealed_at=_parse_time(ai_status.get("sealed_at")),
contract_committed_at=_parse_time(row.contract_committed_at),
committed_at=_parse_time(row.committed_at),
compensated_from_state=compensation.get("compensated_from_state"),
compensation_item_results=compensation.get("compensation_item_results", []),
runtime_snapshot_compensation_results=compensation.get(
"runtime_snapshot_compensation_results", []
),
restored_item_count=compensation.get("restored_item_count", 0),
removed_item_count=compensation.get("removed_item_count", 0),
unchanged_item_count=compensation.get("unchanged_item_count", 0),
compensated_at=_parse_time(compensation.get("compensated_at")),
compensation_evidence_sha256=compensation.get("compensation_evidence_sha256"),
error=row.error_message,
)
@@ -0,0 +1,849 @@
"""Runtime-safe RC06 publication protocol validation.
This module deliberately has no CLI, Git, database, or service imports. The
offline builders perform the broader candidate/source audit; the online
facade repeats the security-critical checks over the exact material presented
for the durable publication transaction.
"""
from __future__ import annotations
import base64
import hashlib
import json
import re
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any, Mapping
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
BATCH_SCHEMA = "yuqei.rc06-publication-batch.v4"
APPROVAL_SCHEMA = "yuqei.rc06-publication-batch-approval.v5"
APPROVAL_PAYLOAD_SCHEMA = "yuqei.rc06-publication-batch-approval-payload.v4"
APPROVAL_KEYRING_SCHEMA = "yuqei.rc06-publication-approval-keyring.v4"
APPLY_REQUEST_SCHEMA = "yuqei.rc06-online-apply-request.v3"
APPLY_RESPONSE_SCHEMA = "yuqei.rc06-online-apply-response.v2"
APPLY_STATUS_SCHEMA = "yuqei.rc06-online-transaction-status.v3"
ROLLBACK_REQUEST_SCHEMA = "yuqei.rc06-online-rollback-request.v2"
ROLLBACK_RESPONSE_SCHEMA = "yuqei.rc06-online-rollback-response.v2"
FINALIZE_REQUEST_SCHEMA = "yuqei.rc06-online-finalize-request.v1"
FINALIZE_RESPONSE_SCHEMA = "yuqei.rc06-online-finalize-response.v1"
FINALIZE_STATUS_SCHEMA = "yuqei.rc06-online-finalize-status.v1"
APPLY_RECEIPT_SCHEMA = "yuqei.rc06-publication-system-item-apply-receipt.v6"
APPLY_RECEIPT_PAYLOAD_SCHEMA = "yuqei.rc06-publication-system-item-apply-payload.v4"
ONLINE_RECEIPT_SCHEMA = "yuqei.rc06-online-transaction-receipt.v5"
ONLINE_PAYLOAD_SCHEMA = "yuqei.rc06-online-transaction-payload.v4"
RUNTIME_RECEIPT_SCHEMA = "yuqei.rc06-runtime-snapshot-receipt.v3"
RUNTIME_PAYLOAD_SCHEMA = "yuqei.rc06-runtime-snapshot-payload.v2"
KMS_ATTESTATION_SCHEMA = "yuqei.rc06-kms-request-id-attestation.v1"
KMS_ATTESTATION_PAYLOAD_SCHEMA = "yuqei.rc06-kms-request-id-attestation-payload.v1"
CHALLENGE_SCHEMA = "yuqei.rc06-apply-challenge.v2"
CHALLENGE_PAYLOAD_SCHEMA = "yuqei.rc06-apply-challenge-payload.v2"
LEGAL_REQUEST_SCHEMA = "yuqei.scene-source-legal-review-request.v1"
LEGAL_KEYRING_SCHEMA = "yuqei.scene-source-legal-review-keyring.v1"
LEGAL_DECISION_SCHEMA = "yuqei.scene-source-legal-review-decision.v1"
APPROVAL_SIGNATURE_DOMAIN = b"YUQEI_RC06_PUBLICATION_BATCH_APPROVAL_V1\0"
APPROVAL_KEYRING_SIGNATURE_DOMAIN = b"YUQEI_RC06_APPROVAL_KEYRING_V1\0"
APPLY_RECEIPT_SIGNATURE_DOMAIN = b"YUQEI_RC06_SYSTEM_APPLY_RECEIPT_V2\0"
CHALLENGE_SIGNATURE_DOMAIN = b"YUQEI_RC06_APPLY_CHALLENGE_V2\0"
LEGAL_SIGNATURE_DOMAIN = b"YUQEI_SCENE_SOURCE_LEGAL_REVIEW_V1\0"
ONLINE_SIGNATURE_DOMAIN = b"YUQEI_RC06_ONLINE_TRANSACTION_RECEIPT_V2\0"
RUNTIME_SIGNATURE_DOMAIN = b"YUQEI_RC06_RUNTIME_SNAPSHOT_RECEIPT_V2\0"
ONLINE_ATTESTATION_DOMAIN = b"YUQEI_RC06_ONLINE_TRANSACTION_REQUEST_ID_ATTESTATION_V1\0"
RUNTIME_ATTESTATION_DOMAIN = b"YUQEI_RC06_RUNTIME_SNAPSHOT_REQUEST_ID_ATTESTATION_V1\0"
APPLY_OPERATION = "apply_rc06_publication_batch"
TRANSACTION_MODE = "durable_saga"
TRUSTED_COMPONENT = "rc06-publication-service"
SOLE_REVIEWER = {"approver_id": "chen-yuqi", "name": "陈钰麒"}
HEX64 = re.compile(r"^[a-f0-9]{64}$")
COMMIT = re.compile(r"^(?:[a-f0-9]{40}|[a-f0-9]{64})$")
IMAGE_DIGEST = re.compile(r"^sha256:[a-f0-9]{64}$")
TRANSACTION_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,63}$")
MAX_APPROVAL_WINDOW = timedelta(days=7)
MAX_LEGAL_WINDOW = timedelta(days=7)
MAX_CHALLENGE_WINDOW = timedelta(minutes=10)
EXPECTED_ASSETS = 60
EXPECTED_MODULES = 14
EXPECTED_RUNTIME = 14
EXPECTED_GOLDEN = 31
class Rc06ProtocolError(RuntimeError):
pass
@dataclass(frozen=True)
class AuthorityTrust:
approval_root_public_key: Ed25519PublicKey
approval_root_key_id: str
approval_keyring_epoch: int
apply_service_public_key: Ed25519PublicKey
legal_keyring_sha256: str
environment_id: str
tenant_id: str
frozen_candidate_commit: str
contract_release_image_digest: str
ai_release_image_digest: str
@dataclass(frozen=True)
class AuthorizedMaterial:
request: dict[str, Any]
manifest: dict[str, Any]
approval: dict[str, Any]
approval_keyring: dict[str, Any]
legal_summary: dict[str, Any]
legal_binding_sha256: str
challenge: dict[str, Any]
expected_publication_results: list[dict[str, Any]]
expected_runtime_results: list[dict[str, Any]]
contract_image_digest: str
ai_image_digest: str
def canonical_json(value: Any) -> bytes:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
def sha256_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def sha256_json(value: Any) -> str:
return sha256_bytes(canonical_json(value))
def require_sha(value: Any, label: str) -> str:
if not isinstance(value, str) or HEX64.fullmatch(value) is None:
raise Rc06ProtocolError(f"{label} must be a lowercase SHA-256 value.")
return value
def require_text(value: Any, label: str, *, maximum: int = 4096) -> str:
if not isinstance(value, str) or not value.strip() or value != value.strip() or len(value) > maximum:
raise Rc06ProtocolError(f"{label} is invalid.")
return value
def require_exact(value: Any, label: str, fields: tuple[str, ...]) -> dict[str, Any]:
if not isinstance(value, dict) or set(value) != set(fields):
raise Rc06ProtocolError(f"{label} fields are invalid.")
return value
def parse_time(value: Any, label: str) -> datetime:
if not isinstance(value, str):
raise Rc06ProtocolError(f"{label} must be a timestamp.")
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise Rc06ProtocolError(f"{label} is invalid.") from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise Rc06ProtocolError(f"{label} must include a timezone.")
return parsed.astimezone(UTC)
def iso_time(value: datetime) -> str:
if value.tzinfo is None or value.utcoffset() is None:
raise Rc06ProtocolError("A protocol timestamp must include a timezone.")
return value.astimezone(UTC).isoformat(timespec="seconds")
def public_key_raw(public_key: Ed25519PublicKey) -> bytes:
return public_key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
def public_key_spki(public_key: Any) -> bytes:
try:
return public_key.public_bytes(
serialization.Encoding.DER,
serialization.PublicFormat.SubjectPublicKeyInfo,
)
except (AttributeError, TypeError, ValueError) as exc:
raise Rc06ProtocolError("Public key is invalid.") from exc
def load_pinned_ed25519(path_value: str, expected_sha256: str, label: str) -> Ed25519PublicKey:
expected = require_sha(expected_sha256, f"{label} pin")
path = Path(path_value)
if not path.is_absolute():
raise Rc06ProtocolError(f"{label} path must be absolute.")
try:
raw = path.read_bytes()
except OSError as exc:
raise Rc06ProtocolError(f"{label} is unavailable.") from exc
try:
if len(raw) == 32:
public_key = Ed25519PublicKey.from_public_bytes(raw)
else:
loaded = serialization.load_pem_public_key(raw)
if not isinstance(loaded, Ed25519PublicKey):
raise ValueError("not Ed25519")
public_key = loaded
except ValueError as exc:
raise Rc06ProtocolError(f"{label} is not an Ed25519 public key.") from exc
if sha256_bytes(public_key_raw(public_key)) != expected:
raise Rc06ProtocolError(f"{label} does not match its configured pin.")
return public_key
def _validate_asset(value: Any) -> dict[str, Any]:
fields = (
"template_id", "name", "version", "document_kind", "contract_type", "source",
"source_lifecycle_status", "body_content", "content_sha256", "input_schema",
"required_variables", "optional_variables", "validation_rules", "clause_ids",
"review_rule_ids", "review_identity", "body_sha256", "input_schema_sha256",
"variables_sha256", "review_identity_sha256", "asset_payload_sha256",
)
row = require_exact(value, "asset", fields)
template_id = require_text(row["template_id"], "asset template_id", maximum=128)
body = require_text(row["body_content"], f"asset {template_id} body", maximum=4_000_000)
if sha256_bytes(body.encode("utf-8")) != require_sha(row["body_sha256"], "asset body hash"):
raise Rc06ProtocolError(f"Asset body hash drift: {template_id}")
schema_hash = sha256_json(row["input_schema"]) if row["input_schema"] is not None else None
if row["input_schema_sha256"] != schema_hash:
raise Rc06ProtocolError(f"Asset input schema hash drift: {template_id}")
if row["variables_sha256"] != sha256_json({
"required": row["required_variables"], "optional": row["optional_variables"]
}):
raise Rc06ProtocolError(f"Asset variable hash drift: {template_id}")
if row["review_identity_sha256"] != sha256_json(row["review_identity"]):
raise Rc06ProtocolError(f"Asset review identity hash drift: {template_id}")
content_projection = {
"id": row["template_id"], "contract_type": row["contract_type"],
"required_variables": row["required_variables"], "optional_variables": row["optional_variables"],
"validation_rules": row["validation_rules"], "input_schema": row["input_schema"],
"clause_ids": row["clause_ids"], "review_rule_ids": row["review_rule_ids"],
"body_content": row["body_content"],
}
if require_sha(row["content_sha256"], "asset content hash") != sha256_json(content_projection):
raise Rc06ProtocolError(f"Asset content hash drift: {template_id}")
if require_sha(row["asset_payload_sha256"], "asset payload hash") != sha256_json(
{key: child for key, child in row.items() if key != "asset_payload_sha256"}
):
raise Rc06ProtocolError(f"Asset payload hash drift: {template_id}")
return row
def _validate_module(value: Any) -> dict[str, Any]:
row = require_exact(value, "Prompt module", (
"category", "key", "version", "content", "content_sha256", "publication_mapping",
"module_payload_sha256",
))
key = require_text(row["key"], "Prompt module key", maximum=128)
if row["category"] not in {"drafting", "review"}:
raise Rc06ProtocolError(f"Prompt module category is invalid: {key}")
content = require_text(row["content"], f"Prompt module {key} content", maximum=4_000_000)
if require_sha(row["content_sha256"], "Prompt module content hash") != sha256_bytes(content.encode("utf-8")):
raise Rc06ProtocolError(f"Prompt module content hash drift: {key}")
if require_sha(row["module_payload_sha256"], "Prompt module payload hash") != sha256_json(
{name: child for name, child in row.items() if name != "module_payload_sha256"}
):
raise Rc06ProtocolError(f"Prompt module payload hash drift: {key}")
return row
def validate_manifest(value: Any) -> dict[str, Any]:
manifest = require_exact(value, "publication batch", (
"schema_version", "candidate", "asset_batch", "prompt_module_batch",
"runtime_prompt_dependencies", "default_prompt_asset_boundary", "golden_dispositions",
"publication_control", "side_effects", "claims_not_made", "manifest_payload_sha256",
))
if manifest["schema_version"] != BATCH_SCHEMA:
raise Rc06ProtocolError("Publication batch schema is unsupported.")
if require_sha(manifest["manifest_payload_sha256"], "manifest payload hash") != sha256_json(
{key: child for key, child in manifest.items() if key != "manifest_payload_sha256"}
):
raise Rc06ProtocolError("Publication batch payload hash drift.")
candidate = manifest["candidate"]
if not isinstance(candidate, dict) or COMMIT.fullmatch(str(candidate.get("commit"))) is None:
raise Rc06ProtocolError("Candidate commit is invalid.")
require_text(candidate.get("tag"), "candidate tag", maximum=128)
images = candidate.get("images")
if not isinstance(images, list):
raise Rc06ProtocolError("Candidate image identities are missing.")
image_by_role: dict[str, str] = {}
for image in images:
if not isinstance(image, dict):
raise Rc06ProtocolError("Candidate image identity is invalid.")
role = require_text(image.get("role"), "candidate image role", maximum=64)
digest = require_text(image.get("manifest_digest"), "candidate image digest", maximum=71)
if IMAGE_DIGEST.fullmatch(digest) is None or role in image_by_role:
raise Rc06ProtocolError("Candidate image identities are invalid or duplicated.")
image_by_role[role] = digest
if "contract-api" not in image_by_role or "ai-platform-api" not in image_by_role:
raise Rc06ProtocolError("Candidate does not identify both publication participants.")
assets = require_exact(manifest["asset_batch"], "asset batch", ("count", "items", "inventory_sha256"))
if assets["count"] != EXPECTED_ASSETS or not isinstance(assets["items"], list) or len(assets["items"]) != EXPECTED_ASSETS:
raise Rc06ProtocolError("Publication batch must contain exactly 60 assets.")
asset_rows = [_validate_asset(item) for item in assets["items"]]
if len({item["template_id"] for item in asset_rows}) != EXPECTED_ASSETS:
raise Rc06ProtocolError("Asset identities are duplicated.")
if require_sha(assets["inventory_sha256"], "asset inventory hash") != sha256_json(asset_rows):
raise Rc06ProtocolError("Asset inventory hash drift.")
modules = manifest["prompt_module_batch"]
if not isinstance(modules, dict) or modules.get("count") != EXPECTED_MODULES:
raise Rc06ProtocolError("Publication batch must contain exactly 14 Prompt modules.")
module_rows = modules.get("items")
if not isinstance(module_rows, list) or len(module_rows) != EXPECTED_MODULES:
raise Rc06ProtocolError("Prompt module inventory is incomplete.")
module_rows = [_validate_module(item) for item in module_rows]
if len({item["key"] for item in module_rows}) != EXPECTED_MODULES:
raise Rc06ProtocolError("Prompt module identities are duplicated.")
if require_sha(modules.get("inventory_sha256"), "Prompt module inventory hash") != sha256_json(module_rows):
raise Rc06ProtocolError("Prompt module inventory hash drift.")
runtime = require_exact(
manifest["runtime_prompt_dependencies"], "runtime Prompt dependencies",
("publication_item_count", "count", "items", "inventory_sha256"),
)
runtime_rows = runtime["items"]
if (
runtime["publication_item_count"] != 74
or runtime["count"] != EXPECTED_RUNTIME
or not isinstance(runtime_rows, list)
or len(runtime_rows) != EXPECTED_RUNTIME
or len({item.get("runtime_key") for item in runtime_rows if isinstance(item, dict)}) != EXPECTED_RUNTIME
):
raise Rc06ProtocolError("Runtime Prompt inventory must contain exactly 14 identities.")
for item in runtime_rows:
require_exact(item, "runtime Prompt", (
"kind", "runtime_key", "version", "content_sha256", "publication_dependency",
"publication_module_dependencies",
))
if item["kind"] not in {"composed_drafting", "new_review_module", "existing_domain_prompt"}:
raise Rc06ProtocolError("Runtime Prompt kind is invalid.")
require_text(item["runtime_key"], "runtime Prompt key", maximum=128)
require_text(item["version"], "runtime Prompt version", maximum=128)
require_sha(item["content_sha256"], "runtime Prompt content hash")
if require_sha(runtime["inventory_sha256"], "runtime Prompt inventory hash") != sha256_json(runtime_rows):
raise Rc06ProtocolError("Runtime Prompt inventory hash drift.")
golden = manifest["golden_dispositions"]
if (
not isinstance(golden, dict)
or golden.get("count") != EXPECTED_GOLDEN
or not isinstance(golden.get("items"), list)
or len(golden["items"]) != EXPECTED_GOLDEN
or any(item.get("status") == "unresolved" for item in golden["items"] if isinstance(item, dict))
or golden.get("inventory_sha256") != sha256_json(golden["items"])
):
raise Rc06ProtocolError("Golden dispositions are incomplete or unresolved.")
control = manifest["publication_control"]
if not isinstance(control, dict) or control.get("rc05_terminal_evidence_complete") is not True or control.get(
"golden_dispositions_complete"
) is not True or control.get("release_ready") is not False:
raise Rc06ProtocolError("Offline publication control is not eligible for RC06 apply.")
if manifest["side_effects"] != {
"http_calls": 0, "database_writes": 0, "model_calls": 0, "publication_api_calls": 0
}:
raise Rc06ProtocolError("Offline manifest side-effect boundary drift.")
return manifest
def expected_publication_results(manifest: Mapping[str, Any]) -> list[dict[str, Any]]:
rows = [
{
"kind": "asset", "item_id": item["template_id"],
"expected_content_sha256": item["content_sha256"],
"online_content_sha256": item["content_sha256"], "status": "applied_and_verified",
}
for item in manifest["asset_batch"]["items"]
] + [
{
"kind": "prompt_module", "item_id": item["key"],
"expected_content_sha256": item["content_sha256"],
"online_content_sha256": item["content_sha256"], "status": "applied_and_verified",
}
for item in manifest["prompt_module_batch"]["items"]
]
return sorted(rows, key=lambda item: (item["kind"], item["item_id"]))
def expected_runtime_results(manifest: Mapping[str, Any]) -> list[dict[str, Any]]:
return [
{
"kind": item["kind"], "runtime_key": item["runtime_key"], "version": item["version"],
"expected_content_sha256": item["content_sha256"],
"online_content_sha256": item["content_sha256"], "status": "published_and_resolved",
"publication_dependency": item["publication_dependency"],
"publication_module_dependencies": item["publication_module_dependencies"],
}
for item in manifest["runtime_prompt_dependencies"]["items"]
]
def publication_idempotency_key(
manifest_raw_sha256: str,
approval_raw_sha256: str,
legal_binding_sha256: str,
environment_id: str,
tenant_id: str,
) -> str:
return sha256_json({
"operation": APPLY_OPERATION,
"manifest_raw_sha256": require_sha(manifest_raw_sha256, "manifest raw hash"),
"approval_raw_sha256": require_sha(approval_raw_sha256, "approval raw hash"),
"legal_review_binding_sha256": require_sha(legal_binding_sha256, "legal binding hash"),
"environment_id": require_text(environment_id, "environment_id", maximum=256),
"tenant_id": require_text(tenant_id, "tenant_id", maximum=64),
})
def publication_transaction_id(idempotency_key: str) -> str:
return "rc06-" + require_sha(idempotency_key, "idempotency key")[:59]
def _validate_approval_keyring(
keyring: Any,
*,
raw_sha256: str,
canonical_sha256: str,
trust: AuthorityTrust,
approved_at: datetime,
manifest_raw_sha256: str,
) -> list[dict[str, Any]]:
row = require_exact(keyring, "approval keyring", (
"schema_version", "root_key_id", "epoch", "issued_at", "expires_at",
"authorized_batch_manifest_raw_sha256", "keys", "keyring_payload_sha256",
"root_signature_base64",
))
if row["schema_version"] != APPROVAL_KEYRING_SCHEMA:
raise Rc06ProtocolError("Approval keyring schema is unsupported.")
require_sha(raw_sha256, "approval keyring raw hash")
if require_sha(canonical_sha256, "approval keyring canonical hash") != sha256_json(row):
raise Rc06ProtocolError("Approval keyring canonical hash drift.")
unsigned_fields = (
"schema_version", "root_key_id", "epoch", "issued_at", "expires_at",
"authorized_batch_manifest_raw_sha256", "keys",
)
unsigned = {field: row[field] for field in unsigned_fields}
if row["keyring_payload_sha256"] != sha256_json(unsigned):
raise Rc06ProtocolError("Approval keyring payload hash drift.")
if (
row["root_key_id"] != trust.approval_root_key_id
or row["epoch"] != trust.approval_keyring_epoch
or row["authorized_batch_manifest_raw_sha256"] != manifest_raw_sha256
):
raise Rc06ProtocolError("Approval keyring trust identity drift.")
issued_at = parse_time(row["issued_at"], "approval keyring issued_at")
expires_at = parse_time(row["expires_at"], "approval keyring expires_at")
if not issued_at <= approved_at < expires_at:
raise Rc06ProtocolError("Approval keyring is not valid at approval time.")
try:
signature = base64.b64decode(require_text(row["root_signature_base64"], "root signature"), validate=True)
trust.approval_root_public_key.verify(signature, APPROVAL_KEYRING_SIGNATURE_DOMAIN + canonical_json(unsigned))
except (ValueError, InvalidSignature) as exc:
raise Rc06ProtocolError("Approval keyring root signature is invalid.") from exc
keys = row["keys"]
if not isinstance(keys, list) or not keys:
raise Rc06ProtocolError("Approval keyring contains no keys.")
validated: list[dict[str, Any]] = []
seen: set[str] = set()
for value in keys:
key = require_exact(value, "approval key", (
"key_id", "algorithm", "public_key_pem", "approver_id", "name",
"not_before", "not_after", "revoked",
))
key_id = require_text(key["key_id"], "approval key id", maximum=128)
if key_id in seen or key["algorithm"] != "Ed25519" or key["revoked"] is not False:
raise Rc06ProtocolError("Approval key is duplicated, revoked, or unsupported.")
if {"approver_id": key["approver_id"], "name": key["name"]} != SOLE_REVIEWER:
raise Rc06ProtocolError("Approval key does not belong to the sole release reviewer.")
try:
public_key = serialization.load_pem_public_key(require_text(
key["public_key_pem"], "approval public key", maximum=16384
).encode("ascii"))
except (UnicodeError, ValueError) as exc:
raise Rc06ProtocolError("Approval public key is invalid.") from exc
if not isinstance(public_key, Ed25519PublicKey) or public_key_raw(public_key) == public_key_raw(
trust.approval_root_public_key
):
raise Rc06ProtocolError("Approval key is invalid or reuses the offline root.")
not_before = parse_time(key["not_before"], "approval key not_before")
not_after = parse_time(key["not_after"], "approval key not_after")
if not not_before <= approved_at < not_after:
raise Rc06ProtocolError("Approval key is outside its validity window.")
seen.add(key_id)
validated.append({**key, "public_key": public_key, "not_before_parsed": not_before, "not_after_parsed": not_after})
return validated
def _approval_summary(manifest: Mapping[str, Any], manifest_raw_sha256: str, reviewer: Mapping[str, Any]) -> dict[str, Any]:
candidate = manifest["candidate"]
terminal = candidate["rc05_terminal_evidence"]
identity = {field: reviewer[field] for field in ("approver_id", "name", "role", "organization", "authorization_scope")}
return {
"schema_version": "yuqei.rc06-single-reviewer-summary.v2",
"decision_scope": "approve_exact_rc06_publication_batch",
"reviewer": identity,
"candidate": {
"commit": candidate["commit"], "tag": candidate["tag"],
"source_manifest_sha256": candidate["source_manifest_sha256"],
"source_manifest_payload_sha256": candidate["source_manifest_payload_sha256"],
"image_identities_sha256": candidate["image_identities_sha256"],
"image_identity_set_sha256": candidate["image_identity_set_sha256"],
"rc05_plan_sha256": candidate["rc05_plan_sha256"],
"rc05_summary_raw_sha256": terminal["summary_raw_sha256"],
"rc05_ledger_identity_raw_sha256": terminal["ledger_identity_raw_sha256"],
"rc05_campaign_identity_raw_sha256": terminal["campaign_identity_raw_sha256"],
"rc05_complete_real_green": terminal["complete_real_green"],
},
"batch": {
"asset_count": manifest["asset_batch"]["count"],
"asset_inventory_sha256": manifest["asset_batch"]["inventory_sha256"],
"prompt_module_count": manifest["prompt_module_batch"]["count"],
"prompt_module_inventory_sha256": manifest["prompt_module_batch"]["inventory_sha256"],
"golden_disposition_count": manifest["golden_dispositions"]["count"],
"golden_disposition_counts_by_status": manifest["golden_dispositions"]["counts_by_status"],
"golden_disposition_inventory_sha256": manifest["golden_dispositions"]["inventory_sha256"],
},
"manifest_raw_sha256": manifest_raw_sha256,
"manifest_payload_sha256": manifest["manifest_payload_sha256"],
"approval_validity_max_hours": 168,
"remaining_controls_after_approval": [
"system_generated_item_apply_receipts_and_online_snapshots",
"runtime_executable_prompt_snapshots_1_drafting_plus_13_review",
],
"claims_not_made": [
"This approval does not itself publish any asset or Prompt module.",
"This approval does not authorize public production traffic cutover.",
"System apply evidence and executable runtime Prompt snapshots remain separate gates.",
],
}
def _validate_approval(
manifest: Mapping[str, Any], request: Mapping[str, Any], trust: AuthorityTrust, now: datetime
) -> dict[str, Any]:
receipt = require_exact(request["approval_receipt"], "approval receipt", (
"schema_version", "approval_id", "payload", "signature",
))
if receipt["schema_version"] != APPROVAL_SCHEMA:
raise Rc06ProtocolError("Approval receipt schema is unsupported.")
payload = require_exact(receipt["payload"], "approval payload", (
"schema_version", "batch_manifest_raw_sha256", "batch_manifest_payload_sha256",
"decision", "approved_at", "expires_at", "key_id", "approval_keyring_epoch",
"approval_keyring_payload_sha256", "approver", "asset_payload_sha256s",
"prompt_module_payload_sha256s", "golden_disposition_inventory_sha256",
"review_summary_sha256",
))
approved_at = parse_time(payload["approved_at"], "approval approved_at")
expires_at = parse_time(payload["expires_at"], "approval expires_at")
if (
payload["schema_version"] != APPROVAL_PAYLOAD_SCHEMA
or payload["decision"] != "approved"
or payload["batch_manifest_raw_sha256"] != request["batch_manifest_raw_sha256"]
or payload["batch_manifest_payload_sha256"] != manifest["manifest_payload_sha256"]
or expires_at <= approved_at
or expires_at - approved_at > MAX_APPROVAL_WINDOW
or not approved_at <= now < expires_at
):
raise Rc06ProtocolError("Approval receipt identity or validity window drift.")
reviewer = require_exact(payload["approver"], "approval approver", (
"approver_id", "name", "role", "organization", "authorization_scope", "human_confirmed",
))
if (
{"approver_id": reviewer["approver_id"], "name": reviewer["name"]} != SOLE_REVIEWER
or reviewer["human_confirmed"] is not True
or any(not isinstance(reviewer[field], str) or not reviewer[field].strip() for field in (
"role", "organization", "authorization_scope"
))
):
raise Rc06ProtocolError("Approval does not identify the confirmed sole release reviewer.")
keyring = request["approval_keyring"]
trusted_keys = _validate_approval_keyring(
keyring,
raw_sha256=request["approval_keyring_raw_sha256"],
canonical_sha256=request["approval_keyring_canonical_sha256"],
trust=trust,
approved_at=approved_at,
manifest_raw_sha256=request["batch_manifest_raw_sha256"],
)
if (
payload["approval_keyring_epoch"] != keyring["epoch"]
or payload["approval_keyring_payload_sha256"] != keyring["keyring_payload_sha256"]
or payload["asset_payload_sha256s"] != {
item["template_id"]: item["asset_payload_sha256"] for item in manifest["asset_batch"]["items"]
}
or payload["prompt_module_payload_sha256s"] != {
item["key"]: item["module_payload_sha256"] for item in manifest["prompt_module_batch"]["items"]
}
or payload["golden_disposition_inventory_sha256"] != manifest["golden_dispositions"]["inventory_sha256"]
or payload["review_summary_sha256"] != sha256_json(_approval_summary(
manifest, request["batch_manifest_raw_sha256"], reviewer
))
):
raise Rc06ProtocolError("Approval receipt material drift.")
matches = [key for key in trusted_keys if key["key_id"] == payload["key_id"]]
if len(matches) != 1 or expires_at > matches[0]["not_after_parsed"]:
raise Rc06ProtocolError("Approval signing key is not uniquely valid for the receipt window.")
try:
signature = base64.b64decode(require_text(receipt["signature"], "approval signature"), validate=True)
matches[0]["public_key"].verify(signature, APPROVAL_SIGNATURE_DOMAIN + canonical_json(payload))
except (ValueError, InvalidSignature) as exc:
raise Rc06ProtocolError("Approval signature is invalid.") from exc
if receipt["approval_id"] != "rc06-batch-approval-" + sha256_bytes(canonical_json(payload) + signature)[:24]:
raise Rc06ProtocolError("Approval receipt id drift.")
return receipt
def _validate_legal(
manifest: Mapping[str, Any], request: Mapping[str, Any], trust: AuthorityTrust, now: datetime
) -> tuple[dict[str, Any], str]:
keyring = require_exact(request["legal_source_review_keyring"], "legal review keyring", (
"schema_version", "keys",
))
if keyring["schema_version"] != LEGAL_KEYRING_SCHEMA:
raise Rc06ProtocolError("Legal review keyring schema is unsupported.")
raw_pin = require_sha(request["legal_source_review_keyring_raw_sha256"], "legal keyring hash")
if raw_pin != trust.legal_keyring_sha256 or sha256_json(keyring) != raw_pin:
raise Rc06ProtocolError("Legal review keyring does not match the server pin.")
legal_request = require_exact(request["legal_source_review_request"], "legal review request", (
"schema_version", "status", "candidate_commit", "created_at", "scope", "source_files",
"occurrences", "guardrails", "request_sha256",
))
request_sha = require_sha(legal_request["request_sha256"], "legal review request hash")
if request_sha != sha256_json({key: child for key, child in legal_request.items() if key != "request_sha256"}):
raise Rc06ProtocolError("Legal review request embedded hash drift.")
scope = legal_request["scope"]
occurrences = legal_request["occurrences"]
if (
legal_request["schema_version"] != LEGAL_REQUEST_SCHEMA
or legal_request["status"] != "pending_qualified_human_review"
or legal_request["candidate_commit"] != manifest["candidate"]["commit"]
or not isinstance(scope, dict)
or scope.get("scene_count") != 30
or scope.get("reference_occurrence_count") != 43
or not isinstance(occurrences, list)
or len(occurrences) != 43
):
raise Rc06ProtocolError("Legal review request does not cover exact 30 scenes and 43 occurrences.")
occurrence_by_id: dict[str, dict[str, Any]] = {}
for occurrence in occurrences:
if not isinstance(occurrence, dict):
raise Rc06ProtocolError("Legal review occurrence is invalid.")
occurrence_id = require_text(occurrence.get("occurrence_id"), "legal occurrence id", maximum=512)
require_sha(occurrence.get("source_record_sha256"), "legal source record hash")
if occurrence_id in occurrence_by_id:
raise Rc06ProtocolError("Legal review occurrence is duplicated.")
occurrence_by_id[occurrence_id] = occurrence
if list(occurrence_by_id) != sorted(occurrence_by_id):
raise Rc06ProtocolError("Legal review occurrences are not canonically ordered.")
decision = request["legal_source_review_decision"]
if not isinstance(decision, dict) or decision.get("schema_version") != LEGAL_DECISION_SCHEMA:
raise Rc06ProtocolError("Legal review decision schema is unsupported.")
if decision.get("request_sha256") != request_sha or decision.get("candidate_commit") != manifest["candidate"]["commit"]:
raise Rc06ProtocolError("Legal review decision binding drift.")
reviewed_at = parse_time(decision.get("reviewed_at"), "legal reviewed_at")
expires_at = parse_time(decision.get("expires_at"), "legal expires_at")
if not reviewed_at <= now <= expires_at or expires_at - reviewed_at > MAX_LEGAL_WINDOW:
raise Rc06ProtocolError("Legal review decision is outside its validity window.")
reviewer = decision.get("reviewer")
if not isinstance(reviewer, dict) or any(not isinstance(reviewer.get(field), str) or not reviewer[field].strip() for field in (
"name", "organization", "qualification", "credential_reference"
)):
raise Rc06ProtocolError("Legal reviewer identity is incomplete.")
items = decision.get("items")
if not isinstance(items, list) or len(items) != 43:
raise Rc06ProtocolError("Legal review decision must contain exactly 43 items.")
seen: set[str] = set()
for item in items:
if not isinstance(item, dict):
raise Rc06ProtocolError("Legal review item is invalid.")
occurrence_id = item.get("occurrence_id")
if occurrence_id in seen or occurrence_id not in occurrence_by_id:
raise Rc06ProtocolError("Legal review item identity is unexpected or duplicated.")
expected = occurrence_by_id[occurrence_id]
if (
item.get("source_record_sha256") != expected["source_record_sha256"]
or item.get("current_effectiveness_verified") is not True
or item.get("transaction_applicability_verified") is not True
):
raise Rc06ProtocolError("Legal review item is not affirmatively verified.")
parse_time(item.get("checked_at"), "legal item checked_at")
require_text(item.get("authoritative_status_url"), "legal authoritative URL", maximum=4096)
if len(require_text(item.get("rationale"), "legal rationale", maximum=4096)) < 20:
raise Rc06ProtocolError("Legal review rationale is insufficient.")
seen.add(occurrence_id)
signature = decision.get("signature")
if not isinstance(signature, dict) or signature.get("algorithm") != "Ed25519":
raise Rc06ProtocolError("Legal review signature is missing.")
key_id = require_text(signature.get("key_id"), "legal signature key id", maximum=128)
matches = [key for key in keyring["keys"] if isinstance(key, dict) and key.get("key_id") == key_id]
if len(matches) != 1:
raise Rc06ProtocolError("Legal review key is not uniquely trusted.")
key = matches[0]
if key.get("status") != "trusted" or key.get("revoked_at") is not None:
raise Rc06ProtocolError("Legal review key is not active.")
if not parse_time(key.get("valid_from"), "legal key valid_from") <= reviewed_at <= parse_time(
key.get("valid_until"), "legal key valid_until"
):
raise Rc06ProtocolError("Legal review key is outside its validity window.")
for decision_field, key_field in (
("name", "reviewer_name"), ("organization", "reviewer_organization"),
("qualification", "reviewer_qualification"), ("credential_reference", "credential_reference"),
):
if reviewer[decision_field] != key.get(key_field):
raise Rc06ProtocolError("Legal reviewer identity does not match the trusted key.")
try:
public_raw = base64.b64decode(require_text(key.get("public_key_raw_base64"), "legal public key"), validate=True)
signature_raw = base64.b64decode(require_text(signature.get("value_base64"), "legal signature"), validate=True)
Ed25519PublicKey.from_public_bytes(public_raw).verify(
signature_raw,
LEGAL_SIGNATURE_DOMAIN + canonical_json({field: child for field, child in decision.items() if field != "signature"}),
)
except (ValueError, InvalidSignature) as exc:
raise Rc06ProtocolError("Legal review signature verification failed.") from exc
summary = {
"status": "verified_qualified_human_review",
"candidate_commit": legal_request["candidate_commit"],
"request_sha256": request_sha,
"reviewer_name": reviewer["name"],
"reviewed_occurrence_count": 43,
"all_current_effectiveness_verified": True,
"all_transaction_applicability_verified": True,
"expires_at": decision["expires_at"],
"authorizes_publication_by_itself": False,
}
binding = sha256_json({
"request_sha256": request_sha,
"decision_sha256": sha256_json(decision),
"keyring_raw_sha256": raw_pin,
})
return summary, binding
def _validate_challenge(
manifest: Mapping[str, Any], request: Mapping[str, Any], trust: AuthorityTrust,
legal_binding: str, legal_expires_at: str, now: datetime,
) -> dict[str, Any]:
challenge = require_exact(request["authority_challenge"], "apply challenge", (
"schema_version", "challenge_id", "payload", "signature",
))
if challenge["schema_version"] != CHALLENGE_SCHEMA:
raise Rc06ProtocolError("Apply challenge schema is unsupported.")
payload = require_exact(challenge["payload"], "apply challenge payload", (
"schema_version", "nonce_base64", "candidate_commit", "candidate_tag",
"batch_manifest_raw_sha256", "batch_manifest_payload_sha256", "approval_receipt_sha256",
"legal_source_review_binding_sha256", "legal_source_review_expires_at", "operation",
"environment_id", "tenant_id", "idempotency_key", "transaction_id", "actor",
"issued_at", "expires_at",
))
expected_idempotency = publication_idempotency_key(
request["batch_manifest_raw_sha256"], request["approval_receipt_sha256"], legal_binding,
trust.environment_id, trust.tenant_id,
)
if (
payload["schema_version"] != CHALLENGE_PAYLOAD_SCHEMA
or payload["candidate_commit"] != manifest["candidate"]["commit"]
or payload["candidate_tag"] != manifest["candidate"]["tag"]
or payload["batch_manifest_raw_sha256"] != request["batch_manifest_raw_sha256"]
or payload["batch_manifest_payload_sha256"] != manifest["manifest_payload_sha256"]
or payload["approval_receipt_sha256"] != request["approval_receipt_sha256"]
or payload["legal_source_review_binding_sha256"] != legal_binding
or payload["legal_source_review_expires_at"] != legal_expires_at
or payload["operation"] != APPLY_OPERATION
or payload["environment_id"] != trust.environment_id
or payload["tenant_id"] != trust.tenant_id
or payload["idempotency_key"] != expected_idempotency
or payload["transaction_id"] != publication_transaction_id(expected_idempotency)
or payload["actor"] != {"type": "system", "component": TRUSTED_COMPONENT}
):
raise Rc06ProtocolError("Apply challenge binding drift.")
issued_at = parse_time(payload["issued_at"], "challenge issued_at")
expires_at = parse_time(payload["expires_at"], "challenge expires_at")
if (
expires_at <= issued_at
or expires_at - issued_at > MAX_CHALLENGE_WINDOW
or expires_at > parse_time(legal_expires_at, "legal expires_at")
or not issued_at <= now < expires_at
):
raise Rc06ProtocolError("Apply challenge is outside its authority window.")
try:
nonce = base64.b64decode(require_text(payload["nonce_base64"], "challenge nonce", maximum=128), validate=True)
signature = base64.b64decode(require_text(challenge["signature"], "challenge signature", maximum=1024), validate=True)
if len(nonce) != 32:
raise ValueError("nonce length")
trust.apply_service_public_key.verify(signature, CHALLENGE_SIGNATURE_DOMAIN + canonical_json(payload))
except (ValueError, InvalidSignature) as exc:
raise Rc06ProtocolError("Apply challenge signature verification failed.") from exc
if challenge["challenge_id"] != "rc06-apply-challenge-" + sha256_bytes(canonical_json(payload) + signature)[:24]:
raise Rc06ProtocolError("Apply challenge id drift.")
return challenge
def validate_authority(request: Any, trust: AuthorityTrust, *, now: datetime) -> AuthorizedMaterial:
fields = (
"schema_version", "idempotency_key", "transaction_id", "environment_id", "tenant_id",
"transaction_mode", "cross_database_atomicity", "batch_manifest_raw_sha256",
"batch_manifest_payload_sha256", "approval_receipt_sha256", "approval_keyring",
"approval_keyring_raw_sha256", "approval_keyring_canonical_sha256",
"legal_source_review_binding_sha256", "legal_source_review_expires_at", "authority_challenge",
"manifest", "approval_receipt", "legal_source_review_request", "legal_source_review_decision",
"legal_source_review_keyring", "legal_source_review_keyring_raw_sha256",
"expected_publication_item_results", "expected_publication_item_count",
"expected_runtime_snapshot_dependencies", "expected_runtime_snapshot_dependency_count",
)
row = require_exact(request, "online apply request", fields)
if (
row["schema_version"] != APPLY_REQUEST_SCHEMA
or row["environment_id"] != trust.environment_id
or row["tenant_id"] != trust.tenant_id
or row["transaction_mode"] != TRANSACTION_MODE
or row["cross_database_atomicity"] is not False
):
raise Rc06ProtocolError("Online apply request trust boundary drift.")
manifest = validate_manifest(row["manifest"])
if (
row["batch_manifest_payload_sha256"] != manifest["manifest_payload_sha256"]
or manifest["candidate"]["commit"] != trust.frozen_candidate_commit
):
raise Rc06ProtocolError("Online apply candidate binding drift.")
image_by_role = {item["role"]: item["manifest_digest"] for item in manifest["candidate"]["images"]}
if (
image_by_role["contract-api"] != trust.contract_release_image_digest
or image_by_role["ai-platform-api"] != trust.ai_release_image_digest
):
raise Rc06ProtocolError("Participant deployment image does not match the approved candidate.")
approval = _validate_approval(manifest, row, trust, now)
legal_summary, legal_binding = _validate_legal(manifest, row, trust, now)
if (
row["legal_source_review_binding_sha256"] != legal_binding
or row["legal_source_review_expires_at"] != legal_summary["expires_at"]
):
raise Rc06ProtocolError("Legal review binding drift.")
challenge = _validate_challenge(
manifest, row, trust, legal_binding, legal_summary["expires_at"], now
)
expected_items = expected_publication_results(manifest)
expected_runtime = expected_runtime_results(manifest)
expected_key = publication_idempotency_key(
row["batch_manifest_raw_sha256"], row["approval_receipt_sha256"], legal_binding,
trust.environment_id, trust.tenant_id,
)
if (
row["idempotency_key"] != expected_key
or row["transaction_id"] != publication_transaction_id(expected_key)
or row["expected_publication_item_count"] != 74
or row["expected_publication_item_results"] != expected_items
or row["expected_runtime_snapshot_dependency_count"] != 14
or row["expected_runtime_snapshot_dependencies"] != expected_runtime
):
raise Rc06ProtocolError("Online apply expected result or transaction identity drift.")
return AuthorizedMaterial(
request=dict(row), manifest=manifest, approval=approval,
approval_keyring=dict(row["approval_keyring"]), legal_summary=legal_summary,
legal_binding_sha256=legal_binding, challenge=challenge,
expected_publication_results=expected_items, expected_runtime_results=expected_runtime,
contract_image_digest=image_by_role["contract-api"],
ai_image_digest=image_by_role["ai-platform-api"],
)
@@ -62,6 +62,8 @@ CORE_TABLES = (
"template_published_snapshots",
"rc06_publication_sagas",
"rc06_template_publication_items",
"rc06_online_publication_transactions",
"rc06_apply_challenge_uses",
"template_edit_drafts",
"legal_asset_lifecycle_events",
"legal_asset_published_snapshots",
@@ -193,6 +195,7 @@ RC06_COLUMNS_BY_TABLE = {
"ai_candidate_commit",
"ai_release_image_digest",
"compensation_capability",
"ai_compensation_status_json",
"request_json",
"state",
"contract_committed_at",
@@ -209,6 +212,49 @@ RC06_COLUMNS_BY_TABLE = {
"online_content_sha256",
"state",
),
"rc06_online_publication_transactions": (
"transaction_id",
"tenant_id",
"idempotency_key",
"request_sha256",
"environment_id",
"candidate_commit",
"contract_release_image_digest",
"ai_release_image_digest",
"challenge_id",
"challenge_sha256",
"state",
"request_json",
"prepare_request_json",
"participant_evidence_json",
"online_primary_kms_request_id",
"online_attestation_kms_request_id",
"online_receipt_json",
"runtime_primary_kms_request_id",
"runtime_attestation_kms_request_id",
"runtime_receipt_json",
"rollback_response_json",
"rollback_response_sha256",
"apply_receipt_json",
"failure_code",
"failure_at",
"failure_evidence_sha256",
"contract_committed_at",
"pending_countersign_at",
"finalized_at",
),
"rc06_apply_challenge_uses": (
"challenge_id",
"nonce_sha256",
"challenge_sha256",
"tenant_id",
"idempotency_key",
"transaction_id",
"request_sha256",
"outcome",
"reserved_at",
"consumed_at",
),
}
RC06_CONSTRAINTS_BY_TABLE = {
"rc06_publication_sagas": (
@@ -219,6 +265,15 @@ RC06_CONSTRAINTS_BY_TABLE = {
"ux_rc06_template_publication_items_transaction_template",
"ck_rc06_template_item_state",
),
"rc06_online_publication_transactions": (
"ux_rc06_online_publication_idempotency",
"ck_rc06_online_publication_state",
),
"rc06_apply_challenge_uses": (
"ux_rc06_apply_challenge_nonce",
"ux_rc06_apply_challenge_transaction",
"ck_rc06_apply_challenge_outcome",
),
}
PLAYBOOK_OPERATIONS_COLUMNS_BY_TABLE = {
"product_experience_events": (
@@ -125,7 +125,7 @@ def test_fresh_alembic_upgrade_then_old_head_round_trip(tmp_path, monkeypatch):
with engine.connect() as connection:
assert connection.exec_driver_sql(
"SELECT version_num FROM contract_api_alembic_version"
).scalar_one() == "20261006_0081"
).scalar_one() == "20261006_0082"
engine.dispose()
command.downgrade(config, "20261006_0080")
@@ -138,3 +138,65 @@ def test_fresh_alembic_upgrade_then_old_head_round_trip(tmp_path, monkeypatch):
engine.dispose()
finally:
get_settings.cache_clear()
def test_rc06_facade_migration_round_trip_and_refuses_evidence_erasure(tmp_path):
engine = sa.create_engine(f"sqlite+pysqlite:///{(tmp_path / 'contract-facade.sqlite3').as_posix()}")
hardening = _load("20261006_0081_harden_rc06_publication_saga.py")
facade = _load("20261006_0082_add_rc06_publication_facade.py")
with engine.begin() as connection:
_old_head(connection)
_run(connection, hardening, "upgrade")
_run(connection, facade, "upgrade")
inspector = sa.inspect(connection)
assert {
"rc06_online_publication_transactions",
"rc06_apply_challenge_uses",
} <= set(inspector.get_table_names())
assert "ai_compensation_status_json" in {
item["name"] for item in inspector.get_columns("rc06_publication_sagas")
}
transaction_columns = {
item["name"] for item in inspector.get_columns("rc06_online_publication_transactions")
}
assert {
"online_primary_kms_request_id",
"online_attestation_kms_request_id",
"runtime_primary_kms_request_id",
"runtime_attestation_kms_request_id",
"apply_receipt_json",
"rollback_response_json",
} <= transaction_columns
now = datetime.now(UTC)
connection.execute(sa.text(
"INSERT INTO rc06_online_publication_transactions ("
"transaction_id,tenant_id,idempotency_key,request_sha256,environment_id,candidate_commit,"
"contract_release_image_digest,ai_release_image_digest,batch_manifest_raw_sha256,"
"batch_manifest_payload_sha256,approval_receipt_sha256,legal_source_review_binding_sha256,"
"legal_source_review_expires_at,challenge_id,challenge_sha256,state,request_json,"
"prepare_request_json,created_at,updated_at) VALUES ("
":transaction_id,'tenant',:idempotency,:request_sha,'environment',:commit,"
":contract_image,:ai_image,:manifest_raw,:manifest_payload,:approval,:legal,"
":expires_at,:challenge_id,:challenge_sha,'authorized','{}','{}',:created_at,:updated_at)"
), {
"transaction_id": "rc06-migration-facade-0001",
"idempotency": "1" * 64,
"request_sha": "2" * 64,
"commit": "3" * 40,
"contract_image": "sha256:" + "4" * 64,
"ai_image": "sha256:" + "5" * 64,
"manifest_raw": "6" * 64,
"manifest_payload": "7" * 64,
"approval": "8" * 64,
"legal": "9" * 64,
"expires_at": now,
"challenge_id": "rc06-apply-challenge-migration",
"challenge_sha": "a" * 64,
"created_at": now,
"updated_at": now,
})
with pytest.raises(RuntimeError, match="refuses to erase"):
_run(connection, facade, "downgrade")
assert "rc06_online_publication_transactions" in sa.inspect(connection).get_table_names()
engine.dispose()
@@ -0,0 +1,494 @@
from __future__ import annotations
import base64
from datetime import UTC, datetime, timedelta
import pytest
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from yuqei_contract_api.config import ContractApiSettings
from yuqei_contract_api.persistence.models import Rc06OnlinePublicationTransactionModel
from yuqei_contract_api.rc06_publication import Rc06SagaStatus
from yuqei_contract_api.rc06_publication_facade import (
Rc06FacadeConflict,
Rc06FacadeError,
Rc06FacadeHeaders,
Rc06PublicationFacadeService,
)
from yuqei_contract_api.rc06_publication_protocol import (
APPLY_RECEIPT_PAYLOAD_SCHEMA,
APPLY_RECEIPT_SCHEMA,
APPLY_RECEIPT_SIGNATURE_DOMAIN,
APPLY_STATUS_SCHEMA,
FINALIZE_REQUEST_SCHEMA,
ROLLBACK_REQUEST_SCHEMA,
AuthorizedMaterial,
canonical_json,
iso_time,
publication_transaction_id,
sha256_bytes,
sha256_json,
)
from yuqei_contract_api.sqlalchemy_contract_repository import SqlAlchemyContractRepository
CANDIDATE = "a" * 40
CONTRACT_IMAGE = "sha256:" + "b" * 64
AI_IMAGE = "sha256:" + "c" * 64
TENANT = "rc06-production-tenant"
ENVIRONMENT = "production-cn-1"
class FakeAuthority:
def __init__(self, material: AuthorizedMaterial):
self.material = material
self.checked_at: list[datetime] = []
def authorize(self, request, *, now):
assert request == self.material.request
self.checked_at.append(now)
return self.material
class FakeSigner:
def __init__(self, *, fail_once_at: int | None = None):
self.calls: list[tuple[bytes, dict, bool]] = []
self.fail_once_at = fail_once_at
self.failed = False
def sign(self, *, domain, payload, include_request_id):
self.calls.append((domain, dict(payload), include_request_id))
if len(self.calls) == self.fail_once_at and not self.failed:
self.failed = True
raise Rc06FacadeError("injected KMS response loss")
request_id = f"kms-request-{len(self.calls)}"
block = {
"provider": "test-kms",
"key_reference": "rc06-online",
"key_version": "v1",
"algorithm": "RSA_PSS_SHA_256",
"value_base64": base64.b64encode(f"signature-{len(self.calls)}".encode()).decode(),
}
if include_request_id:
block["request_id"] = request_id
return block, request_id
class FakeCoordinator:
def __init__(self, material: AuthorizedMaterial, *, fail_commit: bool = False):
self.material = material
self.state: Rc06SagaStatus | None = None
self.fail_commit = fail_commit
self.compensate_calls = 0
def _status(self, state: str) -> Rc06SagaStatus:
now = datetime.now(UTC)
committed = state == "committed"
return Rc06SagaStatus(
transaction_id=self.material.request["transaction_id"],
state=state,
candidate_commit=CANDIDATE,
contract_candidate_commit=CANDIDATE,
contract_release_image_digest=CONTRACT_IMAGE,
ai_candidate_commit=CANDIDATE,
ai_release_image_digest=AI_IMAGE,
candidate_tag="rc06-test",
batch_manifest_raw_sha256=self.material.request["batch_manifest_raw_sha256"],
batch_manifest_payload_sha256=self.material.request["batch_manifest_payload_sha256"],
approval_receipt_sha256=self.material.request["approval_receipt_sha256"],
item_results=self.material.expected_publication_results if committed else [],
runtime_snapshots=[
{
"name": item["runtime_key"],
"version": item["version"],
"online_content_sha256": item["online_content_sha256"],
"status": "published",
"enabled": True,
"verified": True,
}
for item in self.material.expected_runtime_results
] if committed else [],
write_count=74 if committed else 0,
runtime_snapshot_count=14 if committed else 0,
no_go=not committed,
cross_database_atomicity=False,
ai_committed_at=now if committed else None,
contract_committed_at=now if committed else None,
committed_at=now if committed else None,
)
def prepare(self, payload, *, tenant_id):
assert tenant_id == TENANT
assert payload.contract_release_image_digest == CONTRACT_IMAGE
self.state = self._status("prepared")
return self.state
def commit(self, transaction_id, *, tenant_id=None):
assert tenant_id == TENANT
if self.fail_commit:
raise Rc06FacadeError("injected pre-Contract commit failure")
self.state = self._status("committed")
return self.state
def status(self, transaction_id, *, tenant_id=None):
if self.state is None:
raise KeyError(transaction_id)
return self.state
def compensate(self, transaction_id, *, tenant_id=None):
self.compensate_calls += 1
self.state = self._status("compensated")
self.state.compensated_from_state = "prepared"
self.state.compensated_at = datetime.now(UTC)
self.state.compensation_evidence_sha256 = "d" * 64
return self.state
def _material(now: datetime | None = None):
now = now or datetime.now(UTC)
expires = now + timedelta(hours=1)
idempotency = "1" * 64
transaction_id = publication_transaction_id(idempotency)
assets = [{"template_id": f"asset-{index:02d}"} for index in range(60)]
modules = [{"key": f"module-{index:02d}"} for index in range(14)]
runtime = [
{
"kind": "existing_domain_prompt" if index < 5 else "new_review_module",
"runtime_key": f"runtime-{index:02d}",
"version": "v1",
"content_sha256": f"{index + 10:064x}",
"publication_dependency": f"dependency-{index:02d}",
"publication_module_dependencies": [],
}
for index in range(14)
]
expected = sorted([
{
"kind": "asset",
"item_id": item["template_id"],
"expected_content_sha256": "e" * 64,
"online_content_sha256": "e" * 64,
"status": "applied_and_verified",
}
for item in assets
] + [
{
"kind": "prompt_module",
"item_id": item["key"],
"expected_content_sha256": "f" * 64,
"online_content_sha256": "f" * 64,
"status": "applied_and_verified",
}
for item in modules
], key=lambda item: (item["kind"], item["item_id"]))
expected_runtime = [
{
"kind": item["kind"],
"runtime_key": item["runtime_key"],
"version": item["version"],
"expected_content_sha256": item["content_sha256"],
"online_content_sha256": item["content_sha256"],
"status": "published_and_resolved",
"publication_dependency": item["publication_dependency"],
"publication_module_dependencies": [],
}
for item in runtime
]
challenge = {
"schema_version": "yuqei.rc06-apply-challenge.v2",
"challenge_id": "rc06-apply-challenge-test0001",
"payload": {
"nonce_base64": base64.b64encode(b"n" * 32).decode(),
"expires_at": iso_time(expires),
},
"signature": base64.b64encode(b"challenge").decode(),
}
approval = {"payload": {"expires_at": iso_time(expires)}}
manifest = {
"candidate": {"commit": CANDIDATE, "tag": "rc06-test"},
"asset_batch": {"items": assets},
"prompt_module_batch": {"items": modules},
"runtime_prompt_dependencies": {"items": runtime},
}
request = {
"idempotency_key": idempotency,
"transaction_id": transaction_id,
"environment_id": ENVIRONMENT,
"tenant_id": TENANT,
"batch_manifest_raw_sha256": "2" * 64,
"batch_manifest_payload_sha256": "3" * 64,
"approval_receipt_sha256": "4" * 64,
"legal_source_review_binding_sha256": "5" * 64,
"legal_source_review_expires_at": iso_time(expires),
"authority_challenge": challenge,
"approval_receipt": approval,
"expected_publication_item_results": expected,
"expected_runtime_snapshot_dependencies": expected_runtime,
}
return AuthorizedMaterial(
request=request,
manifest=manifest,
approval=approval,
approval_keyring={},
legal_summary={"expires_at": iso_time(expires)},
legal_binding_sha256="5" * 64,
challenge=challenge,
expected_publication_results=expected,
expected_runtime_results=expected_runtime,
contract_image_digest=CONTRACT_IMAGE,
ai_image_digest=AI_IMAGE,
)
def _service(tmp_path, *, fail_commit=False, fail_sign_once_at=None):
repository = SqlAlchemyContractRepository(
f"sqlite:///{tmp_path / 'contract.sqlite3'}",
auto_create=True,
seed_demo_data=False,
file_storage_dir=tmp_path / "files",
)
material = _material()
signer = FakeSigner(fail_once_at=fail_sign_once_at)
coordinator = FakeCoordinator(material, fail_commit=fail_commit)
apply_private = Ed25519PrivateKey.generate()
token = tmp_path / "rc06-token"
token.write_text("test-only-dedicated-token-0123456789abcdef", encoding="utf-8")
settings = ContractApiSettings(
environment="test",
rc06_publication_facade_enabled=True,
rc06_publication_environment_id=ENVIRONMENT,
rc06_publication_tenant_id=TENANT,
rc06_publication_bearer_token_file=str(token.resolve()),
rc06_frozen_candidate_commit=CANDIDATE,
rc06_release_image_digest=CONTRACT_IMAGE,
rc06_ai_release_image_digest=AI_IMAGE,
)
service = Rc06PublicationFacadeService(
settings=settings,
repository=repository,
authority=FakeAuthority(material),
signer=signer,
coordinator=coordinator,
apply_service_public_key=apply_private.public_key(),
)
request = material.request
headers = Rc06FacadeHeaders(
idempotency_key=request["idempotency_key"],
manifest_sha256=request["batch_manifest_raw_sha256"],
approval_sha256=request["approval_receipt_sha256"],
legal_sha256=request["legal_source_review_binding_sha256"],
tenant_id=TENANT,
request_sha256=sha256_json(request),
)
return repository, service, signer, coordinator, apply_private, material, headers
def test_apply_uses_exactly_four_kms_calls_and_recovers_without_resigning(tmp_path):
repository, service, signer, coordinator, _, material, headers = _service(tmp_path)
try:
response = service.apply(material.request, headers)
assert len(signer.calls) == 4
assert [item[2] for item in signer.calls] == [True, False, True, False]
assert response["publication_item_count"] == 74
assert response["runtime_snapshot_dependency_count"] == 14
status = service.status(material.request["transaction_id"], headers)
assert status["schema_version"] == APPLY_STATUS_SCHEMA
assert status["state"] == "committed_and_verified"
assert status["state_version"] == 3
assert status["no_go"] is True
assert status["online_transaction_receipt"] == response["online_transaction_receipt"]
assert status["runtime_snapshot_receipt"] == response["runtime_snapshot_receipt"]
replay = service.apply(
material.request,
headers,
now=datetime.now(UTC) + timedelta(days=1),
)
assert replay == response
assert len(signer.calls) == 4
with repository._session_factory() as session:
row = session.get(
Rc06OnlinePublicationTransactionModel,
material.request["transaction_id"],
)
assert row.online_primary_kms_request_id == "kms-request-1"
assert row.online_attestation_kms_request_id == "kms-request-2"
assert row.runtime_primary_kms_request_id == "kms-request-3"
assert row.runtime_attestation_kms_request_id == "kms-request-4"
finally:
repository.engine.dispose()
def test_facade_requires_dedicated_bearer_and_rejects_caller_tenant_override(tmp_path):
repository, service, _, _, _, material, headers = _service(tmp_path)
try:
with pytest.raises(Rc06FacadeError, match="Authenticated"):
service.authenticate(None)
with pytest.raises(Rc06FacadeError, match="Authenticated"):
service.authenticate("Bearer wrong")
service.authenticate("Bearer test-only-dedicated-token-0123456789abcdef")
forged = Rc06FacadeHeaders(**{**headers.__dict__, "tenant_id": "caller-controlled"})
with pytest.raises(Rc06FacadeConflict, match="server-bound"):
service.apply(material.request, forged)
with repository._session_factory() as session:
assert session.get(
Rc06OnlinePublicationTransactionModel,
material.request["transaction_id"],
) is None
finally:
repository.engine.dispose()
def test_finalize_is_idempotent_and_status_retains_all_receipts(tmp_path):
repository, service, _, _, apply_private, material, headers = _service(tmp_path)
try:
execution = service.apply(material.request, headers)
runtime_at = execution["runtime_snapshot_receipt"]["payload"]["captured_at"]
apply_payload = {
"schema_version": APPLY_RECEIPT_PAYLOAD_SCHEMA,
"batch_manifest_raw_sha256": material.request["batch_manifest_raw_sha256"],
"batch_manifest_payload_sha256": material.request["batch_manifest_payload_sha256"],
"approval_receipt_sha256": material.request["approval_receipt_sha256"],
"legal_source_review_binding_sha256": material.legal_binding_sha256,
"environment_id": ENVIRONMENT,
"tenant_id": TENANT,
"idempotency_key": material.request["idempotency_key"],
"transaction_id": material.request["transaction_id"],
"authorized_commit_at": runtime_at,
"countersigned_at": runtime_at,
"generated_by": {"type": "system", "component": "rc06-apply-authority"},
"transaction_mode": "durable_saga",
"cross_database_atomicity": False,
"item_results": material.expected_publication_results,
"write_count": 74,
"status": "applied_and_verified",
"online_transaction_receipt": execution["online_transaction_receipt"],
"runtime_snapshot_receipt": execution["runtime_snapshot_receipt"],
}
signature = apply_private.sign(APPLY_RECEIPT_SIGNATURE_DOMAIN + canonical_json(apply_payload))
apply_receipt = {
"schema_version": APPLY_RECEIPT_SCHEMA,
"receipt_id": "rc06-apply-receipt-" + sha256_bytes(canonical_json(apply_payload) + signature)[:24],
"payload": apply_payload,
"signature": base64.b64encode(signature).decode(),
}
finalize_request = {
"schema_version": FINALIZE_REQUEST_SCHEMA,
"idempotency_key": material.request["idempotency_key"],
"transaction_id": material.request["transaction_id"],
"environment_id": ENVIRONMENT,
"tenant_id": TENANT,
"batch_manifest_raw_sha256": material.request["batch_manifest_raw_sha256"],
"approval_receipt_sha256": material.request["approval_receipt_sha256"],
"legal_source_review_binding_sha256": material.legal_binding_sha256,
"online_transaction_receipt_sha256": sha256_json(execution["online_transaction_receipt"]),
"runtime_snapshot_receipt_sha256": sha256_json(execution["runtime_snapshot_receipt"]),
"apply_receipt_sha256": sha256_json(apply_receipt),
"apply_receipt": apply_receipt,
}
finalize_headers = Rc06FacadeHeaders(**{
**headers.__dict__, "request_sha256": sha256_json(finalize_request)
})
first = service.finalize(material.request["transaction_id"], finalize_request, finalize_headers)
assert first["state"] == "finalized"
assert service.finalize(material.request["transaction_id"], finalize_request, finalize_headers) == first
status = service.status(material.request["transaction_id"], headers)
assert status["state"] == "finalized"
assert status["state_version"] == 4
assert status["no_go"] is False
assert status["online_transaction_receipt"] == execution["online_transaction_receipt"]
assert status["runtime_snapshot_receipt"] == execution["runtime_snapshot_receipt"]
assert status["apply_receipt"] == apply_receipt
finally:
repository.engine.dispose()
def test_post_contract_rollback_is_forbidden_without_calling_compensation(tmp_path):
repository, service, _, coordinator, _, material, headers = _service(tmp_path)
try:
service.apply(material.request, headers)
rollback_request = {
"schema_version": ROLLBACK_REQUEST_SCHEMA,
"idempotency_key": material.request["idempotency_key"],
"transaction_id": material.request["transaction_id"],
"batch_manifest_raw_sha256": material.request["batch_manifest_raw_sha256"],
"approval_receipt_sha256": material.request["approval_receipt_sha256"],
"legal_source_review_binding_sha256": material.legal_binding_sha256,
"environment_id": ENVIRONMENT,
"tenant_id": TENANT,
"transaction_mode": "durable_saga",
"cross_database_atomicity": False,
}
with pytest.raises(Rc06FacadeConflict, match="irreversible"):
service.rollback(material.request["transaction_id"], rollback_request, headers)
assert coordinator.compensate_calls == 0
status = service.status(material.request["transaction_id"], headers)
assert status["state"] == "committed_and_verified"
finally:
repository.engine.dispose()
def test_pre_contract_failure_can_produce_exact_rollback_v2(tmp_path):
repository, service, _, coordinator, _, material, headers = _service(tmp_path, fail_commit=True)
try:
with pytest.raises(Rc06FacadeError, match="did not reach"):
service.apply(material.request, headers)
rollback_request = {
"schema_version": ROLLBACK_REQUEST_SCHEMA,
"idempotency_key": material.request["idempotency_key"],
"transaction_id": material.request["transaction_id"],
"batch_manifest_raw_sha256": material.request["batch_manifest_raw_sha256"],
"approval_receipt_sha256": material.request["approval_receipt_sha256"],
"legal_source_review_binding_sha256": material.legal_binding_sha256,
"environment_id": ENVIRONMENT,
"tenant_id": TENANT,
"transaction_mode": "durable_saga",
"cross_database_atomicity": False,
}
rollback = service.rollback(material.request["transaction_id"], rollback_request, headers)
assert rollback["state"] == "rolled_back"
assert rollback["compensation_item_results"] == []
assert rollback["runtime_snapshot_compensation_results"] == []
assert rollback["rollback_evidence_sha256"] == sha256_json({
key: value for key, value in rollback.items() if key != "rollback_evidence_sha256"
})
status = service.status(material.request["transaction_id"], headers)
assert status["state"] == "rolled_back"
assert status["state_version"] == 2
assert status["rollback_response"] == rollback
assert status["rollback_response_sha256"] == sha256_json(rollback)
assert coordinator.compensate_calls == 1
finally:
repository.engine.dispose()
def test_post_contract_kms_failure_is_no_go_recoverable_and_never_compensated(tmp_path):
repository, service, signer, coordinator, _, material, headers = _service(
tmp_path, fail_sign_once_at=2
)
try:
with pytest.raises(Rc06FacadeError, match="KMS response loss"):
service.apply(material.request, headers)
failed = service.status(material.request["transaction_id"], headers)
assert failed["state"] == "irreversible_failure"
assert failed["state_version"] == 2
assert failed["no_go"] is True
assert failed["publication_item_results"] == []
assert failed["online_transaction_receipt"] is None
assert failed["failure_code"] == "rc06facadeerror"
assert len(failed["failure_evidence_sha256"]) == 64
assert coordinator.compensate_calls == 0
recovered = service.apply(
material.request,
headers,
now=datetime.now(UTC) + timedelta(days=1),
)
assert recovered["publication_item_count"] == 74
assert len(signer.calls) == 5 # one ambiguous failure plus four durable signatures
committed = service.status(material.request["transaction_id"], headers)
assert committed["state"] == "committed_and_verified"
assert committed["failure_code"] is None
assert coordinator.compensate_calls == 0
finally:
repository.engine.dispose()