fix(integration): reconcile release loaders and internal contracts
Keep frozen-pin validation strict while recognizing the current verified-byte restore loader, and publish the existing private RC06 participant contract without opening public routes. Make isolated regression collection and two-process fixture diagnostics reliable without changing signer runtime behavior.
This commit is contained in:
@@ -7,6 +7,7 @@ testpaths = [
|
||||
]
|
||||
pythonpath = [
|
||||
"yuqei-contract-platform/services/contract-api/src",
|
||||
"yuqei-contract-platform/services/contract-api/tests",
|
||||
"yuqei-ai-platform/services/ai-platform-api/src",
|
||||
"yuqei-ai-platform/services/worker/src",
|
||||
"yuqei-ai-sdk-python/src",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
components:
|
||||
schemas:
|
||||
PromptCapabilityRequest: {"properties":{"capability":{"type":"string","maxLength":128,"minLength":43,"title":"Capability"}},"additionalProperties":false,"type":"object","required":["capability"],"title":"PromptCapabilityRequest"}
|
||||
PromptModuleInput: {"properties":{"category":{"type":"string","enum":["drafting","review"],"title":"Category"},"key":{"type":"string","maxLength":128,"minLength":1,"title":"Key"},"version":{"type":"string","maxLength":128,"minLength":1,"title":"Version"},"content":{"type":"string","maxLength":200000,"minLength":1,"title":"Content"},"content_sha256":{"type":"string","pattern":"^[a-f0-9]{64}$","title":"Content Sha256"},"publication_mapping":{"additionalProperties":{"type":"string"},"type":"object","title":"Publication Mapping"},"module_payload_sha256":{"type":"string","pattern":"^[a-f0-9]{64}$","title":"Module Payload Sha256"}},"additionalProperties":false,"type":"object","required":["category","key","version","content","content_sha256","publication_mapping","module_payload_sha256"],"title":"PromptModuleInput"}
|
||||
PromptPrepareRequest: {"properties":{"transaction_id":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{7,63}$","title":"Transaction Id"},"idempotency_key":{"type":"string","maxLength":160,"minLength":8,"title":"Idempotency Key"},"candidate_commit":{"type":"string","pattern":"^(?:[a-f0-9]{40}|[a-f0-9]{64})$","title":"Candidate Commit"},"release_image_digest":{"type":"string","pattern":"^sha256:[a-f0-9]{64}$","title":"Release Image Digest"},"candidate_tag":{"type":"string","maxLength":128,"minLength":1,"title":"Candidate Tag"},"batch_manifest_raw_sha256":{"type":"string","pattern":"^[a-f0-9]{64}$","title":"Batch Manifest Raw Sha256"},"batch_manifest_payload_sha256":{"type":"string","pattern":"^[a-f0-9]{64}$","title":"Batch Manifest Payload Sha256"},"approval_receipt_sha256":{"type":"string","pattern":"^[a-f0-9]{64}$","title":"Approval Receipt Sha256"},"authority_expires_at":{"type":"string","format":"date-time","title":"Authority Expires At"},"compensation_capability_sha256":{"type":"string","pattern":"^[a-f0-9]{64}$","title":"Compensation Capability Sha256"},"modules":{"items":{"$ref":"#/components/schemas/PromptModuleInput"},"type":"array","title":"Modules"},"runtime_identities":{"items":{"$ref":"#/components/schemas/RuntimeIdentityInput"},"type":"array","title":"Runtime Identities"}},"additionalProperties":false,"type":"object","required":["transaction_id","idempotency_key","candidate_commit","release_image_digest","candidate_tag","batch_manifest_raw_sha256","batch_manifest_payload_sha256","approval_receipt_sha256","authority_expires_at","compensation_capability_sha256","modules","runtime_identities"],"title":"PromptPrepareRequest"}
|
||||
PromptTransactionStatus: {"properties":{"transaction_id":{"type":"string","title":"Transaction Id"},"state":{"type":"string","title":"State"},"candidate_commit":{"type":"string","title":"Candidate Commit"},"release_image_digest":{"type":"string","title":"Release Image Digest"},"candidate_tag":{"type":"string","title":"Candidate Tag"},"batch_manifest_raw_sha256":{"type":"string","title":"Batch Manifest Raw Sha256"},"batch_manifest_payload_sha256":{"type":"string","title":"Batch Manifest Payload Sha256"},"approval_receipt_sha256":{"type":"string","title":"Approval Receipt Sha256"},"authority_expires_at":{"type":"string","format":"date-time","title":"Authority Expires At"},"item_results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Item Results"},"runtime_snapshots":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Runtime Snapshots"},"write_count":{"type":"integer","title":"Write Count","default":0},"runtime_snapshot_count":{"type":"integer","title":"Runtime Snapshot Count","default":0},"no_go":{"type":"boolean","title":"No Go","default":false},"committed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Committed At"},"sealed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Sealed At"},"compensated_from_state":{"anyOf":[{"type":"string","enum":["prepared","committed"]},{"type":"null"}],"title":"Compensated From State"},"compensation_item_results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Compensation Item Results"},"runtime_snapshot_compensation_results":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Runtime Snapshot Compensation Results"},"restored_item_count":{"type":"integer","title":"Restored Item Count","default":0},"removed_item_count":{"type":"integer","title":"Removed Item Count","default":0},"unchanged_item_count":{"type":"integer","title":"Unchanged Item Count","default":0},"compensated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Compensated At"},"compensation_evidence_sha256":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Compensation Evidence Sha256"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["transaction_id","state","candidate_commit","release_image_digest","candidate_tag","batch_manifest_raw_sha256","batch_manifest_payload_sha256","approval_receipt_sha256","authority_expires_at"],"title":"PromptTransactionStatus"}
|
||||
RuntimeIdentityInput: {"properties":{"name":{"type":"string","maxLength":128,"minLength":1,"title":"Name"},"version":{"type":"string","maxLength":128,"minLength":1,"title":"Version"},"content_sha256":{"type":"string","pattern":"^[a-f0-9]{64}$","title":"Content Sha256"},"source_kind":{"type":"string","enum":["composed_drafting_modules","new_review_module_composite","existing_domain_prompt_asset"],"title":"Source Kind"}},"additionalProperties":false,"type":"object","required":["name","version","content_sha256","source_kind"],"title":"RuntimeIdentityInput"}
|
||||
AiQualityAttribution:
|
||||
properties:
|
||||
by_knowledge_base:
|
||||
@@ -6870,6 +6875,11 @@ info:
|
||||
description: V2 AI platform contract for model routing, legal QA, legal review, knowledge bases, and AI audit.
|
||||
openapi: 3.1.0
|
||||
paths:
|
||||
/api/v1/internal/rc06-publications/prepare: {"post":{"tags":["internal","rc06"],"summary":"Prepare","operationId":"prepare_api_v1_internal_rc06_publications_prepare_post","parameters":[{"name":"x-rc06-publication-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Rc06-Publication-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptPrepareRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptTransactionStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}
|
||||
/api/v1/internal/rc06-publications/{transaction_id}/commit: {"post":{"tags":["internal","rc06"],"summary":"Commit","operationId":"commit_api_v1_internal_rc06_publications__transaction_id__commit_post","parameters":[{"name":"transaction_id","in":"path","required":true,"schema":{"type":"string","title":"Transaction Id"}},{"name":"x-rc06-publication-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Rc06-Publication-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptTransactionStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}
|
||||
/api/v1/internal/rc06-publications/{transaction_id}: {"get":{"tags":["internal","rc06"],"summary":"Status","operationId":"status_api_v1_internal_rc06_publications__transaction_id__get","parameters":[{"name":"transaction_id","in":"path","required":true,"schema":{"type":"string","title":"Transaction Id"}},{"name":"x-rc06-publication-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Rc06-Publication-Service-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptTransactionStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}
|
||||
/api/v1/internal/rc06-publications/{transaction_id}/seal: {"post":{"tags":["internal","rc06"],"summary":"Seal","operationId":"seal_api_v1_internal_rc06_publications__transaction_id__seal_post","parameters":[{"name":"transaction_id","in":"path","required":true,"schema":{"type":"string","title":"Transaction Id"}},{"name":"x-rc06-publication-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Rc06-Publication-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptCapabilityRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptTransactionStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}
|
||||
/api/v1/internal/rc06-publications/{transaction_id}/compensate: {"post":{"tags":["internal","rc06"],"summary":"Compensate","operationId":"compensate_api_v1_internal_rc06_publications__transaction_id__compensate_post","parameters":[{"name":"transaction_id","in":"path","required":true,"schema":{"type":"string","title":"Transaction Id"}},{"name":"x-rc06-publication-service-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Rc06-Publication-Service-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptCapabilityRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptTransactionStatus"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}
|
||||
/api/v1/audit/ai-runs:
|
||||
get:
|
||||
operationId: list_ai_runs_api_v1_audit_ai_runs_get
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -32,7 +33,7 @@ from yuqei_ai_platform_api.rc06_publication import (
|
||||
Rc06PublicationError,
|
||||
_source_inventory,
|
||||
)
|
||||
from yuqei_ai_platform_api.repository import SqlAlchemyAiPlatformRepository
|
||||
from yuqei_ai_platform_api.repository import InMemoryAiPlatformRepository, SqlAlchemyAiPlatformRepository
|
||||
|
||||
|
||||
CANDIDATE_COMMIT = "a" * 40
|
||||
@@ -95,6 +96,43 @@ def _request(transaction_id: str = "rc06-ai-transaction-0001") -> PromptPrepareR
|
||||
)
|
||||
|
||||
|
||||
def test_internal_rc06_openapi_fragment_matches_current_participant(tmp_path):
|
||||
app = create_app(
|
||||
AiPlatformSettings(
|
||||
_env_file=None,
|
||||
environment="test",
|
||||
repository_backend="memory",
|
||||
provider_call_mode="disabled",
|
||||
object_storage_local_dir=str(tmp_path / "objects"),
|
||||
),
|
||||
repository=InMemoryAiPlatformRepository(),
|
||||
)
|
||||
runtime = app.openapi()
|
||||
published = (
|
||||
Path(__file__).resolve().parents[3] / "packages/ai-openapi/openapi.yaml"
|
||||
).read_text(encoding="utf-8")
|
||||
decoder = json.JSONDecoder()
|
||||
paths = {
|
||||
path: value for path, value in runtime["paths"].items()
|
||||
if path.startswith("/api/v1/internal/rc06-publications")
|
||||
}
|
||||
assert len(paths) == 5
|
||||
# The internal fragment uses JSON, which is valid YAML, without a test-only parser dependency.
|
||||
for path, expected in paths.items():
|
||||
marker = f" {path}: "
|
||||
start = published.index(marker) + len(marker)
|
||||
actual, _ = decoder.raw_decode(published[start:])
|
||||
assert actual == expected
|
||||
for name in (
|
||||
"PromptCapabilityRequest", "PromptModuleInput", "PromptPrepareRequest",
|
||||
"PromptTransactionStatus", "RuntimeIdentityInput",
|
||||
):
|
||||
marker = f" {name}: "
|
||||
start = published.index(marker) + len(marker)
|
||||
actual, _ = decoder.raw_decode(published[start:])
|
||||
assert actual == runtime["components"]["schemas"][name]
|
||||
|
||||
|
||||
def test_prepare_rejects_expired_authority_without_journal_or_prompt_writes(tmp_path):
|
||||
repository = _repository(tmp_path)
|
||||
try:
|
||||
|
||||
@@ -2516,16 +2516,42 @@ def _exec_verified_module(name: str, path: Path, raw: bytes) -> Any:
|
||||
"""
|
||||
|
||||
|
||||
_VERIFIED_SNAPSHOT_MODULE_EXEC_TEMPLATE = """
|
||||
def _exec_verified_module(raw: bytes, path: Path, module_name: str) -> Any:
|
||||
try:
|
||||
code = compile(raw, os.fspath(path), "exec", dont_inherit=True)
|
||||
except (SyntaxError, UnicodeError, ValueError) as exc:
|
||||
raise SnapshotRestorePlanError("verified validator source cannot be compiled") from exc
|
||||
module = types.ModuleType(module_name)
|
||||
module.__file__ = os.fspath(path)
|
||||
module.__package__ = ""
|
||||
module.__loader__ = None
|
||||
module.__spec__ = importlib.util.spec_from_loader(
|
||||
module_name, loader=None, origin=os.fspath(path)
|
||||
)
|
||||
sys.modules[module_name] = module
|
||||
try:
|
||||
exec(code, module.__dict__)
|
||||
except Exception as exc:
|
||||
sys.modules.pop(module_name, None)
|
||||
raise SnapshotRestorePlanError("verified validator source failed to load") from exc
|
||||
return module
|
||||
"""
|
||||
|
||||
|
||||
def _safe_verified_module_exec(call: ast.Call, parents: Mapping[ast.AST, ast.AST]) -> bool:
|
||||
parent: ast.AST | None = call
|
||||
while parent is not None and not isinstance(parent, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
parent = parents.get(parent)
|
||||
if not isinstance(parent, ast.FunctionDef):
|
||||
return False
|
||||
expected = ast.parse(_VERIFIED_MODULE_EXEC_TEMPLATE).body[0]
|
||||
return ast.dump(parent, include_attributes=False) == ast.dump(
|
||||
expected,
|
||||
include_attributes=False,
|
||||
observed = ast.dump(parent, include_attributes=False)
|
||||
return any(
|
||||
observed == ast.dump(ast.parse(template).body[0], include_attributes=False)
|
||||
for template in (
|
||||
_VERIFIED_MODULE_EXEC_TEMPLATE,
|
||||
_VERIFIED_SNAPSHOT_MODULE_EXEC_TEMPLATE,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import multiscene_eval_plan
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
RUNNER_PATH = ROOT / "yuqei-infra/scripts/run-core-drafting-smoke.py"
|
||||
@@ -18,6 +16,8 @@ assert SPEC and SPEC.loader
|
||||
RUNNER = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(RUNNER)
|
||||
|
||||
import multiscene_eval_plan
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw", "matches"),
|
||||
|
||||
@@ -2876,13 +2876,16 @@ def test_two_process_legacy_adoption_never_deletes_the_other_process_marker(
|
||||
state_directory, key_directory = _make_legacy_default_state(tmp_path, monkeypatch)
|
||||
ready = tmp_path / "holder-ready"
|
||||
release = tmp_path / "holder-release"
|
||||
# Windows validates owner ACLs in child processes before reaching the barrier.
|
||||
timeout_seconds = 120 if os.name == "nt" else 20
|
||||
child = r'''
|
||||
import importlib.util
|
||||
import pathlib
|
||||
import sys
|
||||
import time
|
||||
|
||||
script, state_raw, ready_raw, release_raw, mode = sys.argv[1:]
|
||||
script, state_raw, ready_raw, release_raw, timeout_raw, mode = sys.argv[1:]
|
||||
timeout_seconds = float(timeout_raw)
|
||||
spec = importlib.util.spec_from_file_location("signer_concurrency_fixture", script)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec is not None and spec.loader is not None
|
||||
@@ -2898,7 +2901,7 @@ if mode == "holder":
|
||||
original(directory, **kwargs)
|
||||
if directory == state / "keys" and not ready.exists():
|
||||
ready.write_text("ready", encoding="utf-8")
|
||||
deadline = time.monotonic() + 20
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
while not release.exists() and time.monotonic() < deadline:
|
||||
time.sleep(0.05)
|
||||
if not release.exists():
|
||||
@@ -2923,30 +2926,37 @@ print("ACQUIRED")
|
||||
os.fspath(state_directory),
|
||||
os.fspath(ready),
|
||||
os.fspath(release),
|
||||
str(timeout_seconds),
|
||||
]
|
||||
holder = subprocess.Popen(
|
||||
[*command, "holder"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
try:
|
||||
deadline = time.monotonic() + 20
|
||||
while not ready.exists() and time.monotonic() < deadline and holder.poll() is None:
|
||||
time.sleep(0.05)
|
||||
assert ready.is_file(), holder.communicate(timeout=5)
|
||||
contender = subprocess.run(
|
||||
[*command, "contender"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
assert contender.returncode == 0, contender.stderr or contender.stdout
|
||||
assert "BUSY" in contender.stdout
|
||||
finally:
|
||||
release.write_text("release", encoding="utf-8")
|
||||
holder_stdout, holder_stderr = holder.communicate(timeout=30)
|
||||
stdout_path = tmp_path / "holder.stdout"
|
||||
stderr_path = tmp_path / "holder.stderr"
|
||||
with stdout_path.open("w", encoding="utf-8") as stdout, stderr_path.open(
|
||||
"w", encoding="utf-8"
|
||||
) as stderr:
|
||||
holder = subprocess.Popen([*command, "holder"], stdout=stdout, stderr=stderr)
|
||||
try:
|
||||
deadline = time.monotonic() + timeout_seconds
|
||||
while not ready.exists() and time.monotonic() < deadline and holder.poll() is None:
|
||||
time.sleep(0.05)
|
||||
assert ready.is_file(), (
|
||||
stderr_path.read_text(encoding="utf-8")
|
||||
or stdout_path.read_text(encoding="utf-8")
|
||||
or "holder did not reach the owner-marker barrier"
|
||||
)
|
||||
contender = subprocess.run(
|
||||
[*command, "contender"],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_seconds,
|
||||
)
|
||||
assert contender.returncode == 0, contender.stderr or contender.stdout
|
||||
assert "BUSY" in contender.stdout
|
||||
finally:
|
||||
release.write_text("release", encoding="utf-8")
|
||||
holder.wait(timeout=timeout_seconds)
|
||||
holder_stdout = stdout_path.read_text(encoding="utf-8")
|
||||
holder_stderr = stderr_path.read_text(encoding="utf-8")
|
||||
assert holder.returncode == 0, holder_stderr or holder_stdout
|
||||
assert "ACQUIRED" in holder_stdout
|
||||
assert (key_directory / signer.OWNER_MARKER_FILENAME).is_file()
|
||||
|
||||
@@ -1121,13 +1121,22 @@ def test_pin_planner_isolated_runtime_detects_import_side_effect_override(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("filename", "module_name"),
|
||||
(
|
||||
("plan-rollback-rehearsal.py", "name"),
|
||||
("plan-first-release-snapshot-restore.py", "module_name"),
|
||||
),
|
||||
)
|
||||
def test_pin_planner_rejects_modified_verified_module_exec_loader(
|
||||
tmp_path: Path,
|
||||
filename: str,
|
||||
module_name: str,
|
||||
) -> None:
|
||||
state, repo, _plan = _pin_plan_fixture(tmp_path, include_online=False)
|
||||
rollback = repo / "yuqei-infra/deploy/plan-rollback-rehearsal.py"
|
||||
original = rollback.read_text(encoding="utf-8")
|
||||
needle = ' module = types.ModuleType(name)\n'
|
||||
planner = repo / "yuqei-infra/deploy" / filename
|
||||
original = planner.read_text(encoding="utf-8")
|
||||
needle = f' module = types.ModuleType({module_name})\n'
|
||||
replacement = (
|
||||
" if sys.argv[0] != '-c':\n"
|
||||
" module = sys.modules[__name__]\n"
|
||||
@@ -1135,10 +1144,10 @@ def test_pin_planner_rejects_modified_verified_module_exec_loader(
|
||||
+ "f" * 64
|
||||
+ "'\"\n"
|
||||
" else:\n"
|
||||
" module = types.ModuleType(name)\n"
|
||||
f" module = types.ModuleType({module_name})\n"
|
||||
)
|
||||
assert needle in original
|
||||
rollback.write_text(original.replace(needle, replacement, 1), encoding="utf-8")
|
||||
planner.write_text(original.replace(needle, replacement, 1), encoding="utf-8")
|
||||
|
||||
with pytest.raises(authority.AuthorityError, match="untrusted dynamic exec"):
|
||||
authority.plan_pin_updates(
|
||||
|
||||
Reference in New Issue
Block a user