Files
cdsl-cad/backend/tests/test_cad_agent_v3.py
T
2026-09-01 14:10:23 +08:00

3837 lines
230 KiB
Python

from __future__ import annotations
import asyncio
import ast
from dataclasses import replace
from hashlib import sha256
import json
import math
from pathlib import Path
import sys
import tempfile
import unittest
from unittest.mock import AsyncMock, patch
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend"))
from app.cad_agent.adapters.artifact_store import FileArtifactStore
from app.cad_agent.adapters.event_publisher import IdempotentInProcessPublisher
from app.cad_agent.adapters.runtime import ProfileCadRuntime, RuntimeAdapterError
from app.cad_agent.adapters.sqlite_repository import SqliteTaskRepository
from app.cad_agent.adapters.structured_llm import StructuredModelError, StructuredModelGateway, StructuredTransportError
from app.cad_agent.adapters.verifier import RegistryVerifierExecutor
from app.cad_agent.application.action_handlers import ActionCommandHandler
from app.cad_agent.application.outbox import OutboxDispatcher
from app.cad_agent.application.results import Accepted, Rejected, Waiting
from app.cad_agent.application.capabilities import conformance_tools
from app.cad_agent.application.llm_contracts import ( # noqa: E402
CandidateReview,
ClaimCoverage,
FinalReview,
GeometryConclusion,
NextAction,
RequirementsDraftBatch,
RequirementsPatchBatch,
RequirementsReview,
RollbackCheckpoint,
canonical_json_object,
canonical_validate_schema,
canonical_validate,
candidate_review_schema,
final_review_schema,
geometry_conclusion_schema,
operation_contract_request_schema,
requirements_draft_schema,
rollback_checkpoint_schema,
topology_request_schema,
)
from app.cad_agent.evals.live import _acceptance_coverage, _capability_block_reason, _failure_attribution, _fixture, _run_checks, _safe_artifact_manifest
from app.cad_agent.evals.token_baseline import (
BASELINE_SCHEMA_VERSION,
author_request_identity,
compare_token_baseline,
request_sha256,
validate_token_baseline_provenance,
)
from app.cad_agent.application.requirements_review import RequirementsCommandHandler
from app.cad_agent.application.workflow import ModelIdentity, WorkflowConfig, WorkflowCoordinator
from app.cad_agent.domain.errors import ErrorCode, WorkflowError
from app.cad_agent.domain.operation_contract import (
SEMANTIC_PREFLIGHT_NAMES,
OperationContractError,
fragment_schema,
validate_fragment,
validate_operation_contract,
)
from app.cad_agent.domain.state import PendingAction, TaskPhase, TaskState, legal_transitions, retry_resume_event, transition
from app.cad_agent.domain.verifier_registry import default_registry
from app.models.contracts import ChatMessage
from app.services.agent_service import AgentService
from app.services.library import CdslLibrary
from app.services.storage import WorkspaceStore
from app.settings import ProviderConfig, ProviderModel, Settings
def settings(root: Path) -> Settings:
provider = ProviderConfig("test", "Test", "https://example.invalid/v1", "test-key", (ProviderModel("test-model"),))
return Settings(
task_root=root / "tasks",
conversation_root=root / "conversations",
library_root=ROOT / "backend" / "cdsl_library",
engine_root=ROOT / "backend" / "engine" / "cdsl_engine",
llm_base_url=provider.base_url,
llm_api_key=provider.api_key,
llm_model="test-model",
llm_timeout_s=1,
default_provider_id="test",
providers=(provider,),
)
def service_settings(root: Path) -> Settings:
author = ProviderConfig("author", "Author", "https://author.invalid/v1", "author-key", (ProviderModel("author-model"),))
reviewer = ProviderConfig("reviewer", "Reviewer", "https://reviewer.invalid/v1", "reviewer-key", (ProviderModel("reviewer-model", vision=True),))
return Settings(
task_root=root / "tasks",
conversation_root=root / "conversations",
library_root=ROOT / "backend" / "cdsl_library",
engine_root=ROOT / "backend" / "engine" / "cdsl_engine",
llm_base_url=author.base_url,
llm_api_key=author.api_key,
llm_model="author-model",
llm_timeout_s=1,
default_provider_id="author",
providers=(author, reviewer),
review_provider_id="reviewer",
review_model_id="reviewer-model",
)
def schema_example(schema: object) -> object:
"""Build a smallest valid fixture from a closed operation-param schema."""
if not isinstance(schema, dict):
raise AssertionError("Schema fixture must be an object")
if "const" in schema:
return schema["const"]
if isinstance(schema.get("enum"), list) and schema["enum"]:
return schema["enum"][0]
for branch_name in ("oneOf", "anyOf"):
branches = schema.get(branch_name)
if isinstance(branches, list) and branches:
return schema_example(branches[0])
kind = schema.get("type")
if kind == "object":
properties = schema.get("properties") if isinstance(schema.get("properties"), dict) else {}
required = schema.get("required") if isinstance(schema.get("required"), list) else []
return {str(name): schema_example(properties[name]) for name in required if name in properties}
if kind == "array":
count = int(schema.get("minItems") or 0)
return [schema_example(schema.get("items") or {}) for _ in range(count)]
if kind == "integer":
lower = schema.get("minimum", schema.get("exclusiveMinimum", 0))
return int(lower) + (1 if "exclusiveMinimum" in schema else 0)
if kind == "number":
lower = schema.get("minimum", schema.get("exclusiveMinimum", 0))
return float(lower) + (1.0 if "exclusiveMinimum" in schema else 0.0)
if kind == "boolean":
return False
if kind == "string":
return "x" * max(1, int(schema.get("minLength") or 0))
if kind == "null":
return None
raise AssertionError(f"No generic fixture for schema type: {kind!r}")
class V3ProtocolTests(unittest.TestCase):
def _ready_action(self, root: Path) -> tuple[SqliteTaskRepository, FileArtifactStore, ActionCommandHandler, str, PendingAction]:
runtime = ProfileCadRuntime(settings(root))
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
registry = default_registry()
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(registry))
task_id = "cad_123456abcdef"
state = repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
contract = {
"schema_version": "cad.requirements-contract.v1", "task_id": task_id, "contract_hash": "test-contract",
"requirements": [{"requirement_id": "req_001", "draft_id": "draft_001", "source_ids": ["src_001"], "statement": "One solid", "assumptions": [], "acceptance_claims": [{"claim_id": "claim_001", "claim_kind": "solid_count_equals", "expected": {"value": 1}}]}],
}
artifacts.write_requirements_contract(task_id, contract)
contract_path = artifacts.write_requirements_contract(task_id, contract, invocation_id="test_contract")
for event in ("draft_updated", "requirements_finalized", "requirements_approved"):
state = transition(state, event, requirements_contract_path=contract_path if event == "requirements_approved" else None)
self.assertTrue(repository.compare_and_swap(state))
operation = runtime.operation_contract("extrude_add_blind")
action = PendingAction("act_004", state.working_head, "Create base", ("req_001",), "extrude_add_blind", "Add one base", operation["contract_hash"], "candidate-key")
state = transition(state, "action_proposed", pending_action=action)
self.assertTrue(repository.compare_and_swap(state))
return repository, artifacts, actions, task_id, action
def test_canonical_dto_rejects_extra_fields_without_side_effect(self) -> None:
result = canonical_validate(
json.dumps({"working_head": "cad_123456abcdef:root:v1", "intent": "Create base", "requirement_ids": ["req_001"], "atomic_id": "extrude_add_blind", "expected_change": "Add base", "plan_step_id": "old"}),
NextAction,
)
self.assertEqual(getattr(result, "code", None), ErrorCode.AUTHOR_FORMAT_INVALID)
self.assertTrue(any(error["path"] == "/plan_step_id" for error in result.field_errors))
def test_domain_transition_table_covers_every_legal_and_illegal_edge(self) -> None:
table = legal_transitions()
events = {event for _phase, event in table}
for (phase, event), target in table.items():
state = TaskState(
"cad_123456abcdef", phase, 7,
retry_from_phase=target if phase == TaskPhase.WAITING_RETRY and event.startswith("resume_") else None,
)
advanced = transition(state, event)
self.assertEqual(advanced.phase, target, f"{phase} --{event}-->")
self.assertEqual(advanced.version, state.version + 1)
for phase in TaskPhase:
for event in events:
if (phase, event) in table:
continue
with self.assertRaises(ValueError, msg=f"{phase} must reject {event}"):
transition(TaskState("cad_123456abcdef", phase, 7), event)
def test_waiting_retry_resumes_the_persisted_source_phase(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
task_id = "cad_123456abcdef"
initial = repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
waiting = transition(initial, "waiting_retry", error=ErrorCode.AUTHOR_TRANSPORT_UNAVAILABLE)
self.assertEqual(waiting.retry_from_phase, TaskPhase.DRAFTING_REQUIREMENTS)
self.assertTrue(repository.compare_and_swap(waiting))
self.assertTrue(workflow.resume(task_id))
resumed = repository.get_state(task_id)
self.assertEqual(resumed.phase, TaskPhase.DRAFTING_REQUIREMENTS)
self.assertIsNone(resumed.retry_from_phase)
reviewing = transition(resumed, "requirements_finalized")
self.assertTrue(repository.compare_and_swap(reviewing))
waiting_review = transition(reviewing, "waiting_retry", error=ErrorCode.REVIEW_SERVICE_UNAVAILABLE)
self.assertEqual(retry_resume_event(waiting_review), "resume_reviewing_requirements")
self.assertTrue(repository.compare_and_swap(waiting_review))
self.assertTrue(workflow.resume(task_id))
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.REVIEWING_REQUIREMENTS)
def test_waiting_retry_without_a_persisted_source_is_not_guessed(self) -> None:
state = TaskState("cad_123456abcdef", TaskPhase.WAITING_RETRY, 7)
self.assertIsNone(retry_resume_event(state))
def test_waiting_for_user_cannot_be_resumed_without_new_user_input(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
task_id = "cad_123456abcdef"
initial = repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
reviewing = transition(initial, "requirements_finalized")
self.assertTrue(repository.compare_and_swap(reviewing))
waiting = transition(reviewing, "waiting_for_user", error=ErrorCode.WAITING_FOR_USER)
self.assertTrue(repository.compare_and_swap(waiting))
self.assertFalse(workflow.resume(task_id))
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.WAITING_FOR_USER)
def test_waiting_for_user_terminal_exposes_persisted_review_question(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
task_id = "cad_123456abcdef"
initial = workflow.create_task(task_id, "Create a plate.")
reviewing = transition(initial, "requirements_finalized")
self.assertTrue(repository.compare_and_swap(reviewing))
question = "Should the spacing be corrected to 45 degrees or should the count change?"
review_path = artifacts.write_requirements_review(task_id, {
"schema_version": "cad.requirements-review.v2",
"decision": "waiting_for_user",
"findings": [{"draft_id": "draft_001", "source_ids": ["src_001"], "finding_type": "ambiguous_conflict", "description": "Count and spacing conflict.", "question": question}],
}, invocation_id="inv_waiting")
waiting = transition(
reviewing,
"waiting_for_user",
error=ErrorCode.WAITING_FOR_USER,
requirements_review_path=review_path,
)
self.assertTrue(repository.compare_and_swap(waiting))
async def collect() -> list[tuple[str, dict[str, object]]]:
return [item async for item in workflow.run(task_id=task_id, author=None, reviewer=None)] # type: ignore[arg-type]
events = asyncio.run(collect())
self.assertEqual(events, [("task_terminal", {
"taskId": task_id,
"lifecycle": "waiting_for_user",
"revisionId": "",
"code": ErrorCode.WAITING_FOR_USER.value,
"message": f"Requirements need a user decision. {question}",
"questions": [question],
"reviewPath": review_path,
"blockerType": "requirements_ambiguity",
"userActionRequired": True,
"issues": ["Count and spacing conflict."],
"unresolved": [{"draftId": "draft_001", "reasonCode": "ambiguous_conflict", "question": question}],
})])
def test_requirements_review_limit_is_a_non_user_failure(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
task_id = "cad_123456abcdef"
initial = workflow.create_task(task_id, "Create a plate.")
issue = "The requested local position has no available deterministic verifier."
failed = transition(initial, "failed", error=ErrorCode.REQUIREMENTS_REVIEW_NOT_CONVERGED)
self.assertTrue(repository.compare_and_swap(failed, events=[{
"event": "requirements_review_limit_reached",
"code": ErrorCode.REQUIREMENTS_REVIEW_NOT_CONVERGED.value,
"message": "Requirements review did not converge within the configured author-revision limit.",
"findings": [{"description": issue}],
}]))
payload = workflow._projected_terminal(task_id, failed)
self.assertEqual(payload["lifecycle"], "failed")
self.assertFalse(payload["userActionRequired"])
self.assertEqual(payload["issues"], [issue])
self.assertEqual(payload["code"], ErrorCode.REQUIREMENTS_REVIEW_NOT_CONVERGED.value)
def test_waiting_review_limit_reply_resumes_the_same_task_with_clarification(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
configured = service_settings(root)
service = AgentService(configured, WorkspaceStore(configured), CdslLibrary(configured))
task_id = "cad_123456abcdef"
state = service.v3.workflow.create_task(task_id, "Create a flange.")
reviewing = transition(state, "requirements_finalized")
self.assertTrue(service.v3.repository.compare_and_swap(reviewing))
question = "Should the flange use four holes or six holes?"
review_path = service.v3.artifacts.write_requirements_review(task_id, {
"schema_version": "cad.requirements-review.v2",
"decision": "waiting_for_user",
"findings": [{"draft_id": "draft_001", "source_ids": ["src_001"], "finding_type": "ambiguous_conflict", "description": "Two hole counts are stated.", "question": question}],
}, invocation_id="inv_limit")
waiting = transition(reviewing, "waiting_for_user", error=ErrorCode.WAITING_FOR_USER, requirements_review_path=review_path)
self.assertTrue(service.v3.repository.compare_and_swap(waiting, events=[{"event": "requirements_waiting_for_user", "review_path": review_path, "questions": [question]}]))
conversation = service.store.ensure_conversation("conv_123456abcdef", task_id)
message = ChatMessage.model_validate({"id": "user_1", "role": "user", "parts": [{"type": "text", "text": "允许"}]})
async def fake_run(*, task_id: str, **_kwargs: object):
self.assertEqual(task_id, "cad_123456abcdef")
yield "task_terminal", {"taskId": task_id, "lifecycle": "waiting_for_user", "message": "Still needs review."}
service.v3.workflow.run = fake_run # type: ignore[method-assign]
async def collect() -> bytes:
return b"".join([chunk async for chunk in service.stream([message], conversation["conversation_id"], task_id)])
with patch("app.services.agent_service.verify_model_capability", AsyncMock(return_value={"supported": True})):
output = asyncio.run(collect()).decode("utf-8")
self.assertIn("已应用补充说明并恢复 CAD 任务", output)
self.assertNotIn("event: cad_error", output)
self.assertEqual([item.name for item in configured.task_root.iterdir()], [task_id])
resumed = service.v3.repository.get_state(task_id)
self.assertEqual(resumed.phase, TaskPhase.DRAFTING_REQUIREMENTS)
clarification = next(event for event in service.v3.repository.ledger_events(task_id) if event.get("event") == "user_clarification_received")
payload = service.v3.artifacts.read_json(task_id, clarification["clarification_path"])
self.assertEqual(payload["text"], "允许")
def test_service_retry_resume_checks_capabilities_and_preserves_source_phase(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
configured = service_settings(root)
service = AgentService(configured, WorkspaceStore(configured), CdslLibrary(configured))
task_id = "cad_123456abcdef"
state = service.v3.workflow.create_task(task_id, "Create a plate.")
reviewing = transition(state, "requirements_finalized")
self.assertTrue(service.v3.repository.compare_and_swap(reviewing))
waiting = transition(reviewing, "waiting_retry", error=ErrorCode.REVIEW_SERVICE_UNAVAILABLE)
self.assertTrue(service.v3.repository.compare_and_swap(waiting))
async def consume_without_provider_calls(*_args: object) -> None:
await asyncio.sleep(0)
service._consume_discarding = consume_without_provider_calls # type: ignore[method-assign]
capability = AsyncMock(return_value={"supported": True})
with patch("app.services.agent_service.verify_model_capability", capability):
projection = asyncio.run(service.resume_retry(task_id))
self.assertEqual(capability.await_count, 2)
self.assertEqual((projection or {}).get("phase"), TaskPhase.REVIEWING_REQUIREMENTS.value)
resumed = service.v3.repository.get_state(task_id)
self.assertEqual(resumed.phase, TaskPhase.REVIEWING_REQUIREMENTS)
self.assertIsNone(resumed.retry_from_phase)
def test_service_retry_resume_rejects_waiting_for_user_before_provider_preflight(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
configured = service_settings(root)
service = AgentService(configured, WorkspaceStore(configured), CdslLibrary(configured))
task_id = "cad_123456abcdef"
state = service.v3.workflow.create_task(task_id, "Create a plate.")
reviewing = transition(state, "requirements_finalized")
self.assertTrue(service.v3.repository.compare_and_swap(reviewing))
waiting = transition(reviewing, "waiting_for_user", error=ErrorCode.WAITING_FOR_USER)
self.assertTrue(service.v3.repository.compare_and_swap(waiting))
capability = AsyncMock(return_value={"supported": True})
with patch("app.services.agent_service.verify_model_capability", capability):
with self.assertRaisesRegex(ValueError, "Only a WAITING_RETRY"):
asyncio.run(service.resume_retry(task_id))
capability.assert_not_awaited()
def test_service_cancel_persists_terminal_state_and_preserves_checkpoint(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
configured = service_settings(root)
service = AgentService(configured, WorkspaceStore(configured), CdslLibrary(configured))
task_id = "cad_123456abcdef"
initial = service.v3.workflow.create_task(task_id, "Create a plate.")
checkpoint = transition(initial, "requirements_finalized")
self.assertTrue(service.v3.repository.compare_and_swap(checkpoint))
projection = asyncio.run(service.cancel(task_id))
state = service.v3.repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.CANCELLED)
self.assertEqual(state.last_error, ErrorCode.CANCELLED)
self.assertEqual((projection or {}).get("lifecycle"), "cancelled")
self.assertTrue(any(event.get("event") == "task_cancelled" for event in service.v3.repository.ledger_events(task_id)))
self.assertEqual(service.v3.repository.running_task_ids(), [])
def test_cancelled_workflow_is_terminal_and_never_reclassified_as_internal_failure(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
task_id = "cad_123456abcdef"
initial = workflow.create_task(task_id, "Create a plate.")
cancelled = transition(initial, "cancelled", error=ErrorCode.CANCELLED)
self.assertTrue(repository.compare_and_swap(cancelled))
async def collect() -> list[tuple[str, dict[str, object]]]:
return [item async for item in workflow.run(task_id=task_id, author=None, reviewer=None)] # type: ignore[arg-type]
events = asyncio.run(collect())
self.assertEqual(events[-1][0], "task_terminal")
self.assertEqual(events[-1][1]["lifecycle"], "cancelled")
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.CANCELLED)
def test_cancelled_state_rejects_failure_and_retry_transitions(self) -> None:
cancelled = TaskState("cad_123456abcdef", TaskPhase.CANCELLED, 4)
for event in ("failed", "waiting_retry", "cancelled"):
with self.assertRaisesRegex(ValueError, "Illegal v3 transition"):
transition(cancelled, event, error=ErrorCode.CANCELLED)
def test_task_creation_freezes_document_attachment_as_a_stable_source_block(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
configured = service_settings(root)
service = AgentService(configured, WorkspaceStore(configured), CdslLibrary(configured))
conversation = service.store.ensure_conversation("conv_123456abcdef")
document = b"Outer diameter is 120 mm.\nCenter bore is through."
relative_path, _ = service.store.write_conversation_upload(conversation["conversation_id"], "flange.txt", document)
extracted_path = relative_path + ".txt"
service.store.conversation_attachment_path(conversation["conversation_id"], extracted_path).write_text(
document.decode("utf-8"), encoding="utf-8"
)
attachment = {
"id": "upload_flange", "conversation_id": conversation["conversation_id"], "name": "flange.txt",
"kind": "document", "path": relative_path, "mime": "text/plain", "size": len(document),
"sha256": sha256(document).hexdigest(), "extracted_path": extracted_path,
}
conversation = service.store.add_conversation_attachment(conversation["conversation_id"], attachment)
task_id = "cad_123456abcdef"
blocks = service._task_source_blocks(conversation, "Create a flange.\n\nUse the attached dimensions.")
service.v3.workflow.create_task(task_id, "Create a flange.\n\nUse the attached dimensions.", source_blocks=blocks)
source_index = service.v3.artifacts.read_json(task_id, "documents/source-index.json") or {}
self.assertEqual(source_index["sources"]["src_001"], "Create a flange.")
self.assertEqual(source_index["sources"]["src_002"], "Use the attached dimensions.")
self.assertEqual(source_index["sources"]["src_003"], document.decode("utf-8"))
self.assertEqual(source_index["attachment_blocks"]["src_003"]["sha256"], sha256(document).hexdigest())
self.assertIn(document.decode("utf-8"), service.v3.artifacts.read_source_requirements(task_id))
service.store.conversation_attachment_path(conversation["conversation_id"], extracted_path).write_text(
"changed after task creation", encoding="utf-8"
)
self.assertEqual(
(service.v3.artifacts.read_json(task_id, "documents/source-index.json") or {})["sources"]["src_003"],
document.decode("utf-8"),
)
def test_startup_resume_requires_author_and_reviewer_capabilities(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
configured = service_settings(root)
service = AgentService(configured, WorkspaceStore(configured), CdslLibrary(configured))
service.v3.workflow.create_task("cad_123456abcdef", "Create a plate.")
capability = AsyncMock(side_effect=[{"supported": True}, {"supported": False}])
with patch("app.services.agent_service.verify_model_capability", capability):
asyncio.run(service.resume_running_tasks())
self.assertEqual(capability.await_count, 2)
self.assertEqual(service._autonomous_runs, {})
state = service.v3.repository.get_state("cad_123456abcdef")
self.assertEqual(state.phase, TaskPhase.FAILED)
self.assertEqual(state.last_error, ErrorCode.MODEL_STRUCTURED_OUTPUT_UNSUPPORTED)
def test_domain_and_application_do_not_import_adapter_or_io_implementations(self) -> None:
package = ROOT / "backend" / "app" / "cad_agent"
forbidden_modules = {
"sqlite3", "pathlib", "os", "shutil",
"app.cad_agent.adapters", "app.services.engine_service", "app.services.review_renderer",
}
for layer in ("domain", "application"):
for path in (package / layer).glob("*.py"):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
imported: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
imported.update(alias.name for alias in node.names)
elif isinstance(node, ast.ImportFrom) and node.module:
imported.add(node.module)
for module in imported:
self.assertFalse(
any(module == forbidden or module.startswith(forbidden + ".") for forbidden in forbidden_modules),
f"{path.relative_to(ROOT)} imports forbidden implementation module {module}",
)
def test_dynamic_requirements_schema_rejects_unknown_claim_fields(self) -> None:
schema = requirements_draft_schema(default_registry().expected_one_of_schema(), ["src_001"])
error = canonical_validate_schema(json.dumps({
"items": [{
"source_ids": ["src_001"], "statement": "One solid", "assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1, "unexpected": True}}],
}],
}), schema)
self.assertIsNotNone(error)
self.assertEqual(error.code if error else None, ErrorCode.AUTHOR_FORMAT_INVALID)
def test_requirement_claims_reject_vacuous_geometry_tolerances(self) -> None:
registry = default_registry()
bbox_errors = registry.validate_expected(
"bbox_dimension_mm",
{"axis": "z", "value": 10, "tolerance_mm": 1.01},
)
bore_errors = registry.validate_expected(
"through_cylindrical_bore",
{"diameter_mm": 12, "count": 3, "tolerance_mm": 5},
)
coaxial_errors = registry.validate_expected(
"coaxial",
{"record_ids": ["topology_a", "topology_b"], "tolerance": 0.1},
)
self.assertTrue(any(error["path"] == "/tolerance_mm" for error in bbox_errors))
self.assertTrue(any(error["path"] == "/tolerance_mm" for error in bore_errors))
self.assertTrue(any(error["path"] == "/tolerance" for error in coaxial_errors))
def test_dynamic_requirements_schema_rejects_unknown_source_before_write(self) -> None:
schema = requirements_draft_schema(default_registry().expected_one_of_schema(), ["src_001"])
error = canonical_validate_schema(json.dumps({
"items": [{
"source_ids": ["src_unknown"], "statement": "One solid", "assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
}],
}), schema)
self.assertEqual(error.code if error else None, ErrorCode.AUTHOR_FORMAT_INVALID)
def test_provider_conformance_rejects_tool_arguments_that_violate_schema(self) -> None:
gateway = StructuredModelGateway(settings(Path(tempfile.gettempdir())))
async def invalid_response(**_kwargs: object) -> dict[str, object]:
return {
"tool_calls": [{"function": {"name": "probe", "arguments": "{}"}}],
"usage": {"prompt_tokens": 1, "completion_tokens": 1},
}
gateway.call_tool = invalid_response # type: ignore[method-assign]
report = asyncio.run(gateway.conformance(
provider_id="test",
model_id="test-model",
tools=[{"type": "function", "function": {"name": "probe", "parameters": {"type": "object", "properties": {"value": {"type": "integer"}}, "required": ["value"], "additionalProperties": False}}}],
))
self.assertFalse(report["supported"])
self.assertIn("violate schema", report["failures"][0]["message"])
self.assertFalse(report["probe_unavailable"])
def test_provider_conformance_stops_after_transport_failure(self) -> None:
gateway = StructuredModelGateway(settings(Path(tempfile.gettempdir())))
calls = 0
async def unavailable(**_kwargs: object) -> dict[str, object]:
nonlocal calls
calls += 1
raise StructuredTransportError("Provider transport unavailable after 3 attempts")
gateway.call_tool = unavailable # type: ignore[method-assign]
report = asyncio.run(gateway.conformance(
provider_id="test",
model_id="test-model",
tools=[
{"type": "function", "function": {"name": "probe_one", "parameters": {"type": "object"}}},
{"type": "function", "function": {"name": "probe_two", "parameters": {"type": "object"}}},
],
))
self.assertEqual(calls, 1)
self.assertFalse(report["supported"])
self.assertTrue(report["probe_unavailable"])
self.assertEqual([failure["tool"] for failure in report["failures"]], ["probe_one"])
def test_live_eval_main_persists_unexpected_preflight_failure(self) -> None:
"""A failed conformance preflight must still produce a release-gate report."""
import app.cad_agent.evals.live as live
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
async def unexpected(*_args: object, **_kwargs: object) -> dict[str, object]:
raise RuntimeError("conformance bootstrap broke")
arguments = type("Arguments", (), {
"suite": "smoke", "require_live": True, "allow_skip": False,
})()
with patch.object(live, "BACKEND_ROOT", root), patch.object(live, "_arguments", return_value=arguments), patch.object(live, "_run", unexpected):
self.assertEqual(live.main(), 2)
reports = list((root / "live-evals").glob("*/report.json"))
self.assertEqual(len(reports), 1)
report = json.loads(reports[0].read_text(encoding="utf-8"))
self.assertEqual(report["status"], "LIVE_EVAL_BLOCKED")
self.assertIn("RuntimeError: conformance bootstrap broke", report["error"])
self.assertEqual(report["failure_layer"], "configuration_or_network")
def test_provider_protocol_error_is_not_reclassified_as_author_format_error(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
class ProviderRejectedGateway:
async def call_tool(self, **_kwargs: object) -> dict[str, object]:
raise StructuredModelError("Provider rejected structured request (400)")
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=1, format_error_limit=1), repository, artifacts, runtime,
ProviderRejectedGateway(), None, RequirementsCommandHandler(repository, artifacts, default_registry()),
ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry())),
)
async def run() -> list[tuple[str, dict[str, object]]]:
author = type("Model", (), {"provider_id": "test", "model_id": "test-model"})()
return [item async for item in workflow.run(task_id=task_id, author=author, reviewer=None)]
events = asyncio.run(run())
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(state.last_error, ErrorCode.AUTHOR_TRANSPORT_UNAVAILABLE)
self.assertEqual(events[-1][1]["code"], ErrorCode.AUTHOR_TRANSPORT_UNAVAILABLE.value)
def test_runtime_contract_error_fails_once_without_author_format_retry(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
workflow = WorkflowCoordinator(WorkflowConfig(max_turns=4, format_error_limit=4), repository, artifacts, runtime, None, None, requirements, actions)
terminal = workflow._model_rejection_or_service_failure(
task_id,
repository.get_state(task_id), # type: ignore[arg-type]
"propose_next_action",
WorkflowError(ErrorCode.RUNTIME_CONTRACT_INVALID, "profile registry is invalid"),
{},
[],
)
self.assertIsNotNone(terminal)
self.assertEqual(terminal[1]["code"], ErrorCode.RUNTIME_CONTRACT_INVALID.value) # type: ignore[index]
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.FAILED)
self.assertEqual(state.last_error, ErrorCode.RUNTIME_CONTRACT_INVALID)
self.assertFalse(any(event.get("event") == "failed_author_format" for event in repository.ledger_events(task_id)))
def test_runtime_precondition_rejection_returns_to_action_selection_without_format_failure(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=4, format_error_limit=1), repository, artifacts, actions.runtime,
None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions,
)
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.ACTION_PENDING)
feedback: list[dict[str, object]] = []
terminal = workflow._model_rejection_or_service_failure(
task_id,
state,
"submit_cdsl_fragment",
WorkflowError(
ErrorCode.RUNTIME_PRECONDITION_FAILED,
"cut depth 12 mm must exceed measured host-body thickness 12 mm",
),
{},
feedback,
)
after = repository.get_state(task_id)
self.assertIsNone(terminal)
self.assertEqual(after.phase, TaskPhase.AWAITING_ACTION)
self.assertEqual(after.active_revision, state.active_revision)
self.assertIsNone(after.pending_action)
self.assertEqual(after.last_error, ErrorCode.RUNTIME_PRECONDITION_FAILED)
self.assertIn("must exceed measured host-body thickness", str(feedback[0]["content"]))
events = repository.ledger_events(task_id)
self.assertTrue(any(event.get("event") == "runtime_precondition_rejected" for event in events))
self.assertFalse(any(event.get("event") == "failed_author_format" for event in events))
def test_repeated_exact_fragment_is_rejected_without_creating_a_stage(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=8, format_error_limit=2), repository, artifacts, actions.runtime,
None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions,
)
fragment = {
"sketch": {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "analytic_contours", "contours": [{
"role": "outer", "closed": True,
"segments": [{"type": "arc", "start": [1, 0], "end": [0, 1], "center": [0, 0], "radius_mm": 2}],
}]},
},
"feature": {"atomic_id": "extrude_add_blind", "params": {"distance_mm": 5}},
}
first_state = repository.get_state(task_id)
first = actions.submit_cdsl_fragment(task_id, fragment, invocation_id="fragment_1")
self.assertIsInstance(first, Rejected)
self.assertEqual(first.error.code, ErrorCode.RUNTIME_PRECONDITION_FAILED)
self.assertIsNone(workflow._model_rejection_or_service_failure(
task_id, first_state, "submit_cdsl_fragment", first.error, {}, [],
))
selection_state = repository.get_state(task_id)
proposal = NextAction(
working_head=selection_state.working_head, intent="Try a corrected base", requirement_ids=["req_001"],
atomic_id="extrude_add_blind", expected_change="Create the base solid",
)
self.assertIsInstance(actions.propose_next_action(task_id, proposal, invocation_id="proposal_2"), Accepted)
with patch.object(artifacts, "start_candidate_stage") as start_stage:
duplicate = actions.submit_cdsl_fragment(task_id, fragment, invocation_id="fragment_2")
self.assertIsInstance(duplicate, Rejected)
self.assertTrue(duplicate.error.details["duplicate_fragment"])
start_stage.assert_not_called()
def test_third_failure_class_terminates_with_no_progress_limit(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=8, format_error_limit=2), repository, artifacts, actions.runtime,
None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions,
)
terminal = None
for attempt in range(3):
state = repository.get_state(task_id)
error = WorkflowError(ErrorCode.RUNTIME_PRECONDITION_FAILED, "profile is degenerate", details={
"active_revision": state.active_revision, "atomic_id": "extrude_add_blind",
"fragment_hash": f"fragment-{attempt}", "failure_exact_fingerprint": f"exact-{attempt}",
"normalized_error_code": ErrorCode.RUNTIME_PRECONDITION_FAILED.value,
})
terminal = workflow._model_rejection_or_service_failure(
task_id, state, "submit_cdsl_fragment", error, {}, [],
)
if attempt < 2:
self.assertIsNone(terminal)
selection = repository.get_state(task_id)
proposal = NextAction(
working_head=selection.working_head, intent=f"Alternative {attempt}", requirement_ids=["req_001"],
atomic_id="extrude_add_blind", expected_change="Create the base solid",
)
self.assertIsInstance(actions.propose_next_action(task_id, proposal, invocation_id=f"proposal_{attempt}"), Accepted)
self.assertEqual(terminal[1]["code"], ErrorCode.NO_PROGRESS_LIMIT.value)
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.FAILED)
def test_author_call_budget_fails_without_calling_model_and_preserves_checkpoint(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
class UnexpectedAuthor:
async def call_tool(self, **_kwargs: object) -> dict[str, object]:
raise AssertionError("the author budget must be checked before a provider call")
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=8, format_error_limit=1, max_author_turns=0),
repository,
artifacts,
actions.runtime,
UnexpectedAuthor(),
None,
RequirementsCommandHandler(repository, artifacts, default_registry()),
actions,
)
async def run() -> list[tuple[str, dict[str, object]]]:
identity = ModelIdentity("test", "test-model")
return [item async for item in workflow.run(task_id=task_id, author=identity, reviewer=identity)]
events = asyncio.run(run())
after = repository.get_state(task_id)
self.assertEqual(after.phase, TaskPhase.FAILED)
self.assertEqual(after.last_error, ErrorCode.CALL_BUDGET_EXHAUSTED)
self.assertIsNone(after.pending_action)
self.assertEqual(repository.usage_summary(task_id)["calls"], 0)
self.assertEqual(events[-1][1]["code"], ErrorCode.CALL_BUDGET_EXHAUSTED.value)
self.assertFalse(events[-1][1]["userActionRequired"])
self.assertFalse(any(name in {"action_selection", "candidate_result"} for name, _payload in events))
budget_event = next(event for event in repository.ledger_events(task_id) if event.get("event") == "call_budget_exhausted")
self.assertEqual(budget_event["next_actor"], "author")
self.assertEqual(budget_event["author_calls"], 0)
def test_reviewer_call_budget_fails_and_preserves_candidate_stage(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
stage = artifacts.start_candidate_stage(task_id, "budget-stage-key", {
"candidate_id": "candidate_123456789012",
"action_id": action.action_id,
})
artifacts.write_stage_json(task_id, stage.stage_id, "candidate.json", {
"candidate_id": "candidate_123456789012",
"stage_id": stage.stage_id,
"action_id": action.action_id,
"claim_results": [{
"claim_id": "claim_001",
"claim_kind": "solid_count_equals",
"deterministic": True,
"status": "pass",
}],
})
building = transition(
state,
"candidate_started",
candidate_id="candidate_123456789012",
candidate_stage_id=stage.stage_id,
)
reviewing = transition(
building,
"candidate_built",
candidate_id="candidate_123456789012",
candidate_stage_id=stage.stage_id,
)
self.assertTrue(repository.compare_and_swap(building))
self.assertTrue(repository.compare_and_swap(reviewing))
class UnexpectedReviewer:
async def review(self, **_kwargs: object) -> dict[str, object]:
raise AssertionError("the reviewer budget must be checked before a provider call")
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=8, format_error_limit=1, max_reviewer_turns=0),
repository,
artifacts,
actions.runtime,
None,
UnexpectedReviewer(),
RequirementsCommandHandler(repository, artifacts, default_registry()),
actions,
)
async def run() -> list[tuple[str, dict[str, object]]]:
identity = ModelIdentity("test", "test-model")
return [item async for item in workflow.run(task_id=task_id, author=identity, reviewer=identity)]
events = asyncio.run(run())
after = repository.get_state(task_id)
self.assertEqual(after.phase, TaskPhase.FAILED)
self.assertEqual(after.last_error, ErrorCode.CALL_BUDGET_EXHAUSTED)
self.assertEqual(after.candidate_stage_id, "")
self.assertTrue(Path(stage.output_dir).is_dir())
self.assertEqual(repository.usage_summary(task_id)["calls"], 0)
self.assertEqual(events[-1][1]["code"], ErrorCode.CALL_BUDGET_EXHAUSTED.value)
self.assertFalse(events[-1][1]["userActionRequired"])
budget_event = next(event for event in repository.ledger_events(task_id) if event.get("event") == "call_budget_exhausted")
self.assertEqual(budget_event["next_actor"], "reviewer")
self.assertEqual(budget_event["reviewer_calls"], 0)
def test_unexpected_author_gateway_error_fails_once_as_internal(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
class BrokenGateway:
async def call_tool(self, **_kwargs: object) -> dict[str, object]:
raise RuntimeError("unexpected adapter bug")
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=4, format_error_limit=4), repository, artifacts, runtime,
BrokenGateway(), None, RequirementsCommandHandler(repository, artifacts, default_registry()),
ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry())),
)
async def run() -> list[tuple[str, dict[str, object]]]:
author = type("Model", (), {"provider_id": "test", "model_id": "test-model"})()
return [item async for item in workflow.run(task_id=task_id, author=author, reviewer=None)]
events = asyncio.run(run())
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.FAILED)
self.assertEqual(state.last_error, ErrorCode.FAILED_INTERNAL)
self.assertEqual(events[-1][1]["code"], ErrorCode.FAILED_INTERNAL.value)
self.assertFalse(any(event.get("event") == "failed_author_format" for event in repository.ledger_events(task_id)))
def test_responses_gateway_normalizes_input_tools_and_function_output(self) -> None:
provider = ProviderConfig("openai", "OpenAI", "https://example.invalid/v1", "test-key", (ProviderModel("test-model", vision=True),), api_style="responses")
gateway = StructuredModelGateway(settings(Path(tempfile.gettempdir())))
payload = gateway._payload(
provider,
"test-model",
[{"role": "system", "content": "Follow the contract."}, {"role": "user", "content": [{"type": "text", "text": "Review this."}, {"type": "image_url", "image_url": {"url": "data:image/png;base64,AA=="}}]}],
[{"type": "function", "function": {"name": "submit", "description": "Submit", "parameters": {"type": "object", "additionalProperties": False}}}],
"submit",
)
self.assertEqual(payload["input"][0]["role"], "developer")
self.assertEqual(payload["input"][1]["content"][1]["type"], "input_image")
self.assertEqual(payload["tools"][0]["name"], "submit")
self.assertNotIn("strict", payload["tools"][0])
self.assertEqual(payload["tool_choice"], {"type": "function", "name": "submit"})
response = gateway._normalized_response(provider, {"output": [{"type": "function_call", "name": "submit", "arguments": "{}"}], "usage": {"input_tokens": 7, "output_tokens": 3}})
self.assertEqual(response["tool_calls"], [{"function": {"name": "submit", "arguments": "{}"}}])
self.assertEqual(response["usage"]["total_tokens"], 10)
def test_capability_probe_exposes_required_selector_as_author_input(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
parameters = fragment_schema(runtime.operation_contract("hole_counterbore"), selector_tokens=["sel_conformance"])
gateway = StructuredModelGateway(settings(Path(tempfile.gettempdir())))
messages = gateway._conformance_messages({
"type": "function",
"function": {"name": "conformance_hole_counterbore", "parameters": parameters},
})
instruction = str(messages[0]["content"])
selector = parameters["properties"]["feature"]["properties"]["selector_tokens"]
self.assertIn("Required author input", str(selector["description"]))
self.assertIn("sel_conformance", instruction)
self.assertIn('"selector_tokens":["sel_conformance"]', instruction)
self.assertIn("must not be moved into params", instruction)
def test_capability_probe_names_required_candidate_review_decision(self) -> None:
gateway = StructuredModelGateway(settings(Path(tempfile.gettempdir())))
messages = gateway._conformance_messages({
"type": "function",
"function": {
"name": "review_candidate",
"parameters": candidate_review_schema("candidate_conformance", "cad_conformance:root:v1", ["claim_001"]),
},
})
instruction = str(messages[0]["content"])
self.assertIn("provide a verdict and claim_coverage", instruction)
self.assertIn("every required top-level and nested field", instruction)
def test_model_gateway_rejects_any_unnamed_or_multi_tool_request(self) -> None:
gateway = StructuredModelGateway(settings(Path(tempfile.gettempdir())))
provider = ProviderConfig("openai", "OpenAI", "https://example.invalid/v1", "test-key", (ProviderModel("test-model"),))
tool = {"type": "function", "function": {"name": "submit", "parameters": {"type": "object", "additionalProperties": False}}}
with self.assertRaisesRegex(StructuredModelError, "exactly one named tool"):
gateway._payload(provider, "test-model", [], [tool], "")
with self.assertRaisesRegex(StructuredModelError, "exactly one named tool"):
gateway._payload(provider, "test-model", [], [tool, tool], "submit")
def test_provider_conformance_covers_fixed_and_representative_dynamic_contracts(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
tools = conformance_tools(runtime)
names = {str((tool.get("function") or {}).get("name") or "") for tool in tools}
self.assertEqual(
{f"conformance_{atomic_id}" for atomic_id in runtime.supported_atomic_ids()},
{name for name in names if name.startswith("conformance_")},
)
self.assertTrue({
"submit_requirements_draft_batch", "patch_requirements_draft", "finalize_requirements_draft",
"review_requirements", "propose_next_action", "inspect_topology",
"get_cdsl_operation_contract", "review_candidate", "complete_task", "review_final",
"record_geometry_conclusion", "rollback_checkpoint",
}.issubset(names))
def test_live_fixture_can_select_one_stable_comprehensive_scenario(self) -> None:
selected = _fixture("comprehensive", "circular_flange_pcd")
self.assertEqual([item["id"] for item in selected], ["circular_flange_pcd"])
with self.assertRaisesRegex(ValueError, "Unknown scenario"):
_fixture("comprehensive", "unknown_scenario")
def test_comprehensive_fixture_is_bound_to_the_target_document_hash(self) -> None:
fixture_path = ROOT / "backend" / "app" / "cad_agent" / "evals" / "fixtures" / "comprehensive.json"
fixture = json.loads(fixture_path.read_text(encoding="utf-8"))
source = ROOT / str(fixture["source_document"])
self.assertEqual(fixture["source_document_sha256"], sha256(source.read_bytes()).hexdigest())
def test_live_capability_probe_separates_transport_failure_from_schema_unsupported(self) -> None:
self.assertEqual(
_capability_block_reason({"failures": [{"message": "Provider transport unavailable after 3 attempts"}]}),
("MODEL_CAPABILITY_PROBE_UNAVAILABLE", "configuration_or_network"),
)
self.assertEqual(
_capability_block_reason({"failures": [{"message": "Provider arguments violate schema: required property"}]}),
("MODEL_STRUCTURED_OUTPUT_UNSUPPORTED", "model_format_or_decision"),
)
def test_live_failure_attribution_covers_contract_engine_visual_and_timeout_layers(self) -> None:
def attribution(*, outcome: str, checks: dict[str, bool], code: str = "") -> dict[str, object] | None:
events = [{"payload": {"code": code}}] if code else []
return _failure_attribution(checks=checks, outcome=outcome, events=events, projection={})
self.assertEqual(
attribution(outcome="failed", checks={"expected_terminal_phase": False}, code="RUNTIME_CONTRACT_INVALID")["layer"],
"v3_contract_or_verifier",
)
self.assertEqual(
attribution(outcome="failed", checks={"expected_terminal_phase": False}, code="CANDIDATE_BUILD_FAILED")["layer"],
"engine_execution",
)
self.assertEqual(
attribution(outcome="failed", checks={"expected_terminal_phase": False}, code="CANDIDATE_REVIEW_REJECTED")["layer"],
"independent_visual_review",
)
self.assertEqual(
attribution(outcome="failed", checks={"expected_terminal_phase": False}, code="LIVE_EVAL_TIMEOUT")["layer"],
"configuration_or_network",
)
self.assertEqual(
attribution(outcome="validation_capability_gap", checks={"acceptance_contract_coverage": False})["reason_code"],
"VALIDATION_CAPABILITY_GAP",
)
self.assertIsNone(_failure_attribution(checks={"expected_terminal_phase": True}, outcome="passed", events=[], projection={}))
def test_release_gate_checks_audits_claims_and_immutable_manifest(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
revision = root / "revisions" / "rev_001"
render_dir = revision / "renders"
render_dir.mkdir(parents=True)
evidence = {
"candidate.json": b"{}",
"candidate-review.json": b"{}",
"model.cdsl.json": b"{}",
"model.step": b"step",
"model.glb": b"glb",
"model.topology.json": b"{}",
"rebuild-report.json": b"{}",
"renders/render-manifest.json": b"{}",
"renders/contact-sheet.jpg": b"jpeg",
}
for relative, contents in evidence.items():
path = revision / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(contents)
(revision / "manifest.json").write_text(json.dumps({
"files": {relative: sha256(contents).hexdigest() for relative, contents in evidence.items()},
}), encoding="utf-8")
(root / "requirements-contract.json").write_text("{}", encoding="utf-8")
final_review = root / "reviews" / "final" / "rev_001" / "final-review.json"
final_review.parent.mkdir(parents=True)
final_review.write_text("{}", encoding="utf-8")
scenario = {
"expected_phase": "COMPLETED", "max_total_tokens": 10, "max_total_calls": 2,
"max_author_turns": 1, "max_reviewer_turns": 1,
"required_claim_kinds": ["solid_count_equals"], "required_atomic_ids": ["extrude_add_blind"],
}
digest = sha256(b"arguments").hexdigest()
(root / "actions").mkdir()
(root / "actions" / "action-ledger.jsonl").write_text('{"sequence":1}\n', encoding="utf-8")
ledger = [
{"event": "accepted", "revision_id": "rev_001", "actual_atomic_id": "extrude_add_blind"},
{"event": "completed", "claim_results": [{"claim_id": "claim_001", "deterministic": True, "status": "pass"}]},
]
projection = {"phase": "COMPLETED", "active_revision": "rev_001", "action_ledger_summary": ledger}
usage = {"prompt_tokens": 3, "completion_tokens": 2, "records": [{"raw_arguments_hash": digest}, {"role": "reviewer", "raw_arguments_hash": digest}]}
contract = {"requirements": [{"acceptance_claims": [{"claim_kind": "solid_count_equals"}]}]}
self.assertTrue(all(_run_checks(scenario, projection, usage, contract, [], root, ledger).values()))
semantic_alternative = {
**scenario,
"required_atomic_ids": [],
"required_any_atomic_id_groups": [["extrude_add_blind", "revolve_add"]],
}
alternative_ledger = [
{"event": "accepted", "revision_id": "rev_001", "actual_atomic_id": "revolve_add"},
{"event": "completed", "claim_results": [{"claim_id": "claim_001", "deterministic": True, "status": "pass"}]},
]
alternative_checks = _run_checks(semantic_alternative, projection, usage, contract, [], root, alternative_ledger)
self.assertTrue(alternative_checks["required_operations"])
self.assertTrue(alternative_checks["required_operation_alternative"])
audits = [{
"raw_arguments_hash": digest,
"canonical_schema_valid": True,
"single_allowed_call": True,
"state_binding": {"phase": "AWAITING_ACTION", "binding_valid": True},
}]
self.assertTrue(_run_checks(scenario, projection, usage, contract, [], root, ledger, audits)["raw_argument_audit"])
audits[0]["state_binding"]["binding_valid"] = False
self.assertFalse(_run_checks(scenario, projection, usage, contract, [], root, ledger, audits)["raw_argument_audit"])
self.assertFalse(_run_checks(scenario, projection, usage, contract, [{"payload": {"result": {"code": "AUTHOR_FORMAT_INVALID"}}}], root, ledger)["no_schema_or_decision_rejections"])
(revision / "model.glb").unlink()
self.assertFalse(_run_checks(scenario, projection, usage, contract, [], root, ledger)["required_artifact_evidence"])
def test_comprehensive_prompt_fixture_covers_all_document_cases_and_declares_gaps(self) -> None:
scenarios = _fixture("comprehensive")
supported_claim_kinds = set(default_registry().claim_kinds)
supported_atomic_ids = set(ProfileCadRuntime(settings(Path(tempfile.gettempdir()))).supported_atomic_ids())
source_document = ROOT / "docs" / "cad-agent-v3-comprehensive-prompt-test-target.md"
document_requests = [
columns[3].strip()
for line in source_document.read_text(encoding="utf-8").splitlines()
if line.startswith("| ")
for columns in [line.split("|")]
if len(columns) >= 5 and columns[1].strip().isdigit()
]
self.assertEqual(len(scenarios), 20)
self.assertEqual([str(scenario["request"]) for scenario in scenarios], document_requests)
self.assertEqual(
[scenario["id"] for scenario in scenarios],
[
"rectangular_mounting_plate", "circular_flange_pcd", "square_flange", "counterbored_mounting_plate",
"countersunk_cover_plate", "obround_slot_plate", "t_slot_test_block", "rounded_rectangular_pocket",
"two_level_pocket_plate", "cross_drilled_valve_block", "double_hole_linkage_arm", "three_hole_linkage",
"l_bracket", "ribbed_l_bracket", "u_bearing_support", "double_lug_mount", "stepped_shaft",
"keyed_stepped_shaft", "flanged_sleeve", "chamfered_bushing",
],
)
for scenario in scenarios:
self.assertEqual(scenario["units"], "mm")
self.assertEqual(scenario["expected_phase"], "COMPLETED")
self.assertTrue(scenario["request"])
self.assertTrue(scenario["required_claim_kinds"])
self.assertTrue(scenario["required_claims"])
self.assertGreaterEqual(
scenario["max_total_calls"],
scenario["max_author_turns"] + scenario["max_reviewer_turns"],
)
self.assertGreater(scenario["max_wall_seconds"], 0)
self.assertGreater(scenario["max_total_tokens"], 0)
self.assertTrue(set(scenario["required_claim_kinds"]).issubset(supported_claim_kinds))
self.assertTrue({claim["claim_kind"] for claim in scenario["required_claims"]}.issubset(supported_claim_kinds))
required_operations = {
str(atomic_id)
for atomic_id in [
*(scenario.get("required_atomic_ids") or ()),
*(scenario.get("required_any_atomic_ids") or ()),
*(
atomic_id
for group in scenario.get("required_any_atomic_id_groups") or ()
if isinstance(group, list)
for atomic_id in group
),
]
}
self.assertTrue(required_operations.issubset(supported_atomic_ids))
for group in scenario.get("required_any_atomic_id_groups") or ():
self.assertIsInstance(group, list)
self.assertTrue(group)
gap_ids = [str(gap.get("id") or "") for gap in scenario["validation_capability_gaps"]]
self.assertEqual(len(gap_ids), len(set(gap_ids)), f"{scenario['id']} repeats a capability gap")
self.assertTrue(all(gap_ids))
self.assertTrue(all(str(gap.get("description") or "") for gap in scenario["validation_capability_gaps"]))
linkage = next(scenario for scenario in scenarios if scenario["id"] == "three_hole_linkage")
assessment = _acceptance_coverage(
linkage,
{"requirements": [{"acceptance_claims": linkage["required_claims"]}]},
)
self.assertEqual(assessment["missing_claims"], [])
self.assertFalse(assessment["complete"])
self.assertEqual(
{gap["id"] for gap in assessment["validation_capability_gaps"]},
{"linkage_outer_circles"},
)
wrong_thickness = _acceptance_coverage(
linkage,
{"requirements": [{"acceptance_claims": [
*[claim for claim in linkage["required_claims"] if claim["claim_kind"] != "bbox_rank_dimension_mm"],
{"claim_kind": "bbox_rank_dimension_mm", "expected": {"rank": "minimum", "value": 12}},
]}]},
)
self.assertIn(
{"claim_kind": "bbox_rank_dimension_mm", "expected": {"rank": "minimum", "value": 10}},
wrong_thickness["missing_claims"],
)
def test_release_fixture_has_oracle_claims_for_each_scenario(self) -> None:
scenarios = _fixture("release")
registry = default_registry()
self.assertEqual([scenario["id"] for scenario in scenarios], [
"rectangular_plate", "simple_flange", "ribbed_mounting_plate", "selector_finish",
])
for scenario in scenarios:
claims = scenario.get("required_claims")
self.assertIsInstance(claims, list)
self.assertTrue(claims, scenario["id"])
for claim in claims:
self.assertIsInstance(claim, dict)
self.assertEqual(registry.validate_expected(str(claim.get("claim_kind") or ""), claim.get("expected")), [])
rectangular = scenarios[0]
self.assertEqual(
_acceptance_coverage(rectangular, {"requirements": [{"acceptance_claims": rectangular["required_claims"]}]}),
{
"required_claim_kinds": ["bbox_dimension_mm", "solid_count_equals"],
"covered_claim_kinds": ["bbox_dimension_mm", "solid_count_equals"],
"required_claims": rectangular["required_claims"],
"missing_claims": [],
"validation_capability_gaps": [],
"complete": True,
},
)
def test_ranked_bbox_dimension_is_orientation_independent(self) -> None:
registry = default_registry()
expected = {"rank": "minimum", "value": 10, "tolerance_mm": 0.1}
self.assertEqual(
registry.evaluate("bbox_rank_dimension_mm", expected, {"health": {"bbox_mm": {"dimensions": [140, 10, 32]}}})["status"],
"pass",
)
self.assertEqual(
registry.evaluate("bbox_rank_dimension_mm", expected, {"health": {"bbox_mm": {"dimensions": [140, 12, 32]}}})["status"],
"fail",
)
def test_bore_chain_and_circular_pattern_verifiers_prove_arrangement(self) -> None:
registry = default_registry()
def cylinder(
record_id: str,
centre: list[float],
radius: float = 6,
axis_origin: list[float] | None = None,
axis_direction: list[float] | None = None,
) -> dict[str, object]:
geometry: dict[str, object] = {
"surface_type": "cylinder",
"radius_mm": radius,
"center_mm": centre,
"cylinder_role": "inner",
"through": True,
}
if axis_origin is not None:
geometry["axis_origin_mm"] = axis_origin
if axis_direction is not None:
geometry["axis_direction"] = axis_direction
return {
"record_id": record_id,
"geometry": geometry,
}
chain_facts = {"topology": {"records": [
cylinder("bore_1", [0, 0, 0]),
cylinder("bore_2", [60, 0, 0]),
cylinder("bore_3", [140, 0, 0]),
]}}
chain_expected = {"diameter_mm": 12, "adjacent_distances_mm": [60, 80], "tolerance_mm": 0.1}
self.assertEqual(
registry.evaluate(
"collinear_through_bore_chain",
chain_expected,
{"topology": {"records": [cylinder("base_outer", [0, 0, 0], 16)]}},
)["status"],
"pending",
)
self.assertEqual(
registry.evaluate(
"collinear_through_bore_chain",
chain_expected,
{"topology": {"records": chain_facts["topology"]["records"][:2]}},
)["status"],
"pending",
)
forward_chain = registry.evaluate("collinear_through_bore_chain", chain_expected, chain_facts)
self.assertEqual(forward_chain["status"], "pass")
self.assertEqual(forward_chain["evidence"]["expected_orientation"], "forward")
reverse_chain_facts = {"topology": {"records": list(reversed(chain_facts["topology"]["records"]))}}
reversed_chain = registry.evaluate("collinear_through_bore_chain", chain_expected, reverse_chain_facts)
self.assertEqual(reversed_chain["status"], "pass")
self.assertEqual(reversed_chain["evidence"]["adjacent_distances_mm"], [80.0, 60.0])
self.assertEqual(reversed_chain["evidence"]["expected_orientation"], "reversed")
chain_facts["topology"]["records"][1]["geometry"]["center_mm"] = [60, 0.2, 0] # type: ignore[index]
self.assertEqual(registry.evaluate("collinear_through_bore_chain", chain_expected, chain_facts)["status"], "fail")
mixed_host_chain_facts = {"topology": {"records": [
cylinder("bore_top_1", [0, 0, 5], axis_origin=[0, 0, 10], axis_direction=[0, 0, -1]),
cylinder("bore_bottom", [60, 0, 5], axis_origin=[60, 0, 0], axis_direction=[0, 0, 1]),
cylinder("bore_top_2", [140, 0, 5], axis_origin=[140, 0, 10], axis_direction=[0, 0, -1]),
]}}
mixed_host_chain = registry.evaluate("collinear_through_bore_chain", chain_expected, mixed_host_chain_facts)
self.assertEqual(mixed_host_chain["status"], "pass", mixed_host_chain)
self.assertEqual(mixed_host_chain["evidence"]["adjacent_distances_mm"], [60.0, 80.0])
pattern_facts = {"topology": {"records": [
cylinder("hole_1", [10, 0, 0], 1),
cylinder("hole_2", [0, 10, 0], 1),
cylinder("hole_3", [-10, 0, 0], 1),
cylinder("hole_4", [0, -10, 0], 1),
]}}
pattern_expected = {"diameter_mm": 2, "count": 4, "pitch_radius_mm": 10, "tolerance_mm": 0.1}
self.assertEqual(registry.evaluate("circular_hole_pattern", pattern_expected, pattern_facts)["status"], "pass")
pattern_facts["topology"]["records"][1]["geometry"]["center_mm"] = [5, 8.6602540378, 0] # type: ignore[index]
pattern_facts["topology"]["records"][3]["geometry"]["center_mm"] = [-5, -8.6602540378, 0] # type: ignore[index]
self.assertEqual(registry.evaluate("circular_hole_pattern", pattern_expected, pattern_facts)["status"], "fail")
concentric_facts = {"topology": {"records": [
*[
{
**record,
"geometry": {
**record["geometry"],
"axis_origin_mm": record["geometry"]["center_mm"], "axis_direction": [0, 0, 1],
},
}
for record in [
cylinder("hole_1", [10, 0, 0], 1),
cylinder("hole_2", [0, 10, 0], 1),
cylinder("hole_3", [-10, 0, 0], 1),
cylinder("hole_4", [0, -10, 0], 1),
]
],
cylinder("central_bore", [0, 0, 0], 4),
]}}
concentric_facts["topology"]["records"][-1]["geometry"].update({"axis_origin_mm": [0, 0, 0], "axis_direction": [0, 0, 1]}) # type: ignore[index]
concentric_expected = {"diameter_mm": 2, "count": 4, "pitch_radius_mm": 10, "concentric_bore_diameter_mm": 8, "tolerance_mm": 0.1}
self.assertEqual(registry.evaluate("circular_hole_pattern", concentric_expected, concentric_facts)["status"], "pass")
for record in concentric_facts["topology"]["records"][:4]:
record["geometry"]["axis_origin_mm"][0] += 1 # type: ignore[index]
self.assertEqual(registry.evaluate("circular_hole_pattern", concentric_expected, concentric_facts)["status"], "fail")
def test_operation_bore_verifier_counts_from_the_parent_checkpoint(self) -> None:
"""Adding a second single-position hole must not be compared to zero."""
with tempfile.TemporaryDirectory() as temporary:
_repository, _artifacts, actions, _task_id, _action = self._ready_action(Path(temporary))
contract = actions.runtime.operation_contract("hole_wizard")
action = PendingAction(
"act_005", "cad_123456abcdef:rev_001:v1", "Add another bore", ("req_001",),
"hole_wizard", "Add one 12 mm bore", contract["contract_hash"], "operation-bore-key",
)
def bore(record_id: str, x: float, *, through: bool = False) -> dict[str, object]:
return {
"record_id": record_id,
"kind": "face",
"geometry": {
"surface_type": "cylinder", "cylinder_role": "inner", "radius_mm": 6,
"center_mm": [x, 0, 0], "axis_direction": [0, 0, 1], "through": through,
},
}
cdsl = {"features": [{"atomic_id": "hole_wizard", "params": {"diameter_mm": 12, "positions": [{"mm": [60, 0, 0]}]}}]}
parent_facts = {"topology": {"records": [bore("bore_1", 0)]}}
rebuilt = {"health": {}, "topology": {"records": [bore("bore_1", 0), bore("bore_2", 60)]}, "report": {}}
results = actions._operation_candidate_results(
action, contract, cdsl, rebuilt, parent_facts=parent_facts, require_through=False,
)
self.assertEqual(results[0]["claim_kind"], "cylindrical_bore")
self.assertEqual(results[0]["status"], "pass", results)
self.assertEqual(results[0]["evidence"]["parent_matching_count"], 1)
self.assertEqual(results[0]["evidence"]["expected_increment"], 1)
self.assertEqual(results[0]["evidence"]["expected_total_count"], 2)
no_delta = actions._operation_candidate_results(
action, contract, cdsl,
{"health": {}, "topology": {"records": [bore("bore_1", 0)]}, "report": {}},
parent_facts=parent_facts,
require_through=False,
)
self.assertEqual(no_delta[0]["status"], "pending", no_delta)
through_contract = actions.runtime.operation_contract("hole_blind")
through_action = PendingAction(
"act_006", "cad_123456abcdef:rev_001:v2", "Add another through bore", ("req_001",),
"hole_blind", "Add one through 12 mm bore", through_contract["contract_hash"], "operation-through-bore-key",
)
through_cdsl = {"features": [{"atomic_id": "hole_blind", "params": {"diameter_mm": 12, "positions": [{"mm": [60, 0, 0]}]}}]}
through_parent = {"topology": {"records": [bore("through_bore_1", 0, through=True)]}}
through_rebuilt = {"health": {}, "topology": {"records": [bore("through_bore_1", 0, through=True), bore("through_bore_2", 60, through=True)]}, "report": {}}
through_results = actions._operation_candidate_results(
through_action, through_contract, through_cdsl, through_rebuilt,
parent_facts=through_parent, require_through=True,
)
self.assertTrue(all(item["status"] == "pass" for item in through_results), through_results)
self.assertTrue(all(item["evidence"]["expected_total_count"] == 2 for item in through_results), through_results)
def test_bore_count_claims_reject_extra_matching_holes_but_defer_incomplete_patterns(self) -> None:
registry = default_registry()
records = [
{
"record_id": f"bore_{index}",
"geometry": {
"surface_type": "cylinder", "radius_mm": 4, "center_mm": [index * 10, 0, 0],
"cylinder_role": "inner", "through": True,
},
}
for index in range(5)
]
expected = {"diameter_mm": 8, "count": 4, "tolerance_mm": 0.1}
self.assertEqual(
registry.evaluate("through_cylindrical_bore", expected, {"topology": {"records": records[:3]}})["status"],
"pending",
)
self.assertEqual(
registry.evaluate("through_cylindrical_bore", expected, {"topology": {"records": records}})["status"],
"fail",
)
def test_bore_axis_verifiers_prove_coaxiality_and_orthogonal_intersection(self) -> None:
registry = default_registry()
def bore(record_id: str, diameter: float, origin: list[float], direction: list[float]) -> dict[str, object]:
return {
"record_id": record_id,
"geometry": {
"surface_type": "cylinder", "radius_mm": diameter / 2,
"axis_origin_mm": origin, "axis_direction": direction,
"cylinder_role": "inner", "through": True,
},
}
coaxial = [
bore("lug_a", 16, [0, 0, 0], [1, 0, 0]),
bore("lug_b", 16, [20, 0, 0], [1, 0, 0]),
]
coaxial_expected = {"diameter_mm": 16, "count": 2, "tolerance_mm": 0.01}
self.assertEqual(
registry.evaluate("coaxial_through_bore_group", coaxial_expected, {"topology": {"records": coaxial}})["status"],
"pass",
)
coaxial[1]["geometry"]["axis_origin_mm"] = [20, 0.02, 0] # type: ignore[index]
self.assertEqual(
registry.evaluate("coaxial_through_bore_group", coaxial_expected, {"topology": {"records": coaxial}})["status"],
"fail",
)
crossing = [
bore("longitudinal", 20, [0, 0, 0], [1, 0, 0]),
bore("transverse", 12, [0, 0, 0], [0, 1, 0]),
]
crossing_expected = {"first_diameter_mm": 20, "second_diameter_mm": 12, "first_axis": "x", "second_axis": "y", "tolerance_mm": 0.01}
self.assertEqual(
registry.evaluate("orthogonal_intersecting_through_bores", crossing_expected, {"topology": {"records": crossing}})["status"],
"pass",
)
crossing[1]["geometry"]["axis_origin_mm"] = [0, 0, 0.02] # type: ignore[index]
self.assertEqual(
registry.evaluate("orthogonal_intersecting_through_bores", crossing_expected, {"topology": {"records": crossing}})["status"],
"fail",
)
crossing[1]["geometry"]["axis_origin_mm"] = [0, 0, 0] # type: ignore[index]
crossing[1]["geometry"]["axis_direction"] = [0, 0, 1] # type: ignore[index]
self.assertEqual(
registry.evaluate("orthogonal_intersecting_through_bores", crossing_expected, {"topology": {"records": crossing}})["status"],
"fail",
)
def test_subtractive_operation_volume_verifier_rejects_a_noop_cut(self) -> None:
registry = default_registry()
self.assertEqual(
registry.evaluate(
"volume_decreased",
{},
{"parent_health": {"volume_mm3": 1200}, "health": {"volume_mm3": 1200}},
)["status"],
"fail",
)
self.assertEqual(
registry.evaluate(
"volume_decreased",
{},
{"parent_health": {"volume_mm3": 1200}, "health": {"volume_mm3": 1199.5}},
)["status"],
"pass",
)
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
runtime = ProfileCadRuntime(settings(root))
extrude_cut_contract = runtime.operation_contract("extrude_cut_blind")
self.assertIn("volume_decreased", extrude_cut_contract["candidate_verifiers"])
self.assertIn("volume_decreased", runtime.operation_contract("revolve_cut")["candidate_verifiers"])
_repository, _artifacts, actions, _task_id, action = self._ready_action(root)
cut_action = replace(
action,
atomic_id="extrude_cut_blind",
contract_hash=str(extrude_cut_contract["contract_hash"]),
)
results = actions._operation_candidate_results(
cut_action,
extrude_cut_contract,
{"features": [{"atomic_id": "extrude_cut_blind", "params": {"distance_mm": 1}}]},
{"health": {"volume_mm3": 1200}, "topology": {}, "report": {}},
parent_facts={"health": {"volume_mm3": 1200}},
require_through=False,
)
self.assertEqual(
next(item for item in results if item["claim_kind"] == "volume_decreased")["status"],
"fail",
)
def test_live_artifact_manifest_excludes_source_content_and_hashes_cad_evidence(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
(root / "actions").mkdir()
(root / "actions" / "action-ledger.jsonl").write_text('{"event":"accepted"}\n', encoding="utf-8")
(root / "revisions" / "rev_001").mkdir(parents=True)
(root / "revisions" / "rev_001" / "model.cdsl.json").write_text("{}", encoding="utf-8")
(root / "documents").mkdir()
(root / "documents" / "source-index.json").write_text('{"sources":{"src_001":"secret source"}}', encoding="utf-8")
(root / "source-requirements.md").write_text("secret source", encoding="utf-8")
manifest = _safe_artifact_manifest(root)
self.assertEqual([item["path"] for item in manifest["files"]], ["actions/action-ledger.jsonl", "revisions/rev_001/model.cdsl.json"])
self.assertTrue(all(len(item["sha256"]) == 64 for item in manifest["files"]))
def test_rejected_provider_tool_calls_keep_per_call_audit_evidence(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
configured = service_settings(root)
service = AgentService(configured, WorkspaceStore(configured), CdslLibrary(configured))
task_id = "cad_123456abcdef"
service.v3.workflow.create_task(task_id, "Create a plate.")
service.v3.workflow._record_rejected_tool_calls(
task_id,
actor="author",
expected_tool="submit_requirements_draft_batch",
tool_calls=[
{"function": {"name": "wrong_tool", "arguments": "{}"}},
{"function": {"name": "another_wrong_tool", "arguments": "{\"extra\":true}"}},
],
schema={"type": "object", "additionalProperties": False},
)
audits = service.v3.repository.tool_audits(task_id)
self.assertEqual([audit["returned_tool"] for audit in audits], ["wrong_tool", "another_wrong_tool"])
self.assertTrue(all(audit["single_allowed_call"] is False for audit in audits))
self.assertTrue(all(audit["state_binding"]["binding_valid"] is False for audit in audits))
self.assertEqual(audits[0]["raw_arguments_hash"], sha256(b"{}").hexdigest())
def test_token_baseline_requires_matching_provenance_and_proves_median_reduction(self) -> None:
root = Path(tempfile.gettempdir())
identity = author_request_identity(settings(root).providers[0], settings(root).providers[0].models[0])
scenarios = [{"id": "plate", "request": "Create a plate."}]
baseline = {
"schema_version": BASELINE_SCHEMA_VERSION,
"protocol_version": "2.0",
"measurement": "prompt_tokens",
"author": identity,
"runtime_profile_sha256": "profile-current",
"scenarios": [{
"scenario": "plate", "request_sha256": request_sha256("Create a plate."),
"repetitions": [
{"repetition": 1, "author_metric": 120, "plan_review_metric": 80, "completed": True, "final_review_passed": True, "deterministic_claims_pass": True},
{"repetition": 2, "author_metric": 130, "plan_review_metric": 70, "completed": True, "final_review_passed": True, "deterministic_claims_pass": True},
{"repetition": 3, "author_metric": 110, "plan_review_metric": 90, "completed": True, "final_review_passed": True, "deterministic_claims_pass": True},
],
}],
}
results = [
{
"scenario": "plate", "repetition": repetition,
"usage": {"records": [{"prompt_tokens": 100, "usage_available": True}, {"role": "reviewer", "prompt_tokens": 500, "usage_available": True}]},
"projection": {"phase": "COMPLETED"}, "terminal": {"lifecycle": "completed"},
"checks": {"deterministic_claims_pass": True},
}
for repetition in range(1, 4)
]
comparison = compare_token_baseline(
baseline, scenarios=scenarios, v3_results=results,
author_identity=identity, runtime_profile_hash="profile-current",
)
self.assertTrue(all(comparison["checks"].values()))
self.assertEqual(comparison["baseline_median_metric"], 200)
self.assertEqual(comparison["v3_median_author_metric"], 100)
self.assertEqual(comparison["median_metric_reduction"], 0.5)
mismatched = {**baseline, "author": {**identity, "model": "another-model"}}
self.assertTrue(validate_token_baseline_provenance(
mismatched, scenarios=scenarios, author_identity=identity,
runtime_profile_hash="profile-current",
))
rejected = compare_token_baseline(
mismatched, scenarios=scenarios, v3_results=results,
author_identity=identity, runtime_profile_hash="profile-current",
)
self.assertFalse(rejected["checks"]["baseline_valid"])
self.assertFalse(rejected["checks"]["median_metric_reduction"])
def test_dynamic_operation_schema_has_exact_shape_and_snapshot_enum(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("hole_blind")
schema = fragment_schema(contract, selector_tokens=["sel_current"])
self.assertEqual(schema["properties"]["feature"]["properties"]["selector_tokens"]["items"], {"enum": ["sel_current"]})
self.assertNotIn("sketch", schema["properties"])
invalid = {
"sketch": {"workplane": {}, "profile": {}},
"feature": {"atomic_id": "hole_blind", "selector_tokens": ["sel_old"], "params": {"diameter_mm": 10, "depth_mm": 4, "positions": [{"mm": [0, 0, 0]}]}},
}
self.assertTrue(validate_fragment(contract, invalid, selector_tokens=["sel_current"]))
def test_runtime_materialization_revalidates_current_contract_and_selector_bounds(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("hole_blind")
selectors = {
"sel_host": {
"kind": "face",
"selector": {"kind": "face", "stable_id": "face_top"},
"geometry": {
"surface_type": "plane",
"center_mm": [0, 0, 10],
"normal": [0, 0, 1],
"bbox_mm": [-5, -5, 10, 5, 5, 10],
},
},
}
fragment = {
"feature": {
"atomic_id": "hole_blind",
"selector_tokens": [],
"params": {"diameter_mm": 2, "depth_mm": 4, "positions": [{"mm": [0, 0, 10]}]},
},
}
with self.assertRaisesRegex(RuntimeAdapterError, "active operation schema"):
runtime.materialize_fragment(None, fragment, contract, selectors, {})
stale = dict(contract)
stale["contract_hash"] = "stale"
with self.assertRaisesRegex(RuntimeAdapterError, "current verified registry entry"):
runtime.materialize_fragment(None, {**fragment, "feature": {**fragment["feature"], "selector_tokens": ["sel_host"]}}, stale, selectors, {})
def test_selector_bound_hole_materializes_a_replay_stable_host_frame(self) -> None:
"""A patterned hole must not resolve its source face after that cut splits it."""
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
runtime = ProfileCadRuntime(settings(root))
base = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "patterned-hole",
"geometry": {"sketches": [{
"id": "sketch_001",
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "polygon", "vertices": [[-5, -5], [5, -5], [5, 5], [-5, 5]]},
}]},
"features": [{
"id": "feature_001", "atomic_id": "extrude_add_blind", "depends_on": [],
"params": {"distance_mm": 10}, "sketch_id": "sketch_001",
}],
}
first = runtime.rebuild(base, str(root / "base"), "cad_123456abcdef", "candidate_base")
tokens = runtime.selector_tokens(first["topology"])
host_token = next(
token for token, value in tokens.items()
if value["kind"] == "face"
and value["geometry"].get("surface_type") == "plane"
and float(value["geometry"].get("normal", [0, 0, 0])[2]) > 0.9
)
hole_fragment = {
"feature": {
"atomic_id": "hole_blind", "selector_tokens": [host_token],
"params": {"diameter_mm": 2, "depth_mm": 12, "positions": [{"mm": [0, 0, 10]}]},
},
}
with_hole, _audit = runtime.materialize_fragment(
base, hole_fragment, runtime.operation_contract("hole_blind"), tokens, {}, require_through=True,
)
host = with_hole["features"][-1]["params"]["host_face"]
self.assertEqual(set(host), {"frame"})
self.assertAlmostEqual(with_hole["features"][-1]["params"]["positions"][0]["mm"][2], 0.0)
with_hole["features"].append({
"id": "feature_003", "atomic_id": "pattern_linear", "depends_on": ["feature_002"],
"params": {
"source_feature_ids": ["feature_002"], "direction_1": [1, 0, 0],
"spacing_1_mm": 4, "pattern_count_1": 2,
},
})
patterned = runtime.rebuild(with_hole, str(root / "patterned"), "cad_123456abcdef", "candidate_patterned")
self.assertEqual(patterned["health"]["solid_count"], 1)
self.assertAlmostEqual(patterned["health"]["volume_mm3"], 1000 - 2 * math.pi * 10, places=5)
def test_selector_bound_hole_wizard_materializes_against_engine_schema(self) -> None:
"""The author contract must contain every CDSL-required Hole Wizard field."""
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
runtime = ProfileCadRuntime(settings(root))
base = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "wizard-host",
"geometry": {"sketches": [{
"id": "sketch_001",
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "polygon", "vertices": [[-5, -5], [5, -5], [5, 5], [-5, 5]]},
}]},
"features": [{
"id": "feature_001", "atomic_id": "extrude_add_blind", "depends_on": [],
"params": {"distance_mm": 10}, "sketch_id": "sketch_001",
}],
}
first = runtime.rebuild(base, str(root / "base"), "cad_123456abcdef", "candidate_base")
tokens = runtime.selector_tokens(first["topology"])
host_token = next(
token for token, value in tokens.items()
if value["kind"] == "face"
and value["geometry"].get("surface_type") == "plane"
and float(value["geometry"].get("normal", [0, 0, 0])[2]) > 0.9
)
fragment = {
"feature": {
"atomic_id": "hole_wizard", "selector_tokens": [host_token],
"params": {
"hole_type": "simple", "diameter_mm": 2, "depth_mm": 12,
"end_condition": {"type": "blind", "solidworks_code": 0},
"positions": [{"mm": [0, 0, 10]}],
},
},
}
materialized, _audit = runtime.materialize_fragment(
base, fragment, runtime.operation_contract("hole_wizard"), tokens, {}, require_through=True,
)
self.assertEqual(materialized["features"][-1]["params"]["end_condition"], {"type": "blind", "solidworks_code": 0})
def test_operation_contract_rejects_inconsistent_server_selector_and_reference_bindings(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
selector_mismatch = runtime.operation_contract("hole_blind")
selector_mismatch["server_injected_paths"] = []
with self.assertRaisesRegex(OperationContractError, "injection paths disagree"):
validate_operation_contract(selector_mismatch)
self.assertEqual(
WorkflowCoordinator._selector_tokens_for_contract(
runtime.operation_contract("hole_blind"),
{"sel_face": {"kind": "face"}, "sel_edge": {"kind": "edge"}},
),
["sel_face"],
)
reference_mismatch = runtime.operation_contract("pattern_linear")
reference_mismatch["author_params_schema"]["required"].remove("source_feature_ids")
with self.assertRaisesRegex(OperationContractError, "required author array"):
validate_operation_contract(reference_mismatch)
def test_author_context_exposes_claim_coverage_and_compact_model_facts_without_source_text(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository, artifacts, actions, task_id, _action = self._ready_action(root)
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
context = workflow._author_context(task_id, [])
payload = json.loads(str(context[1]["content"]))
self.assertEqual(payload["model_summary"], {"revision_id": "", "available": False})
self.assertEqual(payload["claim_coverage"][0]["claim_id"], "claim_001")
self.assertNotIn("source_requirements", payload)
def test_author_context_retains_claim_targets_and_measured_bore_facts(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository, artifacts, actions, task_id, action = self._ready_action(root)
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
state = repository.get_state(task_id)
contract = {
"schema_version": "cad.requirements-contract.v1",
"task_id": task_id,
"contract_hash": "bore-context-contract",
"requirements": [{
"requirement_id": "req_001",
"draft_id": "draft_001",
"source_ids": ["src_001"],
"statement": "Three collinear bores",
"assumptions": [],
"acceptance_claims": [{
"claim_id": "claim_001",
"claim_kind": "through_cylindrical_bore",
"expected": {"diameter_mm": 12, "count": 3},
}],
}],
}
contract_path = artifacts.write_requirements_contract(task_id, contract, invocation_id="bore_contract")
artifacts.write_json_once(task_id, "revisions/rev_001/rebuild-report.json", {
"health": {"solid_count": 1, "bbox_mm": {"dimensions": [32, 140, 10]}},
})
artifacts.write_json_once(task_id, "revisions/rev_001/model.topology.json", {
"snapshot_id": "snapshot_bores",
"records": [{
"kind": "face",
"geometry": {
"surface_type": "cylinder",
"cylinder_role": "inner",
"radius_mm": 6,
"axis_origin_mm": [0, -70, 10],
"through": True,
},
}],
})
state = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id="stage_1234567890abcdef12")
self.assertTrue(repository.compare_and_swap(state))
state = transition(state, "candidate_built")
self.assertTrue(repository.compare_and_swap(state))
state = transition(
state,
"candidate_accepted",
active_revision="rev_001",
requirements_contract_path=contract_path,
repair_required=False,
)
self.assertTrue(repository.compare_and_swap(state))
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, runtime, None, None, requirements, actions)
payload = json.loads(workflow._author_context(task_id, [])[1]["content"])
self.assertEqual(
payload["requirements"][0]["acceptance_claims"],
[{"claim_id": "claim_001", "claim_kind": "through_cylindrical_bore", "expected": {"diameter_mm": 12, "count": 3}}],
)
self.assertEqual(
payload["model_summary"]["inner_cylindrical_bores"],
[{"diameter_mm": 12.0, "axis_origin_mm": [0.0, -70.0, 10.0], "through": True}],
)
def test_repair_context_exposes_compact_rejected_candidate_facts(self) -> None:
"""Repair turns retain measured failure evidence after staging clears."""
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository, artifacts, actions, task_id, action = self._ready_action(root)
state = repository.get_state(task_id)
stage = artifacts.start_candidate_stage(task_id, action.idempotency_key, {"schema_version": "test"})
candidate_id = "candidate_123456789012"
artifacts.write_stage_json(task_id, stage.stage_id, "candidate.json", {
"candidate_id": candidate_id,
"actual_atomic_id": "extrude_add_blind",
"health": {
"solid_count": 3,
"feature_count": 1,
"bbox_mm": {"dimensions": [80, 50, 8]},
},
"claim_results": [{
"claim_id": "claim_001",
"claim_kind": "solid_count_equals",
"deterministic": True,
"status": "fail",
"evidence": {"actual": 3, "expected": 1},
}],
"operation_verifier_results": [{
"claim_id": "operation_act_004_cylindrical_bore",
"claim_kind": "cylindrical_bore",
"deterministic": True,
"status": "pending",
"evidence": {
"parent_matching_count": 1,
"expected_increment": 1,
"expected_total_count": 2,
},
}],
})
artifacts.write_stage_json(task_id, stage.stage_id, "candidate-review.json", {
"evidence": ["render_top: the requested pocket opens at the right edge instead of being centred."],
"issues": ["No material was removed from the centred pocket region."],
})
building = transition(state, "candidate_started", candidate_id=candidate_id, candidate_stage_id=stage.stage_id)
self.assertTrue(repository.compare_and_swap(building))
rejected = transition(
building,
"candidate_rejected",
candidate_id="",
candidate_stage_id="",
repair_required=True,
error=ErrorCode.CLAIM_VERIFICATION_FAILED,
)
self.assertTrue(repository.compare_and_swap(rejected, events=[{
"event": "candidate_rejected",
"candidate_id": candidate_id,
"stage_id": stage.stage_id,
"action_id": action.action_id,
"working_head": action.working_head,
}]))
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, runtime, None, None, requirements, actions)
payload = json.loads(workflow._author_context(task_id, [])[1]["content"])
diagnostic = payload["repair_diagnostics"][0]
self.assertEqual(diagnostic["candidate_id"], candidate_id)
self.assertEqual(diagnostic["atomic_id"], "extrude_add_blind")
self.assertEqual(diagnostic["measured"], {"solid_count": 3, "feature_count": 1, "bbox_dimensions_mm": [80, 50, 8]})
self.assertEqual(diagnostic["failed_claims"], [{
"claim_id": "claim_001",
"claim_kind": "solid_count_equals",
"status": "fail",
"evidence": {"actual": 3, "expected": 1},
}])
self.assertEqual(diagnostic["operation_blockers"], [{
"claim_id": "operation_act_004_cylindrical_bore",
"claim_kind": "cylindrical_bore",
"status": "pending",
"evidence": {
"parent_matching_count": 1,
"expected_increment": 1,
"expected_total_count": 2,
},
}])
self.assertEqual(diagnostic["review_evidence"], ["render_top: the requested pocket opens at the right edge instead of being centred."])
self.assertEqual(diagnostic["review_issues"], ["No material was removed from the centred pocket region."])
self.assertNotIn("fragment", diagnostic)
self.assertIn("fragment_schema", payload["operation_contract"])
def test_hole_tool_and_topology_payload_declare_world_space_positions(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
hole_contract = actions.runtime.operation_contract("hole_blind")
hole_action = PendingAction(
action.action_id,
state.working_head,
"Add one bore",
action.requirement_ids,
"hole_blind",
"Add a 12 mm bore",
str(hole_contract["contract_hash"]),
action.idempotency_key,
)
# Construct the context helper against an immutable pending action;
# this test concerns the generated tool contract wording only.
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions)
state_with_hole = replace(state, pending_action=hole_action)
tools = workflow._action_tools(task_id, state_with_hole, {"contract", "topology"})
self.assertIn("absolute world-space mm point", tools[0]["function"]["description"])
self.assertEqual(workflow._topology_payload(task_id, state_with_hole, None, 1)["coordinate_system"], "world_mm")
def test_canonical_arguments_reject_non_finite_numbers_before_schema_validation(self) -> None:
rejected = canonical_json_object('{"value": NaN}')
self.assertIsInstance(rejected, WorkflowError)
self.assertEqual(rejected.code, ErrorCode.AUTHOR_FORMAT_INVALID)
def test_hole_position_preflight_requires_selected_host_plane_and_bounds(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("hole_blind")
selectors = {
"sel_host": {
"kind": "face",
"selector": {"kind": "face", "stable_id": "face_top"},
"geometry": {
"surface_type": "plane",
"center_mm": [0, 0, 10],
"normal": [0, 0, 1],
"bbox_mm": [-5, -5, 10, 5, 5, 10],
},
},
}
outside_plane = {
"feature": {
"atomic_id": "hole_blind",
"selector_tokens": ["sel_host"],
"params": {"diameter_mm": 2, "depth_mm": 4, "positions": [{"mm": [0, 0, 9]}]},
},
}
with self.assertRaisesRegex(RuntimeAdapterError, "not on the selected host plane"):
runtime.materialize_fragment(None, outside_plane, contract, selectors, {})
outside_bounds = {
"feature": {
"atomic_id": "hole_blind",
"selector_tokens": ["sel_host"],
"params": {"diameter_mm": 2, "depth_mm": 4, "positions": [{"mm": [6, 0, 10]}]},
},
}
with self.assertRaisesRegex(RuntimeAdapterError, "outside the selected host-face bounds"):
runtime.materialize_fragment(None, outside_bounds, contract, selectors, {})
selectors["sel_host"]["geometry"]["boundary_loops_mm"] = [
[[-5, -5, 10], [5, -5, 10], [5, 5, 10], [-5, 5, 10]],
[[-1, -1, 10], [1, -1, 10], [1, 1, 10], [-1, 1, 10]],
]
inside_existing_opening = {
"feature": {
"atomic_id": "hole_blind", "selector_tokens": ["sel_host"],
"params": {"diameter_mm": 2, "depth_mm": 4, "positions": [{"mm": [0, 0, 10]}]},
},
}
with self.assertRaisesRegex(RuntimeAdapterError, "effective boundary"):
runtime.materialize_fragment(None, inside_existing_opening, contract, selectors, {})
def test_analytic_arc_preflight_rejects_off_circle_and_degenerate_arcs(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("extrude_add_blind")
def fragment(arc: dict[str, object]) -> dict[str, object]:
return {
"sketch": {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "analytic_contours", "contours": [{"role": "outer", "closed": True, "segments": [arc]}]},
},
"feature": {"atomic_id": "extrude_add_blind", "params": {"distance_mm": 5}},
}
with self.assertRaisesRegex(RuntimeAdapterError, "endpoints are not on the declared circle"):
runtime.materialize_fragment(None, fragment({
"type": "arc", "start": [1, 0], "end": [0, 1], "center": [0, 0], "radius_mm": 2,
}), contract, {}, {})
with self.assertRaisesRegex(RuntimeAdapterError, "is degenerate"):
runtime.materialize_fragment(None, fragment({
"type": "arc", "start": [1, 0], "end": [1, 0], "center": [0, 0], "radius_mm": 1,
}), contract, {}, {})
def test_hole_materialization_rejects_unusable_host_frame_before_engine(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("hole_blind")
selectors = {"sel_host": {
"kind": "face", "selector": {"kind": "face", "stable_id": "face_bad"},
"geometry": {"surface_type": "plane", "center_mm": [0, 0, None], "normal": [0, 0, 1], "bbox_mm": [-5, -5, 0, 5, 5, 0]},
}}
fragment = {"feature": {
"atomic_id": "hole_blind", "selector_tokens": ["sel_host"],
"params": {"diameter_mm": 2, "depth_mm": 4, "positions": [{"mm": [0, 0, 0]}]},
}}
with self.assertRaisesRegex(RuntimeAdapterError, "observable planar face"):
runtime.materialize_fragment(None, fragment, contract, selectors, {})
def test_revolve_axis_preflight_requires_axis_to_lie_on_sketch_plane(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("revolve_add")
fragment = {
"sketch": {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "circle", "radius_mm": 1},
},
"feature": {
"atomic_id": "revolve_add",
"params": {"angle_deg": 180, "axis": {"origin_mm": [0, 0, 1], "direction": [1, 0, 0]}},
},
}
with self.assertRaisesRegex(RuntimeAdapterError, "revolve axis is not on the sketch plane"):
runtime.materialize_fragment(None, fragment, contract, {}, {})
def test_every_runtime_operation_has_closed_valid_and_invalid_dynamic_fragments(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
for atomic_id in runtime.supported_atomic_ids():
with self.subTest(atomic_id=atomic_id):
contract = runtime.operation_contract(atomic_id)
params = schema_example(contract["author_params_schema"])
self.assertIsInstance(params, dict)
if contract["reference_policy"]["mode"] == "snapshot_bound":
slot = str(contract["reference_policy"]["slot"]).removeprefix("params.")
params[slot] = ["ref_current"]
feature = {"atomic_id": atomic_id, "params": params}
if contract["fragment_shape"]["selector_tokens"] == "required":
feature["selector_tokens"] = ["sel_current"]
fragment: dict[str, object] = {"feature": feature}
if contract["fragment_shape"]["sketch"] == "required":
fragment["sketch"] = {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "circle", "radius_mm": 1},
}
self.assertEqual(validate_fragment(contract, fragment, selector_tokens=["sel_current"], reference_tokens=["ref_current"]), [])
malformed = json.loads(json.dumps(fragment))
malformed["feature"]["atomic_id"] = "wrong_atomic"
self.assertTrue(validate_fragment(contract, malformed, selector_tokens=["sel_current"], reference_tokens=["ref_current"]))
if contract["fragment_shape"]["sketch"] == "required":
missing_sketch = json.loads(json.dumps(fragment))
del missing_sketch["sketch"]
self.assertTrue(validate_fragment(contract, missing_sketch, selector_tokens=["sel_current"], reference_tokens=["ref_current"]))
else:
extra_sketch = json.loads(json.dumps(fragment))
extra_sketch["sketch"] = {"workplane": {}, "profile": {}}
self.assertTrue(validate_fragment(contract, extra_sketch, selector_tokens=["sel_current"], reference_tokens=["ref_current"]))
if contract["fragment_shape"]["selector_tokens"] == "required":
stale_selector = json.loads(json.dumps(fragment))
stale_selector["feature"]["selector_tokens"] = ["sel_stale"]
self.assertTrue(validate_fragment(contract, stale_selector, selector_tokens=["sel_current"], reference_tokens=["ref_current"]))
if contract["reference_policy"]["mode"] == "snapshot_bound":
stale_reference = json.loads(json.dumps(fragment))
slot = str(contract["reference_policy"]["slot"]).removeprefix("params.")
stale_reference["feature"]["params"][slot] = ["ref_stale"]
self.assertTrue(validate_fragment(contract, stale_reference, selector_tokens=["sel_current"], reference_tokens=["ref_current"]))
def test_hole_wizard_dynamic_schema_requires_positions_and_variant_details(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("hole_wizard")
schema = fragment_schema(contract, selector_tokens=["sel_host"])
missing_positions = {
"feature": {
"atomic_id": "hole_wizard", "selector_tokens": ["sel_host"],
"params": {
"hole_type": "simple", "diameter_mm": 3, "depth_mm": 5,
"end_condition": {"type": "blind", "solidworks_code": 0},
},
},
}
self.assertTrue(validate_fragment(contract, missing_positions, selector_tokens=["sel_host"]))
missing_end_condition = {
"feature": {
"atomic_id": "hole_wizard", "selector_tokens": ["sel_host"],
"params": {
"hole_type": "simple", "diameter_mm": 3, "depth_mm": 5,
"positions": [{"mm": [0, 0, 0]}],
},
},
}
self.assertTrue(validate_fragment(contract, missing_end_condition, selector_tokens=["sel_host"]))
missing_variant_details = {
"feature": {
"atomic_id": "hole_wizard", "selector_tokens": ["sel_host"],
"params": {
"hole_type": "countersink", "diameter_mm": 3, "depth_mm": 5,
"end_condition": {"type": "blind", "solidworks_code": 0},
"positions": [{"mm": [0, 0, 0]}],
},
},
}
self.assertTrue(validate_fragment(contract, missing_variant_details, selector_tokens=["sel_host"]))
valid_countersink = {
"feature": {
"atomic_id": "hole_wizard", "selector_tokens": ["sel_host"],
"params": {
"hole_type": "countersink", "diameter_mm": 3, "depth_mm": 5,
"end_condition": {"type": "blind", "solidworks_code": 0},
"positions": [{"mm": [0, 0, 0]}],
"countersink": {"diameter_mm": 5, "angle_rad": 1.2},
},
},
}
self.assertEqual(validate_fragment(contract, valid_countersink, selector_tokens=["sel_host"]), [])
def test_materialized_cdsl_schema_drift_is_a_runtime_contract_error(self) -> None:
error = ActionCommandHandler._runtime_error(
ValueError("CDSL schema violation at $.features[1].params: 'end_condition' is a required property")
)
self.assertEqual(error.code, ErrorCode.RUNTIME_CONTRACT_INVALID)
def test_materialized_profile_resolution_failure_is_a_recoverable_precondition_error(self) -> None:
error = ActionCommandHandler._runtime_error(
RuntimeAdapterError(
"RUNTIME_PRECONDITION_FAILED: materialized fragment is not executable by the engine: "
"CDSL engine runtime preflight failed: feature feature_001: profile_resolution_failed"
)
)
self.assertEqual(error.code, ErrorCode.RUNTIME_PRECONDITION_FAILED)
def test_runtime_contract_registry_rejects_unknown_preflights_and_exposes_only_registered_verifiers(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
verifier_kinds = set(default_registry().claim_kinds)
declared_preflights: set[str] = set()
for atomic_id in runtime.supported_atomic_ids():
contract = runtime.operation_contract(atomic_id)
declared_preflights.update(contract["semantic_preflight"])
self.assertTrue(set(contract["candidate_verifiers"]).issubset(verifier_kinds), atomic_id)
self.assertTrue(declared_preflights.issubset(SEMANTIC_PREFLIGHT_NAMES))
invalid = runtime.operation_contract("extrude_add_blind")
invalid["semantic_preflight"] = ["unknown_preflight"]
with self.assertRaisesRegex(OperationContractError, "preflight"):
validate_operation_contract(invalid)
def test_engine_materializes_metadata_from_the_author_contract_registry(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
from cdsl_engine.operation_contracts import materialized_feature_contracts
materialized_contracts = materialized_feature_contracts(runtime._profile)
self.assertEqual(set(materialized_contracts), set(runtime.supported_atomic_ids()))
self.assertEqual(
materialized_contracts["hole_blind"]["required_params"],
["diameter_mm", "depth_mm", "positions", "host_face"],
)
self.assertTrue(materialized_contracts["extrude_add_blind"]["requires_sketch"])
def test_selectorless_extrude_cut_can_preflight_a_through_bore_from_active_body(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
runtime = ProfileCadRuntime(settings(root))
base = {
"schema": "cad.cdsl.llm.v1", "schema_version": "1.1.0", "kind": "part", "part_id": "cut-proof",
"geometry": {"sketches": [{
"id": "sketch_001",
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "circle", "radius_mm": 60},
}]},
"features": [{
"id": "feature_001", "atomic_id": "extrude_add_blind", "params": {"distance_mm": 12},
"depends_on": [], "sketch_id": "sketch_001",
}],
}
base_result = runtime.rebuild(base, str(root / "base"), "cad_123456abcdef", "rev_001")
selectors = runtime.selector_tokens(base_result["topology"])
contract = runtime.operation_contract("extrude_cut_blind")
fragment = {
"sketch": {
"workplane": {"origin_mm": [0, 0, 12], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "circle", "radius_mm": 20},
},
"feature": {"atomic_id": "extrude_cut_blind", "params": {"distance_mm": 14, "reverse": True}},
}
cdsl, _audit = runtime.materialize_fragment(base, fragment, contract, selectors, {}, require_through=True)
rebuilt = runtime.rebuild(cdsl, str(root / "through"), "cad_123456abcdef", "candidate_cut")
verdict = default_registry().evaluate(
"through_cylindrical_bore", {"diameter_mm": 40, "count": 1, "tolerance_mm": 0.01},
{"topology": rebuilt["topology"]},
)
self.assertEqual(verdict["status"], "pass")
shallow = {**fragment, "feature": {"atomic_id": "extrude_cut_blind", "params": {"distance_mm": 12, "reverse": True}}}
with self.assertRaisesRegex(RuntimeAdapterError, "must exceed measured host-body thickness 12"):
runtime.materialize_fragment(base, shallow, contract, selectors, {}, require_through=True)
def test_selector_bound_fragment_receives_contract_without_ritual_reads(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
contract = actions.runtime.operation_contract("hole_blind")
pending = PendingAction("act_005", state.working_head, "Cut bore", ("req_001",), "hole_blind", "Add through bore", contract["contract_hash"], "hole-key")
selector_state = replace(state, pending_action=pending)
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions)
initial = {item["function"]["name"] for item in workflow._action_tools(task_id, selector_state, set())}
self.assertEqual(initial, {"submit_cdsl_fragment"})
after_contract = {item["function"]["name"] for item in workflow._action_tools(task_id, selector_state, {"contract"})}
self.assertEqual(after_contract, {"submit_cdsl_fragment"})
after_both = {item["function"]["name"] for item in workflow._action_tools(task_id, selector_state, {"topology", "contract"})}
self.assertEqual(after_both, {"submit_cdsl_fragment"})
def test_fragment_contract_is_available_immediately_after_action_proposal(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions)
before = {item["function"]["name"] for item in workflow._action_tools(task_id, state, set())}
self.assertEqual(before, {"submit_cdsl_fragment"})
after = {item["function"]["name"] for item in workflow._action_tools(task_id, state, {"contract"})}
self.assertEqual(after, {"submit_cdsl_fragment"})
def test_final_review_schema_binds_current_head_and_claim_ids(self) -> None:
schema = final_review_schema("cad_123456abcdef:rev_001:v8", ["claim_001"])
rejected = canonical_validate_schema(json.dumps({
"working_head": "cad_123456abcdef:rev_001:v7", "verdict": "pass",
"claim_coverage": [{"claim_id": "claim_001", "status": "pass"}],
}), schema)
self.assertEqual(rejected.code if rejected else None, ErrorCode.AUTHOR_FORMAT_INVALID)
def test_observation_schemas_reject_stale_state_before_side_effects(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
self.assertIsNotNone(state)
before_version = state.version
before_head = state.working_head
stale_topology = canonical_validate_schema(json.dumps({
"working_head": "cad_123456abcdef:root:v0", "kind": "face", "limit": 16,
}), topology_request_schema(state.working_head))
wrong_operation = canonical_validate_schema(json.dumps({
"working_head": state.working_head, "atomic_id": "hole_blind",
}), operation_contract_request_schema(state.working_head, action.atomic_id))
self.assertEqual(stale_topology.code if stale_topology else None, ErrorCode.AUTHOR_FORMAT_INVALID)
self.assertEqual(wrong_operation.code if wrong_operation else None, ErrorCode.AUTHOR_FORMAT_INVALID)
after = repository.get_state(task_id)
self.assertEqual(after.version, before_version)
self.assertEqual(after.working_head, before_head)
self.assertEqual(list((artifacts.task_dir(task_id) / ".staging").iterdir()), [])
def test_geometry_conclusion_is_head_and_evidence_bound_before_state_change(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, _artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
self.assertIsNotNone(state)
schema = geometry_conclusion_schema(state.working_head, list(actions.diagnostic_evidence_refs(task_id, state)))
invalid = canonical_validate_schema(json.dumps({
"working_head": state.working_head,
"evidence_refs": ["evidence_unknown"],
"root_cause": "The candidate contradicts the action.",
"decision": "return_to_action_selection",
"corrective_intent": "Select a corrected action.",
}), schema)
self.assertEqual(invalid.code if invalid else None, ErrorCode.AUTHOR_FORMAT_INVALID)
self.assertEqual(repository.get_state(task_id).working_head, state.working_head)
conclusion = GeometryConclusion(
working_head=state.working_head,
evidence_refs=["evidence_current_state"],
root_cause="The candidate contradicts the action.",
decision="return_to_action_selection",
corrective_intent="Select a corrected action.",
)
result = actions.record_geometry_conclusion(task_id, conclusion, invocation_id="diagnose-return")
self.assertEqual(getattr(result, "payload", {}).get("phase"), TaskPhase.AWAITING_ACTION.value)
after = repository.get_state(task_id)
self.assertIsNotNone(after)
self.assertTrue(after.repair_required)
self.assertIsNone(after.pending_action)
event = repository.ledger_events(task_id)[-1]
self.assertEqual(event["event"], "geometry_conclusion")
self.assertEqual(event["decision"], "return_to_action_selection")
def test_rollback_requires_current_diagnosis_and_an_ancestor_checkpoint(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, _artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id="stage_1234567890abcdef12")
reviewing = transition(building, "candidate_built")
awaiting = transition(reviewing, "candidate_accepted", active_revision="rev_001")
self.assertTrue(repository.compare_and_swap(building))
self.assertTrue(repository.compare_and_swap(reviewing))
self.assertTrue(repository.compare_and_swap(awaiting, events=[{"event": "accepted", "revision_id": "rev_001"}]))
before_diagnosis = repository.get_state(task_id)
denied = actions.rollback_checkpoint(
task_id,
RollbackCheckpoint(working_head=before_diagnosis.working_head, checkpoint_token="checkpoint_root", reason="Discard the checkpoint."),
invocation_id="rollback-without-diagnosis",
)
self.assertEqual(getattr(denied, "error", None).code, ErrorCode.AUTHOR_DECISION_REJECTED)
conclusion = GeometryConclusion(
working_head=before_diagnosis.working_head,
evidence_refs=["evidence_current_model"],
root_cause="The accepted checkpoint has the wrong feature.",
decision="rollback",
corrective_intent="Return to the base checkpoint.",
)
self.assertIsNotNone(actions.record_geometry_conclusion(task_id, conclusion, invocation_id="diagnose-rollback"))
state = repository.get_state(task_id)
self.assertTrue(actions.rollback_available(task_id, state))
schema = rollback_checkpoint_schema(state.working_head, list(actions.checkpoint_tokens(task_id, state)))
invalid = canonical_validate_schema(json.dumps({
"working_head": state.working_head,
"checkpoint_token": "checkpoint_rev_999",
"reason": "Not in lineage.",
}), schema)
self.assertEqual(invalid.code if invalid else None, ErrorCode.AUTHOR_FORMAT_INVALID)
result = actions.rollback_checkpoint(
task_id,
RollbackCheckpoint(working_head=state.working_head, checkpoint_token="checkpoint_root", reason="Discard the wrong checkpoint."),
invocation_id="rollback-root",
)
self.assertEqual(getattr(result, "payload", {}).get("status"), "rolled_back")
after = repository.get_state(task_id)
self.assertEqual(after.active_revision, "")
self.assertTrue(after.repair_required)
self.assertEqual(repository.ledger_events(task_id)[-1]["event"], "rollback")
self.assertEqual(actions._next_revision(task_id), "rev_002")
def test_repair_cannot_select_or_submit_again_before_a_geometry_conclusion(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, _artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id="stage_1234567890abcdef12")
reviewing = transition(building, "candidate_built")
repairing = transition(reviewing, "candidate_rejected", repair_required=True, error=ErrorCode.CANDIDATE_REVIEW_REJECTED)
for value in (building, reviewing, repairing):
self.assertTrue(repository.compare_and_swap(value))
state = repository.get_state(task_id)
self.assertFalse(actions.repair_action_ready(task_id, state))
proposal = NextAction(
working_head=state.working_head,
intent="Create a corrected base.",
requirement_ids=["req_001"],
atomic_id="extrude_add_blind",
expected_change="Replace the rejected geometry.",
)
rejected = actions.propose_next_action(task_id, proposal, invocation_id="repair-before-diagnosis")
self.assertEqual(getattr(rejected, "error", None).code, ErrorCode.AUTHOR_DECISION_REJECTED)
conclusion = GeometryConclusion(
working_head=state.working_head,
evidence_refs=["evidence_current_state"],
root_cause="The rejected feature needs replacement.",
decision="return_to_action_selection",
corrective_intent="Choose a corrected base action.",
)
self.assertIsNotNone(actions.record_geometry_conclusion(task_id, conclusion, invocation_id="repair-diagnosis"))
state = repository.get_state(task_id)
self.assertTrue(actions.repair_action_ready(task_id, state))
accepted = actions.propose_next_action(
task_id,
NextAction(
working_head=state.working_head,
intent="Create a corrected base.",
requirement_ids=["req_001"],
atomic_id="extrude_add_blind",
expected_change="Replace the rejected geometry.",
),
invocation_id="repair-after-diagnosis",
)
self.assertEqual(getattr(accepted, "payload", {}).get("atomic_id"), "extrude_add_blind")
pending_state = repository.get_state(task_id)
self.assertTrue(actions.repair_action_ready(task_id, pending_state))
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, _artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, _artifacts, default_registry()), actions)
self.assertEqual(workflow._recovery_tools(task_id, pending_state), [])
self.assertEqual(
{item["function"]["name"] for item in workflow._action_tools(task_id, pending_state, set())},
{"submit_cdsl_fragment"},
)
def test_action_availability_hides_required_selector_operations_without_a_snapshot(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, _artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
available = set(actions.available_atomic_ids(task_id, state))
selector_required = {
atomic_id
for atomic_id in actions.runtime.supported_atomic_ids()
if str((actions.runtime.operation_contract(atomic_id).get("fragment_shape") or {}).get("selector_tokens") or "forbidden") == "required"
}
self.assertIn("extrude_add_blind", available)
self.assertTrue(selector_required)
self.assertTrue(selector_required.isdisjoint(available))
rejected = actions.propose_next_action(
task_id,
NextAction(
working_head=state.working_head,
intent="Cut a hole before any base exists.", requirement_ids=["req_001"],
atomic_id=next(iter(selector_required)), expected_change="This needs a face selector.",
),
invocation_id="unavailable-selector-action",
)
self.assertEqual(getattr(rejected, "error", None).code, ErrorCode.AUTHOR_DECISION_REJECTED)
def test_action_availability_hides_snapshot_bound_pattern_operations_without_source_features(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, _artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
available = set(actions.available_atomic_ids(task_id, state))
reference_required = {
atomic_id
for atomic_id in actions.runtime.supported_atomic_ids()
if (actions.runtime.operation_contract(atomic_id).get("reference_policy") or {}).get("mode") == "snapshot_bound"
}
self.assertIn("pattern_linear", reference_required)
self.assertTrue(reference_required.isdisjoint(available))
rejected = actions.propose_next_action(
task_id,
NextAction(
working_head=state.working_head,
intent="Pattern a source feature before any feature exists.", requirement_ids=["req_001"],
atomic_id="pattern_linear", expected_change="This needs a source feature reference.",
),
invocation_id="unavailable-reference-action",
)
self.assertEqual(getattr(rejected, "error", None).code, ErrorCode.AUTHOR_DECISION_REJECTED)
def test_action_availability_hides_operations_requiring_an_active_solid_at_root(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, _artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
available = set(actions.available_atomic_ids(task_id, state))
active_solid_required = {
atomic_id
for atomic_id in actions.runtime.supported_atomic_ids()
if "requires_active_solid" in (actions.runtime.operation_contract(atomic_id).get("semantic_preflight") or ())
}
self.assertEqual(
active_solid_required,
{"extrude_cut_blind", "revolve_cut", "reference_axis", "reference_plane"},
)
self.assertTrue(active_solid_required.isdisjoint(available))
rejected = actions.propose_next_action(
task_id,
NextAction(
working_head=state.working_head,
intent="Create a reference axis before any solid exists.", requirement_ids=["req_001"],
atomic_id="reference_axis", expected_change="This needs an active solid checkpoint.",
),
invocation_id="unavailable-root-reference-action",
)
self.assertEqual(getattr(rejected, "error", None).code, ErrorCode.AUTHOR_DECISION_REJECTED)
def test_new_candidate_rejection_invalidates_a_prior_repair_conclusion(self) -> None:
"""A conclusion may unlock one repair action, never its failed retries."""
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, first_action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
first_build = transition(state, "candidate_started", candidate_id="candidate_111111111111", candidate_stage_id="stage_111111111111111111")
first_rejected = transition(first_build, "candidate_rejected", repair_required=True, error=ErrorCode.CLAIM_VERIFICATION_FAILED)
self.assertTrue(repository.compare_and_swap(first_build))
self.assertTrue(repository.compare_and_swap(first_rejected, events=[{
"event": "candidate_rejected", "candidate_id": "candidate_111111111111",
"stage_id": "stage_111111111111111111", "action_id": first_action.action_id,
"working_head": first_action.working_head,
}]))
diagnosis_state = repository.get_state(task_id)
conclusion = GeometryConclusion(
working_head=diagnosis_state.working_head,
evidence_refs=["evidence_current_state"],
root_cause="The rejected base needs a different repair action.",
decision="return_to_action_selection",
corrective_intent="Select a corrected base feature.",
)
self.assertIsInstance(actions.record_geometry_conclusion(task_id, conclusion, invocation_id="first-diagnosis"), Accepted)
ready = repository.get_state(task_id)
self.assertTrue(actions.repair_action_ready(task_id, ready))
accepted = actions.propose_next_action(
task_id,
NextAction(
working_head=ready.working_head,
intent="Create a corrected base.", requirement_ids=["req_001"],
atomic_id="extrude_add_blind", expected_change="Replace the rejected geometry.",
),
invocation_id="second-action",
)
self.assertIsInstance(accepted, Accepted)
second_pending = repository.get_state(task_id)
second_action = second_pending.pending_action
self.assertIsNotNone(second_action)
second_build = transition(second_pending, "candidate_started", candidate_id="candidate_222222222222", candidate_stage_id="stage_222222222222222222")
second_rejected = transition(second_build, "candidate_rejected", repair_required=True, error=ErrorCode.CLAIM_VERIFICATION_FAILED)
self.assertTrue(repository.compare_and_swap(second_build))
self.assertTrue(repository.compare_and_swap(second_rejected, events=[{
"event": "candidate_rejected", "candidate_id": "candidate_222222222222",
"stage_id": "stage_222222222222222222", "action_id": second_action.action_id,
"working_head": second_action.working_head,
}]))
retrying = repository.get_state(task_id)
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions)
self.assertFalse(actions.repair_action_ready(task_id, retrying))
self.assertEqual(
{item["function"]["name"] for item in workflow._recovery_tools(task_id, retrying)},
{"record_geometry_conclusion"},
)
def test_final_review_rejection_is_bounded_and_receives_diagnostic_feedback(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
artifacts.write_json_once(task_id, "revisions/rev_001/rebuild-report.json", {"health": {"solid_count": 1}})
artifacts.write_json_once(task_id, "revisions/rev_001/model.topology.json", {"records": []})
state = repository.get_state(task_id)
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id="stage_1234567890abcdef12")
reviewing = transition(building, "candidate_built")
awaiting = transition(reviewing, "candidate_accepted", active_revision="rev_001")
for value in (building, reviewing, awaiting):
self.assertTrue(repository.compare_and_swap(value))
self.assertIsInstance(actions.complete_task(task_id, invocation_id="complete"), object)
payloads: list[dict[str, object]] = []
class InvalidCoverageGateway:
async def review(self, *, payload: dict[str, object], tool: dict[str, object], **_kwargs: object) -> dict[str, object]:
payloads.append(payload)
parameters = tool["function"]["parameters"] # type: ignore[index]
head = parameters["properties"]["working_head"]["const"] # type: ignore[index]
raw = json.dumps({
"working_head": head, "verdict": "pass",
"claim_coverage": [
{"claim_id": "claim_001", "status": "pass"},
{"claim_id": "claim_001", "status": "pass"},
],
})
return {"tool_calls": [{"function": {"name": "review_final", "arguments": raw}}], "usage": {"prompt_tokens": 1, "completion_tokens": 1}}
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=4, format_error_limit=2), repository, artifacts, actions.runtime,
None, InvalidCoverageGateway(), RequirementsCommandHandler(repository, artifacts, default_registry()), actions,
)
async def run() -> list[tuple[str, dict[str, object]]]:
reviewer = type("Model", (), {"provider_id": "test", "model_id": "test-model"})()
return [item async for item in workflow.run(task_id=task_id, author=None, reviewer=reviewer)]
events = asyncio.run(run())
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.FAILED)
self.assertEqual(len(payloads), 2)
self.assertNotIn("previous_schema_or_state_error", payloads[0])
self.assertIn("previous_schema_or_state_error", payloads[1])
self.assertEqual(events[-1][1]["code"], ErrorCode.FAILED_AUTHOR_FORMAT.value)
def test_preflight_rejected_fragment_is_bounded_before_candidate_creation(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
calls = 0
class InvalidFragmentGateway:
async def call_tool(self, **_kwargs: object) -> dict[str, object]:
nonlocal calls
calls += 1
raw = json.dumps({"feature": {"atomic_id": "extrude_add_blind", "params": {"distance_mm": -1}}})
return {"tool_calls": [{"function": {"name": "submit_cdsl_fragment", "arguments": raw}}], "usage": {"prompt_tokens": 1, "completion_tokens": 1}}
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=4, format_error_limit=2), repository, artifacts, actions.runtime,
InvalidFragmentGateway(), None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions,
)
async def run() -> list[tuple[str, dict[str, object]]]:
author = type("Model", (), {"provider_id": "test", "model_id": "test-model"})()
return [item async for item in workflow.run(task_id=task_id, author=author, reviewer=None)]
events = asyncio.run(run())
self.assertEqual(calls, 2)
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.FAILED)
self.assertEqual(list((artifacts.task_dir(task_id) / ".staging").iterdir()), [])
self.assertEqual(events[-1][1]["code"], ErrorCode.FAILED_AUTHOR_FORMAT.value)
def test_candidate_artifact_io_failure_enters_waiting_retry_without_reclassifying_build(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
fragment = {
"sketch": {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "circle", "radius_mm": 1},
},
"feature": {"atomic_id": action.atomic_id, "params": {"distance_mm": 2}},
}
with patch.object(actions.runtime, "rebuild", side_effect=OSError("artifact volume unavailable")):
result = actions.submit_cdsl_fragment(task_id, fragment, invocation_id="fragment_io")
self.assertIsInstance(result, Rejected)
self.assertEqual(result.error.code, ErrorCode.STORAGE_FAILURE)
after = repository.get_state(task_id)
self.assertEqual(after.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(after.last_error, ErrorCode.STORAGE_FAILURE)
def test_candidate_render_failure_enters_render_retry_without_reclassifying_build(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
fragment = {
"sketch": {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "circle", "radius_mm": 1},
},
"feature": {"atomic_id": action.atomic_id, "params": {"distance_mm": 2}},
}
with patch.object(actions.runtime, "rebuild", side_effect=RuntimeAdapterError("RENDER_SERVICE_UNAVAILABLE: renderer offline")):
result = actions.submit_cdsl_fragment(task_id, fragment, invocation_id="fragment_render")
self.assertIsInstance(result, Rejected)
self.assertEqual(result.error.code, ErrorCode.RENDER_SERVICE_UNAVAILABLE)
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(state.last_error, ErrorCode.RENDER_SERVICE_UNAVAILABLE)
def test_candidate_stage_creation_failure_enters_waiting_retry_before_build(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
fragment = {
"sketch": {
"workplane": {"origin_mm": [0, 0, 0], "x_dir": [1, 0, 0], "normal": [0, 0, 1]},
"profile": {"type": "circle", "radius_mm": 1},
},
"feature": {"atomic_id": action.atomic_id, "params": {"distance_mm": 2}},
}
with patch.object(artifacts, "start_candidate_stage", side_effect=OSError("disk full")):
result = actions.submit_cdsl_fragment(task_id, fragment, invocation_id="fragment_stage_io")
self.assertIsInstance(result, Rejected)
self.assertEqual(result.error.code, ErrorCode.STORAGE_FAILURE)
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(state.retry_from_phase, TaskPhase.ACTION_PENDING)
def test_requirements_artifact_write_failure_enters_waiting_retry(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
batch = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": "One solid", "assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
}]})
with patch.object(artifacts, "write_requirements_draft", side_effect=OSError("disk full")):
result = handler.submit_draft(task_id, batch, invocation_id="draft_io")
self.assertIsInstance(result, Rejected)
self.assertEqual(result.error.code, ErrorCode.STORAGE_FAILURE)
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(state.retry_from_phase, TaskPhase.DRAFTING_REQUIREMENTS)
def test_requirements_freeze_is_json_first_and_markdown_is_derived(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
registry = default_registry()
handler = RequirementsCommandHandler(repository, artifacts, registry)
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a rectangular base.")
artifacts.initialize_task(task_id, "Create a rectangular base.")
batch = RequirementsDraftBatch.model_validate({"items": [{"source_ids": ["src_001"], "statement": "One connected rectangular base", "assumptions": [], "acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}]}]})
self.assertEqual(handler.submit_draft(task_id, batch, invocation_id="draft_1").payload["draft_revision"], 1)
self.assertEqual(handler.finalize_draft(task_id, invocation_id="finalize_1").payload["phase"], TaskPhase.REVIEWING_REQUIREMENTS.value)
review = RequirementsReview.model_validate({"findings": []})
accepted = handler.record_review(task_id, review, invocation_id="review_1")
self.assertEqual(accepted.payload["phase"], TaskPhase.AWAITING_ACTION.value)
state = repository.get_state(task_id)
self.assertTrue(state.requirements_review_path.startswith("documents/requirements-review-"))
self.assertTrue(state.requirements_contract_path.startswith("documents/requirements-contract-"))
contract = artifacts.read_requirements_contract(task_id, state.requirements_contract_path)
self.assertEqual(contract["schema_version"], "cad.requirements-contract.v2")
self.assertEqual(contract["requirements"][0]["requirement_id"], "req_001")
self.assertEqual(contract["requirements"][0]["acceptance_claims"][0]["verification_mode"], "deterministic")
self.assertFalse((artifacts.task_dir(task_id) / "requirements.md").exists())
handler.ensure_rendered_contract_views(task_id, state)
self.assertTrue((artifacts.task_dir(task_id) / "requirements.md").is_file())
self.assertTrue((artifacts.task_dir(task_id) / "completion.md").is_file())
def test_live_evaluation_oracle_revises_reviewer_false_positive_before_freeze(self) -> None:
"""Fixture-only claims must block a reviewer pass before CAD begins."""
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
registry = default_registry()
handler = RequirementsCommandHandler(repository, artifacts, registry)
runtime = ProfileCadRuntime(settings(root))
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(registry))
task_id = "cad_123456abcdef"
request = "Create a single connected plate, thickness 10 mm."
repository.create_task(task_id, request)
artifacts.initialize_task(task_id, request)
handler.register_evaluation_contract_oracle(task_id, [
{"claim_kind": "solid_count_equals", "expected": {"value": 1}},
{"claim_kind": "bbox_dimension_mm", "expected": {"axis": "z", "value": 10}},
])
incomplete = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": "One solid plate", "assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
}]})
reviewer_pass = RequirementsReview.model_validate({"findings": []})
self.assertIsInstance(handler.submit_draft(task_id, incomplete, invocation_id="draft_1"), Accepted)
self.assertIsInstance(handler.finalize_draft(task_id, invocation_id="finalize_1"), Accepted)
blocked = handler.record_review(task_id, reviewer_pass, invocation_id="review_1")
self.assertIsInstance(blocked, Accepted)
self.assertEqual(blocked.payload["phase"], TaskPhase.DRAFTING_REQUIREMENTS.value)
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.DRAFTING_REQUIREMENTS)
self.assertEqual(state.requirements_contract_path, "")
oracle = artifacts.read_requirements_review(task_id, state.requirements_review_path)
self.assertEqual(oracle["schema_version"], "cad.requirements-evaluation-oracle.v1")
self.assertEqual(oracle["reviewer_decision"], "freeze")
self.assertEqual(oracle["missing_claims"], [{
"claim_kind": "bbox_dimension_mm",
"expected": {"axis": "z", "value": 10},
}])
reviewer_artifacts = list((artifacts.task_dir(task_id) / "documents").glob("requirements-review-*.json"))
self.assertEqual(len(reviewer_artifacts), 1)
self.assertEqual(json.loads(reviewer_artifacts[0].read_text(encoding="utf-8"))["decision"], "freeze")
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, runtime, None, None, handler, actions)
author_context = json.loads(workflow._author_context(task_id, [])[1]["content"])
self.assertEqual(author_context["review"]["missing_claims"], oracle["missing_claims"])
patched = RequirementsPatchBatch.model_validate({"patches": [{
"target_draft_id": "draft_001", "op": "replace", "item": {
"source_ids": ["src_001"], "statement": "One solid plate, thickness 10 mm", "assumptions": [],
"acceptance_claims": [
{"claim_kind": "solid_count_equals", "expected": {"value": 1}},
{"claim_kind": "bbox_dimension_mm", "expected": {"axis": "z", "value": 10, "tolerance_mm": 0.01}},
],
},
}]})
self.assertIsInstance(handler.patch_draft(task_id, patched, invocation_id="patch_1"), Accepted)
self.assertIsInstance(handler.finalize_draft(task_id, invocation_id="finalize_2"), Accepted)
approved = handler.record_review(task_id, reviewer_pass, invocation_id="review_2")
self.assertIsInstance(approved, Accepted)
self.assertEqual(approved.payload["phase"], TaskPhase.AWAITING_ACTION.value)
contract = artifacts.read_requirements_contract(task_id, repository.get_state(task_id).requirements_contract_path)
self.assertEqual(
contract["requirements"][0]["acceptance_claims"][1]["expected"],
{"axis": "z", "value": 10, "tolerance_mm": 0.01},
)
def test_production_requirements_review_without_evaluation_oracle_still_freezes(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
batch = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": "One solid", "assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
}]})
review = RequirementsReview.model_validate({"findings": []})
handler.submit_draft(task_id, batch, invocation_id="draft_1")
handler.finalize_draft(task_id, invocation_id="finalize_1")
result = handler.record_review(task_id, review, invocation_id="review_1")
self.assertIsInstance(result, Accepted)
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.AWAITING_ACTION)
def test_full_circle_equal_spacing_is_normalized_without_user_confirmation(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
request = "Create eight equally spaced holes around a full circle at 60 degree spacing."
repository.create_task(task_id, request)
artifacts.initialize_task(task_id, request)
batch = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": request, "assumptions": [],
"acceptance_claims": [{"claim_kind": "visual", "expected": {"description": "Eight holes are evenly distributed around the full circle."}}],
}]})
handler.submit_draft(task_id, batch, invocation_id="draft_1")
handler.finalize_draft(task_id, invocation_id="finalize_1")
review = RequirementsReview.model_validate({"findings": [{
"draft_id": "draft_001", "source_ids": ["src_001"],
"finding_type": "derivable_conflict", "description": "A full circle with eight equal instances has 45 degree spacing.",
"normalization": {"rule_id": "full_circle_equal_spacing", "count": 8, "declared_spacing_degrees": 60, "full_circle": True},
}]})
result = handler.record_review(task_id, review, invocation_id="review_1")
self.assertIsInstance(result, Accepted)
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.AWAITING_ACTION)
contract = artifacts.read_requirements_contract(task_id, repository.get_state(task_id).requirements_contract_path)
self.assertEqual(contract["applied_normalizations"][0]["adopted"]["spacing_degrees"], 45.0)
self.assertTrue(any(event.get("event") == "requirements_normalized" for event in repository.ledger_events(task_id)))
def test_verification_gap_with_visual_claim_freezes_with_risk(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
request = "Place the counterbores on the front side."
repository.create_task(task_id, request)
artifacts.initialize_task(task_id, request)
batch = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": request, "assumptions": [],
"acceptance_claims": [{"claim_kind": "visual", "expected": {"description": "Counterbores are visibly on the front side."}}],
}]})
handler.submit_draft(task_id, batch, invocation_id="draft_1")
handler.finalize_draft(task_id, invocation_id="finalize_1")
review = RequirementsReview.model_validate({"findings": [{
"draft_id": "draft_001", "source_ids": ["src_001"],
"finding_type": "verification_gap", "description": "Feature-local front-side placement requires independent visual review.",
}]})
result = handler.record_review(task_id, review, invocation_id="review_1")
self.assertIsInstance(result, Accepted)
contract = artifacts.read_requirements_contract(task_id, repository.get_state(task_id).requirements_contract_path)
self.assertEqual(contract["requirements"][0]["acceptance_claims"][0]["verification_mode"], "visual")
self.assertEqual(len(contract["verification_warnings"]), 1)
projection = repository.get_task_projection(task_id)
self.assertEqual(projection["verification_status"], "completed_with_risks")
def test_ambiguous_conflict_waits_with_a_precise_question(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Use either four or six holes.")
artifacts.initialize_task(task_id, "Use either four or six holes.")
batch = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": "Use either four or six holes.", "assumptions": [],
"acceptance_claims": [{"claim_kind": "visual", "expected": {"description": "The chosen hole pattern is visible."}}],
}]})
handler.submit_draft(task_id, batch, invocation_id="draft_1")
handler.finalize_draft(task_id, invocation_id="finalize_1")
question = "Should the part contain four holes or six holes?"
review = RequirementsReview.model_validate({"findings": [{
"draft_id": "draft_001", "source_ids": ["src_001"], "finding_type": "ambiguous_conflict",
"description": "Two different hole counts are allowed.", "question": question,
}]})
result = handler.record_review(task_id, review, invocation_id="review_1")
self.assertIsInstance(result, Waiting)
self.assertEqual(result.error.details["questions"], [question])
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.WAITING_FOR_USER)
projection = repository.get_task_projection(task_id)
self.assertEqual(projection["message"], "Requirements contain an ambiguity that cannot be resolved deterministically.")
self.assertEqual(projection["questions"], [question])
self.assertEqual(projection["issues"], ["Two different hole counts are allowed."])
self.assertTrue(projection["user_action_required"])
def test_missing_derived_contract_views_park_for_storage_retry_not_internal_failure(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository, artifacts, actions, task_id, _action = self._ready_action(root)
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
with patch.object(requirements, "ensure_rendered_contract_views", side_effect=OSError("disk full")):
async def run() -> list[tuple[str, dict[str, object]]]:
return [item async for item in workflow.run(task_id=task_id, author=None, reviewer=None)] # type: ignore[arg-type]
events = asyncio.run(run())
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(state.last_error, ErrorCode.STORAGE_FAILURE)
self.assertEqual(events[-1][1]["code"], ErrorCode.STORAGE_FAILURE.value)
def test_action_ledger_mirror_failure_parks_for_storage_retry(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository, artifacts, actions, task_id, _action = self._ready_action(root)
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
workflow = WorkflowCoordinator(WorkflowConfig(1, 1), repository, artifacts, runtime, None, None, requirements, actions)
with patch.object(artifacts, "sync_action_ledger", side_effect=OSError("ledger disk unavailable")):
async def run() -> list[tuple[str, dict[str, object]]]:
return [item async for item in workflow.run(task_id=task_id, author=None, reviewer=None)] # type: ignore[arg-type]
events = asyncio.run(run())
state = repository.get_state(task_id)
self.assertEqual(state.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(state.last_error, ErrorCode.STORAGE_FAILURE)
self.assertEqual(events[-1][1]["lifecycle"], "waiting_retry")
def test_requirements_review_coverage_is_derived_from_draft_sources(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
request = "Create a single connected base.\n\nAdd one cylindrical bore."
repository.create_task(task_id, request)
artifacts.initialize_task(task_id, request)
batch = RequirementsDraftBatch.model_validate({"items": [
{"source_ids": ["src_001"], "statement": "One connected base", "assumptions": [], "acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}]},
{"source_ids": ["src_002"], "statement": "One bore", "assumptions": [], "acceptance_claims": [{"claim_kind": "cylindrical_bore", "expected": {"diameter_mm": 5}}]},
]})
handler.submit_draft(task_id, batch, invocation_id="draft_1")
handler.finalize_draft(task_id, invocation_id="finalize_1")
review = RequirementsReview.model_validate({"findings": []})
result = handler.record_review(task_id, review, invocation_id="review_1")
self.assertIsInstance(result, Accepted)
persisted = artifacts.read_requirements_review(task_id, repository.get_state(task_id).requirements_review_path)
self.assertEqual(persisted["coverage"], [
{"source_id": "src_001", "requirement_ids": ["req_001"]},
{"source_id": "src_002", "requirement_ids": ["req_002"]},
])
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.AWAITING_ACTION)
def test_requirements_review_rejects_service_owned_decision_fields(self) -> None:
with self.assertRaisesRegex(ValueError, "Extra inputs are not permitted"):
RequirementsReview.model_validate({
"verdict": "pass",
"coverage": [{"source_id": "src_001", "requirement_ids": ["req_001"]}],
"item_verdicts": [{"draft_id": "draft_001", "verdict": "pass"}],
"issues": ["Conflicting bore diameter."],
})
def test_requirements_invocation_replays_after_the_state_has_advanced(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a rectangular base.")
artifacts.initialize_task(task_id, "Create a rectangular base.")
batch = RequirementsDraftBatch.model_validate({"items": [{"source_ids": ["src_001"], "statement": "One connected base", "assumptions": [], "acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}]}]})
first = handler.submit_draft(task_id, batch, invocation_id="draft_replay")
replay = handler.submit_draft(task_id, batch, invocation_id="draft_replay")
self.assertEqual(first.payload, replay.payload)
self.assertEqual(repository.get_state(task_id).version, 1)
self.assertEqual(len(artifacts.read_requirements_draft(task_id)["items"]), 1)
records = repository.invocation_records(task_id)
self.assertEqual(len(records), 1)
self.assertEqual(records[0]["status"], "finished")
def test_requirements_patch_tool_is_hidden_until_reviewer_requests_revision(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
registry = default_registry()
requirements = RequirementsCommandHandler(repository, artifacts, registry)
runtime = ProfileCadRuntime(settings(root))
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(registry))
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, runtime, None, None, requirements, actions)
draft_schema = requirements.draft_schema(task_id)
patch_schema = requirements.patch_schema(task_id)
initial = {item["function"]["name"] for item in workflow._requirements_author_tools(task_id, draft_schema, patch_schema)}
self.assertEqual(initial, {"submit_requirements_draft_batch"})
batch = RequirementsDraftBatch.model_validate({"items": [{"source_ids": ["src_001"], "statement": "One solid", "assumptions": [], "acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}]}]})
requirements.submit_draft(task_id, batch, invocation_id="draft_1")
finalizable = {item["function"]["name"] for item in workflow._requirements_author_tools(task_id, draft_schema, patch_schema)}
self.assertEqual(finalizable, {"finalize_requirements_draft"})
requirements.finalize_draft(task_id, invocation_id="finalize_1")
review = RequirementsReview.model_validate({"findings": [{"draft_id": "draft_001", "source_ids": ["src_001"], "finding_type": "missing_source_semantics", "description": "Clarify the requirement."}]})
requirements.record_review(task_id, review, invocation_id="review_1")
revised = {item["function"]["name"] for item in workflow._requirements_author_tools(task_id, draft_schema, requirements.patch_schema(task_id))}
self.assertEqual(revised, {"patch_requirements_draft"})
def test_requirements_patch_context_distinguishes_server_owned_draft_id(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
registry = default_registry()
requirements = RequirementsCommandHandler(repository, artifacts, registry)
runtime = ProfileCadRuntime(settings(root))
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(registry))
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
batch = RequirementsDraftBatch.model_validate({"items": [{"source_ids": ["src_001"], "statement": "One solid", "assumptions": [], "acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}]}]})
requirements.submit_draft(task_id, batch, invocation_id="draft_1")
requirements.finalize_draft(task_id, invocation_id="finalize_1")
requirements.record_review(task_id, RequirementsReview.model_validate({"findings": [{"draft_id": "draft_001", "source_ids": ["src_001"], "finding_type": "claim_mismatch", "description": "Revise dimensions."}]}), invocation_id="review_1")
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, runtime, None, None, requirements, actions)
context = json.loads(workflow._author_context(task_id, [])[1]["content"])
instruction = str(context["instruction"])
schema = requirements.patch_schema(task_id)
patch = schema["$defs"]["RequirementsPatch"]
self.assertIn("target_draft_id", instruction)
self.assertIn('"patches"', instruction)
self.assertIn('"op":"replace"', instruction)
self.assertIn("MUST NOT include draft_id", instruction)
self.assertIn("inside one patches[] entry", patch["properties"]["target_draft_id"]["description"])
def test_requirement_patch_recovers_from_the_cad_02df83c6d283_top_level_shape(self) -> None:
"""A reviewer-requested patch must be repairable without touching CAD state.
This is the exact malformed outer shape returned by cad_02df83c6d283:
target_draft_id/op/item were emitted at the tool-call root instead of
inside RequirementsPatchBatch.patches. The next author turn receives
precise feedback and can submit the correctly nested payload.
"""
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
task_id = "cad_02df83c6d283"
request = "生成一个三孔机械连杆,三个孔的中心位于同一直线上,相邻孔中心距分别为60毫米和80毫米,三个孔直径均为12毫米,连杆厚度10毫米,每个孔周围外圆直径32毫米,各段外轮廓平滑连接。"
artifacts.initialize_task(task_id, request)
repository.create_task(task_id, request)
incomplete_item = {
"source_ids": ["src_001"],
"statement": "生成一个连通的三孔机械连杆。",
"assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
}
revised_item = {
"source_ids": ["src_001"],
"statement": "生成一个三孔机械连杆:孔中心共线,相邻中心距为60 mm和80 mm,孔径12 mm,厚度10 mm,外圆直径32 mm且轮廓平滑连接。",
"assumptions": [],
"acceptance_claims": [
{"claim_kind": "solid_count_equals", "expected": {"value": 1}},
{"claim_kind": "bbox_dimension_mm", "expected": {"axis": "z", "value": 10, "tolerance_mm": 0.01}},
{"claim_kind": "cylindrical_bore", "expected": {"diameter_mm": 12, "count": 3, "tolerance_mm": 0.01}},
{"claim_kind": "through_cylindrical_bore", "expected": {"diameter_mm": 12, "count": 3, "tolerance_mm": 0.01}},
{"claim_kind": "collinear_through_bore_chain", "expected": {"diameter_mm": 12, "adjacent_distances_mm": [60, 80], "tolerance_mm": 0.01}},
],
}
class RepairingAuthor:
def __init__(self) -> None:
self.patch_messages: list[list[dict[str, object]]] = []
self.patch_draft_revisions: list[int] = []
self.calls: list[str] = []
async def call_tool(self, *, messages: list[dict[str, object]], tool: dict[str, object], required_tool_name: str, **_kwargs: object) -> dict[str, object]:
self.calls.append(required_tool_name)
if required_tool_name == "submit_requirements_draft_batch":
arguments: dict[str, object] = {"items": [incomplete_item]}
elif required_tool_name == "finalize_requirements_draft":
arguments = {}
elif required_tool_name == "patch_requirements_draft":
self.patch_messages.append(messages)
self.patch_draft_revisions.append(int(artifacts.read_requirements_draft(task_id)["revision"]))
arguments = (
{
"target_draft_id": "draft_001",
"op": "replace",
"reason": "补充可量测的尺寸验收声明。",
"item": revised_item,
}
if len(self.patch_messages) == 1
else {"patches": [{"target_draft_id": "draft_001", "op": "replace", "item": revised_item}]}
)
elif required_tool_name == "propose_next_action":
raise StructuredModelError("author transport paused after requirements approval")
else:
raise AssertionError(f"unexpected author tool: {required_tool_name}")
return {
"tool_calls": [{"function": {"name": required_tool_name, "arguments": json.dumps(arguments)}}],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
}
class RevisingReviewer:
def __init__(self) -> None:
self.calls = 0
async def review(self, *, tool: dict[str, object], **_kwargs: object) -> dict[str, object]:
self.calls += 1
arguments = {"findings": [{
"draft_id": "draft_001",
"source_ids": ["src_001"],
"finding_type": "missing_source_semantics",
"description": "补充孔距、共线、厚度和外圆尺寸的可量测验收声明。",
}]} if self.calls == 1 else {"findings": []}
return {
"tool_calls": [{"function": {"name": "review_requirements", "arguments": json.dumps(arguments)}}],
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
}
author = RepairingAuthor()
reviewer = RevisingReviewer()
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=8, format_error_limit=2), repository, artifacts, runtime,
author, reviewer, requirements, actions,
)
async def run() -> list[tuple[str, dict[str, object]]]:
identity = type("Model", (), {"provider_id": "test", "model_id": "test-model"})()
return [item async for item in workflow.run(task_id=task_id, author=identity, reviewer=identity)]
events = asyncio.run(run())
state = repository.get_state(task_id)
self.assertEqual(author.calls, [
"submit_requirements_draft_batch", "finalize_requirements_draft",
"patch_requirements_draft", "patch_requirements_draft",
"finalize_requirements_draft", "propose_next_action",
])
self.assertEqual(reviewer.calls, 2)
# The malformed root object is rejected before the handler sees a
# patch: the retry still observes the original frozen draft.
self.assertEqual(author.patch_draft_revisions, [1, 1])
self.assertEqual(state.phase, TaskPhase.WAITING_RETRY)
self.assertEqual(state.retry_from_phase, TaskPhase.AWAITING_ACTION)
self.assertTrue(state.requirements_contract_path)
self.assertEqual(artifacts.read_requirements_draft(task_id)["revision"], 2)
feedback = next(message["content"] for message in author.patch_messages[1] if message["role"] == "user" and "schema_or_state_error" in str(message["content"]))
self.assertIn('"path": "/patches"', str(feedback))
self.assertFalse(any(name == "candidate_result" for name, _payload in events))
self.assertEqual(list((artifacts.task_dir(task_id) / ".staging").iterdir()), [])
audits = [item for item in repository.tool_audits(task_id) if item["tool"] == "patch_requirements_draft"]
self.assertEqual(len(audits), 2)
self.assertFalse(audits[0]["canonical_schema_valid"])
self.assertTrue(audits[1]["canonical_schema_valid"])
malformed_event = next(
payload for name, payload in events
if name == "tool_call" and payload.get("tool") == "patch_requirements_draft" and payload.get("status") == "error"
)
self.assertTrue(any(error["path"] == "/patches" for error in malformed_event["result"]["field_errors"]))
self.assertTrue(any(error["path"] == "/reason" for error in malformed_event["result"]["field_errors"]))
def test_repeated_top_level_requirement_patch_shape_fails_with_field_audit(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
task_id = "cad_123456abcdef"
artifacts.initialize_task(task_id, "Create a plate with a measurable thickness.")
repository.create_task(task_id, "Create a plate with a measurable thickness.")
item = {
"source_ids": ["src_001"], "statement": "One connected plate", "assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
}
class RepeatingAuthor:
async def call_tool(self, *, required_tool_name: str, **_kwargs: object) -> dict[str, object]:
if required_tool_name == "submit_requirements_draft_batch":
arguments: dict[str, object] = {"items": [item]}
elif required_tool_name == "finalize_requirements_draft":
arguments = {}
elif required_tool_name == "patch_requirements_draft":
arguments = {
"target_draft_id": "draft_001",
"op": "replace",
"reason": "Add the missing dimension claim.",
"item": item,
}
else:
raise AssertionError(f"unexpected author tool: {required_tool_name}")
return {"tool_calls": [{"function": {"name": required_tool_name, "arguments": json.dumps(arguments)}}], "usage": {"prompt_tokens": 1, "completion_tokens": 1}}
class RevisingReviewer:
async def review(self, **_kwargs: object) -> dict[str, object]:
arguments = {"findings": [{
"draft_id": "draft_001", "source_ids": ["src_001"],
"finding_type": "missing_source_semantics", "description": "Add the thickness claim.",
}]}
return {"tool_calls": [{"function": {"name": "review_requirements", "arguments": json.dumps(arguments)}}], "usage": {"prompt_tokens": 1, "completion_tokens": 1}}
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=5, format_error_limit=2), repository, artifacts, runtime,
RepeatingAuthor(), RevisingReviewer(), requirements, actions,
)
async def run() -> list[tuple[str, dict[str, object]]]:
identity = type("Model", (), {"provider_id": "test", "model_id": "test-model"})()
return [item async for item in workflow.run(task_id=task_id, author=identity, reviewer=identity)]
events = asyncio.run(run())
terminal = events[-1][1]
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.FAILED)
self.assertEqual(terminal["code"], ErrorCode.FAILED_AUTHOR_FORMAT.value)
self.assertEqual(terminal["tool"], "patch_requirements_draft")
self.assertTrue(any(error["path"] == "/patches" for error in terminal["field_errors"]))
self.assertTrue(any(error["path"] == "/reason" for error in terminal["field_errors"]))
patch_audits = [item for item in repository.tool_audits(task_id) if item["tool"] == "patch_requirements_draft"]
self.assertEqual(len(patch_audits), 2)
self.assertTrue(all(not item["canonical_schema_valid"] for item in patch_audits))
self.assertEqual(list((artifacts.task_dir(task_id) / ".staging").iterdir()), [])
def test_candidate_review_payload_exposes_render_evidence_at_the_top_level(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
stage = artifacts.start_candidate_stage(task_id, "candidate-key", {"idempotency_key": "candidate-key", "candidate_id": "candidate_123456789012", "action_id": action.action_id})
candidate = {"candidate_id": "candidate_123456789012", "stage_id": stage.stage_id, "action_id": action.action_id, "render_manifest": {"contact_sheet_path": "/tmp/contact-sheet.jpg"}, "claim_results": [{"claim_id": "claim_001", "claim_kind": "solid_count_equals", "deterministic": True, "status": "pass"}]}
artifacts.write_stage_json(task_id, stage.stage_id, "candidate.json", candidate)
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
review = transition(building, "candidate_built", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
self.assertTrue(repository.compare_and_swap(building))
self.assertTrue(repository.compare_and_swap(review))
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions)
captured: dict[str, object] = {}
class CapturingGateway:
async def review(self, *, payload: dict[str, object], **_kwargs: object) -> dict[str, object]:
captured.update(payload)
return {"tool_calls": []}
workflow.review_gateway = CapturingGateway()
result = asyncio.run(workflow._review_candidate(task_id, type("Model", (), {"provider_id": "test", "model_id": "test-model"})(), review))
self.assertIsInstance(result, WorkflowError)
self.assertEqual(captured["render_manifest"], candidate["render_manifest"])
self.assertNotIn("source_requirements", captured)
self.assertIn("pending", str(captured["instruction"]))
self.assertIn("MUST NOT cause rejection", str(captured["instruction"]))
def test_candidate_review_audit_uses_checkpoint_head_bound_by_schema(self) -> None:
"""A built candidate is reviewed against its action checkpoint, not v+2 state."""
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
stage = artifacts.start_candidate_stage(task_id, "candidate-key", {
"idempotency_key": "candidate-key", "candidate_id": "candidate_123456789012", "action_id": action.action_id,
})
candidate = {
"candidate_id": "candidate_123456789012", "stage_id": stage.stage_id,
"action_id": action.action_id,
"claim_results": [{"claim_id": "claim_001", "claim_kind": "solid_count_equals", "deterministic": True, "status": "pass"}],
}
artifacts.write_stage_json(task_id, stage.stage_id, "candidate.json", candidate)
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
reviewing = transition(building, "candidate_built", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
self.assertTrue(repository.compare_and_swap(building))
self.assertTrue(repository.compare_and_swap(reviewing))
self.assertNotEqual(repository.get_state(task_id).working_head, action.working_head)
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions)
class ValidGateway:
async def review(self, **_kwargs: object) -> dict[str, object]:
arguments = {
"candidate_id": "candidate_123456789012", "working_head": action.working_head,
"verdict": "accept", "claim_coverage": [{"claim_id": "claim_001", "status": "pass"}],
}
return {"tool_calls": [{"function": {"name": "review_candidate", "arguments": json.dumps(arguments)}}], "usage": {"prompt_tokens": 1, "completion_tokens": 1}}
workflow.review_gateway = ValidGateway()
result = asyncio.run(workflow._review_candidate(task_id, type("Model", (), {"provider_id": "test", "model_id": "test-model"})(), reviewing))
self.assertIsInstance(result, CandidateReview)
audit = repository.tool_audits(task_id)[-1]
self.assertTrue(audit["canonical_schema_valid"])
self.assertTrue(audit["state_binding"]["binding_valid"])
self.assertEqual(audit["state_binding"]["expected_working_head"], action.working_head)
self.assertEqual(audit["state_binding"]["working_head_binding_source"], "schema_const")
def test_requirements_review_instruction_requires_executable_claim_completeness(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
task_id = "cad_123456abcdef"
request = "Create a single connected flange, diameter 120 mm and thickness 12 mm, with six 8 mm holes on a 90 mm PCD."
artifacts.initialize_task(task_id, request)
repository.create_task(task_id, request)
batch = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": "Single flange", "assumptions": [],
"acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
}]})
requirements.submit_draft(task_id, batch, invocation_id="draft_1")
requirements.finalize_draft(task_id, invocation_id="finalize_1")
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, runtime, None, None, requirements, actions)
captured: dict[str, object] = {}
class CapturingGateway:
async def review(self, *, payload: dict[str, object], **_kwargs: object) -> dict[str, object]:
captured.update(payload)
arguments = {"findings": [{
"draft_id": "draft_001", "source_ids": ["src_001"],
"finding_type": "missing_source_semantics", "description": "Missing flange dimensions and hole-pattern claims.",
}]}
return {"tool_calls": [{"function": {"name": "review_requirements", "arguments": json.dumps(arguments)}}], "usage": {"prompt_tokens": 1, "completion_tokens": 1}}
workflow.review_gateway = CapturingGateway()
result = asyncio.run(workflow._review_requirements(task_id, type("Model", (), {"provider_id": "test", "model_id": "test-model"})()))
self.assertIsInstance(result, RequirementsReview)
instruction = str(captured["instruction"])
self.assertIn("service derives coverage", instruction)
self.assertIn("number, count, dimension, relationship", instruction)
self.assertIn("verification_gap", instruction)
self.assertIn("full-circle equally spaced pattern", instruction)
def test_live_evaluation_review_context_marks_only_declared_verifier_gaps(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
requirements = RequirementsCommandHandler(repository, artifacts, default_registry())
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
task_id = "cad_123456abcdef"
request = "Create a plate with a visually smooth outside contour."
repository.create_task(task_id, request)
artifacts.initialize_task(task_id, request)
requirements.register_evaluation_contract_oracle(
task_id,
[{"claim_kind": "solid_count_equals", "expected": {"value": 1}}],
validation_capability_gaps=[{"id": "smooth_outline", "description": "Smooth exterior tangency"}],
)
batch = RequirementsDraftBatch.model_validate({"items": [{
"source_ids": ["src_001"], "statement": "One connected smooth plate", "assumptions": [],
"acceptance_claims": [
{"claim_kind": "solid_count_equals", "expected": {"value": 1}},
{"claim_kind": "visual", "expected": {"description": "The outside contour is visibly smooth."}},
],
}]})
requirements.submit_draft(task_id, batch, invocation_id="draft_1")
requirements.finalize_draft(task_id, invocation_id="finalize_1")
workflow = WorkflowCoordinator(WorkflowConfig(8, 2), repository, artifacts, runtime, None, None, requirements, actions)
captured: dict[str, object] = {}
class CapturingGateway:
async def review(self, *, payload: dict[str, object], **_kwargs: object) -> dict[str, object]:
captured.update(payload)
arguments = {"findings": [{
"draft_id": "draft_001", "source_ids": ["src_001"],
"finding_type": "verification_gap", "description": "Smooth exterior tangency requires visual review.",
}]}
return {"tool_calls": [{"function": {"name": "review_requirements", "arguments": json.dumps(arguments)}}], "usage": {"prompt_tokens": 1, "completion_tokens": 1}}
workflow.review_gateway = CapturingGateway()
result = asyncio.run(workflow._review_requirements(task_id, type("Model", (), {"provider_id": "test", "model_id": "test-model"})()))
self.assertIsInstance(result, RequirementsReview)
self.assertEqual(captured["evaluation_context"], {
"evaluation_only": True,
"known_validation_capability_gaps": [{"id": "smooth_outline", "description": "Smooth exterior tangency"}],
})
self.assertIn("Do not request an invented executable claim", str(captured["instruction"]))
self.assertIn("Do not request an invented executable claim", str(captured["instruction"]))
ordinary = RequirementsCommandHandler(repository, artifacts, default_registry())
self.assertIsNone(ordinary.evaluation_review_context(task_id))
def test_author_and_reviewer_service_failures_record_usage_metadata(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
class UnavailableAuthor:
async def call_tool(self, **_kwargs: object) -> dict[str, object]:
raise StructuredModelError("author unavailable")
workflow = WorkflowCoordinator(
WorkflowConfig(max_turns=1, format_error_limit=1), repository, artifacts, runtime,
UnavailableAuthor(), None, RequirementsCommandHandler(repository, artifacts, default_registry()),
ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry())),
)
async def run() -> list[tuple[str, dict[str, object]]]:
author = type("Model", (), {"provider_id": "test", "model_id": "test-model"})()
return [item async for item in workflow.run(task_id=task_id, author=author, reviewer=None)]
asyncio.run(run())
records = repository.usage_summary(task_id)["records"]
self.assertEqual(len(records), 1)
self.assertEqual(records[0]["retry_reason"], "provider_unavailable")
self.assertFalse(records[0]["usage_available"])
self.assertFalse(records[0]["cache_hit"])
self.assertGreater(records[0]["context_chars"], 0)
def test_dynamic_requirement_claim_schema_rejects_unknown_expected_fields_before_write(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
handler = RequirementsCommandHandler(repository, artifacts, default_registry())
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
batch = RequirementsDraftBatch.model_validate({"items": [{"source_ids": ["src_001"], "statement": "One solid", "assumptions": [], "acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1, "ignored": True}}]}]})
result = handler.submit_draft(task_id, batch, invocation_id="draft_invalid")
self.assertEqual(result.error.code, ErrorCode.AUTHOR_FORMAT_INVALID)
self.assertEqual(artifacts.read_requirements_draft(task_id)["items"], [])
self.assertEqual(repository.get_state(task_id).version, 0)
def test_pattern_contract_uses_opaque_current_head_reference_enum(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
runtime = ProfileCadRuntime(settings(Path(temporary)))
contract = runtime.operation_contract("pattern_linear")
token = next(iter(runtime.reference_tokens({"features": [{"id": "feature_001"}]})))
schema = fragment_schema(contract, selector_tokens=[], reference_tokens=[token])
items = schema["properties"]["feature"]["properties"]["params"]["properties"]["source_feature_ids"]["items"]
self.assertEqual(items, {"enum": [token]})
self.assertTrue(validate_fragment(contract, {"feature": {"atomic_id": "pattern_linear", "params": {"source_feature_ids": ["feature_001"], "direction_1": [1, 0, 0], "spacing_1_mm": 4, "pattern_count_1": 2}}}, selector_tokens=[], reference_tokens=[token]))
def test_candidate_stage_recovery_advances_without_rebuild(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
stage = artifacts.start_candidate_stage(task_id, "candidate-key", {"idempotency_key": "candidate-key", "candidate_id": "candidate_123456789012", "action_id": action.action_id})
artifacts.write_stage_json(task_id, stage.stage_id, "candidate.json", {"candidate_id": "candidate_123456789012", "stage_id": stage.stage_id, "action_id": action.action_id, "claim_results": [{"claim_id": "claim_001", "claim_kind": "solid_count_equals", "deterministic": True, "status": "pass"}]})
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
self.assertTrue(repository.compare_and_swap(building))
result = actions.recover_candidate_build(task_id)
self.assertEqual(result.payload["status"], "awaiting_review")
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.CANDIDATE_REVIEW)
def test_rejected_candidate_stage_is_retained_for_restart_diagnostics(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
stage = artifacts.start_candidate_stage(task_id, "rejected-stage-key", {"candidate_id": "candidate_123456789012"})
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
reviewing = transition(building, "candidate_built")
rejected = transition(reviewing, "candidate_rejected", repair_required=True, error=ErrorCode.CANDIDATE_REVIEW_REJECTED)
self.assertTrue(repository.compare_and_swap(building))
self.assertTrue(repository.compare_and_swap(reviewing))
self.assertTrue(repository.compare_and_swap(rejected, events=[{"event": "candidate_rejected", "candidate_id": "candidate_123456789012", "stage_id": stage.stage_id}]))
workflow = WorkflowCoordinator(WorkflowConfig(0, 1), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), actions)
async def resume_once() -> list[tuple[str, dict[str, object]]]:
return [item async for item in workflow.run(task_id=task_id, author=None, reviewer=None)]
asyncio.run(resume_once())
self.assertTrue(Path(stage.output_dir).is_dir())
def test_published_candidate_review_recovery_commits_once(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
stage = artifacts.start_candidate_stage(task_id, "candidate-key", {"idempotency_key": "candidate-key", "candidate_id": "candidate_123456789012", "action_id": action.action_id})
candidate = {"candidate_id": "candidate_123456789012", "stage_id": stage.stage_id, "action_id": action.action_id, "claim_results": [{"claim_id": "claim_001", "claim_kind": "solid_count_equals", "deterministic": True, "status": "pass"}]}
artifacts.write_stage_json(task_id, stage.stage_id, "candidate.json", candidate)
review = CandidateReview.model_validate({"candidate_id": "candidate_123456789012", "working_head": action.working_head, "verdict": "accept", "claim_coverage": [{"claim_id": "claim_001", "status": "pass"}]})
artifacts.write_stage_json(task_id, stage.stage_id, "candidate-review.json", review.model_dump(mode="json"))
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
reviewing = transition(building, "candidate_built", candidate_id="candidate_123456789012", candidate_stage_id=stage.stage_id)
self.assertTrue(repository.compare_and_swap(building))
self.assertTrue(repository.compare_and_swap(reviewing))
artifacts.publish_candidate(task_id, stage.stage_id, "rev_001")
result = actions.recover_candidate_review(task_id)
self.assertEqual(result.payload["revision_id"], "rev_001")
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.AWAITING_ACTION)
self.assertEqual(len([item for item in repository.get_task_projection(task_id)["action_ledger_summary"] if item.get("event") == "accepted"]), 1)
def test_candidate_publication_rebases_render_evidence_to_the_revision(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
artifacts = FileArtifactStore(root / "tasks")
task_id = "cad_123456abcdef"
artifacts.initialize_task(task_id, "Create a plate.")
stage = artifacts.start_candidate_stage(task_id, "publication-key", {"candidate_id": "candidate_123456789012"})
stage_root = Path(stage.output_dir)
render = stage_root / "renders" / "contact-sheet.jpg"
render.parent.mkdir(parents=True)
render.write_bytes(b"jpeg")
artifacts.write_stage_json(task_id, stage.stage_id, "candidate.json", {"render_manifest": {"contact_sheet_path": str(render)}})
artifacts.publish_candidate(task_id, stage.stage_id, "rev_001")
candidate = artifacts.read_json(task_id, "revisions/rev_001/candidate.json")
path = Path(str(candidate["render_manifest"]["contact_sheet_path"]))
self.assertEqual(path, artifacts.task_dir(task_id) / "revisions" / "rev_001" / "renders" / "contact-sheet.jpg")
self.assertTrue(path.is_file())
def test_complete_task_is_revision_scoped_idempotent(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
# A real candidate would have produced these immutable facts before
# action selection becomes available again.
artifacts.write_json_once(task_id, "revisions/rev_001/rebuild-report.json", {"health": {"solid_count": 1, "bbox_mm": {"dimensions": [10, 10, 1]}}})
artifacts.write_json_once(task_id, "revisions/rev_001/model.topology.json", {"records": []})
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id="stage_1234567890abcdef12")
self.assertTrue(repository.compare_and_swap(building))
awaiting = transition(building, "candidate_built")
self.assertTrue(repository.compare_and_swap(awaiting))
awaiting = transition(awaiting, "candidate_accepted", active_revision="rev_001", repair_required=False)
self.assertTrue(repository.compare_and_swap(awaiting))
first = actions.complete_task(task_id, invocation_id="complete_first")
second = actions.complete_task(task_id, invocation_id="complete_duplicate")
self.assertEqual(first.payload, second.payload)
self.assertEqual(repository.get_state(task_id).phase, TaskPhase.FINAL_VALIDATION)
def test_unexpected_final_review_failure_preserves_checkpoint_once(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
building = transition(state, "candidate_started", candidate_id="candidate_123456789012", candidate_stage_id="stage_1234567890abcdef12")
reviewing = transition(building, "candidate_built")
awaiting = transition(reviewing, "candidate_accepted", active_revision="rev_001")
final = transition(awaiting, "final_requested")
for value in (building, reviewing, awaiting, final):
self.assertTrue(repository.compare_and_swap(value))
class BrokenActions(ActionCommandHandler):
def _facts(self, task_id: str, revision_id: str) -> dict[str, object]:
raise NameError("global_violations")
broken = BrokenActions(actions.repository, actions.artifacts, actions.runtime, actions.verifiers)
workflow = WorkflowCoordinator(WorkflowConfig(2, 1), repository, artifacts, actions.runtime, None, None, RequirementsCommandHandler(repository, artifacts, default_registry()), broken)
async def run() -> list[tuple[str, dict[str, object]]]:
return [item async for item in workflow.run(task_id=task_id, author=None, reviewer=None)]
events = asyncio.run(run())
after = repository.get_state(task_id)
self.assertEqual(after.phase, TaskPhase.FAILED)
self.assertEqual(after.active_revision, "rev_001")
self.assertEqual(after.last_error, ErrorCode.FAILED_INTERNAL)
self.assertEqual(events[-1][1]["code"], ErrorCode.FAILED_INTERNAL.value)
def test_final_review_requires_each_visual_claim_to_pass_independently(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
runtime = ProfileCadRuntime(settings(root))
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(default_registry()))
task_id = "cad_123456abcdef"
state = repository.create_task(task_id, "Create a visually inspectable single solid.")
artifacts.initialize_task(task_id, "Create a visually inspectable single solid.")
contract = {
"schema_version": "cad.requirements-contract.v1",
"task_id": task_id,
"contract_hash": "visual-contract",
"requirements": [{
"requirement_id": "req_001", "draft_id": "draft_001", "source_ids": ["src_001"],
"statement": "One visible solid", "assumptions": [],
"acceptance_claims": [
{"claim_id": "claim_001", "claim_kind": "solid_count_equals", "expected": {"value": 1}},
{"claim_id": "claim_002", "claim_kind": "visual", "expected": {"description": "The rendered part is visibly coherent."}},
],
}],
}
contract_path = artifacts.write_requirements_contract(task_id, contract, invocation_id="visual_contract")
for event in ("draft_updated", "requirements_finalized", "requirements_approved"):
state = transition(state, event, requirements_contract_path=contract_path if event == "requirements_approved" else None)
self.assertTrue(repository.compare_and_swap(state))
state = transition(repository.get_state(task_id), "final_requested", active_revision="rev_001")
self.assertTrue(repository.compare_and_swap(state))
artifacts.write_json_once(task_id, "revisions/rev_001/rebuild-report.json", {"health": {"solid_count": 1}})
artifacts.write_json_once(task_id, "revisions/rev_001/model.topology.json", {"records": []})
result = actions.record_final_review(
task_id,
FinalReview.model_validate({
"working_head": state.working_head,
"verdict": "pass",
"claim_coverage": [
{"claim_id": "claim_001", "status": "pass"},
{"claim_id": "claim_002", "status": "pending"},
],
}),
invocation_id="visual-review-pending",
)
self.assertEqual(getattr(result, "payload", {}).get("status"), "repair")
after = repository.get_state(task_id)
self.assertEqual(after.phase, TaskPhase.AWAITING_ACTION)
self.assertTrue(after.repair_required)
event = repository.ledger_events(task_id)[-1]
self.assertEqual(event["event"], "final_review_repair")
self.assertEqual(event["visual_not_passed"], ["claim_002"])
self.assertTrue(artifacts.read_json(task_id, "reviews/final/rev_001/final-review.json"))
self.assertIsNone(artifacts.read_json(task_id, "revisions/rev_001/final-review.json"))
def test_action_rejections_do_not_create_stage_or_change_head(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
runtime = ProfileCadRuntime(settings(root))
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
registry = default_registry()
requirements = RequirementsCommandHandler(repository, artifacts, registry)
actions = ActionCommandHandler(repository, artifacts, runtime, RegistryVerifierExecutor(registry))
task_id = "cad_123456abcdef"
repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
batch = RequirementsDraftBatch.model_validate({"items": [{"source_ids": ["src_001"], "statement": "One plate", "assumptions": [], "acceptance_claims": [{"claim_kind": "solid_count_equals", "expected": {"value": 1}}]}]})
requirements.submit_draft(task_id, batch, invocation_id="draft_1")
requirements.finalize_draft(task_id, invocation_id="finalize_1")
requirements.record_review(task_id, RequirementsReview.model_validate({"findings": []}), invocation_id="review_1")
state = repository.get_state(task_id)
self.assertIsNotNone(state)
proposal = NextAction.model_validate({"working_head": state.working_head, "intent": "Create base", "requirement_ids": ["req_001"], "atomic_id": "extrude_add_blind", "expected_change": "Add base"})
self.assertTrue(hasattr(actions.propose_next_action(task_id, proposal, invocation_id="inv_1"), "payload"))
before = repository.get_state(task_id)
rejected = actions.submit_cdsl_fragment(task_id, {"feature": {"atomic_id": "extrude_add_blind", "params": {"distance_mm": -1}}}, invocation_id="inv_2")
after = repository.get_state(task_id)
self.assertEqual(rejected.error.code, ErrorCode.AUTHOR_FORMAT_INVALID)
self.assertEqual(before.working_head, after.working_head)
self.assertEqual(list((artifacts.task_dir(task_id) / ".staging").iterdir()), [])
def test_wrong_selector_kind_fails_the_handler_dynamic_schema_before_materialization(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository, artifacts, actions, task_id, _action = self._ready_action(Path(temporary))
state = repository.get_state(task_id)
contract = actions.runtime.operation_contract("hole_blind")
pending = PendingAction("act_selector", state.working_head, "Cut bore", ("req_001",), "hole_blind", "Cut a bore", contract["contract_hash"], "selector-key")
selector_state = transition(state, "diagnosis_return_to_action_selection")
self.assertTrue(repository.compare_and_swap(selector_state))
selector_state = transition(selector_state, "action_proposed", pending_action=pending)
self.assertTrue(repository.compare_and_swap(selector_state))
artifacts.write_json_once(task_id, "revisions/rev_001/model.topology.json", {
"snapshot_id": "snapshot_001",
"records": [{
"record_id": "edge_001", "kind": "edge", "executable": True,
"geometry": {"surface_type": "line"}, "owner_feature_ids": ["feature_001"],
}],
})
# Point state at the topology-bearing checkpoint without changing
# the pending action head: this fixture only tests schema token
# narrowing, not a concurrent command transition.
current = repository.get_state(task_id)
repository.compare_and_swap(replace(current, version=current.version + 1, active_revision="rev_001"))
token = next(iter(actions.runtime.selector_tokens(artifacts.read_topology(task_id, "rev_001"))))
before = repository.get_state(task_id)
result = actions.submit_cdsl_fragment(
task_id,
{"feature": {"atomic_id": "hole_blind", "selector_tokens": [token], "params": {"diameter_mm": 2, "depth_mm": 4, "positions": [{"mm": [0, 0, 0]}]}}},
invocation_id="wrong-selector-kind",
)
after = repository.get_state(task_id)
self.assertEqual(result.error.code, ErrorCode.AUTHOR_FORMAT_INVALID)
self.assertEqual(before.version, after.version)
self.assertEqual(list((artifacts.task_dir(task_id) / ".staging").iterdir()), [])
def test_sqlite_cas_idempotency_and_outbox_are_atomic(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository = SqliteTaskRepository(Path(temporary) / "state.sqlite3")
task_id = "cad_123456abcdef"
initial = repository.create_task(task_id, "x")
changed = transition(initial, "draft_updated")
self.assertTrue(repository.compare_and_swap(changed, events=[{"event": "draft"}]))
self.assertFalse(repository.compare_and_swap(changed, events=[{"event": "duplicate"}]))
invocation = repository.begin_invocation(task_id, "inv_a", "same-key")
repository.finish_invocation(invocation.invocation_id, {"ok": True})
repeat = repository.begin_invocation(task_id, "inv_b", "same-key")
self.assertEqual(repeat.invocation_id, "inv_a")
self.assertEqual(repeat.result, {"ok": True})
outbox = repository.pending_outbox()
self.assertEqual(outbox[0]["event"], "draft")
repository.mark_outbox_published(outbox[0]["event_id"])
self.assertEqual(repository.pending_outbox(), [])
def test_invocation_completion_rolls_back_with_a_failed_state_cas(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository = SqliteTaskRepository(Path(temporary) / "state.sqlite3")
task_id = "cad_123456abcdef"
initial = repository.create_task(task_id, "x")
invocation = repository.begin_invocation(task_id, "inv_atomic", "atomic-key")
changed = transition(initial, "draft_updated")
with self.assertRaisesRegex(ValueError, "Invocation is not an active"):
repository.compare_and_swap(
changed,
events=[{"event": "must_not_commit"}],
invocation_id="missing_invocation",
invocation_result={"status": "accepted"},
)
self.assertEqual(repository.get_state(task_id).version, initial.version)
self.assertEqual(repository.ledger_events(task_id), [])
self.assertEqual(repository.get_invocation(task_id, invocation.invocation_id).status, "processing")
self.assertTrue(repository.compare_and_swap(
changed,
events=[{"event": "committed"}],
invocation_id=invocation.invocation_id,
invocation_result={"status": "accepted"},
))
completed = repository.get_invocation(task_id, invocation.invocation_id)
self.assertEqual(completed.status, "finished")
self.assertEqual(completed.result, {"status": "accepted"})
# A concurrent loser or legacy cleanup must never replace the
# result that was committed with the state transition.
repository.finish_invocation(invocation.invocation_id, {"status": "stale"})
self.assertEqual(repository.get_invocation(task_id, invocation.invocation_id).result, {"status": "accepted"})
def test_outbox_retries_delivery_and_uses_a_stable_idempotency_key(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository = SqliteTaskRepository(Path(temporary) / "state.sqlite3")
task_id = "cad_123456abcdef"
initial = repository.create_task(task_id, "x")
self.assertTrue(repository.compare_and_swap(transition(initial, "draft_updated"), events=[{"event": "draft"}]))
class FlakyPublisher:
def __init__(self) -> None:
self.calls = 0
self.events: list[dict[str, object]] = []
async def publish(self, event: dict[str, object]) -> None:
self.calls += 1
if self.calls == 1:
raise RuntimeError("transport disconnected")
self.events.append(event)
publisher = FlakyPublisher()
dispatcher = OutboxDispatcher(repository, publisher)
with self.assertRaisesRegex(RuntimeError, "transport disconnected"):
asyncio.run(dispatcher.dispatch_pending())
self.assertEqual(len(repository.pending_outbox()), 1)
delivered = asyncio.run(dispatcher.dispatch_pending())
self.assertEqual(len(delivered), 1)
self.assertEqual(delivered[0]["event_id"], publisher.events[0]["event_id"])
self.assertEqual(repository.pending_outbox(), [])
idempotent = IdempotentInProcessPublisher()
asyncio.run(idempotent.publish(delivered[0]))
asyncio.run(idempotent.publish(delivered[0]))
self.assertEqual(len(idempotent.delivered), 1)
def test_outbox_dispatch_scope_does_not_acknowledge_another_task(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
repository = SqliteTaskRepository(Path(temporary) / "state.sqlite3")
first = repository.create_task("cad_123456abcdef", "x")
second = repository.create_task("cad_abcdef123456", "y")
self.assertTrue(repository.compare_and_swap(transition(first, "draft_updated"), events=[{"event": "first"}]))
self.assertTrue(repository.compare_and_swap(transition(second, "draft_updated"), events=[{"event": "second"}]))
publisher = IdempotentInProcessPublisher()
first_events = asyncio.run(OutboxDispatcher(repository, publisher).dispatch_pending(task_id=first.task_id))
self.assertEqual([item["task_id"] for item in first_events], [first.task_id])
self.assertEqual([item["task_id"] for item in repository.pending_outbox()], [second.task_id])
def test_action_ledger_jsonl_replays_committed_sqlite_events_idempotently(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
repository = SqliteTaskRepository(root / "state.sqlite3")
artifacts = FileArtifactStore(root / "tasks")
task_id = "cad_123456abcdef"
state = repository.create_task(task_id, "Create a plate.")
artifacts.initialize_task(task_id, "Create a plate.")
updated = transition(state, "draft_updated")
self.assertTrue(repository.compare_and_swap(updated, events=[{"event": "requirements_draft_updated", "draft_revision": 1}]))
events = repository.ledger_events(task_id)
self.assertEqual(artifacts.sync_action_ledger(task_id, events), "actions/action-ledger.jsonl")
self.assertEqual(artifacts.sync_action_ledger(task_id, events), "actions/action-ledger.jsonl")
ledger = artifacts.task_dir(task_id) / "actions" / "action-ledger.jsonl"
lines = [json.loads(line) for line in ledger.read_text(encoding="utf-8").splitlines()]
self.assertEqual(len(lines), 1)
self.assertEqual(lines[0]["schema_version"], "cad.action-ledger.v1")
self.assertEqual(lines[0]["sequence"], events[0]["sequence"])
self.assertEqual(lines[0]["event"], "requirements_draft_updated")
def test_domain_and_application_do_not_import_delivery_or_adapter_implementations(self) -> None:
forbidden_domain = ("fastapi", "sqlite", "pathlib", "app.services", "app.settings", "langchain", "engine")
forbidden_application = ("app.cad_agent.adapters", "app.services", "app.settings")
for path in (ROOT / "backend" / "app" / "cad_agent" / "domain").glob("*.py"):
source = path.read_text(encoding="utf-8")
self.assertFalse(any(token in source for token in forbidden_domain), path.name)
for path in (ROOT / "backend" / "app" / "cad_agent" / "application").glob("*.py"):
source = path.read_text(encoding="utf-8")
self.assertFalse(any(token in source for token in forbidden_application), path.name)
if __name__ == "__main__":
unittest.main()