feat(gitops): expose governed task continuation contract
This commit is contained in:
@@ -160,6 +160,23 @@ components:
|
||||
properties:
|
||||
text: {type: string}
|
||||
branch: {type: string}
|
||||
CreateGovernedTaskRequest:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [task_id, repository, base_oid, target_branch, risk, description, idempotency_key]
|
||||
properties:
|
||||
task_id: {type: string, minLength: 1}
|
||||
repository: {type: string, const: aios/aios}
|
||||
base_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
|
||||
target_branch: {type: string, pattern: '^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$'}
|
||||
risk: {type: string, enum: [low, medium, high, engineering]}
|
||||
description: {type: string, minLength: 1}
|
||||
idempotency_key: {type: string, minLength: 1, maxLength: 256}
|
||||
expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
|
||||
generation: {type: integer, minimum: 1}
|
||||
parent_task_id: {type: string, minLength: 1}
|
||||
parent_job_id: {type: string, pattern: '^job-[A-Za-z0-9][A-Za-z0-9._-]{7,255}$'}
|
||||
priority: {type: integer, minimum: -100, maximum: 100}
|
||||
DeploymentPlan:
|
||||
type: object
|
||||
required: [service, release_path, release_root, cutover_link, dropin_path, dropin_sha256, systemd_unit_sha256, rollback_path, rollback_sha256, source_commit, tree_oid, artifact_sha256, manifest_sha256, package_closure_sha256, authoritative_git_dir, repository_identity, writer_expected, baseline_before_sha256, registry_before_sha256, production_tag_refset_before_sha256, health_contract_version, timeout_policy, retry_policy, policy_sha256, health_contract]
|
||||
@@ -349,6 +366,23 @@ components:
|
||||
operations: {type: array, items: {type: string}}
|
||||
operation_contracts: {type: object, additionalProperties: true}
|
||||
paths:
|
||||
/createGovernedTask:
|
||||
post:
|
||||
operationId: createGovernedTask
|
||||
summary: Create or idempotently return a governed task binding through the existing Bridge
|
||||
security: [{bearerAuth: []}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/CreateGovernedTaskRequest'}
|
||||
responses:
|
||||
'200': {description: Governed task binding created or returned idempotently}
|
||||
'400': {description: Invalid governed task or continuation binding}
|
||||
'401': {description: Caller authorization required}
|
||||
'409': {description: Durable task, parent, branch, or generation conflict}
|
||||
'503': {description: Governed Bridge or server-side control credential unavailable}
|
||||
x-openai-isConsequential: true
|
||||
/createReleaseIntent:
|
||||
post:
|
||||
operationId: createReleaseIntent
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from aios_chatgpt_bridge_v2 import OPERATION_REQUIRED_FIELDS
|
||||
from aios_chatgpt_bridge_v2_server import BridgeHandler, _HTTPError, ROUTES
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
OPENAPI = ROOT / "docs" / "gitops" / "openapi-chatgpt-actions.yaml"
|
||||
REQUIRED = {
|
||||
"task_id",
|
||||
"repository",
|
||||
"base_oid",
|
||||
"target_branch",
|
||||
"risk",
|
||||
"description",
|
||||
"idempotency_key",
|
||||
}
|
||||
OPTIONAL_CONTINUATION = {
|
||||
"expected_branch_oid",
|
||||
"generation",
|
||||
"parent_task_id",
|
||||
"parent_job_id",
|
||||
"priority",
|
||||
}
|
||||
|
||||
|
||||
def _document():
|
||||
return yaml.safe_load(OPENAPI.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _create_request():
|
||||
return {
|
||||
"task_id": "continuation-contract-test",
|
||||
"repository": "aios/aios",
|
||||
"base_oid": "a" * 40,
|
||||
"target_branch": "agent/continuation-contract-test",
|
||||
"risk": "engineering",
|
||||
"description": "contract test",
|
||||
"idempotency_key": "continuation-contract-test-v1",
|
||||
}
|
||||
|
||||
|
||||
def test_create_governed_task_openapi_contract_exposes_continuation_fields():
|
||||
document = _document()
|
||||
operation = document["paths"]["/createGovernedTask"]["post"]
|
||||
schema_ref = operation["requestBody"]["content"]["application/json"]["schema"]
|
||||
schema = document["components"]["schemas"][schema_ref["$ref"].rsplit("/", 1)[-1]]
|
||||
|
||||
assert operation["operationId"] == "createGovernedTask"
|
||||
assert operation["security"] == [{"bearerAuth": []}]
|
||||
assert operation["x-openai-isConsequential"] is True
|
||||
assert set(schema["required"]) == REQUIRED
|
||||
assert schema["additionalProperties"] is False
|
||||
assert OPTIONAL_CONTINUATION <= set(schema["properties"])
|
||||
assert not OPTIONAL_CONTINUATION.intersection(schema["required"])
|
||||
assert schema["properties"]["repository"]["const"] == "aios/aios"
|
||||
assert schema["properties"]["generation"]["minimum"] == 1
|
||||
assert schema["properties"]["priority"] == {
|
||||
"type": "integer",
|
||||
"minimum": -100,
|
||||
"maximum": 100,
|
||||
}
|
||||
|
||||
|
||||
def test_create_governed_task_route_and_required_fields_match_bridge():
|
||||
assert ROUTES["/v2/governed-task"] == "createGovernedTask"
|
||||
assert tuple(OPERATION_REQUIRED_FIELDS["createGovernedTask"]) == (
|
||||
"task_id",
|
||||
"repository",
|
||||
"base_oid",
|
||||
"target_branch",
|
||||
"risk",
|
||||
"description",
|
||||
"idempotency_key",
|
||||
)
|
||||
|
||||
|
||||
def test_bridge_execution_plan_and_envelope_routes_remain_exposed():
|
||||
assert ROUTES["/v2/execution-envelope"] == "projectOnlineAgentExecution"
|
||||
assert ROUTES["/v2/execution-plan"] == "registerOnlineAgentExecutionPlan"
|
||||
|
||||
|
||||
def test_bridge_allowlist_accepts_parent_pair_and_priority_but_rejects_unknown():
|
||||
handler = BridgeHandler.__new__(BridgeHandler)
|
||||
body = _create_request()
|
||||
body.update(
|
||||
{
|
||||
"expected_branch_oid": "a" * 40,
|
||||
"generation": 2,
|
||||
"parent_task_id": "continuation-parent-test",
|
||||
"parent_job_id": "job-continuation-parent-test",
|
||||
"priority": 100,
|
||||
}
|
||||
)
|
||||
handler._allowed("createGovernedTask", body)
|
||||
|
||||
invalid = dict(body)
|
||||
invalid["unexpected_field"] = True
|
||||
with pytest.raises(_HTTPError, match="UNKNOWN_FIELDS"):
|
||||
handler._allowed("createGovernedTask", invalid)
|
||||
|
||||
|
||||
def test_openapi_does_not_add_credential_fields_to_create_contract():
|
||||
document = _document()
|
||||
properties = set(
|
||||
document["components"]["schemas"]["CreateGovernedTaskRequest"]["properties"]
|
||||
)
|
||||
forbidden = {"authorization", "bearer", "token", "credential", "secret", "password", "api_key"}
|
||||
assert properties.isdisjoint(forbidden)
|
||||
Reference in New Issue
Block a user