Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
341d276f18 |
@@ -160,6 +160,23 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
text: {type: string}
|
text: {type: string}
|
||||||
branch: {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:
|
DeploymentPlan:
|
||||||
type: object
|
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]
|
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}}
|
operations: {type: array, items: {type: string}}
|
||||||
operation_contracts: {type: object, additionalProperties: true}
|
operation_contracts: {type: object, additionalProperties: true}
|
||||||
paths:
|
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:
|
/createReleaseIntent:
|
||||||
post:
|
post:
|
||||||
operationId: createReleaseIntent
|
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