| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 |
- #!/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",
- }
- 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 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: {'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)
- lines.extend(
- [
- " requestBody:",
- " required: "
- + (
- "false"
- if path in OPTIONAL_REQUEST_BODY_PATHS
- else "true" if request_schema 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: true")
- 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/datasource/connectors/runs",
- "/api/datasource/connectors/machine/runs",
- "/api/datasource/connectors/principals",
- "/api/datasource/connectors/principals/{principal_uid}/credentials",
- "/api/datasource/connectors/source-bindings",
- })
- 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.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")
- else []
- )
- lines.extend(
- [
- " responses:",
- f' "{response_status}":',
- ' description: "请求已由当前实现处理"',
- *success_headers,
- " content:",
- " application/json:",
- " schema:",
- f" $ref: '#/components/schemas/{connector_response or edge_response 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}}",
- " 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)
|