From adf60ddd6b142c017e02b67e8b74d54143e60a4e Mon Sep 17 00:00:00 2001 From: Alain Prasquier Date: Sun, 23 Aug 2026 15:41:35 +0300 Subject: [PATCH 1/5] feat: add generic job setup contract --- src/supervaizer/__init__.py | 1 + src/supervaizer/contracts.py | 25 +++++++++++++++++++ tests/test_a2a.py | 11 ++++++++- tests/test_contracts.py | 48 ++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/supervaizer/__init__.py b/src/supervaizer/__init__.py index 115f12b..6177720 100644 --- a/src/supervaizer/__init__.py +++ b/src/supervaizer/__init__.py @@ -175,6 +175,7 @@ "V2DatasetDefinition": ("supervaizer.contracts", "V2DatasetDefinition"), "V2Effect": ("supervaizer.contracts", "V2Effect"), "V2JobPolicy": ("supervaizer.contracts", "V2JobPolicy"), + "V2JobSetupPolicy": ("supervaizer.contracts", "V2JobSetupPolicy"), "V2JobSyncPolicy": ("supervaizer.contracts", "V2JobSyncPolicy"), "V2JobSnapshot": ("supervaizer.contracts", "V2JobSnapshot"), "V2JobSource": ("supervaizer.contracts", "V2JobSource"), diff --git a/src/supervaizer/contracts.py b/src/supervaizer/contracts.py index 08827a5..da469a2 100644 --- a/src/supervaizer/contracts.py +++ b/src/supervaizer/contracts.py @@ -366,6 +366,19 @@ class V2JobPolicy(ContractModel): offline_start_policy: Literal["block"] = "block" offline_running_policy: Literal["fail_in_studio"] = "fail_in_studio" sync: V2JobSyncPolicy | None = None + setup: "V2JobSetupPolicy | None" = None + + +class V2JobSetupPolicy(ContractModel): + """Generic agent-declared job setup actions.""" + + preview_action: str = "job.start.preview" + start_action: str = "job.start" + submit_action: str = "step.awaiting.submit" + action_scopes: list[Literal["workspace", "job", "case", "step"]] = Field( + default_factory=lambda: ["workspace", "job", "case", "step"] + ) + plan: dict[str, Any] | None = None class V2ResourceDisplayDefinition(ContractModel): @@ -600,6 +613,7 @@ def build_v2_agent_registration( *_resource_action_ids(resource_definitions), *_dataset_action_ids(dataset_definitions), *(_job_sync_actions(sync_policy)), + *(_job_setup_actions(sync_policy)), *_workspace_binding_action_ids(workspace_binding_definition), *_agent_method_action_ids(agent_method_definitions), ]) @@ -731,6 +745,16 @@ def _job_sync_actions(job_policy: V2JobPolicy) -> list[str]: return [job_policy.sync.action] +def _job_setup_actions(job_policy: V2JobPolicy) -> list[str]: + if job_policy.setup is None: + return [] + return [ + job_policy.setup.preview_action, + job_policy.setup.start_action, + job_policy.setup.submit_action, + ] + + def _agent_method_action_ids(agent_methods: V2AgentMethods | None) -> list[str]: if agent_methods is None: return [] @@ -880,6 +904,7 @@ class V2ActionResult(ContractModel): effects: list[V2Effect] = Field(default_factory=list) job_state: V2JobStateSnapshot | None = None replay_safety: V2ReplaySafetyMetadata | None = None + setup_plan: dict[str, Any] | None = None class V2SurfaceResult(ContractModel): diff --git a/tests/test_a2a.py b/tests/test_a2a.py index 9127859..36f2908 100644 --- a/tests/test_a2a.py +++ b/tests/test_a2a.py @@ -464,9 +464,14 @@ def test_a2a_controller_dispatches_registered_v2_action( def start_job(request: V2ActionRequest) -> V2ActionResult: assert request.action == "job.start" + assert request.mission_id == "mission-1" + assert request.job_id == "job-123" + assert request.case_id == "case-123" + assert request.step_id == "step-123" return V2ActionResult( status="ok", effects=[V2Effect(type="job.created", job_id="job-123")], + setup_plan={"agent_descriptor": {"campaign_template_id": "opaque-id"}}, ) register_v2_action_handler(server_fixture, "job.start", start_job) @@ -479,7 +484,8 @@ def start_job(request: V2ActionRequest) -> V2ActionResult: "jsonrpc": "2.0", "id": "rpc-3", "method": SUPERVAIZER_ACTION_INVOKE_METHOD, - "params": _v2_action_payload(action="job.start", agent_slug=agent.slug), + "params": _v2_action_payload(action="job.start", agent_slug=agent.slug) + | {"job_id": "job-123", "case_id": "case-123", "step_id": "step-123"}, }, ) @@ -490,6 +496,9 @@ def start_job(request: V2ActionRequest) -> V2ActionResult: assert payload["result"]["effects"] == [ {"type": "job.created", "job_id": "job-123"} ] + assert payload["result"]["setup_plan"] == { + "agent_descriptor": {"campaign_template_id": "opaque-id"} + } def test_a2a_workspace_authorization_missing_token_blocks_action_handler( diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 844321b..674f171 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -545,6 +545,41 @@ def test_v2_action_request_and_result_fixture() -> None: ] +def test_v2_job_setup_policy_round_trips_through_registration_builder() -> None: + registration = build_v2_agent_registration( + agent_id="agent-1", + agent_slug="agent-1", + display_name="Agent 1", + agent_card_url="/.well-known/agent.json", + controller_url="/a2a", + a2ui_catalog_version="test.0", + job_policy={ + "setup": { + "preview_action": "job.start.preview", + "start_action": "job.start", + "submit_action": "step.awaiting.submit", + "action_scopes": ["workspace", "job", "case", "step"], + } + }, + ) + payload = registration.model_dump(mode="json") + round_trip = SupervaizerV2AgentRegistrationContract.model_validate(payload) + + assert "setup" not in payload["capabilities"] + assert round_trip.job_policy.setup is not None + assert round_trip.job_policy.setup.action_scopes == [ + "workspace", + "job", + "case", + "step", + ] + assert set(round_trip.capabilities.actions) >= { + "job.start.preview", + "job.start", + "step.awaiting.submit", + } + + def test_v2_action_result_validates_replay_safety() -> None: with pytest.raises(ValidationError): V2ActionResult.model_validate({ @@ -553,6 +588,19 @@ def test_v2_action_result_validates_replay_safety() -> None: }) +def test_v2_action_result_requires_setup_plan_mapping() -> None: + result = V2ActionResult.model_validate({ + "status": "ok", + "setup_plan": {"agent_descriptor": {"campaign_template_id": "opaque-id"}}, + }) + + assert result.setup_plan == { + "agent_descriptor": {"campaign_template_id": "opaque-id"} + } + with pytest.raises(ValidationError): + V2ActionResult.model_validate({"status": "ok", "setup_plan": ["invalid"]}) + + def test_v2_surface_request_and_result_models() -> None: request = V2SurfaceRequest.model_validate({ "request_id": "surface-request-1", From 937927bf17419232c9ec720bbeac652a1d4929f9 Mon Sep 17 00:00:00 2001 From: Alain Prasquier Date: Wed, 26 Aug 2026 17:22:48 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=90=9B=20fix(contracts):=20stop=20V2J?= =?UTF-8?q?obSetupPolicy=20defaulting=20to=20every=20action=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An agent opting in with job_policy={"setup": {}} advertised support for the workspace, job, case and step scopes without declaring any, which broadens the contract Studio sees. Default to an empty list, matching V2JobSyncPolicy.supported_statuses. Also declare V2JobSetupPolicy before V2JobPolicy so the generated model reference renders the real type instead of a ForwardRef. --- src/supervaizer/contracts.py | 26 +++++++++++++------------- tests/test_contracts.py | 7 +++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/supervaizer/contracts.py b/src/supervaizer/contracts.py index da469a2..8942b32 100644 --- a/src/supervaizer/contracts.py +++ b/src/supervaizer/contracts.py @@ -361,14 +361,6 @@ class V2JobSyncPolicy(ContractModel): supported_statuses: list[str] = Field(default_factory=list) -class V2JobPolicy(ContractModel): - default_timeout_seconds: int | None = None - offline_start_policy: Literal["block"] = "block" - offline_running_policy: Literal["fail_in_studio"] = "fail_in_studio" - sync: V2JobSyncPolicy | None = None - setup: "V2JobSetupPolicy | None" = None - - class V2JobSetupPolicy(ContractModel): """Generic agent-declared job setup actions.""" @@ -376,11 +368,19 @@ class V2JobSetupPolicy(ContractModel): start_action: str = "job.start" submit_action: str = "step.awaiting.submit" action_scopes: list[Literal["workspace", "job", "case", "step"]] = Field( - default_factory=lambda: ["workspace", "job", "case", "step"] + default_factory=list ) plan: dict[str, Any] | None = None +class V2JobPolicy(ContractModel): + default_timeout_seconds: int | None = None + offline_start_policy: Literal["block"] = "block" + offline_running_policy: Literal["fail_in_studio"] = "fail_in_studio" + sync: V2JobSyncPolicy | None = None + setup: V2JobSetupPolicy | None = None + + class V2ResourceDisplayDefinition(ContractModel): title_field: str | None = None columns: list[str] = Field(default_factory=list) @@ -599,7 +599,7 @@ def build_v2_agent_registration( dashboard_definitions = _contract_list(dashboards, V2DashboardDefinition) workspace_binding_definition = _workspace_binding(workspace_binding) agent_method_definitions = _agent_methods(agent_methods) - sync_policy = _job_policy(job_policy) + job_policy_definition = _job_policy(job_policy) capability_surfaces = _unique_strings([ *surfaces, @@ -612,8 +612,8 @@ def build_v2_agent_registration( *actions, *_resource_action_ids(resource_definitions), *_dataset_action_ids(dataset_definitions), - *(_job_sync_actions(sync_policy)), - *(_job_setup_actions(sync_policy)), + *_job_sync_actions(job_policy_definition), + *_job_setup_actions(job_policy_definition), *_workspace_binding_action_ids(workspace_binding_definition), *_agent_method_action_ids(agent_method_definitions), ]) @@ -645,7 +645,7 @@ def build_v2_agent_registration( case_lanes=_contract_list(case_lanes, V2CaseLaneDefinition), artifact_types=_contract_list(artifact_types, V2ArtifactTypeDefinition), ), - job_policy=sync_policy, + job_policy=job_policy_definition, resources=resource_definitions, datasets=dataset_definitions, dashboards=dashboard_definitions, diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 674f171..86166d9 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -38,6 +38,7 @@ V2DashboardWidgetDefinition, V2DashboardWidgetVisualization, V2Effect, + V2JobSetupPolicy, V2JobStateSnapshot, V2JobSyncResult, V2ReplaySafetyMetadata, @@ -580,6 +581,12 @@ def test_v2_job_setup_policy_round_trips_through_registration_builder() -> None: } +def test_v2_job_setup_policy_declares_no_scopes_by_default() -> None: + policy = V2JobSetupPolicy() + + assert policy.action_scopes == [] + + def test_v2_action_result_validates_replay_safety() -> None: with pytest.raises(ValidationError): V2ActionResult.model_validate({ From 478ae49ea48fdadf4bbe3c6a9378322d8a4bd36b Mon Sep 17 00:00:00 2001 From: Alain Prasquier Date: Wed, 26 Aug 2026 17:22:52 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=93=9D=20docs:=20regenerate=20model?= =?UTF-8?q?=20reference=20and=20OpenAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picks up the job setup contract (V2JobPolicy.setup, V2JobSetupPolicy, V2ActionResult.setup_plan) plus accumulated drift since 0.20.1. --- docs/api/openapi.json | 71 +++-- docs/model_reference/model_core.md | 21 +- docs/model_reference/model_extra.md | 455 ++++++++++++++++++++++++---- 3 files changed, 464 insertions(+), 83 deletions(-) diff --git a/docs/api/openapi.json b/docs/api/openapi.json index fc272bb..435428b 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Supervaize API", - "description": "API version: v1 Controller version: 0.20.1\n\nAPI for controlling and managing Supervaize agents. \n\nMore information at [https://doc.supervaize.com](https://doc.supervaize.com)\n\n## Authentication\n\nSome endpoints require API key authentication. Protected endpoints expect the API key in the X-API-Key header.\n\n[Swagger](/docs)\n[Redoc](/redoc)\n[OpenAPI](/openapi.json)\n", + "description": "API version: v1 Controller version: 1.3.1\n\nAPI for controlling and managing Supervaize agents. \n\nMore information at [https://doc.supervaize.com](https://doc.supervaize.com)\n\n## Authentication\n\nSome endpoints require API key authentication. Protected endpoints expect the API key in the X-API-Key header.\n\n[Swagger](/docs)\n[Redoc](/redoc)\n[OpenAPI](/openapi.json)\n", "termsOfService": "https://supervaize.com/terms/", "contact": { "name": "Support Team", @@ -1768,17 +1768,35 @@ "summary": "A2A JSON-RPC Controller", "description": "Dispatches Supervaizer v2 controller methods over A2A JSON-RPC.", "operationId": "post_a2a_controller_a2a_post", + "parameters": [ + { + "name": "X-API-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "requestBody": { + "required": true, "content": { "application/json": { "schema": { - "additionalProperties": true, "type": "object", + "additionalProperties": true, "title": "Body" } } - }, - "required": true + } }, "responses": { "200": { @@ -1786,8 +1804,8 @@ "content": { "application/json": { "schema": { - "additionalProperties": true, "type": "object", + "additionalProperties": true, "title": "Response Post A2A Controller A2A Post" } } @@ -1815,6 +1833,24 @@ "summary": "A2A SSE Event Stream", "description": "Streams Supervaizer v2 controller effects over Server-Sent Events.", "operationId": "get_a2a_events_a2a_events_get", + "parameters": [ + { + "name": "X-API-Key", + "in": "header", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "X-Api-Key" + } + } + ], "responses": { "200": { "description": "Successful Response", @@ -1823,6 +1859,16 @@ "schema": {} } } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } } } } @@ -3973,17 +4019,6 @@ } ], "title": "Server Agent Onboarding Status" - }, - "server_encrypted_parameters": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Server Encrypted Parameters" } }, "type": "object", @@ -4152,7 +4187,7 @@ "type": "string", "format": "date-time", "title": "Timestamp", - "default": "2026-05-15T20:25:32.352375" + "default": "2026-08-26T17:21:02.106370" }, "status_code": { "type": "integer", @@ -4317,7 +4352,7 @@ "additionalProperties": true, "type": "object", "title": "Metadata", - "description": "Agent-provided domain metadata (e.g. campaign context)" + "description": "Agent-provided domain metadata (e.g. source object context)" } }, "type": "object", diff --git a/docs/model_reference/model_core.md b/docs/model_reference/model_core.md index 218af9c..fce37b8 100644 --- a/docs/model_reference/model_core.md +++ b/docs/model_reference/model_core.md @@ -1,10 +1,6 @@ # Model Reference Core - -> **Created:** 2025-08-09 -> **Updated:** 2026-05-16 - -**Version:** 0.20.1 +**Version:** 1.3.1 ### `account.Account` @@ -114,7 +110,7 @@ _No additional fields beyond parent class._ | Field | Type | Default | Description | |---|---|---|---| | `name` | `str` | **required** | Display name of the agent | -| `id` | `str` | **required** | Unique ID generated from name | +| `id` | `str` | **required** | Stable ID derived from name via shortuuid.uuid(name=...). Renaming the agent changes this value. | | `author` | `str` | `None` | Author of the agent | | `developer` | `str` | `None` | Developer of the controller integration | | `maintainer` | `str` | `None` | Maintainer of the integration | @@ -128,13 +124,13 @@ _No additional fields beyond parent class._ | `server_agent_id` | `str` | `None` | ID assigned by server - Do not set this manually | | `server_agent_status` | `str` | `None` | Current status on server - Do not set this manually | | `server_agent_onboarding_status` | `str` | `None` | Onboarding status - Do not set this manually | -| `server_encrypted_parameters` | `str` | `None` | Encrypted parameters from server - Do not set this manually | | `max_execution_time` | `int` | 3600 | Maximum execution time in seconds, defaults to 1 hour | | `supervaize_instructions_template_path` | `str` | `None` | Optional path to a custom template file for supervaize_instructions.html page | | `instructions_path` | `str` | 'supervaize_instructions.html' | Path where the supervaize instructions page is served (relative to agent path) | | `custom_routes` | `Any` | `None` | Optional FastAPI APIRouter; mounted on the API app at /api/agents/{slug}/... | | `data_resources` | `list[data_resource.DataResource]` | — | Data resources this agent exposes for Studio CRUD access | | `supervaizer_v2_registration` | `SupervaizerV2AgentRegistrationContract` | `None` | Optional Supervaizer v2 registration contract for A2A/A2UI Studio integrations | +| `v2_methods` | `V2AgentMethods` | `None` | Optional agent-level Supervaizer v2 method declarations | ### `agent.AgentMethod` @@ -187,8 +183,8 @@ Attributes: |---|---|---|---| | `name` | `str` | **required** | The name of the method | | `method` | `str` | **required** | The name of the method in the project's codebase that will be called with the provided parameters | -| `params` | `typing.Dict[str, typing.Any]` | `None` | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | -| `fields` | `typing.List[supervaizer.agent.AgentMethodField]` | `None` | A list of field specifications for generating forms/UI, following the django.forms.fields definition | +| `params` | `dict[str, typing.Any]` | `None` | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | +| `fields` | `list[supervaizer.agent.AgentMethodField]` | `None` | A list of field specifications for generating forms/UI, following the django.forms.fields definition | | `description` | `str` | `None` | Optional description of what the method does | | `is_async` | `bool` | False | Whether the method is asynchronous | | `timeout` | `int` | 600 | Maximum automatic job duration in seconds. Use None for jobs that must run until Studio stops them manually. | @@ -316,7 +312,7 @@ ParametersSetup.from_list([ | Field | Type | Default | Description | |---|---|---|---| -| `definitions` | `Dict[str, parameter.Parameter]` | **required** | A dictionary of Parameters, where the key is the parameter name and the value is the parameter object. | +| `definitions` | `dict[str, parameter.Parameter]` | **required** | A dictionary of Parameters, where the key is the parameter name and the value is the parameter object. | ### `parameter.Parameter` @@ -403,7 +399,7 @@ public_url: full url (including scheme and port) to use for outbound connections | `environment` | `str` | **required** | Environment name (e.g., dev, staging, prod) | | `mac_addr` | `str` | **required** | MAC address to use for server identification | | `debug` | `bool` | **required** | Whether to enable debug mode | -| `agents` | `List[agent.Agent]` | **required** | List of agents to register with the server | +| `agents` | `list[agent.Agent]` | **required** | List of agents to register with the server | | `app` | `FastAPI` | **required** | FastAPI application instance | | `reload` | `bool` | **required** | Whether to enable auto-reload | | `supervisor_account` | `Account` | `None` | Account of the supervisor - can be created at supervaize.com | @@ -413,6 +409,7 @@ public_url: full url (including scheme and port) to use for outbound connections | `public_url` | `str` | `None` | Public including scheme and port to use for inbound connections | | `api_key` | `str` | `None` | Force the API key to access the supervaizer endpoints - if not provided, a random key will be generated | | `api_key_header` | `APIKeyHeader` | `None` | API key header for authentication | +| `workspace_authorization` | `V2WorkspaceAuthorizationSettings` | — | Optional Studio-signed workspace authorization verifier settings | #### Examples @@ -446,4 +443,4 @@ public_url: full url (including scheme and port) to use for outbound connections ``` -*Uploaded on 2026-05-15 20:25:31* +*Uploaded on 2026-08-26 17:21:01* diff --git a/docs/model_reference/model_extra.md b/docs/model_reference/model_extra.md index f673691..54e034c 100644 --- a/docs/model_reference/model_extra.md +++ b/docs/model_reference/model_extra.md @@ -1,10 +1,6 @@ # Model Reference extra - -> **Created:** 2025-08-08 -> **Updated:** 2026-05-16 - -**Version:** 0.20.1 +**Version:** 1.3.1 ### `common.SvBaseModel` @@ -47,7 +43,7 @@ Base model for agent job context parameters | Field | Type | Default | Description | |---|---|---|---| | `job_context` | `JobContext` | **required** | | -| `job_fields` | `Dict[str, Any]` | **required** | | +| `job_fields` | `dict[str, Any]` | **required** | | ### `agent.AgentMethodParams` @@ -55,7 +51,7 @@ Method parameters for agent operations. | Field | Type | Default | Description | |---|---|---|---| -| `params` | `Dict[str, Any]` | — | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | +| `params` | `dict[str, Any]` | — | A simple key-value dictionary of parameters what will be passed to the AgentMethod.method as kwargs | ### `agent.AgentMethods` @@ -94,11 +90,10 @@ Response model for agent endpoints - values provided by Agent.registration_info | `description` | `str` | **required** | | | `tags` | `list[str]` | `None` | | | `methods` | `AgentMethods` | `None` | | -| `parameters_setup` | `typing.List[typing.Dict[str, typing.Any]]` | `None` | | +| `parameters_setup` | `list[dict[str, typing.Any]]` | `None` | | | `server_agent_id` | `str` | `None` | | | `server_agent_status` | `str` | `None` | | | `server_agent_onboarding_status` | `str` | `None` | | -| `server_encrypted_parameters` | `str` | `None` | | ### `case.CaseNodes` @@ -108,7 +103,7 @@ Response model for agent endpoints - values provided by Agent.registration_info | Field | Type | Default | Description | |---|---|---|---| -| `nodes` | `List[case.CaseNode]` | [] | | +| `nodes` | `list[case.CaseNode]` | [] | | ### `data_resource.DataResource` @@ -145,12 +140,14 @@ Example:: | `fields` | `list[data_resource.DataResourceField]` | — | | | `read_only` | `bool` | False | | | `importable` | `bool` | False | Enables CSV bulk import route | -| `on_list` | `typing.Callable[..., list[dict[str, typing.Any]]]` | `None` | | -| `on_get` | `typing.Callable[..., dict[str, typing.Any] | None]` | `None` | | -| `on_create` | `typing.Callable[..., dict[str, typing.Any]]` | `None` | | -| `on_update` | `typing.Callable[..., dict[str, typing.Any] | None]` | `None` | | -| `on_delete` | `typing.Callable[..., bool]` | `None` | | -| `on_import` | `typing.Callable[..., dict[str, typing.Any]]` | `None` | | +| `scope` | `Literal['workspace', 'mission', 'job']` | 'workspace' | Studio context boundary for this resource. | +| `requires_context` | `list[str]` | — | Context keys Studio must send for resource access control. | +| `on_list` | `collections.abc.Callable[..., list[dict[str, typing.Any]]]` | `None` | | +| `on_get` | `collections.abc.Callable[..., dict[str, typing.Any] | None]` | `None` | | +| `on_create` | `collections.abc.Callable[..., dict[str, typing.Any]]` | `None` | | +| `on_update` | `collections.abc.Callable[..., dict[str, typing.Any] | None]` | `None` | | +| `on_delete` | `collections.abc.Callable[..., bool]` | `None` | | +| `on_import` | `collections.abc.Callable[..., dict[str, typing.Any]]` | `None` | | ### `job.Job` @@ -226,6 +223,56 @@ _No additional fields beyond parent class._ | `job_policy` | `V2JobPolicy` | — | | | `resources` | `list[contracts.V2ResourceDefinition]` | — | | | `datasets` | `list[contracts.V2DatasetDefinition]` | — | | +| `dashboards` | `list[contracts.V2DashboardDefinition]` | — | | +| `workspace_binding` | `V2WorkspaceBindingDefinition` | `None` | | + +### `contracts.V2ActionRequest` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `request_id` | `str` | **required** | | +| `actor` | `V2ActorContext` | **required** | | +| `workspace` | `V2WorkspaceContext` | **required** | | +| `mission_id` | `str` | **required** | | +| `agent_slug` | `str` | **required** | | +| `surface` | `str` | **required** | | +| `action` | `str` | **required** | | +| `input` | `dict[str, Any]` | — | | +| `idempotency_key` | `str` | `None` | | +| `draft_session_id` | `str` | `None` | | +| `job_id` | `str` | `None` | | +| `case_id` | `str` | `None` | | +| `step_id` | `str` | `None` | | +| `workspace_authorization` | `V2VerifiedWorkspaceContext` | `None` | | + +### `contracts.V2AgentMethod` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `method` | `str` | **required** | | +| `params` | `dict[str, Any]` | — | | +| `description` | `str` | `None` | | +| `is_async` | `bool` | False | | +| `timeout` | `int` | 600 | | + +### `contracts.V2AgentMethods` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `refresh` | `V2AgentMethod` | `None` | | +| `custom` | `dict[str, contracts.V2AgentMethod]` | — | | ### `case.Case` @@ -247,11 +294,11 @@ _No additional fields beyond parent class._ | `account` | `ForwardRef('Account')` | **required** | | | `description` | `str` | **required** | | | `status` | `` | **required** | | -| `updates` | `List[case.CaseNodeUpdate]` | [] | | +| `updates` | `list[case.CaseNodeUpdate]` | [] | | | `total_cost` | `float` | 0.0 | | -| `final_delivery` | `typing.Dict[str, typing.Any]` | `None` | | +| `final_delivery` | `dict[str, typing.Any]` | `None` | | | `finished_at` | `datetime` | `None` | | -| `metadata` | `Dict[str, Any]` | — | Agent-provided domain metadata (e.g. contact context) | +| `metadata` | `dict[str, Any]` | — | Agent-provided domain metadata (e.g. contact context) | ### `case.CaseNode` @@ -284,15 +331,75 @@ Returns: | `index` | `int` | `None` | | | `cost` | `float` | `None` | | | `name` | `str` | `None` | | -| `payload` | `typing.Dict[str, typing.Any]` | `None` | | +| `payload` | `dict[str, typing.Any]` | `None` | | | `is_final` | `bool` | False | | | `upsert` | `bool` | False | | | `error` | `str` | `None` | | | `scheduled_at` | `datetime` | `None` | | | `scheduled_method` | `str` | `None` | | -| `scheduled_params` | `typing.Dict[str, typing.Any]` | `None` | | +| `scheduled_params` | `dict[str, typing.Any]` | `None` | | | `scheduled_status` | `str` | `None` | | +### `context.ContextCitation` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `title` | `str` | **required** | | +| `source_type` | `str` | **required** | | +| `version` | `int` | **required** | | + +### `context.ContextOpenResponse` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `title` | `str` | **required** | | +| `scope` | `Literal['workspace', 'mission']` | **required** | | +| `source_type` | `str` | **required** | | +| `version` | `int` | **required** | | +| `instructions` | `str` | '' | | +| `tags` | `list[str]` | — | | +| `content` | `str` | **required** | | +| `citations` | `list[context.ContextCitation]` | — | | + +### `context.ContextSearchResponse` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `query` | `str` | **required** | | +| `results` | `list[context.ContextSearchResult]` | — | | + +### `context.ContextSearchResult` + +**Inherits from:** [`common.SvBaseModel`](#commonsvbasemodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `title` | `str` | **required** | | +| `scope` | `Literal['workspace', 'mission']` | **required** | | +| `source_type` | `str` | **required** | | +| `version` | `int` | **required** | | +| `tags` | `list[str]` | — | | +| `excerpt` | `str` | '' | | +| `score` | `int` | 0 | | +| `citation` | `ContextCitation` | `None` | | + ### `contracts.AgentMethodContract` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -435,6 +542,8 @@ Canonical controller surface advertised by a Supervaizer server. | `read_only` | `bool` | False | | | `importable` | `bool` | False | | | `operations` | `dict[str, bool]` | — | | +| `scope` | `Literal['workspace', 'mission', 'job']` | 'workspace' | | +| `requires_context` | `list[str]` | — | | ### `contracts.DataResourceFieldContract` @@ -493,6 +602,7 @@ Minimal schema for server.register details. | `url` | `str` | **required** | | | `uri` | `str` | **required** | | | `api_version` | `str` | **required** | | +| `controller_version` | `str` | `None` | | | `environment` | `str` | `None` | | | `agents` | `list[contracts.AgentRegistrationContract]` | — | | @@ -532,7 +642,40 @@ Minimal schema for server.register details. | `sse` | `bool` | True | | | `push_notifications` | `bool` | False | | -### `contracts.V2ActionRequest` +### `contracts.V2A2UIResourceImportColumn` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `id` | `str` | **required** | | +| `label` | `str` | `None` | | +| `type` | `str` | 'string' | | +| `required` | `bool` | False | | + +### `contracts.V2A2UIResourceImportDocument` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +A2UI-shaped resource import surface consumed by Studio. + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `type` | `Literal['ResourceImport']` | 'ResourceImport' | | +| `id` | `str` | **required** | | +| `title` | `str` | **required** | | +| `resource` | `str` | **required** | | +| `accepted_formats` | `list[Literal['csv', 'xlsx']]` | — | | +| `fields` | `list[contracts.V2ResourceFieldDefinition]` | — | | +| `columns` | `list[contracts.V2A2UIResourceImportColumn]` | — | | +| `submit` | `V2A2UISubmitDefinition` | **required** | | +| `state` | `dict[str, Any]` | — | | + +### `contracts.V2A2UISubmitDefinition` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -540,19 +683,8 @@ Minimal schema for server.register details. | Field | Type | Default | Description | |---|---|---|---| -| `request_id` | `str` | **required** | | -| `actor` | `V2ActorContext` | **required** | | -| `workspace` | `V2WorkspaceContext` | **required** | | -| `mission_id` | `str` | **required** | | -| `agent_slug` | `str` | **required** | | -| `surface` | `str` | **required** | | | `action` | `str` | **required** | | -| `input` | `dict[str, Any]` | — | | -| `idempotency_key` | `str` | `None` | | -| `draft_session_id` | `str` | `None` | | -| `job_id` | `str` | `None` | | -| `case_id` | `str` | `None` | | -| `step_id` | `str` | `None` | | +| `label` | `str` | `None` | | ### `contracts.V2ActionResult` @@ -564,6 +696,9 @@ Minimal schema for server.register details. |---|---|---|---| | `status` | `Literal['ok', 'error']` | **required** | | | `effects` | `list[contracts.V2Effect]` | — | | +| `job_state` | `ForwardRef('V2JobStateSnapshot | None')` | `None` | | +| `replay_safety` | `V2ReplaySafetyMetadata` | `None` | | +| `setup_plan` | `dict[str, typing.Any]` | `None` | | ### `contracts.V2ActorContext` @@ -677,8 +812,87 @@ Minimal schema for server.register details. | `title` | `str` | `None` | | | `status` | `str` | `None` | | | `external_id` | `str` | `None` | | +| `metadata` | `dict[str, Any]` | — | | | `steps` | `list[contracts.V2StepSnapshot]` | — | | +### `contracts.V2ContextAssignment` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `items` | `list[contracts.V2ContextAssignmentItem]` | **required** | | +| `mission_id` | `str` | `None` | | +| `job_id` | `str` | **required** | | +| `assigned_at` | `str` | **required** | | + +### `contracts.V2ContextAssignmentItem` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `ref` | `str` | **required** | | +| `version` | `int` | **required** | | +| `scope` | `Literal['workspace', 'mission']` | **required** | | +| `title` | `str` | **required** | | + +### `contracts.V2DashboardDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `id` | `str` | **required** | | +| `label` | `str` | **required** | | +| `surface` | `str` | 'mission.analytics' | | +| `widgets` | `list[contracts.V2DashboardWidgetDefinition]` | — | | + +### `contracts.V2DashboardWidgetDataRef` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `mode` | `Literal['ref', 'action', 'inline']` | 'ref' | | +| `datasetId` | `str` | `None` | | +| `action` | `str` | `None` | | +| `input` | `dict[str, Any]` | — | | +| `values` | `list[dict[str, typing.Any]]` \| `dict[str, typing.Any]` | `None` | | + +### `contracts.V2DashboardWidgetDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `id` | `str` | **required** | | +| `title` | `str` | **required** | | +| `data` | `V2DashboardWidgetDataRef` | `None` | | +| `layout` | `dict[str, Any]` | — | | +| `visualization` | `V2DashboardWidgetVisualization` | — | | + +### `contracts.V2DashboardWidgetVisualization` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `type` | `Literal['table', 'metric', 'vega-lite', 'custom']` | 'table' | | +| `spec` | `dict[str, typing.Any]` | `None` | | + ### `contracts.V2DatasetDefinition` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -690,6 +904,7 @@ Minimal schema for server.register details. | `id` | `str` | **required** | | | `label` | `str` | **required** | | | `auto_surface` | `bool` | False | | +| `display` | `V2ResourceDisplayDefinition` | `None` | | ### `contracts.V2Effect` @@ -700,6 +915,23 @@ Minimal schema for server.register details. | Field | Type | Default | Description | |---|---|---|---| | `type` | `str` | **required** | | +| `job_id` | `str` | `None` | | +| `case_id` | `str` | `None` | | +| `step_id` | `str` | `None` | | +| `resource` | `str` | `None` | | +| `dataset` | `str` | `None` | | +| `artifact_id` | `str` | `None` | | +| `status` | `str` | `None` | | +| `message` | `str` | `None` | | +| `count` | `int` | `None` | | +| `item` | `dict[str, typing.Any]` | `None` | | +| `items` | `list[dict[str, typing.Any]]` | `None` | | +| `rows` | `list[dict[str, typing.Any]]` | `None` | | +| `errors` | `list[dict[str, typing.Any]]` | `None` | | +| `gaps` | `list[dict[str, typing.Any]]` | `None` | | +| `summary` | `dict[str, typing.Any]` | `None` | | +| `case` | `dict[str, typing.Any]` | `None` | | +| `data` | `dict[str, typing.Any]` | `None` | | ### `contracts.V2JobPolicy` @@ -713,6 +945,23 @@ Minimal schema for server.register details. | `offline_start_policy` | `Literal['block']` | 'block' | | | `offline_running_policy` | `Literal['fail_in_studio']` | 'fail_in_studio' | | | `sync` | `V2JobSyncPolicy` | `None` | | +| `setup` | `V2JobSetupPolicy` | `None` | | + +### `contracts.V2JobSetupPolicy` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +Generic agent-declared job setup actions. + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `preview_action` | `str` | 'job.start.preview' | | +| `start_action` | `str` | 'job.start' | | +| `submit_action` | `str` | 'step.awaiting.submit' | | +| `action_scopes` | `list[Literal['workspace', 'job', 'case', 'step']]` | — | | +| `plan` | `dict[str, typing.Any]` | `None` | | ### `contracts.V2JobSnapshot` @@ -739,7 +988,7 @@ Minimal schema for server.register details. | `type` | `Literal['fresh_start', 'external']` | **required** | | | `external_ref` | `str` | `None` | | | `previous_job_id` | `str` | `None` | | -| `target_type` | `str` | `None` | | +| `target_type` | `str` | `None` | Agent-declared business object type for external sources (e.g. project). Open vocabulary unlike protocol-fixed fields such as step activity. | ### `contracts.V2JobStateSnapshot` @@ -775,18 +1024,19 @@ Minimal schema for server.register details. | `external_version` | `str` | `None` | | | `sync_cursor` | `str` | `None` | | | `observed_at` | `str` | `None` | | -| `job_state` | `V2JobStateSnapshot` | `None` | | ### `contracts.V2MountedResourceViewDefinition` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) +Agent override that mounts an A2UI surface on a full resource view. + #### Model Fields | Field | Type | Default | Description | |---|---|---|---| -| `view` | `str` | **required** | | -| `surface` | `str` | **required** | | +| `view` | `str` | **required** | Generated resource view replaced by the mount (e.g. import, edit). | +| `surface` | `str` | **required** | Registered A2UI surface id served for this resource view. | ### `contracts.V2ProtocolVersions` @@ -825,6 +1075,8 @@ Minimal schema for server.register details. | `id` | `str` | **required** | | | `label` | `str` | **required** | | | `auto_surface` | `bool` | False | | +| `scope` | `Literal['workspace', 'mission', 'job']` | 'workspace' | | +| `requires_context` | `list[str]` | — | | | `operations` | `list[str]` | — | | | `display` | `V2ResourceDisplayDefinition` | `None` | | | `fields` | `list[contracts.V2ResourceFieldDefinition]` | — | | @@ -906,6 +1158,7 @@ Minimal schema for server.register details. | `job_id` | `str` | `None` | | | `case_id` | `str` | `None` | | | `step_id` | `str` | `None` | | +| `workspace_authorization` | `V2VerifiedWorkspaceContext` | `None` | | ### `contracts.V2SurfaceResult` @@ -920,6 +1173,78 @@ Minimal schema for server.register details. | `a2ui_catalog_version` | `str` | `None` | | | `document` | `dict[str, Any]` | — | | +### `contracts.V2VerifiedWorkspaceContext` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `grant_id` | `str` | **required** | | +| `workspace_id` | `str` | **required** | | +| `workspace_slug` | `str` | `None` | | +| `agent_id` | `str` | **required** | | +| `agent_slug` | `str` | **required** | | +| `server_id` | `str` | **required** | | +| `scopes` | `list[str]` | — | | +| `agent_workspace_ref` | `str` | `None` | | + +### `contracts.V2WorkspaceAuthorizationSettings` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `enabled` | `bool` | False | | +| `issuer` | `str` | `None` | | +| `audience` | `str` | `None` | | +| `public_key_pem` | `str` | `None` | | +| `jwks_url` | `str` | `None` | | +| `leeway_seconds` | `int` | 30 | | + +### `contracts.V2WorkspaceBindingCreateDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `surface` | `str` | 'workspace_binding.create' | | +| `action` | `str` | 'workspace_binding.create' | | +| `fields` | `list[contracts.V2ResourceFieldDefinition]` | — | | + +### `contracts.V2WorkspaceBindingDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `required` | `bool` | False | | +| `modes` | `list[Literal['bind_existing', 'create_and_bind']]` | — | | +| `reference_label` | `str` | 'Agent workspace reference' | | +| `reference_help` | `str` | 'Select or create the agent-side record this Studio workspace may access.' | | +| `reference_placeholder` | `str` | 'Example: workspace-prod' | | +| `existing` | `V2WorkspaceBindingExistingDefinition` | `None` | | +| `create` | `V2WorkspaceBindingCreateDefinition` | `None` | | + +### `contracts.V2WorkspaceBindingExistingDefinition` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `action` | `str` | 'workspace_binding.options' | | +| `value_field` | `str` | 'agent_workspace_ref' | | +| `label_field` | `str` | 'display_name' | | + ### `contracts.V2WorkspaceContext` **Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) @@ -946,6 +1271,7 @@ Studio request context passed to DataResource callbacks. | `mission_id` | `str` | `None` | | | `agent_slug` | `str` | **required** | | | `request_id` | `str` | `None` | | +| `workspace_authorization` | `V2VerifiedWorkspaceContext` | `None` | | ### `data_resource.DataResourceField` @@ -978,7 +1304,7 @@ Deployment plan containing all actions to be taken. | `environment` | `str` | **required** | | | `region` | `str` | **required** | | | `project_id` | `str` | `None` | | -| `actions` | `List[deploy.drivers.base.ResourceAction]` | [] | | +| `actions` | `list[deploy.drivers.base.ResourceAction]` | [] | | | `total_cost_estimate` | `str` | `None` | | | `estimated_duration` | `str` | `None` | | | `current_image` | `str` | `None` | | @@ -986,8 +1312,8 @@ Deployment plan containing all actions to be taken. | `current_status` | `str` | `None` | | | `target_image` | `str` | **required** | | | `target_port` | `int` | 8000 | | -| `target_env_vars` | `Dict[str, str]` | {} | | -| `target_secrets` | `Dict[str, str]` | {} | | +| `target_env_vars` | `dict[str, str]` | {} | | +| `target_secrets` | `dict[str, str]` | {} | | ### `deploy.drivers.base.DeploymentResult` @@ -1004,7 +1330,7 @@ Result of a deployment operation. | `health_status` | `str` | 'unknown' | | | `deployment_time` | `float` | `None` | | | `error_message` | `str` | `None` | | -| `error_details` | `typing.Dict[str, typing.Any]` | `None` | | +| `error_details` | `dict[str, typing.Any]` | `None` | | ### `deploy.state.DeploymentState` @@ -1029,7 +1355,7 @@ Deployment state model. | `port` | `int` | 8000 | Application port | | `api_key_generated` | `bool` | False | Whether API key was generated | | `rsa_key_generated` | `bool` | False | Whether RSA key was generated | -| `provider_data` | `Dict[str, Any]` | — | Platform-specific data | +| `provider_data` | `dict[str, Any]` | — | Platform-specific data | ### `deploy.drivers.base.ResourceAction` @@ -1042,7 +1368,7 @@ Represents an action to be taken on a resource. | `resource_name` | `str` | **required** | | | `description` | `str` | **required** | | | `cost_estimate` | `str` | `None` | | -| `metadata` | `typing.Dict[str, typing.Any]` | `None` | | +| `metadata` | `dict[str, typing.Any]` | `None` | | ### `deploy.health.HealthCheckConfig` @@ -1056,7 +1382,7 @@ Configuration for health check operations. | `max_delay` | `float` | 30.0 | | | `backoff_multiplier` | `float` | 2.0 | | | `success_threshold` | `int` | 1 | | -| `endpoints` | `typing.List[str]` | `None` | | +| `endpoints` | `list[str]` | `None` | | ### `deploy.health.HealthCheckResult` @@ -1079,11 +1405,11 @@ Result of a health check operation. | Field | Type | Default | Description | |---|---|---|---| -| `source` | `Dict[str, Any]` | **required** | | +| `source` | `dict[str, Any]` | **required** | | | `account` | `Any` | **required** | | | `type` | `` | **required** | | | `object_type` | `str` | **required** | | -| `details` | `Dict[str, Any]` | **required** | | +| `details` | `dict[str, Any]` | **required** | | ### `event.AgentRegisterEvent` @@ -1159,7 +1485,7 @@ _No additional fields beyond parent class._ | `created_at` | `datetime` | `None` | | | `agent_parameters` | `list[dict[str, typing.Any]]` | `None` | | | `case_ids` | `list[str]` | [] | | -| `metadata` | `dict[str, Any]` | — | Agent-provided domain metadata (e.g. campaign context) | +| `metadata` | `dict[str, Any]` | — | Agent-provided domain metadata (e.g. source object context) | ### `job.JobInstructions` @@ -1236,7 +1562,7 @@ Standard error response model | `error` | `str` | **required** | | | `error_type` | `` | **required** | | | `detail` | `str` | `None` | | -| `timestamp` | `datetime` | datetime.datetime(2026, 5, 15, 20, 25, 31, 892520) | | +| `timestamp` | `datetime` | datetime.datetime(2026, 8, 26, 17, 21, 1, 586513) | | | `status_code` | `int` | **required** | | ### `routes.RegistrationRefreshRequest` @@ -1252,7 +1578,7 @@ Request model for re-sending the server registration event. | `reason` | `str` | `None` | | | `requested_at` | `str` | `None` | | -### `server.ServerInfo` +### `server_info.ServerInfo` Complete server information for storage. @@ -1263,7 +1589,7 @@ Complete server information for storage. | `port` | `int` | **required** | | | `api_version` | `str` | **required** | | | `environment` | `str` | **required** | | -| `agents` | `List[Dict[str, str]]` | **required** | | +| `agents` | `list[dict[str, str]]` | **required** | | | `start_time` | `float` | **required** | | | `created_at` | `str` | **required** | | | `updated_at` | `str` | **required** | | @@ -1278,7 +1604,30 @@ A base class for creating Pydantic models. | `type` | `` | **required** | | | `category` | `` | **required** | | | `severity` | `` | **required** | | -| `details` | `Dict[str, Any]` | **required** | | +| `details` | `dict[str, Any]` | **required** | | + +### `workspace_authorization.WorkspaceAuthorizationClaims` + +**Inherits from:** [`contracts.ContractModel`](#contractscontractmodel) + +#### Model Fields + +| Field | Type | Default | Description | +|---|---|---|---| +| `iss` | `str` | **required** | | +| `aud` | `str` \| `list[str]` | **required** | | +| `sub` | `str` | `None` | | +| `grant_id` | `str` | **required** | | +| `workspace_id` | `str` | **required** | | +| `workspace_slug` | `str` | `None` | | +| `agent_id` | `str` | **required** | | +| `agent_slug` | `str` | **required** | | +| `server_id` | `str` | **required** | | +| `scopes` | `list[str]` | — | | +| `agent_workspace_ref` | `str` | `None` | | +| `iat` | `int` | `None` | | +| `exp` | `int` | **required** | | +| `jti` | `str` | `None` | | -*Uploaded on 2026-05-15 20:25:31* +*Uploaded on 2026-08-26 17:21:01* From 4213c4ba81983498cf194b07069565a8c99de15f Mon Sep 17 00:00:00 2001 From: Alain Prasquier Date: Wed, 26 Aug 2026 18:10:43 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=93=9D=20docs:=20log=20generic=20job?= =?UTF-8?q?=20setup=20contract=20in=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records the V2JobSetupPolicy contract, the empty action_scopes default, and the model reference regeneration under Unreleased. --- docs/CHANGELOG.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d1781b4..1dc6fa3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -14,8 +14,16 @@ All notable changes to this project will be documented in this file. ### Added +- **Generic job setup contract** — New `V2JobSetupPolicy` on `V2JobPolicy.setup` lets an agent declare the actions Studio may use to preview, start, and submit a job setup (`preview_action`, `start_action`, `submit_action`, defaulting to `job.start.preview`, `job.start`, and `step.awaiting.submit`), the contexts those actions apply to (`action_scopes`), and an opaque `plan` payload. Declared actions are folded into the registration's capability action list. `V2ActionResult.setup_plan` carries the agent's plan back to Studio. `V2JobSetupPolicy` is exported at package level. Additive only; agents that omit `setup` are unaffected. + + `action_scopes` defaults to an empty list: an agent that opts in with `setup: {}` declares no scopes rather than claiming all of `workspace`, `job`, `case`, and `step`. This matches `V2JobSyncPolicy.supported_statuses` and avoids advertising context support the agent never configured. + - **Security & performance review summary** — Added `docs/2026_07_SECURITY_REVIEW.md`, a non-actionable high-level summary of a full-source security and performance/scalability review (posture, verified-sound controls, severity counts, and remediation themes). Per `SECURITY.md`, detailed findings (locations, attack scenarios, remediation specifics) are handled through the private vulnerability channel and are intentionally omitted from the public repository. +### Changed + +- **Regenerated model reference and OpenAPI** — `docs/model_reference/` and `docs/api/openapi.json` were rebuilt from the current models, picking up the job setup contract along with accumulated drift since `0.20.1`. `V2JobSetupPolicy` is now declared before `V2JobPolicy` so the public reference renders the real type instead of a `ForwardRef`. + ### Fixed - **Hardened API-key checks** — API keys are compared in constant time. @@ -28,10 +36,10 @@ All notable changes to this project will be documented in this file. | Status | Count | | ---------- | ----- | -| ✅ Passed | 683 | +| ✅ Passed | 688 | | 🤔 Skipped | 0 | | 🔴 Failed | 0 | -| ⏱️ in | 83s | +| ⏱️ in | 59s | ## [1.3.1] - 2026-07-02 From e9934c04157a60c781afdfbee78938a848618c6a Mon Sep 17 00:00:00 2001 From: Alain Prasquier Date: Wed, 26 Aug 2026 18:18:55 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=90=9B=20fix(contracts):=20reject=20b?= =?UTF-8?q?lank=20job=20setup=20and=20sync=20action=20ids?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An empty preview_action/start_action/submit_action was accepted, then dropped from capabilities.actions by _unique_strings while still being serialized under job_policy.setup, so Studio saw an action it could never invoke. Validate all three as non-blank, plus V2JobSyncPolicy.action, which had the same gap. --- docs/CHANGELOG.md | 4 ++-- src/supervaizer/contracts.py | 14 ++++++++++++++ tests/test_contracts.py | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1dc6fa3..b5c9b11 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -16,7 +16,7 @@ All notable changes to this project will be documented in this file. - **Generic job setup contract** — New `V2JobSetupPolicy` on `V2JobPolicy.setup` lets an agent declare the actions Studio may use to preview, start, and submit a job setup (`preview_action`, `start_action`, `submit_action`, defaulting to `job.start.preview`, `job.start`, and `step.awaiting.submit`), the contexts those actions apply to (`action_scopes`), and an opaque `plan` payload. Declared actions are folded into the registration's capability action list. `V2ActionResult.setup_plan` carries the agent's plan back to Studio. `V2JobSetupPolicy` is exported at package level. Additive only; agents that omit `setup` are unaffected. - `action_scopes` defaults to an empty list: an agent that opts in with `setup: {}` declares no scopes rather than claiming all of `workspace`, `job`, `case`, and `step`. This matches `V2JobSyncPolicy.supported_statuses` and avoids advertising context support the agent never configured. + `action_scopes` defaults to an empty list: an agent that opts in with `setup: {}` declares no scopes rather than claiming all of `workspace`, `job`, `case`, and `step`. This matches `V2JobSyncPolicy.supported_statuses` and avoids advertising context support the agent never configured. The three action ids are validated as non-blank, so a blank id is rejected at registration instead of being silently dropped from `capabilities.actions` while still appearing in the serialized `job_policy.setup`. The same validation was applied to `V2JobSyncPolicy.action`, which had the identical gap. - **Security & performance review summary** — Added `docs/2026_07_SECURITY_REVIEW.md`, a non-actionable high-level summary of a full-source security and performance/scalability review (posture, verified-sound controls, severity counts, and remediation themes). Per `SECURITY.md`, detailed findings (locations, attack scenarios, remediation specifics) are handled through the private vulnerability channel and are intentionally omitted from the public repository. @@ -36,7 +36,7 @@ All notable changes to this project will be documented in this file. | Status | Count | | ---------- | ----- | -| ✅ Passed | 688 | +| ✅ Passed | 690 | | 🤔 Skipped | 0 | | 🔴 Failed | 0 | | ⏱️ in | 59s | diff --git a/src/supervaizer/contracts.py b/src/supervaizer/contracts.py index 8942b32..545ea3b 100644 --- a/src/supervaizer/contracts.py +++ b/src/supervaizer/contracts.py @@ -360,6 +360,13 @@ class V2JobSyncPolicy(ContractModel): action: str = "job.sync" supported_statuses: list[str] = Field(default_factory=list) + @field_validator("action") + @classmethod + def validate_action_is_named(cls, value: str) -> str: + if not value.strip(): + raise ValueError("job sync policy action must be a non-empty action id") + return value + class V2JobSetupPolicy(ContractModel): """Generic agent-declared job setup actions.""" @@ -372,6 +379,13 @@ class V2JobSetupPolicy(ContractModel): ) plan: dict[str, Any] | None = None + @field_validator("preview_action", "start_action", "submit_action") + @classmethod + def validate_action_is_named(cls, value: str) -> str: + if not value.strip(): + raise ValueError("job setup policy actions must be non-empty action ids") + return value + class V2JobPolicy(ContractModel): default_timeout_seconds: int | None = None diff --git a/tests/test_contracts.py b/tests/test_contracts.py index 86166d9..1a4fd53 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -40,6 +40,7 @@ V2Effect, V2JobSetupPolicy, V2JobStateSnapshot, + V2JobSyncPolicy, V2JobSyncResult, V2ReplaySafetyMetadata, V2ResourceFieldDefinition, @@ -587,6 +588,19 @@ def test_v2_job_setup_policy_declares_no_scopes_by_default() -> None: assert policy.action_scopes == [] +def test_v2_job_setup_policy_rejects_blank_action_ids() -> None: + for field in ("preview_action", "start_action", "submit_action"): + with pytest.raises(ValidationError): + V2JobSetupPolicy.model_validate({field: ""}) + with pytest.raises(ValidationError): + V2JobSetupPolicy.model_validate({field: " "}) + + +def test_v2_job_sync_policy_rejects_blank_action_id() -> None: + with pytest.raises(ValidationError): + V2JobSyncPolicy.model_validate({"action": ""}) + + def test_v2_action_result_validates_replay_safety() -> None: with pytest.raises(ValidationError): V2ActionResult.model_validate({