#!/usr/bin/env python3 """Generate the current Flask route inventory as an OpenAPI 3.1 contract.""" from __future__ import annotations import argparse import ast import json import re from collections import defaultdict from pathlib import Path ROOT = Path(__file__).resolve().parents[1] OUTPUT = ROOT / "docs" / "architecture" / "OPENAPI.yaml" PREFIXES = { "meta_data": "/api/meta", "data_interface": "/api/interface", "data_rules": "/api/rules", "graph": "/api/graph", "system": "/api/system", "data_source": "/api/datasource", "data_development": "/api/development/v1", "data_flow": "/api/dataflow", "business_domain": "/api/bd", "data_factory": "/api/datafactory", "data_service": "/api/dataservice", "knowledge_base": "/api/knowledge", } SHORTHAND_METHODS = {"get", "post", "put", "patch", "delete"} EDGE_SAFE_KEY_PATTERN = ( "^(?!.*(?:raw|rows?|sql|credential|secret|private|password|token|local_path))" "[a-z][a-z0-9_]{0,79}$" ) def _edge_safe_schema_lines() -> list[str]: lines = [ " EdgeSafeSummary:", " type: object", " x-max-policy-depth: 12", " maxProperties: 64", f" propertyNames: {{type: string, minLength: 1, maxLength: 80, pattern: '{EDGE_SAFE_KEY_PATTERN}'}}", " additionalProperties: {$ref: '#/components/schemas/EdgeSafeValue12'}", " description: 'Policy-approved bounded metadata/statistics/health/diagnostic fields only; raw rows, SQL, credentials, and local paths are forbidden.'", " EdgeSafeScalar:", " anyOf:", " - {type: string, maxLength: 4096}", " - {type: integer, minimum: -9007199254740991, maximum: 9007199254740991}", " - {type: number, minimum: -1000000000000, maximum: 1000000000000}", " - {type: boolean}", " - {type: 'null'}", " EdgeSafeValue0: {$ref: '#/components/schemas/EdgeSafeScalar'}", ] for depth in range(1, 13): previous = depth - 1 lines.extend( [ f" EdgeSafeValue{depth}:", " anyOf:", " - {$ref: '#/components/schemas/EdgeSafeScalar'}", f" - {{type: array, maxItems: 100, items: {{$ref: '#/components/schemas/EdgeSafeValue{previous}'}}}}", f" - {{type: object, maxProperties: 32, propertyNames: {{type: string, minLength: 1, maxLength: 80, pattern: '{EDGE_SAFE_KEY_PATTERN}'}}, additionalProperties: {{$ref: '#/components/schemas/EdgeSafeValue{previous}'}}}}", ] ) lines.append(" EdgeSafeValue: {$ref: '#/components/schemas/EdgeSafeValue12'}") return lines RESPONSE_FIELDS = { ("data_development", "summary"): [ "scope", "metrics", ], ("data_development", "details"): [ "metric", "state", "records", "page", "page_size", "total", ], ("knowledge_base", "ask"): [ "query_id", "mode", "answer", "answer_status", "degraded_components", "citations", "evidence", "freshness_status", ], } CONNECTOR_REQUEST_SCHEMAS = { "/api/datasource/connectors/config/validate": "ConnectorConfigValidationRequest", "/api/datasource/connectors/runs": "ConnectorRunRequest", "/api/datasource/connectors/machine/runs": "MachineConnectorRunRequest", "/api/datasource/connectors/source-bindings": "ConnectorSourceBindingRequest", "/api/datasource/connectors/principals": "ConnectorPrincipalRequest", "/api/datasource/connectors/{connector_id}/{version}/health": "ConnectorHealthRequest", "/api/datasource/connectors/principals/{principal_uid}/credentials": "ConnectorCredentialRequest", "/api/datasource/connectors/credentials/{credential_uid}/{action}": "ConnectorCredentialRequest", "/api/datasource/graph": "ConnectorGraphRequest", } CONNECTOR_RESPONSE_SCHEMAS = { ("/api/datasource/connectors/manifests", "get"): "ConnectorManifestEnvelope", ("/api/datasource/connectors/runs", "get"): "ConnectorRunListEnvelope", ("/api/datasource/connectors/runs", "post"): "ConnectorRunResultEnvelope", ("/api/datasource/connectors/machine/runs", "post"): "ConnectorRunResultEnvelope", ( "/api/datasource/connectors/runs/{idempotency_key}/cancel", "post", ): "ConnectorRunRecordEnvelope", ( "/api/datasource/connectors/runs/{idempotency_key}/resume", "post", ): "ConnectorRunResultEnvelope", ( "/api/datasource/connectors/config/validate", "post", ): "ConnectorValidationEnvelope", ( "/api/datasource/connectors/{connector_id}/{version}/health", "post", ): "ConnectorHealthEnvelope", ( "/api/datasource/connectors/{connector_id}/{version}/compatibility", "get", ): "ConnectorCompatibilityEnvelope", ("/api/datasource/connectors/principals", "post"): "ConnectorPrincipalEnvelope", ( "/api/datasource/connectors/source-bindings", "post", ): "ConnectorSourceBindingEnvelope", ( "/api/datasource/connectors/principals/{principal_uid}/credentials", "post", ): "ConnectorCredentialEnvelope", ( "/api/datasource/connectors/credentials/{credential_uid}/{action}", "post", ): "ConnectorCredentialEnvelope", ("/api/datasource/graph", "post"): "ConnectorGraphEnvelope", } NO_REQUEST_BODY_PATHS = { "/api/datasource/connectors/runs/{idempotency_key}/cancel", "/api/datasource/connectors/runs/{idempotency_key}/resume", "/api/datasource/connectors/machine/runs/{idempotency_key}/cancel", "/api/datasource/connectors/machine/runs/{idempotency_key}/resume", "/api/datasource/connectors/source-bindings/{binding_uid}/revoke", "/api/datasource/edge/tasks/{task_id}/cancel", "/api/datasource/edge/gateways/{gateway_id}/revoke", } OPTIONAL_REQUEST_BODY_PATHS = { "/api/datasource/connectors/principals/{principal_uid}/credentials", "/api/datasource/connectors/credentials/{credential_uid}/{action}", } EDGE_REQUEST_SCHEMAS = { "/api/datasource/edge/enrollments": "EdgeEnrollmentRequest", "/api/datasource/edge/register": "EdgeRegisterRequest", "/api/datasource/edge/gateways/{gateway_id}/heartbeat": "EdgeHeartbeatRequest", "/api/datasource/edge/gateways/{gateway_id}/rotate": "EdgeRotateRequest", "/api/datasource/edge/tasks": "EdgeTaskContract", "/api/datasource/edge/gateways/{gateway_id}/tasks/pull": "EdgeMachineBinding", "/api/datasource/edge/gateways/{gateway_id}/tasks/{task_id}/outcome": "EdgeTaskOutcomeRequest", "/api/datasource/edge/gateways/{gateway_id}/events": "EdgeEventRequest", "/api/datasource/edge/gateways/{gateway_id}/reconcile": "EdgeReconcileRequest", "/api/datasource/edge/gateways/{gateway_id}/releases": "EdgeReleaseOfferRequest", "/api/datasource/edge/gateways/{gateway_id}/releases/ack": "EdgeReleaseAckRequest", } EDGE_RESPONSE_SCHEMAS = { ("/api/datasource/edge/enrollments", "post"): "EdgeEnrollmentEnvelope", ("/api/datasource/edge/register", "post"): "EdgeRegistrationEnvelope", ("/api/datasource/edge/gateways", "get"): "EdgeGatewayListEnvelope", ( "/api/datasource/edge/gateways/{gateway_id}/tasks/pull", "post", ): "EdgeTaskPullEnvelope", ( "/api/datasource/edge/gateways/{gateway_id}/events", "post", ): "EdgeEventAckEnvelope", ( "/api/datasource/edge/gateways/{gateway_id}/reconcile", "post", ): "EdgeReconcileResponse", ( "/api/datasource/edge/gateways/{gateway_id}/releases", "post", ): "EdgeReleaseEnvelope", } PRODUCTION_OBSERVABILITY_REQUEST_SCHEMAS = { "/api/datafactory/observability/operations/deliveries": "ProductionDeliveryRequest", } PRODUCTION_OBSERVABILITY_RESPONSE_SCHEMAS = { ( "/api/datafactory/observability/operations/deliveries", "post", ): "ProductionDeliveryEnvelope", } TRUSTED_DELIVERY_PERMISSIONS = { "/api/system/trusted-delivery/policy-versions": "security-governance:operate", "/api/system/trusted-delivery/policy-versions/activate": "security-governance:operate", "/api/system/trusted-delivery/policy-versions/rollback": "security-governance:operate", "/api/system/trusted-delivery/decisions": "security-governance:operate", "/api/system/trusted-delivery/provisions": "security-governance:operate", "/api/system/trusted-delivery/legal-holds": "security-governance:manage", "/api/system/trusted-delivery/legal-holds/release": "security-governance:manage", "/api/system/trusted-delivery/reclaim/preview": "security-governance:manage", "/api/system/trusted-delivery/reclaim/execute": "security-governance:manage", "/api/system/trusted-delivery/subscriptions": "security-governance:operate", "/api/system/trusted-delivery/subscriptions/{subscription_uid}/activate": "security-governance:operate", "/api/system/trusted-delivery/subscriptions/{subscription_uid}/pause": "security-governance:operate", "/api/system/trusted-delivery/subscriptions/{subscription_uid}/resume": "security-governance:operate", "/api/system/trusted-delivery/subscriptions/{subscription_uid}/terminate": "security-governance:operate", "/api/system/trusted-delivery/subscriptions/deliveries/{delivery_uid}/compensate": "security-governance:manage", "/api/system/trusted-delivery/subscriptions/anomalies": "security-governance:operate", "/api/system/trusted-delivery/controls/profiles/{profile_id}/active": "security-governance:read", "/api/system/trusted-delivery/controls/release-gates/evaluate": "security-governance:operate", "/api/system/trusted-delivery/controls/profiles/activate": "security-governance:manage", "/api/system/trusted-delivery/controls/profiles/rollback": "security-governance:manage", "/api/system/trusted-delivery/controls/capability-approvals": "security-governance:manage", "/api/system/trusted-delivery/controls/evidence": "security-governance:read", "/api/system/trusted-delivery/controls/destruction-approvals": "security-governance:manage", } TENANT_CONTROL_PERMISSIONS = { "/api/system/tenant/quota-claims": "identity:operate", "/api/system/tenant/approvals": "identity:manage", "/api/system/tenant/provisions": "identity:manage", "/api/system/tenant/lifecycle/activate": "identity:manage", "/api/system/tenant/lifecycle/freeze": "identity:manage", "/api/system/tenant/lifecycle/recover": "identity:manage", "/api/system/tenant/lifecycle/deletion-candidate": "identity:manage", "/api/system/tenant/lifecycle/rollback": "identity:manage", "/api/system/tenant/lifecycle/delete": "identity:manage", "/api/system/tenant/status": "identity:read", "/api/system/tenant/audit": "identity:read", "/api/system/tenant/manifests": "identity:read", } TENANT_CONTROL_BODY_FIELDS = { "/api/system/tenant/quota-claims": ( ("quota_name", "string"), ("amount", "string"), ("idempotency_key", "string"), ), "/api/system/tenant/provisions": (("idempotency_key", "string"),), "/api/system/tenant/approvals": ( ("operation", "string"), ("expected_fence", "string"), ("idempotency_key", "string"), ("backup_digest", "string"), ("retention_seconds", "string"), ), "/api/system/tenant/lifecycle/activate": ( ("expected_fence", "string"), ("idempotency_key", "string"), ("approval_ref", "string"), ("backup_digest", "string"), ("retention_seconds", "string"), ), "/api/system/tenant/lifecycle/freeze": ( ("expected_fence", "string"), ("idempotency_key", "string"), ("approval_ref", "string"), ("backup_digest", "string"), ("retention_seconds", "string"), ), "/api/system/tenant/lifecycle/recover": ( ("expected_fence", "string"), ("idempotency_key", "string"), ("approval_ref", "string"), ("backup_digest", "string"), ("retention_seconds", "string"), ), "/api/system/tenant/lifecycle/deletion-candidate": ( ("expected_fence", "string"), ("idempotency_key", "string"), ("approval_ref", "string"), ("backup_digest", "string"), ("retention_seconds", "string"), ), "/api/system/tenant/lifecycle/rollback": ( ("expected_fence", "string"), ("idempotency_key", "string"), ("approval_ref", "string"), ("backup_digest", "string"), ("retention_seconds", "string"), ), "/api/system/tenant/lifecycle/delete": ( ("expected_fence", "string"), ("idempotency_key", "string"), ("approval_ref", "string"), ("backup_digest", "string"), ("retention_seconds", "string"), ), } TENANT_CONTROL_REQUIRED_FIELDS = { path: tuple(field for field, _ in fields) for path, fields in TENANT_CONTROL_BODY_FIELDS.items() if "/lifecycle/" not in path } TENANT_CONTROL_REQUIRED_FIELDS.update( { path: ("expected_fence", "idempotency_key") for path in TENANT_CONTROL_BODY_FIELDS if "/lifecycle/" in path } ) TENANT_CONTROL_REQUIRED_FIELDS["/api/system/tenant/approvals"] = ( "operation", "expected_fence", "idempotency_key", ) BI_AI_CATALOG_PERMISSIONS = { "/api/system/bi-ai-catalog/local-fixture/sync": "bi-ai-catalog:manage", "/api/system/bi-ai-catalog/assets/search": "bi-ai-catalog:read", "/api/system/bi-ai-catalog/assets/{external_uid}/impact": "bi-ai-catalog:read", "/api/system/bi-ai-catalog/audit": "bi-ai-catalog:read", } METERING_SHOWBACK_PERMISSIONS = { "/api/system/metering/events": "metering:manage", "/api/system/metering/allocations": "metering:manage", "/api/system/metering/budgets": "metering:manage", "/api/system/metering/allocation-replay": "metering:read", "/api/system/metering/showback": "metering:read", "/api/system/metering/reconciliation": "metering:read", } METERING_SHOWBACK_BODY_FIELDS = { "/api/system/metering/events": ( ("schema_version", "integer"), ("event_uid", "string"), ("event_kind", "string"), ("occurred_at", "string"), ("window_start", "string"), ("window_end", "string"), ("quantity", "string"), ("unit", "string"), ("idempotency_key", "string"), ("evidence", "object"), ("mapping", "object"), ), "/api/system/metering/allocations": ( ("schema_version", "integer"), ("rule_uid", "string"), ("rule_version", "integer"), ("effective_start", "string"), ("effective_end", "string"), ("mapping", "object"), ("allocations", "array"), ), "/api/system/metering/budgets": ( ("schema_version", "integer"), ("budget_uid", "string"), ("window", "string"), ("mapping", "object"), ("limit_micros", "integer"), ("threshold_micros", "integer"), ), } METERING_SHOWBACK_REQUIRED_FIELDS = { "/api/system/metering/events": tuple(field for field, _ in METERING_SHOWBACK_BODY_FIELDS["/api/system/metering/events"]), "/api/system/metering/allocations": tuple(field for field, _ in METERING_SHOWBACK_BODY_FIELDS["/api/system/metering/allocations"]), "/api/system/metering/budgets": tuple(field for field, _ in METERING_SHOWBACK_BODY_FIELDS["/api/system/metering/budgets"]), } METERING_SHOWBACK_QUERY_PARAMS = { "/api/system/metering/showback": (("window", "string"),), "/api/system/metering/reconciliation": (("window", "string"),), "/api/system/metering/allocation-replay": (("window", "string"), ("rule_uid", "string"), ("rule_version", "integer")), } BI_AI_CATALOG_BODY_FIELDS = { "/api/system/bi-ai-catalog/local-fixture/sync": (), "/api/system/bi-ai-catalog/assets/search": ( ("query", "string"), ("filters", "object"), ), } BI_AI_CATALOG_REQUIRED_FIELDS = { "/api/system/bi-ai-catalog/local-fixture/sync": (), "/api/system/bi-ai-catalog/assets/search": ("query", "filters"), } EDGE_MACHINE_PATHS = { "/api/datasource/edge/gateways/{gateway_id}/heartbeat", "/api/datasource/edge/gateways/{gateway_id}/tasks/pull", "/api/datasource/edge/gateways/{gateway_id}/tasks/{task_id}/outcome", "/api/datasource/edge/gateways/{gateway_id}/events", "/api/datasource/edge/gateways/{gateway_id}/reconcile", "/api/datasource/edge/gateways/{gateway_id}/releases/ack", } EDGE_CREATED_PATHS = { "/api/datasource/edge/enrollments", "/api/datasource/edge/register", "/api/datasource/edge/tasks", "/api/datasource/edge/gateways/{gateway_id}/releases", } def quoted(value: str) -> str: return json.dumps(value, ensure_ascii=False) def route_path(raw_path: str) -> tuple[str, list[dict[str, str]]]: parameters: list[dict[str, str]] = [] def replace(match: re.Match[str]) -> str: converter = match.group(1) or "string" name = match.group(2) schema_type = "integer" if converter in {"int", "float"} else "string" parameters.append({"name": name, "type": schema_type}) return "{" + name + "}" return re.sub( r"<(?:([a-zA-Z_]+):)?([a-zA-Z_][a-zA-Z0-9_]*)>", replace, raw_path ), parameters def extract_routes() -> list[dict[str, object]]: routes: list[dict[str, object]] = [] for module, prefix in PREFIXES.items(): for route_file in sorted((ROOT / "app" / "api" / module).glob("*.py")): if route_file.name == "__init__.py": continue tree = ast.parse(route_file.read_text(encoding="utf-8")) for node in tree.body: if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): continue for decorator in node.decorator_list: if not isinstance(decorator, ast.Call): continue function = decorator.func if not ( isinstance(function, ast.Attribute) and isinstance(function.value, ast.Name) and function.value.id == "bp" and decorator.args ): continue if function.attr == "route": methods = ["GET"] for keyword in decorator.keywords: if keyword.arg == "methods": methods = ast.literal_eval(keyword.value) elif function.attr in SHORTHAND_METHODS: methods = [function.attr.upper()] else: continue raw_path = ast.literal_eval(decorator.args[0]) path, parameters = route_path(prefix + raw_path) summary = ( ast.get_docstring(node) or node.name.replace("_", " ") ).splitlines()[0] for method in methods: routes.append( { "path": path, "method": method.lower(), "tag": module, "operation_id": f"{module}_{node.name}_{method.lower()}", "summary": summary, "parameters": parameters, "source": str(route_file.relative_to(ROOT)), } ) return sorted(routes, key=lambda item: (str(item["path"]), str(item["method"]))) def render(routes: list[dict[str, object]]) -> str: by_path: dict[str, list[dict[str, object]]] = defaultdict(list) for route in routes: by_path[str(route["path"])].append(route) lines = [ "openapi: 3.1.0", "info:", ' title: "DataOps Platform API(当前代码基线)"', ' version: "2026-08-09"', ' description: "由 scripts/generate_openapi.py 从 app/api/*/routes.py 生成。请求体与响应细节仍以现有专项 API 文档和代码为准。"', f"x-route-count: {len(routes)}", "servers:", ' - url: "http://localhost:15500"', ' description: "全本地隔离测试后端"', "tags:", ] for tag in PREFIXES: lines.extend([f" - name: {tag}", f" description: {quoted(PREFIXES[tag])}"]) lines.append("paths:") for path, operations in sorted(by_path.items()): lines.append(f" {quoted(path)}:") for operation in operations: lines.extend( [ f" {operation['method']}:", f" tags: [{operation['tag']}]", f" operationId: {operation['operation_id']}", f" summary: {quoted(str(operation['summary']))}", f" x-source: {quoted(str(operation['source']))}", ] ) if path.startswith("/api/datasource/edge/"): lines.extend( [ " x-max-request-bytes: 262144", " x-max-json-depth: 32", " x-max-json-nodes: 5000", ] ) parameters = operation["parameters"] if path.startswith("/api/datasource/connectors/machine/runs"): parameters = [ *parameters, { "name": "X-Connector-Credential", "type": "string", "header": True, }, ] if path == "/api/datasource/edge/register": parameters = [ *parameters, {"name": "X-Edge-Enrollment", "type": "string", "header": True}, { "name": "X-DataOps-Edge-Client-Verify", "type": "string", "header": True, }, { "name": "X-DataOps-Edge-Client-Cert", "type": "string", "header": True, }, { "name": "X-Edge-Certificate-SHA256", "type": "string", "header": True, }, ] elif path in EDGE_MACHINE_PATHS: parameters = [ *parameters, {"name": "X-Edge-Credential", "type": "string", "header": True}, { "name": "X-DataOps-Edge-Client-Verify", "type": "string", "header": True, }, { "name": "X-DataOps-Edge-Client-Cert", "type": "string", "header": True, }, { "name": "X-Edge-Certificate-SHA256", "type": "string", "header": True, }, ] if path == "/api/datasource/edge/gateways" and operation["method"] == "get": parameters = [ *parameters, {"name": "limit", "type": "integer", "query": True}, {"name": "offset", "type": "integer", "query": True}, ] if path in METERING_SHOWBACK_QUERY_PARAMS: parameters = [ *parameters, *( {"name": name, "type": schema_type, "query": True, "required": True} for name, schema_type in METERING_SHOWBACK_QUERY_PARAMS[path] ), ] if parameters: lines.append(" parameters:") for parameter in parameters: lines.extend( [ f" - name: {parameter['name']}", f" in: {'header' if parameter.get('header') else 'query' if parameter.get('query') else 'path'}", f" required: {'true' if parameter.get('required') else 'false' if parameter.get('query') else 'true'}", " schema:", f" type: {parameter['type']}", ] ) if parameter["name"] in {"X-Edge-Credential", "X-Edge-Enrollment"}: lines.extend( [ " format: password", " writeOnly: true", ] ) if parameter["name"] == "limit": lines.extend( [" minimum: 1", " maximum: 100"] ) if parameter["name"] == "offset": lines.extend( [" minimum: 0", " maximum: 10000"] ) if ( path == "/api/datasource/connectors/credentials/{credential_uid}/{action}" and parameter["name"] == "action" ): lines.append(" enum: [revoke, rotate]") if ( operation["method"] in {"post", "put", "patch"} and path not in NO_REQUEST_BODY_PATHS ): connector_schema = CONNECTOR_REQUEST_SCHEMAS.get(path) request_schema = ( connector_schema or EDGE_REQUEST_SCHEMAS.get(path) or PRODUCTION_OBSERVABILITY_REQUEST_SCHEMAS.get(path) ) lines.extend( [ " requestBody:", " required: " + ( "false" if path in OPTIONAL_REQUEST_BODY_PATHS else "true" if request_schema or path in TENANT_CONTROL_BODY_FIELDS or path in BI_AI_CATALOG_BODY_FIELDS or path in METERING_SHOWBACK_BODY_FIELDS else "false" ), " content:", " application/json:", " schema:", f" $ref: '#/components/schemas/{request_schema}'" if request_schema else " type: object", ] ) if not request_schema: lines.append( " additionalProperties: false" if path in TRUSTED_DELIVERY_PERMISSIONS or path in TENANT_CONTROL_PERMISSIONS or path in BI_AI_CATALOG_PERMISSIONS or path in METERING_SHOWBACK_PERMISSIONS else " additionalProperties: true" ) if path in TENANT_CONTROL_BODY_FIELDS: fields = TENANT_CONTROL_BODY_FIELDS[path] lines.append( " required: [" + ", ".join(TENANT_CONTROL_REQUIRED_FIELDS[path]) + "]" ) lines.append(" properties:") for field, field_type in fields: lines.extend( [ f" {field}:", f" type: {field_type}", ] ) if ( path == "/api/system/tenant/quota-claims" and field == "amount" ): lines.append( " pattern: '^[0-9]+([.][0-9]{1,6})?$'" ) if path in BI_AI_CATALOG_BODY_FIELDS: fields = BI_AI_CATALOG_BODY_FIELDS[path] lines.append( " required: [" + ", ".join(BI_AI_CATALOG_REQUIRED_FIELDS[path]) + "]" ) if fields: lines.append(" properties:") for field, field_type in fields: lines.extend( [ f" {field}:", f" type: {field_type}", ] ) if path in METERING_SHOWBACK_BODY_FIELDS: fields = METERING_SHOWBACK_BODY_FIELDS[path] lines.append(" required: [" + ", ".join(METERING_SHOWBACK_REQUIRED_FIELDS[path]) + "]") lines.append(" properties:") for field, field_type in fields: lines.extend([f" {field}:", f" type: {field_type}"]) if field == "mapping": lines.extend([" additionalProperties: false", " required: [department, project, cost_center]", " properties:", " department: {type: string}", " project: {type: string}", " cost_center: {type: string}"]) elif field == "evidence": lines.extend([" additionalProperties: false", " required: [digest, reference]", " properties:", " digest: {type: string}", " reference: {type: string}"]) elif field == "allocations": lines.extend([" minItems: 1", " maxItems: 32", " items:", " type: object", " additionalProperties: false", " required: [target, weight_micros]", " properties:", " target: {type: string}", " weight_micros: {type: integer, minimum: 1, maximum: 1000000}"]) response_fields = RESPONSE_FIELDS.get( (str(operation["tag"]), str(operation["operation_id"]).split("_")[-2]) ) if response_fields: lines.append( " x-response-fields: [" + ", ".join(response_fields) + "]" ) connector_response = CONNECTOR_RESPONSE_SCHEMAS.get( (path, str(operation["method"])) ) edge_response = EDGE_RESPONSE_SCHEMAS.get((path, str(operation["method"]))) response_status = ( "201" if operation["method"] == "post" and ( path in EDGE_CREATED_PATHS or path in { "/api/system/bi-ai-catalog/local-fixture/sync", "/api/datasource/connectors/runs", "/api/datasource/connectors/machine/runs", "/api/datasource/connectors/principals", "/api/datasource/connectors/principals/{principal_uid}/credentials", "/api/datasource/connectors/source-bindings", "/api/datafactory/observability/operations/deliveries", "/api/system/tenant/quota-claims", "/api/system/tenant/provisions", "/api/system/tenant/approvals", "/api/system/metering/events", "/api/system/metering/allocations", "/api/system/metering/budgets", } ) else "200" ) if path.startswith("/api/datasource/connectors"): required_permission = ( "machine-credential" if path.startswith("/api/datasource/connectors/machine/runs") else ( "connectors:manage" if "/source-bindings" in path else ( "connectors:read" if operation["method"] == "get" else ( "connectors:manage" if "/principals" in path or "/credentials" in path else "connectors:operate" ) ) ) ) lines.append( f" x-required-permission: {quoted(required_permission)}" ) elif path == "/api/datasource/graph": lines.append(' x-required-permission: "connectors:read"') elif path == "/api/datafactory/observability/operations/deliveries": lines.append( ' x-required-permission: "data-observability:operate"' ) elif path in TRUSTED_DELIVERY_PERMISSIONS: lines.append( f" x-required-permission: {quoted(TRUSTED_DELIVERY_PERMISSIONS[path])}" ) elif path in TENANT_CONTROL_PERMISSIONS: lines.append( f" x-required-permission: {quoted(TENANT_CONTROL_PERMISSIONS[path])}" ) elif path in BI_AI_CATALOG_PERMISSIONS: lines.append( f" x-required-permission: {quoted(BI_AI_CATALOG_PERMISSIONS[path])}" ) elif path in METERING_SHOWBACK_PERMISSIONS: lines.append(f" x-required-permission: {quoted(METERING_SHOWBACK_PERMISSIONS[path])}") elif path.startswith("/api/datasource/edge"): if path == "/api/datasource/edge/register": permission = "one-time-enrollment+mTLS" elif path in EDGE_MACHINE_PATHS: permission = "edge-credential+mTLS" elif ( path == "/api/datasource/edge/gateways" and operation["method"] == "get" ): permission = "edge-gateways:read" elif path == "/api/datasource/edge/enrollments": permission = "edge-gateways:manage" else: permission = "edge-gateways:operate" lines.append(f" x-required-permission: {quoted(permission)}") if path == "/api/datasource/edge/register": lines.extend( [ " security:", " - mutualTLS: []", " edgeEnrollment: []", ] ) elif path in EDGE_MACHINE_PATHS: lines.extend( [ " security:", " - mutualTLS: []", " edgeCredential: []", ] ) else: lines.extend([" security:", " - bearerAuth: []"]) success_headers = ( [ " headers:", " Cache-Control:", " required: true", " schema: {type: string, const: no-store}", ] if path.startswith("/api/datasource/edge") or path == "/api/datafactory/observability/operations/deliveries" or path in TRUSTED_DELIVERY_PERMISSIONS or path in TENANT_CONTROL_PERMISSIONS or path in BI_AI_CATALOG_PERMISSIONS or path in METERING_SHOWBACK_PERMISSIONS else [] ) lines.extend( [ " responses:", f' "{response_status}":', ' description: "请求已由当前实现处理"', *success_headers, " content:", " application/json:", " schema:", f" $ref: '#/components/schemas/{connector_response or edge_response or PRODUCTION_OBSERVABILITY_RESPONSE_SCHEMAS.get((path, str(operation['method']))) or ('EdgeApiEnvelope' if path.startswith('/api/datasource/edge') else 'ApiEnvelope')}'", " default:", ' description: "错误响应"', *success_headers, " content:", " application/json:", " schema:", f" $ref: '#/components/schemas/{'ConnectorErrorEnvelope' if path.startswith('/api/datasource/connectors') or path == '/api/datasource/graph' else 'EdgeErrorEnvelope' if path.startswith('/api/datasource/edge') else 'ApiEnvelope'}'", ] ) lines.extend( [ "components:", " schemas:", " ApiEnvelope:", " type: object", " additionalProperties: true", " required: [code, message, data]", " properties:", " code:", " type: integer", " message:", " type: string", " data: {}", " error: {type: object}", " EdgeApiEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object}}", " ProductionDeliveryRequest:", " type: object", " additionalProperties: false", " required: [incident_uid, alert_uid, channel, summary]", " properties: {incident_uid: {type: string, format: uuid}, alert_uid: {type: string, format: uuid}, channel: {type: string, enum: [monitoring, smtp, enterprise_collaboration, on_call, itsm]}, summary: {type: string, minLength: 1, maxLength: 160}}", " ProductionDeliveryRecord:", " type: object", " additionalProperties: false", " required: [uid, incident_uid, alert_uid, channel, status, attempt_count, lease_fence]", " properties: {uid: {type: string, format: uuid}, incident_uid: {type: string, format: uuid}, alert_uid: {type: string, format: uuid}, channel: {type: string, enum: [monitoring, smtp, enterprise_collaboration, on_call, itsm]}, status: {type: string, enum: [pending, processing, delivered, dead_letter, compensated]}, attempt_count: {type: integer, minimum: 0, maximum: 3}, lease_fence: {type: integer, minimum: 0}, next_attempt_at: {type: string, format: date-time}, lease_owner: {type: [string, 'null'], maxLength: 80}, lease_expires_at: {type: [string, 'null'], format: date-time}, last_reason_code: {type: [string, 'null'], maxLength: 64}, compensation_reason_code: {type: [string, 'null'], maxLength: 64}, compensation_receipt_code: {type: [string, 'null'], maxLength: 64}, idempotency_key: {type: string, maxLength: 200}, payload_digest: {type: string, pattern: '^[a-f0-9]{64}$'}, created_at: {type: string, format: date-time}, updated_at: {type: string, format: date-time}}", " ProductionDeliveryEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {const: 200}, message: {type: string}, data: {$ref: '#/components/schemas/ProductionDeliveryRecord'}}", " EdgeErrorEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data, error]", " properties: {code: {type: integer}, message: {type: string}, data: {type: 'null'}, error: {type: object, additionalProperties: false, required: [code], properties: {code: {type: string, maxLength: 80}}}}", " EdgeMachineBinding:", " type: object", " additionalProperties: false", " required: [environment, network_zone, generation]", " properties: {environment: {type: string, enum: [development, staging, production]}, network_zone: {type: string, minLength: 1, maxLength: 120}, generation: {type: integer, minimum: 1}}", " EdgeEnrollmentRequest:", " type: object", " additionalProperties: false", " required: [gateway_name, environment, network_zone, policy_digest, allowed_control_hosts, allowed_proxy_hosts, expected_certificate_sha256]", " properties: {gateway_name: {type: string, minLength: 1, maxLength: 200}, environment: {type: string, enum: [development, staging, production]}, network_zone: {type: string, minLength: 1, maxLength: 120}, policy_digest: {$ref: '#/components/schemas/Sha256Digest'}, allowed_control_hosts: {$ref: '#/components/schemas/EdgeExactHosts'}, allowed_proxy_hosts: {$ref: '#/components/schemas/EdgeExactHosts'}, expected_certificate_sha256: {$ref: '#/components/schemas/Sha256Digest'}, ttl_seconds: {type: integer, minimum: 60, maximum: 86400}}", " EdgeRegisterRequest:", " type: object", " additionalProperties: false", " required: [gateway_id, environment, network_zone, policy_digest, allowed_control_hosts, allowed_proxy_hosts, version]", " properties: {gateway_id: {type: string, format: uuid}, environment: {type: string, enum: [development, staging, production]}, network_zone: {type: string, minLength: 1, maxLength: 120}, policy_digest: {$ref: '#/components/schemas/Sha256Digest'}, allowed_control_hosts: {$ref: '#/components/schemas/EdgeExactHosts'}, allowed_proxy_hosts: {$ref: '#/components/schemas/EdgeExactHosts'}, version: {$ref: '#/components/schemas/EdgeAgentVersion'}}", " EdgeHeartbeatRequest:", " type: object", " additionalProperties: false", " required: [environment, network_zone, generation, version, safe_summary]", " properties: {environment: {type: string, enum: [development, staging, production]}, network_zone: {type: string, minLength: 1, maxLength: 120}, generation: {type: integer, minimum: 1}, version: {$ref: '#/components/schemas/EdgeAgentVersion'}, safe_summary: {$ref: '#/components/schemas/EdgeSafeSummary'}}", " EdgeRotateRequest:", " type: object", " additionalProperties: false", " required: [certificate_sha256, request_id]", " properties: {certificate_sha256: {$ref: '#/components/schemas/Sha256Digest'}, request_id: {type: string, minLength: 1, maxLength: 255}}", " Sha256Digest: {type: string, pattern: '^[a-f0-9]{64}$'}", " Ed25519Signature: {type: string, pattern: '^[a-f0-9]{128}$'}", " EdgeExactHosts: {type: array, maxItems: 32, uniqueItems: true, items: {type: string, pattern: '^[a-z0-9][a-z0-9.-]*[a-z0-9]$'}}", *_edge_safe_schema_lines(), " EdgeIdentifier: {type: string, minLength: 1, maxLength: 255, pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,254}$'}", " EdgeCursor: {type: [string, 'null'], minLength: 1, maxLength: 255, pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,254}$'}", " EdgeAgentVersion: {type: string, minLength: 1, maxLength: 80, pattern: '^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$'}", " EdgeVersion: {type: string, pattern: '^(0|[1-9][0-9]{0,9})\\.(0|[1-9][0-9]{0,9})\\.(0|[1-9][0-9]{0,9})$'}", " EdgeTaskContract:", " type: object", " additionalProperties: false", " required: [task_id, gateway_id, environment, network_zone, purpose, classification, task_type, contract_version, deadline_at, attempt, idempotency_key, policy_digest]", " properties: {task_id: {$ref: '#/components/schemas/EdgeIdentifier'}, gateway_id: {type: string, format: uuid}, environment: {type: string, enum: [development, staging, production]}, network_zone: {type: string, minLength: 1, maxLength: 120}, purpose: {type: string, minLength: 1, maxLength: 255}, classification: {type: string, enum: [raw, recent_detail, restricted, desensitized_metadata, statistics, lineage, evidence]}, task_type: {type: string, enum: [collect, profile, quality, lineage, controlled_query]}, contract_version: {const: 1}, deadline_at: {type: string, format: date-time}, attempt: {type: integer, minimum: 1, maximum: 5}, idempotency_key: {$ref: '#/components/schemas/EdgeIdentifier'}, policy_digest: {$ref: '#/components/schemas/Sha256Digest'}}", " EdgeSignedTaskEnvelope:", " type: object", " additionalProperties: false", " required: [task, authority_key_id, signature_algorithm, contract_digest, gateway_id, environment, network_zone, policy_digest, purpose, issued_at, expires_at, signature]", " properties: {task: {$ref: '#/components/schemas/EdgeTaskContract'}, authority_key_id: {type: string}, signature_algorithm: {const: Ed25519}, contract_digest: {$ref: '#/components/schemas/Sha256Digest'}, gateway_id: {type: string}, environment: {type: string}, network_zone: {type: string}, policy_digest: {$ref: '#/components/schemas/Sha256Digest'}, purpose: {type: string}, issued_at: {type: string, format: date-time}, expires_at: {type: string, format: date-time}, signature: {$ref: '#/components/schemas/Ed25519Signature'}}", " EdgeEventContract:", " type: object", " additionalProperties: false", " required: [event_id, task_id, gateway_id, environment, network_zone, purpose, classification, contract_version, occurred_at, attempt, idempotency_key, policy_digest, payload]", " properties: {event_id: {type: string}, task_id: {type: string}, gateway_id: {type: string}, environment: {type: string}, network_zone: {type: string}, purpose: {type: string}, classification: {type: string, enum: [desensitized_metadata, statistics, lineage, evidence, health_summary, diagnostic_summary]}, contract_version: {const: 1}, occurred_at: {type: string, format: date-time}, attempt: {type: integer, minimum: 1, maximum: 5}, idempotency_key: {type: string}, policy_digest: {$ref: '#/components/schemas/Sha256Digest'}, payload: {$ref: '#/components/schemas/EdgeSafeSummary'}}", " EdgeEventRequest:", " type: object", " additionalProperties: false", " required: [environment, network_zone, generation, event, lease_token]", " properties: {environment: {type: string}, network_zone: {type: string}, generation: {type: integer, minimum: 1}, event: {$ref: '#/components/schemas/EdgeEventContract'}, lease_token: {type: string, writeOnly: true}}", " EdgeTaskOutcomeRequest:", " type: object", " additionalProperties: false", " required: [environment, network_zone, generation, outcome, lease_token, safe_summary]", " properties: {environment: {type: string}, network_zone: {type: string}, generation: {type: integer, minimum: 1}, outcome: {type: string, enum: [completed, failed, cancelled]}, lease_token: {type: string, writeOnly: true}, safe_summary: {$ref: '#/components/schemas/EdgeSafeSummary'}}", " EdgeReconcileRequest:", " type: object", " additionalProperties: false", " required: [environment, network_zone, generation]", " properties: {environment: {type: string, enum: [development, staging, production]}, network_zone: {type: string, minLength: 1, maxLength: 120}, generation: {type: integer, minimum: 1}, limit: {type: integer, minimum: 1, maximum: 100}, cancel_cursor: {$ref: '#/components/schemas/EdgeCursor'}, release_cursor: {$ref: '#/components/schemas/EdgeCursor'}}", " EdgeReconcileResponse:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string, maxLength: 200}, data: {type: object, additionalProperties: false, required: [cancelled_task_ids, cancel_next_cursor, release_offers, release_next_cursor, release_baseline], properties: {cancelled_task_ids: {type: array, maxItems: 50, items: {$ref: '#/components/schemas/EdgeIdentifier'}}, cancel_next_cursor: {$ref: '#/components/schemas/EdgeCursor'}, release_offers: {type: array, maxItems: 50, items: {$ref: '#/components/schemas/EdgeReleaseManifest'}}, release_next_cursor: {$ref: '#/components/schemas/EdgeCursor'}, release_baseline: {oneOf: [{$ref: '#/components/schemas/EdgeReleaseManifest'}, {type: 'null'}]}}}}", " EdgeReleaseOfferRequest:", " type: object", " additionalProperties: false", " required: [version, artifact_digest, artifact_name, deadline_at, rollback_version, request_id]", " properties: {version: {$ref: '#/components/schemas/EdgeVersion'}, artifact_digest: {$ref: '#/components/schemas/Sha256Digest'}, artifact_name: {type: string, minLength: 1, maxLength: 255, pattern: '^[^/\\\\]+$'}, deadline_at: {type: string, format: date-time}, rollback_version: {$ref: '#/components/schemas/EdgeVersion'}, request_id: {$ref: '#/components/schemas/EdgeIdentifier'}}", " EdgeReleaseAckRequest:", " type: object", " additionalProperties: false", " required: [environment, network_zone, generation, release_id, outcome, safe_summary]", " properties: {environment: {type: string}, network_zone: {type: string}, generation: {type: integer, minimum: 1}, release_id: {type: string, format: uuid}, outcome: {type: string, enum: [accepted, installed, failed, rollback]}, safe_summary: {$ref: '#/components/schemas/EdgeSafeSummary'}}", " EdgeReleaseManifest:", " type: object", " additionalProperties: false", " required: [release_id, version, rollback_version, artifact_digest, artifact_name, deadline_at, status, signature_algorithm, key_id, manifest_digest, signature]", " properties: {release_id: {$ref: '#/components/schemas/EdgeIdentifier'}, version: {$ref: '#/components/schemas/EdgeVersion'}, rollback_version: {$ref: '#/components/schemas/EdgeVersion'}, artifact_digest: {$ref: '#/components/schemas/Sha256Digest'}, artifact_name: {type: string, minLength: 1, maxLength: 255, pattern: '^[^/\\\\]+$'}, deadline_at: {type: string, format: date-time}, status: {type: string, enum: [offered, accepted, failed, installed, rolled_back]}, signature_algorithm: {const: Ed25519}, key_id: {$ref: '#/components/schemas/EdgeIdentifier'}, manifest_digest: {$ref: '#/components/schemas/Sha256Digest'}, signature: {$ref: '#/components/schemas/Ed25519Signature'}}", " EdgeEnrollmentEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [enrollment_id, gateway_id, enrollment_token, expires_in, returned_once], properties: {enrollment_id: {type: string, format: uuid}, gateway_id: {type: string, format: uuid}, enrollment_token: {type: string, writeOnly: true}, expires_in: {type: integer}, returned_once: {const: true}}}}", " EdgeRegistrationEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [gateway_id, generation, certificate_sha256, credential, credential_returned_once], properties: {gateway_id: {type: string, format: uuid}, generation: {type: integer}, certificate_sha256: {$ref: '#/components/schemas/Sha256Digest'}, credential: {type: string, writeOnly: true}, credential_returned_once: {const: true}}}}", " EdgeGatewayListEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [gateways], properties: {gateways: {type: array, maxItems: 100, items: {type: object, description: 'Bounded gateway metadata; credential/token/private key are never included.'}}}}}", " EdgeTaskPullEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {oneOf: [{type: object, additionalProperties: false, required: [task], properties: {task: {type: 'null'}}}, {type: object, additionalProperties: false, required: [task, signed_task_envelope, lease_token, lease_expires_at], properties: {task: {$ref: '#/components/schemas/EdgeTaskContract'}, signed_task_envelope: {$ref: '#/components/schemas/EdgeSignedTaskEnvelope'}, lease_token: {type: string, writeOnly: true}, lease_expires_at: {type: string, format: date-time}}}]}}", " EdgeEventAckEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [event_id, event_digest, remote_lease_digest, received_at, status], properties: {event_id: {type: string}, event_digest: {$ref: '#/components/schemas/Sha256Digest'}, remote_lease_digest: {$ref: '#/components/schemas/Sha256Digest'}, received_at: {type: string, format: date-time}, status: {const: accepted}}}}", " EdgeReleaseEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string, maxLength: 200}, data: {type: object, additionalProperties: false, required: [release_id, status, version, artifact_digest, rollback_version, manifest], properties: {release_id: {type: string, format: uuid}, status: {type: string, enum: [offered, accepted, failed, installed, rolled_back]}, version: {$ref: '#/components/schemas/EdgeVersion'}, artifact_digest: {$ref: '#/components/schemas/Sha256Digest'}, rollback_version: {$ref: '#/components/schemas/EdgeVersion'}, manifest: {$ref: '#/components/schemas/EdgeReleaseManifest'}}}}", " ConnectorConfigValidationRequest:", " type: object", " additionalProperties: false", " required: [connector_id, version, config]", " properties:", " connector_id: {type: string, minLength: 3, maxLength: 64}", " version: {type: string, pattern: '^[0-9]+\\.[0-9]+\\.[0-9]+'}", " config: {type: object}", " ConnectorRunRequest:", " type: object", " additionalProperties: false", " required: [connector_id, version, source_uid, operation, config, scope, dry_run]", " properties:", " connector_id: {type: string}", " version: {type: string}", " source_uid: {type: string, format: uuid}", " operation: {type: string, enum: [discover, snapshot, incremental, lineage, profile, evidence]}", " config: {type: object}", " scope: {$ref: '#/components/schemas/ConnectorScope'}", " cursor: {type: object}", " checkpoint: {type: object}", " idempotency_key: {type: string, pattern: '^[a-f0-9]{64}$'}", " dry_run: {const: true}", " process_key: {type: string, minLength: 1, maxLength: 300}", " MachineConnectorRunRequest:", " type: object", " additionalProperties: false", " required: [connector_id, version, source_uid, business_domain_uid, environment, process_key, operation, scope]", " properties:", " connector_id: {type: string}", " version: {type: string}", " source_uid: {type: string, format: uuid}", " business_domain_uid: {type: string, format: uuid}", " environment: {type: string, enum: [development, staging, production]}", " process_key: {type: string, minLength: 1, maxLength: 300}", " operation: {type: string, enum: [discover, snapshot, incremental, lineage, profile, evidence]}", " scope: {$ref: '#/components/schemas/ConnectorScope'}", " cursor: {type: object}", " checkpoint: {type: object}", " idempotency_key: {type: string, pattern: '^[a-f0-9]{64}$'}", " dry_run: {type: boolean, default: false}", " ConnectorPrincipalRequest:", " type: object", " additionalProperties: false", " required: [connector_id, version, source_uid, business_domain_uid, environment, operations, scopes]", " properties:", " connector_id: {type: string}", " version: {type: string}", " source_uid: {type: string, format: uuid}", " business_domain_uid: {type: string, format: uuid}", " environment: {type: string, enum: [development, staging, production]}", " operations: {type: array, minItems: 1, uniqueItems: true, items: {type: string}}", " scopes: {$ref: '#/components/schemas/ConnectorScope'}", " source_binding_uid: {type: string, format: uuid}", " source_binding_version: {type: integer, minimum: 1}", " ConnectorSourceBindingRequest:", " type: object", " additionalProperties: false", " required: [connector_id, version, source_uid, business_domain_uid, environment, approved_config]", " properties:", " binding_uid: {type: string, format: uuid}", " connector_id: {type: string}", " version: {type: string}", " source_uid: {type: string, format: uuid}", " business_domain_uid: {type: string, format: uuid}", " environment: {type: string, enum: [development, staging, production]}", " approved_config: {type: object}", " ConnectorScope:", " type: object", " additionalProperties: false", " properties:", " include_schemas: {type: array, uniqueItems: true, items: {type: string, minLength: 1}}", " exclude_schemas: {type: array, uniqueItems: true, items: {type: string, minLength: 1}}", " include_tables: {type: array, uniqueItems: true, items: {type: string, minLength: 1}}", " exclude_tables: {type: array, uniqueItems: true, items: {type: string, minLength: 1}}", " ConnectorHealthRequest:", " type: object", " additionalProperties: false", " required: [config]", " properties: {config: {type: object}}", " ConnectorCredentialRequest:", " type: object", " additionalProperties: false", " properties: {ttl_seconds: {type: integer, minimum: 60, maximum: 900}}", " ConnectorGraphRequest:", " type: object", " additionalProperties: false", " properties: {source_uid: {type: string, format: uuid}, business_domain_uid: {type: string, format: uuid}, process_key: {type: string}, run_uid: {type: string, format: uuid}, limit: {type: integer, minimum: 1, maximum: 1000}}", " ConnectorManifestEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties:", " code: {type: integer}", " message: {type: string}", " data: {type: object, additionalProperties: false, required: [manifests], properties: {manifests: {type: array, items: {$ref: '#/components/schemas/ConnectorManifest'}}}}", " ConnectorManifest:", " type: object", " additionalProperties: false", " required: [connector_id, version, sdk_version, display_name, capabilities, config_schema]", " properties: {connector_id: {type: string}, version: {type: string}, sdk_version: {type: string}, display_name: {type: string}, capabilities: {type: array, items: {type: string}}, config_schema: {type: object}}", " ConnectorOperationResult:", " type: object", " additionalProperties: false", " required: [records, cursor, checkpoint, evidence, status]", " properties: {records: {type: array, items: {type: object}}, cursor: {type: object}, checkpoint: {type: object}, evidence: {type: object}, status: {type: string, enum: [succeeded, dry_run]}}", " ConnectorRunResultEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {$ref: '#/components/schemas/ConnectorOperationResult'}}", " ConnectorRunRecord:", " type: object", " additionalProperties: false", " required: [uid, idempotency_key, connector_id, connector_version, source_uid, operation, status, attempt_count, checkpoint_summary, cursor_summary, dry_run]", " properties: {uid: {type: string, format: uuid}, idempotency_key: {type: string}, connector_id: {type: string}, connector_version: {type: string}, source_uid: {type: string, format: uuid}, principal_uid: {type: [string, 'null'], format: uuid}, business_domain_uid: {type: [string, 'null'], format: uuid}, environment: {type: [string, 'null']}, process_key: {type: [string, 'null']}, operation: {type: string}, status: {type: string}, attempt_count: {type: integer}, checkpoint_summary: {type: object}, cursor_summary: {type: object}, scope: {type: object}, error_category: {type: [string, 'null']}, dry_run: {type: boolean}, created_at: {type: string}, updated_at: {type: string}}", " ConnectorRunRecordEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {$ref: '#/components/schemas/ConnectorRunRecord'}}", " ConnectorRunListEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [runs], properties: {runs: {type: array, items: {$ref: '#/components/schemas/ConnectorRunRecord'}}}}}", " ConnectorValidationEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, required: [valid, config_keys], properties: {valid: {const: true}, config_keys: {type: array, items: {type: string}}}, additionalProperties: false}}", " ConnectorErrorEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data, error]", " properties:", " code: {type: integer}", " message: {type: string}", " data: {type: 'null'}", " error: {type: object, additionalProperties: false, required: [code, category, retryable], properties: {code: {const: CONNECTOR_ERROR}, category: {type: string, enum: [configuration, conflict, authentication, permission, rate_limit, timeout, upstream, contract, cancelled]}, retryable: {type: boolean}}}", " ConnectorHealthEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [status, detail], properties: {status: {type: string}, detail: {type: string}}}}", " ConnectorCompatibilityEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [compatible, connector_version, sdk_version, detail], properties: {compatible: {type: boolean}, connector_version: {type: string}, sdk_version: {type: string}, detail: {type: string}}}}", " ConnectorPrincipalEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [principal_uid], properties: {principal_uid: {type: string, format: uuid}}}}", " ConnectorSourceBindingEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [binding_uid, binding_version, status, rebound_principals], properties: {binding_uid: {type: string, format: uuid}, binding_version: {type: integer, minimum: 1}, status: {const: approved}, rebound_principals: {type: integer, minimum: 0}}}}", " ConnectorCredentialEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {oneOf: [{type: object, additionalProperties: false, required: [credential_uid, credential, expires_in, returned_once], properties: {credential_uid: {type: string, format: uuid}, credential: {type: string, writeOnly: true}, expires_in: {type: integer}, returned_once: {const: true}}}, {type: object, additionalProperties: false, required: [revoked], properties: {revoked: {type: boolean}}}]}}", " ConnectorGraphEnvelope:", " type: object", " additionalProperties: false", " required: [code, message, data]", " properties: {code: {type: integer}, message: {type: string}, data: {type: object, additionalProperties: false, required: [nodes, edges, summary], properties: {nodes: {type: array, items: {type: object, additionalProperties: false, required: [type, key], properties: {type: {type: string, enum: [source, asset, process, business_domain, run]}, key: {type: string}}}}, edges: {type: array, items: {type: object}}, summary: {type: object, additionalProperties: false, required: [node_count, edge_count], properties: {node_count: {type: integer}, edge_count: {type: integer}}}}}}", " securitySchemes:", " mutualTLS:", " type: mutualTLS", " edgeCredential:", " type: apiKey", " in: header", " name: X-Edge-Credential", ' description: "One-time-returned edge credential; always combined with mutualTLS certificate proof."', " edgeEnrollment:", " type: apiKey", " in: header", " name: X-Edge-Enrollment", ' description: "Single-use enrollment token; always combined with mutualTLS certificate proof."', " bearerAuth:", " type: http", " scheme: bearer", " bearerFormat: JWT", ' description: "下一阶段统一认证方案;当前路由尚未全部接入。"', ] ) return "\n".join(lines) + "\n" def main(output: Path = OUTPUT) -> None: routes = extract_routes() if not routes: raise SystemExit("No Flask routes found") output.parent.mkdir(parents=True, exist_ok=True) output.write_text(render(routes), encoding="utf-8") try: label = output.relative_to(ROOT) except ValueError: label = output print(f"Generated {label} with {len(routes)} operations") if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, default=OUTPUT) args = parser.parse_args() main(args.output)