#!/bin/sh # Check that the server, SDK and machine-readable WebSocket schemas name the # same current protocol line. This is a contract gate, not a historical pair # compatibility test. set -eu TASK_ROOT=${ORBIT_ROOT_DIR:-.} python3 - "$TASK_ROOT" <<'PY' import json import re import sys from pathlib import Path root = Path(sys.argv[1]).resolve() errors = [] def read(relative): path = root / relative if not path.is_file(): errors.append(f"missing {relative}") return "" return path.read_text() def exact_constant(relative, name): text = read(relative) match = re.search(rf'\bconst\s+{re.escape(name)}\s*=\s*"([^"]+)"', text) if not match: errors.append(f"missing constant {name} in {relative}") return "" return match.group(1) worker_server = exact_constant("internal/transport/httpserver/worker.go", "WorkerSubprotocol") worker_sdk = exact_constant("sdk/go/worker_connection.go", "workerSubprotocol") event_server = exact_constant("internal/transport/httpserver/event_codec.go", "EventSubprotocol") event_sdk = exact_constant("sdk/go/event_connection.go", "eventSubprotocol") if worker_server != worker_sdk or worker_server != "pulse.worker.v1": errors.append(f"worker subprotocol mismatch: server={worker_server!r} sdk={worker_sdk!r}") if event_server != event_sdk or event_server != "orbit.events.v1": errors.append(f"event subprotocol mismatch: server={event_server!r} sdk={event_sdk!r}") def version_literals(relative): text = read(relative) # Frame constructors and schema-facing values must stay on protocol line 1. return set(re.findall(r'\bVersion\s*:\s*"([^"]+)"', text)) for relative in ( "internal/transport/httpserver/worker.go", "internal/transport/httpserver/event_hub.go", "sdk/go/worker_connection.go", "sdk/go/event_connection.go", ): values = version_literals(relative) if values and values != {"1"}: errors.append(f"unexpected frame versions in {relative}: {sorted(values)}") def schema_protocol_versions(relative, expected_id): path = root / relative try: document = json.loads(path.read_text()) except Exception as exc: errors.append(f"cannot parse {relative}: {exc}") return if document.get("$id") != expected_id: errors.append(f"schema id mismatch in {relative}: {document.get('$id')!r}") values = [] def walk(value): if isinstance(value, dict): field = value.get("protocol_version") if isinstance(field, dict) and "const" in field: values.append(field["const"]) for child in value.values(): walk(child) elif isinstance(value, list): for child in value: walk(child) walk(document) if not values or set(values) != {"1"}: errors.append(f"schema protocol versions in {relative}: {sorted(set(values))}") schema_protocol_versions("protocol/workerv1/worker.schema.json", "https://git.yuntuo.hk/k2safe/orbit/schema/worker.v1.json") schema_protocol_versions("protocol/eventsv1/events.schema.json", "https://git.yuntuo.hk/k2safe/orbit/schema/events.v1.json") worker_server_text = read("internal/transport/httpserver/worker.go") worker_sdk_text = read("sdk/go/worker_connection.go") event_server_text = read("internal/transport/httpserver/event_hub.go") event_sdk_text = read("sdk/go/event_connection.go") if 'GET /v1/worker/connect' not in worker_server_text or '"/v1/worker/connect"' not in worker_sdk_text: errors.append("worker v1 route mismatch") if 'GET /v1/events/connect' not in event_server_text or '"/v1/events/connect"' not in event_sdk_text: errors.append("event v1 route mismatch") if errors: for error in errors: print(error, file=sys.stderr) raise SystemExit(1) print(json.dumps({ "http_api": "/v1", "worker_subprotocol": worker_server, "event_subprotocol": event_server, "worker_frame_version": "1", "event_frame_version": "1", "workflow_schema_version": 1, "status": "current_contracts_match", "historical_pairs": "not_verified", }, separators=(",", ":"))) PY