diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6936c48 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Vendored cas-contracts schemas are hashed byte-for-byte — store exactly, no EOL conversion. +tests/contracts/** -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b13aa8d..2f13bd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,12 @@ jobs: - name: Verify dependency consistency run: python -m pip check + - name: Contract compatibility (pinned cas-contracts v1.1) + # Consumer-side gate: fails red if autogen's pinned CAS contract version + # or the vendored v1.1 schema release drifts. See + # tests/test_contract_compatibility.py for the validated contract surface. + run: python -m pytest tests/test_contract_compatibility.py -q --tb=short + - name: Run full test suite run: python -m pytest -q --tb=short diff --git a/autogen_dashboard/session_runner.py b/autogen_dashboard/session_runner.py index 17c7239..8dabbb5 100644 --- a/autogen_dashboard/session_runner.py +++ b/autogen_dashboard/session_runner.py @@ -43,7 +43,6 @@ from autogen_starter.config import Settings from autogen_starter.providers import ProviderConfigError, collect_provider_statuses, create_model_client from maf_starter.approval_policy import ( - ApprovalScope, classify_validation_commands, classify_write_operations, is_execution_approved, @@ -54,8 +53,6 @@ AutoAnswerRecord, RunOrchestrationState, RunStagePauseKind, - SpecialistHandoff, - SpecialistState, StageName, StageSummary, specialist_role_for_stage, diff --git a/maf_starter/provider_fallback.py b/maf_starter/provider_fallback.py index 11752bd..758b1ec 100644 --- a/maf_starter/provider_fallback.py +++ b/maf_starter/provider_fallback.py @@ -65,7 +65,7 @@ async def get_final_response(self): AnthropicClient = None from maf_starter.config import Settings, activate_run_scope, reset_run_scope -from maf_starter.execution_profile import CLOUD_SAFE_PROFILE, LOCAL_PROFILE, ExecutionProfile +from maf_starter.execution_profile import LOCAL_PROFILE, ExecutionProfile from maf_starter.routing_policy import RoutingPlan, build_routing_plan from maf_starter.routing_types import CapabilityChange, ChainStep, RouteAttempt diff --git a/maf_starter/routing_policy.py b/maf_starter/routing_policy.py index a504fa3..9a55749 100644 --- a/maf_starter/routing_policy.py +++ b/maf_starter/routing_policy.py @@ -6,7 +6,7 @@ from agent_framework import Message from maf_starter.config import Settings -from maf_starter.routing_types import CapabilityChange, ChainStep, RouteAttempt, RouteLane, parse_chain_steps +from maf_starter.routing_types import CapabilityChange, ChainStep, RouteAttempt, RouteLane SIMPLE_KEYWORDS = ( diff --git a/maf_starter/team_factory.py b/maf_starter/team_factory.py index d68a53c..8e5e903 100644 --- a/maf_starter/team_factory.py +++ b/maf_starter/team_factory.py @@ -4,7 +4,7 @@ from agent_framework import WorkflowBuilder -from maf_starter.agent_factory import build_agent, build_agent_for_model +from maf_starter.agent_factory import build_agent_for_model from maf_starter.config import Settings, load_settings from maf_starter.orchestration import ( CANONICAL_STAGE_NAMES, diff --git a/requirements.txt b/requirements.txt index d1bfa6f..8856013 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ autogen-agentchat>=0.7.5,<0.8.0 autogen-core>=0.7.5,<0.8.0 autogen-ext[anthropic,ollama,openai]>=0.7.5,<0.8.0 fastapi>=0.115,<1.0 +jsonschema>=4.23,<5.0 openapi-spec-validator>=0.7,<1.0 pydantic>=2.0,<3.0 pytest>=8.0,<10.0 diff --git a/tests/contracts/cas-contracts/v1.1.0/common.schema.json b/tests/contracts/cas-contracts/v1.1.0/common.schema.json new file mode 100644 index 0000000..60ab249 --- /dev/null +++ b/tests/contracts/cas-contracts/v1.1.0/common.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.coding-autopilot.dev/v1.1/common.schema.json", + "title": "CAS Common Definitions v1.1", + "$defs": { + "actor": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type"], + "properties": { + "id": { "type": "string", "minLength": 1, "maxLength": 256 }, + "type": { + "type": "string", + "enum": ["human", "agent", "service", "workflow"] + }, + "displayName": { "type": "string", "minLength": 1, "maxLength": 256 } + } + }, + "traceContext": { + "type": "object", + "additionalProperties": false, + "required": ["traceparent"], + "properties": { + "traceparent": { + "type": "string", + "pattern": "^[\\da-f]{2}-[\\da-f]{32}-[\\da-f]{16}-[\\da-f]{2}$" + }, + "tracestate": { "type": "string", "maxLength": 512 } + } + }, + "lifecycleMetadata": { + "type": "object", + "required": [ + "correlationId", + "promptId", + "runId", + "repo", + "actor", + "timestamp", + "schemaVersion", + "traceContext" + ], + "properties": { + "correlationId": { "type": "string", "minLength": 1, "maxLength": 128 }, + "promptId": { "type": "string", "minLength": 1, "maxLength": 128 }, + "runId": { "type": "string", "minLength": 1, "maxLength": 128 }, + "repo": { + "type": "string", + "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" + }, + "actor": { "$ref": "#/$defs/actor" }, + "timestamp": { "type": "string", "format": "date-time" }, + "schemaVersion": { "const": "1.1.0" }, + "traceContext": { "$ref": "#/$defs/traceContext" } + } + }, + "evidence": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "uri"], + "properties": { + "kind": { "type": "string", "minLength": 1, "maxLength": 64 }, + "uri": { "type": "string", "format": "uri" }, + "sha256": { "type": "string", "pattern": "^[\\da-f]{64}$" } + } + }, + "phaseId": { + "type": "string", + "enum": [ + "understand", + "research", + "analyze", + "plan", + "risk-assessment", + "implement", + "verify", + "review", + "improve", + "document", + "update-memory", + "finished" + ] + }, + "executionBatch": { + "type": "string", + "enum": ["discovery", "design", "change", "assurance", "closure"] + }, + "phaseStatus": { + "type": "string", + "enum": [ + "pending", + "ready", + "running", + "passed", + "failed", + "invalidated", + "rolled-back", + "waiting", + "terminal" + ] + } + } +} diff --git a/tests/contracts/cas-contracts/v1.1.0/manifest.json b/tests/contracts/cas-contracts/v1.1.0/manifest.json new file mode 100644 index 0000000..c0bd518 --- /dev/null +++ b/tests/contracts/cas-contracts/v1.1.0/manifest.json @@ -0,0 +1,35 @@ +{ + "version": "1.1.0", + "schemas": [ + { + "id": "https://schemas.coding-autopilot.dev/v1.1/common.schema.json", + "path": "common.schema.json", + "sha256": "078026d60c219658fe0fc5cf7c4f786acdcf0c492121f7d688d5078b33ca25af" + }, + { + "id": "https://schemas.coding-autopilot.dev/v1.1/phase-execution-request.schema.json", + "path": "phase-execution-request.schema.json", + "sha256": "927ffda5ba809e58627ba25b3275355644b510b0ae1c76f446aa7b7f291f08fd" + }, + { + "id": "https://schemas.coding-autopilot.dev/v1.1/phase-execution-result.schema.json", + "path": "phase-execution-result.schema.json", + "sha256": "1d353e20cf6338c2de4a9bec0bad83ca0dec6c9feb5434c5e6264fdc075e7c25" + }, + { + "id": "https://schemas.coding-autopilot.dev/v1.1/phase-verification-result.schema.json", + "path": "phase-verification-result.schema.json", + "sha256": "ec5b97a2bf75892530442f666d23a8b6dbb8f13bda0efe49553d3f64cd41b995" + }, + { + "id": "https://schemas.coding-autopilot.dev/v1.1/sdlc-lifecycle-event.schema.json", + "path": "sdlc-lifecycle-event.schema.json", + "sha256": "bd955637761679d3b6e8ee09fb7b693b1d0362615b0865204c5ae3bacb3809dc" + }, + { + "id": "https://schemas.coding-autopilot.dev/v1.1/sdlc-profile.schema.json", + "path": "sdlc-profile.schema.json", + "sha256": "f4974e911e7cb88191c1a331470c8cd7a79d1c7311c963dd70cd9c8f524ebe45" + } + ] +} diff --git a/tests/contracts/cas-contracts/v1.1.0/phase-execution-request.schema.json b/tests/contracts/cas-contracts/v1.1.0/phase-execution-request.schema.json new file mode 100644 index 0000000..a9b9ab7 --- /dev/null +++ b/tests/contracts/cas-contracts/v1.1.0/phase-execution-request.schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.coding-autopilot.dev/v1.1/phase-execution-request.schema.json", + "title": "PhaseExecutionRequest", + "type": "object", + "allOf": [ + { "$ref": "common.schema.json#/$defs/lifecycleMetadata" }, + { + "type": "object", + "required": ["kind", "profileVersion", "phase", "batch", "goal", "constraints"], + "properties": { + "kind": { "const": "PhaseExecutionRequest" }, + "profileVersion": { "type": "string", "const": "v1.1" }, + "phase": { "$ref": "common.schema.json#/$defs/phaseId" }, + "batch": { "$ref": "common.schema.json#/$defs/executionBatch" }, + "goal": { "type": "string", "minLength": 1, "maxLength": 5000 }, + "constraints": { + "type": "array", + "items": { "type": "string", "minLength": 1, "maxLength": 1000 } + }, + "validatedInputs": { "type": "array", "items": { "type": "string" } }, + "priorEvidence": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidence" } }, + "requiredOutputSchema": { "type": "string", "minLength": 1, "maxLength": 256 }, + "requiredArtifacts": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 256 } }, + "successCriteria": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 1000 } }, + "verifier": { "type": "string", "minLength": 1, "maxLength": 256 }, + "failureBehavior": { "type": "string", "minLength": 1, "maxLength": 5000 }, + "rollbackBehavior": { "type": "string", "minLength": 1, "maxLength": 5000 }, + "memoryCandidateFields": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 256 } }, + "humanEscalationConditions": { "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 1000 } } + } + } + ], + "unevaluatedProperties": false +} diff --git a/tests/contracts/cas-contracts/v1.1.0/phase-execution-result.schema.json b/tests/contracts/cas-contracts/v1.1.0/phase-execution-result.schema.json new file mode 100644 index 0000000..db58ea3 --- /dev/null +++ b/tests/contracts/cas-contracts/v1.1.0/phase-execution-result.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.coding-autopilot.dev/v1.1/phase-execution-result.schema.json", + "title": "PhaseExecutionResult", + "type": "object", + "allOf": [ + { "$ref": "common.schema.json#/$defs/lifecycleMetadata" }, + { + "type": "object", + "required": ["kind", "phase", "status", "promptDigest"], + "properties": { + "kind": { "const": "PhaseExecutionResult" }, + "phase": { "$ref": "common.schema.json#/$defs/phaseId" }, + "batch": { "$ref": "common.schema.json#/$defs/executionBatch" }, + "status": { "$ref": "common.schema.json#/$defs/phaseStatus" }, + "promptDigest": { "type": "string", "pattern": "^[\\da-f]{64}$" }, + "artifacts": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidence" } }, + "output": { "type": "object" }, + "promptMetadata": { "type": "object" }, + "sanitizedPrompt": { "type": "string", "maxLength": 50000 }, + "verifier": { "type": "string", "minLength": 1, "maxLength": 256 }, + "checkpoint": { "type": "string", "minLength": 1, "maxLength": 256 } + } + } + ], + "unevaluatedProperties": false +} diff --git a/tests/contracts/cas-contracts/v1.1.0/phase-verification-result.schema.json b/tests/contracts/cas-contracts/v1.1.0/phase-verification-result.schema.json new file mode 100644 index 0000000..90b6819 --- /dev/null +++ b/tests/contracts/cas-contracts/v1.1.0/phase-verification-result.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.coding-autopilot.dev/v1.1/phase-verification-result.schema.json", + "title": "PhaseVerificationResult", + "type": "object", + "allOf": [ + { "$ref": "common.schema.json#/$defs/lifecycleMetadata" }, + { + "type": "object", + "required": ["kind", "phase", "verifier", "outcome"], + "properties": { + "kind": { "const": "PhaseVerificationResult" }, + "phase": { "$ref": "common.schema.json#/$defs/phaseId" }, + "verifier": { "type": "string", "minLength": 1, "maxLength": 256 }, + "outcome": { "enum": ["passed", "failed", "inconclusive"] }, + "invalidatedPhaseIds": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/phaseId" }, + "uniqueItems": true + }, + "evidence": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidence" } }, + "reason": { "type": "string", "maxLength": 5000 } + } + } + ], + "unevaluatedProperties": false +} diff --git a/tests/contracts/cas-contracts/v1.1.0/sdlc-lifecycle-event.schema.json b/tests/contracts/cas-contracts/v1.1.0/sdlc-lifecycle-event.schema.json new file mode 100644 index 0000000..0d7d8bf --- /dev/null +++ b/tests/contracts/cas-contracts/v1.1.0/sdlc-lifecycle-event.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.coding-autopilot.dev/v1.1/sdlc-lifecycle-event.schema.json", + "title": "SdlcLifecycleEvent", + "type": "object", + "allOf": [ + { "$ref": "common.schema.json#/$defs/lifecycleMetadata" }, + { + "type": "object", + "required": ["kind", "eventType", "phase"], + "properties": { + "kind": { "const": "SdlcLifecycleEvent" }, + "eventType": { "type": "string", "minLength": 1, "maxLength": 128 }, + "phase": { "$ref": "common.schema.json#/$defs/phaseId" }, + "batch": { "$ref": "common.schema.json#/$defs/executionBatch" }, + "status": { "$ref": "common.schema.json#/$defs/phaseStatus" }, + "message": { "type": "string", "maxLength": 5000 }, + "evidence": { "type": "array", "items": { "$ref": "common.schema.json#/$defs/evidence" } } + } + } + ], + "unevaluatedProperties": false +} diff --git a/tests/contracts/cas-contracts/v1.1.0/sdlc-profile.schema.json b/tests/contracts/cas-contracts/v1.1.0/sdlc-profile.schema.json new file mode 100644 index 0000000..92a0404 --- /dev/null +++ b/tests/contracts/cas-contracts/v1.1.0/sdlc-profile.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.coding-autopilot.dev/v1.1/sdlc-profile.schema.json", + "title": "SdlcProfile", + "type": "object", + "allOf": [ + { "$ref": "common.schema.json#/$defs/lifecycleMetadata" }, + { + "type": "object", + "required": ["kind", "profileId", "profileVersion", "phases", "goalBudget"], + "properties": { + "kind": { "const": "SdlcProfile" }, + "profileId": { "type": "string", "minLength": 1, "maxLength": 128 }, + "profileVersion": { "type": "string", "pattern": "^v1\\.1$" }, + "goalBudget": { "type": "integer", "minimum": 1 }, + "phases": { + "type": "array", + "minItems": 12, + "maxItems": 12, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "batch", "dependencies", "verifier"], + "properties": { + "id": { "$ref": "common.schema.json#/$defs/phaseId" }, + "name": { "type": "string", "minLength": 1, "maxLength": 128 }, + "batch": { "$ref": "common.schema.json#/$defs/executionBatch" }, + "dependencies": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/phaseId" }, + "uniqueItems": true + }, + "verifier": { "type": "string", "minLength": 1, "maxLength": 256 }, + "requiredArtifacts": { + "type": "array", + "items": { "type": "string", "minLength": 1, "maxLength": 256 } + }, + "successCriteria": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1, "maxLength": 1000 } + }, + "rollbackTo": { "$ref": "common.schema.json#/$defs/phaseId" }, + "maxAttempts": { "type": "integer", "minimum": 1 } + } + } + }, + "goalAttempts": { "type": "integer", "minimum": 1 }, + "iterations": { "type": "integer", "minimum": 1 }, + "runtimeMinutes": { "type": "integer", "minimum": 1 }, + "modelCalls": { "type": "integer", "minimum": 1 }, + "noProgressLimit": { "type": "integer", "minimum": 1 }, + "repositoryOverrides": { + "type": "object", + "additionalProperties": false, + "properties": { + "promptFragments": { "type": "object" }, + "verifierCommands": { "type": "object" }, + "requiredArtifacts": { "type": "object" }, + "phaseLimits": { "type": "object" } + } + } + } + } + ], + "unevaluatedProperties": false +} diff --git a/tests/test_contract_compatibility.py b/tests/test_contract_compatibility.py new file mode 100644 index 0000000..a165d6e --- /dev/null +++ b/tests/test_contract_compatibility.py @@ -0,0 +1,198 @@ +"""Consumer-side contract-compatibility check against pinned cas-contracts schemas. + +autogen consumes the CAS v1.1 SDLC contract family. Its dashboard/session models +(``autogen_dashboard/schemas.py``) are an *internal* snake_case representation and +are intentionally NOT the wire shape. The authoritative wire shape is the camelCase +JSON Schema published by ``cas-contracts`` at ``v1.1`` (schemaVersion ``1.1.0``). + +This module pins the contract version autogen targets and fails CI (red check) when: + +* the pinned ``schemaVersion`` / ``profileVersion`` autogen emits no longer matches + the vendored cas-contracts release, +* the vendored schema release has been tampered with (manifest SHA-256 drift), or +* a representative wire payload for each contract autogen produces/consumes stops + validating against the pinned schema. + +Cross-repo reference note: cas-contracts is a *separate* repo. In this local polyrepo +CI checks out only this repo, so we vendor the pinned ``v1.1.0`` release under +``tests/contracts/cas-contracts/v1.1.0/`` (same convention as cas-reference-product). +When the sibling ``../../cas-contracts`` checkout is present (local dev), we additionally +assert the vendored copy has not drifted from the upstream source of truth. +""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any + +import pytest + +jsonschema = pytest.importorskip("jsonschema") +referencing = pytest.importorskip("referencing") +from jsonschema import Draft202012Validator # noqa: E402 +from referencing import Registry, Resource # noqa: E402 + +# The contract version autogen's models pin themselves to. These MUST stay in lockstep +# with autogen_dashboard/schemas.py (SdlcProfileModel.profile_version, the various +# kind/profile_version literals) and with the vendored release below. +PINNED_SCHEMA_VERSION = "1.1.0" +PINNED_PROFILE_VERSION = "v1.1" + +CONTRACT_ROOT = Path(__file__).parent / "contracts" / "cas-contracts" / f"v{PINNED_SCHEMA_VERSION}" +UPSTREAM_ROOT = ( + Path(__file__).resolve().parents[2] + / "cas-contracts" + / "registry" + / "releases" + / f"v{PINNED_SCHEMA_VERSION}" +) + + +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def _registry() -> Registry[Any]: + resources = [] + for path in CONTRACT_ROOT.glob("*.schema.json"): + schema = _load_json(path) + resources.append((schema["$id"], Resource.from_contents(schema))) + return Registry().with_resources(resources) + + +def _validate(schema_name: str, instance: dict[str, Any]) -> None: + schema = _load_json(CONTRACT_ROOT / schema_name) + Draft202012Validator(schema, registry=_registry()).validate(instance) + + +# Shared lifecycle metadata every v1.1 contract requires (common.schema.json#lifecycleMetadata). +def _lifecycle() -> dict[str, Any]: + return { + "correlationId": "corr-001", + "promptId": "prompt-001", + "runId": "run-001", + "repo": "Coding-Autopilot-System/autogen", + "actor": {"id": "maf-manager", "type": "agent"}, + "timestamp": "2026-07-03T00:00:00Z", + "schemaVersion": PINNED_SCHEMA_VERSION, + "traceContext": { + "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" + }, + } + + +def test_vendored_release_matches_manifest_hashes() -> None: + """The vendored pinned release must be internally consistent (tamper check).""" + manifest = _load_json(CONTRACT_ROOT / "manifest.json") + assert manifest["version"] == PINNED_SCHEMA_VERSION + for entry in manifest["schemas"]: + content = (CONTRACT_ROOT / entry["path"]).read_bytes() + assert hashlib.sha256(content).hexdigest() == entry["sha256"], entry["path"] + + +def test_pinned_version_is_the_version_autogen_emits() -> None: + """autogen's models pin profileVersion v1.1 -> schemaVersion 1.1.0. + + Guards against a silent bump of the vendored release without updating the models. + """ + common = _load_json(CONTRACT_ROOT / "common.schema.json") + schema_version_const = common["$defs"]["lifecycleMetadata"]["properties"]["schemaVersion"]["const"] + assert schema_version_const == PINNED_SCHEMA_VERSION + + request = _load_json(CONTRACT_ROOT / "phase-execution-request.schema.json") + profile_version_const = ( + request["allOf"][1]["properties"]["profileVersion"]["const"] + ) + assert profile_version_const == PINNED_PROFILE_VERSION + + +def test_phase_execution_request_wire_payload_conforms() -> None: + payload = { + **_lifecycle(), + "kind": "PhaseExecutionRequest", + "profileVersion": PINNED_PROFILE_VERSION, + "phase": "implement", + "batch": "change", + "goal": "Apply the bounded change for the current work item.", + "constraints": ["No secrets", "Stay within budget"], + } + _validate("phase-execution-request.schema.json", payload) + + +def test_phase_execution_result_wire_payload_conforms() -> None: + payload = { + **_lifecycle(), + "kind": "PhaseExecutionResult", + "phase": "implement", + "batch": "change", + "status": "passed", + "promptDigest": "a" * 64, + "artifacts": [{"kind": "patch", "uri": "cas://evidence/patch/1"}], + "verifier": "mutation-owner", + } + _validate("phase-execution-result.schema.json", payload) + + +def test_phase_verification_result_wire_payload_conforms() -> None: + payload = { + **_lifecycle(), + "kind": "PhaseVerificationResult", + "phase": "verify", + "verifier": "verifier", + "outcome": "passed", + "invalidatedPhaseIds": [], + } + _validate("phase-verification-result.schema.json", payload) + + +def test_sdlc_profile_wire_payload_conforms() -> None: + # v1.1 sdlc-profile requires exactly 12 phases — the same 12 autogen models enumerate. + phase_ids = [ + "understand", "research", "analyze", "plan", "risk-assessment", "implement", + "verify", "review", "improve", "document", "update-memory", "finished", + ] + batch_for = { + "understand": "discovery", "research": "discovery", "analyze": "discovery", + "plan": "design", "risk-assessment": "design", "implement": "change", + "verify": "assurance", "review": "assurance", "improve": "assurance", + "document": "closure", "update-memory": "closure", "finished": "closure", + } + payload = { + **_lifecycle(), + "kind": "SdlcProfile", + "profileId": "cas-sdlc-v1", + "profileVersion": PINNED_PROFILE_VERSION, + "goalBudget": 17, + "phases": [ + { + "id": pid, + "name": pid.replace("-", " ").title(), + "batch": batch_for[pid], + "dependencies": [] if i == 0 else [phase_ids[i - 1]], + "verifier": "repo-verifier", + "successCriteria": ["criterion met"], + } + for i, pid in enumerate(phase_ids) + ], + } + _validate("sdlc-profile.schema.json", payload) + + +@pytest.mark.skipif( + not UPSTREAM_ROOT.exists(), + reason="sibling cas-contracts checkout not present (expected in isolated CI)", +) +def test_vendored_release_matches_upstream_source_of_truth() -> None: + """Local-only drift guard: vendored copy must equal the sibling cas-contracts release. + + Skipped automatically in isolated CI where the sibling repo is not checked out. + """ + for path in CONTRACT_ROOT.glob("*.json"): + upstream = UPSTREAM_ROOT / path.name + assert upstream.exists(), f"upstream missing {path.name}" + assert ( + hashlib.sha256(path.read_bytes()).hexdigest() + == hashlib.sha256(upstream.read_bytes()).hexdigest() + ), f"vendored {path.name} drifted from upstream cas-contracts {PINNED_SCHEMA_VERSION}" diff --git a/tests/test_loop_workers.py b/tests/test_loop_workers.py index 3e7b89b..aebec48 100644 --- a/tests/test_loop_workers.py +++ b/tests/test_loop_workers.py @@ -72,10 +72,13 @@ def __init__(self) -> None: self.fan_out = None self.fan_in = None def add_fan_out_edges(self, source, targets): - self.fan_out = (source, tuple(targets)); return self + self.fan_out = (source, tuple(targets)) + return self def add_fan_in_edges(self, sources, target): - self.fan_in = (tuple(sources), target); return self - def build(self): return self + self.fan_in = (tuple(sources), target) + return self + def build(self): + return self builder = BuilderSpy() result = build_maf_fanout_workflow("dispatcher", [1, 2, 3, 4], "aggregator", builder=builder) diff --git a/tests/test_phase1_api.py b/tests/test_phase1_api.py index 4063ad0..4173edf 100644 --- a/tests/test_phase1_api.py +++ b/tests/test_phase1_api.py @@ -7,7 +7,7 @@ from autogen_dashboard.app import create_app from autogen_dashboard.dependencies import get_session_service -from autogen_dashboard.schemas import RepoContext, SessionActionResponse, SessionCreateResponse, SessionDetail +from autogen_dashboard.schemas import RepoContext, SessionDetail def utc_now() -> datetime: diff --git a/tests/test_phase3_specialists.py b/tests/test_phase3_specialists.py index 8cd9137..9951fc7 100644 --- a/tests/test_phase3_specialists.py +++ b/tests/test_phase3_specialists.py @@ -1,6 +1,5 @@ from __future__ import annotations -import os import shutil import unittest import uuid diff --git a/tests/test_phase7_e2e.py b/tests/test_phase7_e2e.py index 6622902..213bc46 100644 --- a/tests/test_phase7_e2e.py +++ b/tests/test_phase7_e2e.py @@ -1,5 +1,3 @@ -from __future__ import annotations - """Phase 7 end-to-end integration tests. Validates three Phase 7 success criteria: @@ -7,6 +5,7 @@ 2. Local profile accepts all providers without raising. 3. Async dispatch via WorkerBoundary.submit_async returns run_id without blocking. """ +from __future__ import annotations import asyncio import unittest diff --git a/tests/test_workspace_contract.py b/tests/test_workspace_contract.py index 7130462..0443b3e 100644 --- a/tests/test_workspace_contract.py +++ b/tests/test_workspace_contract.py @@ -2,7 +2,6 @@ import shutil import subprocess -import tempfile import unittest import uuid from datetime import datetime, timezone