32 Incheckningar 487b57d538 ... 8a0e8049c0

Upphovsman SHA1 Meddelande Datum
  马小龙 8a0e8049c0 feat: clarify data product navigation names 2 månader sedan
  马小龙 c147c83ae3 test: define data product navigation names 2 månader sedan
  马小龙 fa99dd4148 docs: plan data product navigation naming 2 månader sedan
  马小龙 2d5a9073d7 docs: define data product navigation naming 2 månader sedan
  马小龙 4061ade0ee feat: integrate rest catalog with metadata governance 2 månader sedan
  马小龙 4ef7f6a78b test: close connector boundary contract gaps 2 månader sedan
  马小龙 3c2bbda905 test: strengthen connector operation contracts 2 månader sedan
  马小龙 bca758e461 refactor: isolate database connector operations 2 månader sedan
  马小龙 4f39db22d3 feat: move enterprise connectors to data factory 2 månader sedan
  马小龙 57a481e3c7 feat: add postgresql enterprise connector 2 månader sedan
  马小龙 10e42a080d docs: plan connector metadata boundary 2 månader sedan
  马小龙 9e45370348 docs: add postgresql enterprise connector scope 2 månader sedan
  马小龙 d38bb2f9a1 docs: define connector metadata navigation boundary 2 månader sedan
  马小龙 d42b3fd234 fix: keep menu helper outside vuex module scan 2 månader sedan
  马小龙 67d426030c feat: move enterprise identity to system management 2 månader sedan
  马小龙 b3c79060dc test: define enterprise identity menu ownership 2 månader sedan
  马小龙 a5c2831975 docs: plan enterprise identity navigation move 2 månader sedan
  马小龙 ac0190e2a4 docs: move enterprise identity navigation design 2 månader sedan
  马小龙 2e56a32260 test: lock orchestration navigation metadata 2 månader sedan
  马小龙 2c97aec2d6 fix: align legacy n8n navigation metadata 2 månader sedan
  马小龙 6303b33e2b feat: separate Kestra and legacy n8n navigation 2 månader sedan
  马小龙 7ee4846ee1 test: reuse dedicated deployment page path 2 månader sedan
  马小龙 0e5e503c3f refactor: isolate Kestra production deployment page 2 månader sedan
  马小龙 f795ef8473 test: define Kestra and n8n navigation boundary 2 månader sedan
  马小龙 f9f22181ff docs: plan Kestra and n8n navigation separation 2 månader sedan
  马小龙 f4f13f9628 docs: separate Kestra and n8n navigation design 2 månader sedan
  马小龙 ab32507135 feat: complete phase 1-3 UAT baseline 2 månader sedan
  马小龙 84dc0adc7c feat: complete enterprise edge gateway 2 månader sedan
  马小龙 b4af37ca38 feat: establish enterprise connector platform 3 månader sedan
  马小龙 6bc4144dc1 feat: establish enterprise identity and sso controls 3 månader sedan
  马小龙 655fea6cd0 feat: establish phase three enterprise acceptance gate 3 månader sedan
  马小龙 b104e81aef feat: establish phase three baseline 3 månader sedan
100 ändrade filer med 23077 tillägg och 157 borttagningar
  1. 1 0
      .dockerignore
  2. 1 0
      .gitignore
  3. 7 2
      app/__init__.py
  4. 73 0
      app/api/data_factory/observability_routes.py
  5. 3 0
      app/api/data_source/__init__.py
  6. 299 0
      app/api/data_source/edge_routes.py
  7. 507 15
      app/api/data_source/routes.py
  8. 108 0
      app/api/knowledge_base/agent_governance_routes.py
  9. 7 0
      app/api/system/__init__.py
  10. 166 0
      app/api/system/bi_ai_catalog_routes.py
  11. 648 0
      app/api/system/enterprise_identity.py
  12. 137 0
      app/api/system/metering_showback_routes.py
  13. 140 0
      app/api/system/plugin_platform_routes.py
  14. 207 0
      app/api/system/tenant_routes.py
  15. 182 0
      app/api/system/trusted_delivery.py
  16. 99 0
      app/api/system/trusted_delivery_controls.py
  17. 34 1
      app/api/system/users.py
  18. 154 24
      app/config/config.py
  19. 121 0
      app/config/database_urls.py
  20. 15 0
      app/core/connectors/__init__.py
  21. 224 0
      app/core/connectors/bindings.py
  22. 32 0
      app/core/connectors/builtin/__init__.py
  23. 184 0
      app/core/connectors/builtin/database.py
  24. 63 0
      app/core/connectors/builtin/oracle.py
  25. 68 0
      app/core/connectors/builtin/postgresql.py
  26. 381 0
      app/core/connectors/builtin/rest_catalog.py
  27. 78 0
      app/core/connectors/builtin/sqlserver.py
  28. 84 0
      app/core/connectors/errors.py
  29. 379 0
      app/core/connectors/identity.py
  30. 52 0
      app/core/connectors/registry.py
  31. 513 0
      app/core/connectors/repository.py
  32. 573 0
      app/core/connectors/runtime.py
  33. 233 0
      app/core/connectors/sdk.py
  34. 39 0
      app/core/connectors/secrets.py
  35. 16 0
      app/core/connectors/store.py
  36. 7 1
      app/core/data_source/adapters/__init__.py
  37. 31 4
      app/core/data_source/adapters/base.py
  38. 25 0
      app/core/data_source/adapters/oracle.py
  39. 92 0
      app/core/data_source/adapters/sqlserver.py
  40. 6 0
      app/core/data_source/errors.py
  41. 32 5
      app/core/data_source/manager.py
  42. 4 1
      app/core/data_source/service.py
  43. 63 0
      app/core/edge_gateway/__init__.py
  44. 706 0
      app/core/edge_gateway/contracts.py
  45. 475 0
      app/core/edge_gateway/policy.py
  46. 2189 0
      app/core/edge_gateway/queue.py
  47. 609 0
      app/core/edge_gateway/repository.py
  48. 413 0
      app/core/edge_gateway/runtime_roles.py
  49. 700 0
      app/core/edge_gateway/service.py
  50. 518 0
      app/core/events/production_observability.py
  51. 431 0
      app/core/events/production_operations.py
  52. 262 0
      app/core/events/production_operations_repository.py
  53. 901 0
      app/core/governance/bi_ai_asset_catalog.py
  54. 616 82
      app/core/governance/domain_replication.py
  55. 88 0
      app/core/governance/domain_replication_contract_registry.py
  56. 599 0
      app/core/governance/metering_showback.py
  57. 1 1
      app/core/llm/agent_governance.py
  58. 4 0
      app/core/llm/agent_governance_repository.py
  59. 379 0
      app/core/llm/runtime_governance.py
  60. 168 0
      app/core/llm/runtime_governance_repository.py
  61. 111 0
      app/core/llm/runtime_server.py
  62. 74 0
      app/core/mcp/governed_invocation.py
  63. 5 0
      app/core/plugins/__init__.py
  64. 178 0
      app/core/plugins/governance.py
  65. 113 0
      app/core/plugins/persistence.py
  66. 190 0
      app/core/plugins/repository.py
  67. 56 0
      app/core/plugins/runtime.py
  68. 131 6
      app/core/system/auth.py
  69. 345 0
      app/core/system/enterprise_identity.py
  70. 570 0
      app/core/system/identity_repository.py
  71. 103 0
      app/core/system/identity_sessions.py
  72. 261 0
      app/core/system/oidc.py
  73. 140 10
      app/core/system/permissions.py
  74. 34 0
      app/core/system/tenant_approval_repository.py
  75. 130 0
      app/core/system/tenant_context.py
  76. 149 0
      app/core/system/tenant_control_repository.py
  77. 160 0
      app/core/system/tenant_control_service.py
  78. 113 0
      app/core/system/tenant_lifecycle.py
  79. 102 0
      app/core/system/tenant_repository.py
  80. 66 0
      app/core/system/tenant_resources.py
  81. 14 4
      app/core/system/tokens.py
  82. 670 0
      app/core/system/trusted_delivery.py
  83. 293 0
      app/core/system/trusted_delivery_controls.py
  84. 150 0
      app/core/system/trusted_delivery_controls_repository.py
  85. 103 0
      app/core/system/trusted_delivery_controls_service.py
  86. 534 0
      app/core/system/trusted_delivery_repository.py
  87. 203 0
      app/core/system/trusted_delivery_subscription_repository.py
  88. 255 0
      app/core/system/trusted_delivery_subscriptions.py
  89. 19 0
      app/edge_gateway/__init__.py
  90. 5 0
      app/edge_gateway/__main__.py
  91. 1273 0
      app/edge_gateway/agent.py
  92. 333 0
      app/edge_gateway/bootstrap.py
  93. 32 0
      app/edge_gateway/healthcheck.py
  94. 276 0
      app/edge_gateway/runtime.py
  95. 427 0
      app/edge_gateway/transport.py
  96. 30 0
      app/models/__init__.py
  97. 83 0
      app/models/connectors.py
  98. 133 0
      app/models/edge_gateway.py
  99. 51 0
      app/runner/api.py
  100. 15 1
      app/runner/bootstrap.py

+ 1 - 0
.dockerignore

@@ -3,6 +3,7 @@
 .pytest_cache
 **/__pycache__
 **/*.pyc
+._*
 frontend/node_modules
 frontend/dist
 deployment

+ 1 - 0
.gitignore

@@ -60,6 +60,7 @@ log/
 .env.test.local
 .env.production.local
 .known_hosts_dataops
+deploy/docker/edge-secrets/
 work/
 .worktrees/
 

+ 7 - 2
app/__init__.py

@@ -12,6 +12,7 @@ from app.config.config import (
     current_env,
     log_llm_env_status,
     log_service_env_status,
+    validate_production_database_identity,
 )
 from app.config.cors import CORS_OPTIONS
 
@@ -30,6 +31,8 @@ def create_app():
     # 配置CORS以解决跨域问题
     CORS(app, **CORS_OPTIONS)
     db.init_app(app)
+    with app.app_context():
+        validate_production_database_identity(app, db.engine)
 
     # 注册蓝图
     from app.api.business_domain import bp as business_domain_bp
@@ -139,7 +142,8 @@ def configure_response_headers(app):
                 headers = (
                     "Content-Type, Authorization, X-Requested-With, "
                     "Accept, Origin, Cache-Control, X-File-Name, If-Match, "
-                    "X-Agent-Credential"
+                    "X-Agent-Credential, X-Edge-Enrollment, X-Edge-Credential, "
+                    "X-Edge-Certificate-SHA256"
                 )
                 response.headers["Access-Control-Allow-Headers"] = headers
                 response.headers["Access-Control-Max-Age"] = "86400"
@@ -161,7 +165,8 @@ def configure_response_headers(app):
             if "Access-Control-Allow-Headers" not in response.headers:
                 headers = (
                     "Content-Type, Authorization, X-Requested-With, Accept, Origin, "
-                    "If-Match, X-Agent-Credential"
+                    "If-Match, X-Agent-Credential, X-Edge-Enrollment, "
+                    "X-Edge-Credential, X-Edge-Certificate-SHA256"
                 )
                 response.headers["Access-Control-Allow-Headers"] = headers
 

+ 73 - 0
app/api/data_factory/observability_routes.py

@@ -2,6 +2,9 @@
 
 from __future__ import annotations
 
+import uuid
+from contextlib import suppress
+
 from flask import g, jsonify, request
 
 from app import db
@@ -22,18 +25,40 @@ def get_data_observability_service():
     )
 
 
+def get_production_operations_service():
+    import time
+
+    from app.core.events.production_operations import ProductionOperationsService
+    from app.core.events.production_operations_repository import (
+        SqlAlchemyProductionOperationsRepository,
+    )
+
+    return ProductionOperationsService(
+        SqlAlchemyProductionOperationsRepository(db.session), now=time.time
+    )
+
+
 def _actor_uid():
     identity = getattr(g, "current_user", {}) or {}
     return identity.get("id") or identity.get("sub")
 
 
 def _payload():
+    body = request.get_data(cache=True)
+    if len(body) > 4096:
+        raise ValueError("request body exceeds 4096 bytes")
     value = request.get_json(silent=True)
     if not isinstance(value, dict):
         raise ValueError("request body must be an object")
     return value
 
 
+def _no_store(value, status):
+    response = jsonify(success(value))
+    response.headers["Cache-Control"] = "no-store"
+    return response, status
+
+
 def _error(error):
     db.session.rollback()
     if isinstance(error, LookupError):
@@ -45,6 +70,35 @@ def _error(error):
     return jsonify(failed("数据可观测与事故请求处理失败", code=500)), 500
 
 
+def _production_delivery_error(error, service):
+    """Rollback first, then record only a redacted rejection receipt."""
+    db.session.rollback()
+    message = str(error)
+    reason_code = (
+        "conflict"
+        if isinstance(error, RuntimeError)
+        or message in {"idempotency conflict", "delivery queue hard limit reached"}
+        else "invalid_request"
+        if isinstance(error, ValueError)
+        else "internal_error"
+    )
+    with suppress(Exception):
+        service.audit_rejection_independently(
+            actor_uid=_actor_uid(),
+            reason_code=reason_code,
+            correlation_id=str(uuid.uuid4()),
+        )
+    status = {"invalid_request": 400, "conflict": 409, "internal_error": 500}[reason_code]
+    return jsonify(failed(reason_code, code=status)), status
+
+
+@bp.after_app_request
+def _no_store_production_delivery_responses(response):
+    if request.path == "/api/datafactory/observability/operations/deliveries":
+        response.headers["Cache-Control"] = "no-store"
+    return response
+
+
 @bp.get("/observability/overview")
 def get_observability_overview():
     try:
@@ -84,6 +138,25 @@ def collect_observability_signals():
         return _error(error)
 
 
+@bp.post("/observability/operations/deliveries")
+def enqueue_production_delivery():
+    service = None
+    try:
+        payload = _payload()
+        if set(payload) != {"incident_uid", "alert_uid", "channel", "summary"}:
+            raise ValueError("production delivery request has unsupported fields")
+        service = get_production_operations_service()
+        record = service.enqueue_incident_delivery(
+            payload, actor_uid=_actor_uid()
+        )
+        db.session.commit()
+        return _no_store(record, 201)
+    except Exception as error:
+        return _production_delivery_error(
+            error, service or get_production_operations_service()
+        )
+
+
 @bp.get("/observability/alerts")
 def list_observability_alerts():
     try:

+ 3 - 0
app/api/data_source/__init__.py

@@ -1,5 +1,8 @@
+# ruff: noqa: E402, F401, I001
+
 from flask import Blueprint
 
 bp = Blueprint("data_source", __name__)
 
+from app.api.data_source import edge_routes  # noqa: E402, F401
 from app.api.data_source import routes  # noqa: E402, F401

+ 299 - 0
app/api/data_source/edge_routes.py

@@ -0,0 +1,299 @@
+"""Bounded HTTP boundary for human and machine edge-gateway operations."""
+
+from __future__ import annotations
+
+import hmac
+import ipaddress
+import logging
+import re
+from datetime import UTC, datetime
+from urllib.parse import unquote
+
+from cryptography import x509
+from cryptography.hazmat.primitives import hashes
+from flask import current_app, g, jsonify, request
+
+from app.api.data_source import bp
+from app.core.edge_gateway.contracts import EdgeContractError, preflight_json
+from app.core.edge_gateway.service import (
+    EdgeGatewayAuthenticationError,
+    EdgeGatewayError,
+    EdgeGatewayPayloadTooLargeError,
+    EdgeGatewayService,
+    EdgeGatewayValidationError,
+)
+from app.models.result import failed, success
+
+logger = logging.getLogger(__name__)
+MAX_EDGE_REQUEST_BYTES = 262_144
+MAX_EDGE_REQUEST_NODES = 5_000
+MAX_EDGE_RESPONSE_BYTES = 1_048_576
+
+
+def _service() -> EdgeGatewayService:
+    from app import db
+    from app.core.edge_gateway.repository import EdgeGatewayRepository
+
+    return EdgeGatewayService(
+        EdgeGatewayRepository(db.session),
+        signing_private_key=current_app.config.get(
+            "EDGE_GATEWAY_SIGNING_PRIVATE_KEY"
+        ),
+        signing_private_key_file=current_app.config.get(
+            "EDGE_GATEWAY_SIGNING_PRIVATE_KEY_FILE"
+        ),
+        signing_public_key=current_app.config.get(
+            "EDGE_GATEWAY_SIGNING_PUBLIC_KEY"
+        ),
+        signing_key_id=current_app.config.get("EDGE_GATEWAY_SIGNING_KEY_ID"),
+        production=(
+            str(current_app.config.get("FLASK_ENV", "")).lower() == "production"
+        ),
+    )
+
+
+def _actor_uid():
+    identity = getattr(g, "current_user", {}) or {}
+    return identity.get("id") or identity.get("sub")
+
+
+def _payload() -> dict:
+    if request.content_length is not None and request.content_length > MAX_EDGE_REQUEST_BYTES:
+        raise EdgeGatewayPayloadTooLargeError("edge request is too large")
+    raw = request.get_data(cache=True)
+    if len(raw) > MAX_EDGE_REQUEST_BYTES:
+        raise EdgeGatewayPayloadTooLargeError("edge request is too large")
+    value = request.get_json(silent=True)
+    if value is None and not raw:
+        return {}
+    if not isinstance(value, dict):
+        raise EdgeGatewayValidationError("edge request body is invalid")
+    try:
+        preflight_json(
+            value, max_depth=32, max_nodes=MAX_EDGE_REQUEST_NODES,
+            max_string_bytes=131_072, max_total_bytes=MAX_EDGE_REQUEST_BYTES,
+        )
+    except EdgeContractError as exc:
+        raise EdgeGatewayPayloadTooLargeError("edge request exceeds safe limits") from exc
+    return value
+
+
+def _tls_certificate_sha256() -> str:
+    configured = current_app.config.get("EDGE_MTLS_TRUSTED_PROXY_IPS", ())
+    if isinstance(configured, str):
+        configured = tuple(
+            item.strip() for item in configured.split(",") if item.strip()
+        )
+    if not isinstance(configured, (tuple, list, set, frozenset)) or not configured:
+        raise EdgeGatewayAuthenticationError(
+            "trusted mTLS proxy is not configured"
+        )
+    try:
+        trusted = {str(ipaddress.ip_address(item)) for item in configured}
+        remote = str(ipaddress.ip_address(request.remote_addr or ""))
+    except ValueError as exc:
+        raise EdgeGatewayAuthenticationError(
+            "request did not originate from a trusted mTLS proxy"
+        ) from exc
+    if remote not in trusted:
+        raise EdgeGatewayAuthenticationError(
+            "request did not originate from a trusted mTLS proxy"
+        )
+    if request.headers.get("X-DataOps-Edge-Client-Verify") != "SUCCESS":
+        raise EdgeGatewayAuthenticationError(
+            "verified TLS client certificate is required"
+        )
+    escaped_certificate = request.headers.get("X-DataOps-Edge-Client-Cert", "")
+    if not escaped_certificate or len(escaped_certificate.encode()) > 32_768:
+        raise EdgeGatewayAuthenticationError(
+            "verified TLS client certificate is required"
+        )
+    try:
+        certificate = x509.load_pem_x509_certificate(
+            unquote(escaped_certificate).encode()
+        )
+    except ValueError as exc:
+        raise EdgeGatewayAuthenticationError(
+            "verified TLS client certificate is invalid"
+        ) from exc
+    now = datetime.now(UTC)
+    if (
+        certificate.not_valid_before_utc > now
+        or certificate.not_valid_after_utc <= now
+    ):
+        raise EdgeGatewayAuthenticationError(
+            "verified TLS client certificate is not currently valid"
+        )
+    proof = certificate.fingerprint(hashes.SHA256()).hex()
+    header = request.headers.get("X-Edge-Certificate-SHA256")
+    if header and (
+        not re.fullmatch(r"[0-9a-f]{64}", header)
+        or not hmac.compare_digest(header, proof)
+    ):
+        raise EdgeGatewayAuthenticationError(
+            "TLS client certificate proof does not match"
+        )
+    return proof
+
+
+def _machine_auth(gateway_id: str, payload: dict) -> dict:
+    return {
+        "credential": request.headers.get("X-Edge-Credential", ""),
+        "certificate_sha256": _tls_certificate_sha256(),
+        "gateway_id": gateway_id,
+        "environment": payload.get("environment"),
+        "network_zone": payload.get("network_zone"),
+        "generation": payload.get("generation"),
+    }
+
+
+def _respond(call, created: bool = False):
+    try:
+        result = call()
+        response = jsonify(success(result))
+        if len(response.get_data()) > MAX_EDGE_RESPONSE_BYTES:
+            response = jsonify(failed(
+                "边缘网关响应超过安全上限", code=500,
+                error={"code": "EDGE_GATEWAY_RESPONSE_LIMIT"},
+            ))
+            response.headers["Cache-Control"] = "no-store"
+            return response, 500
+        response.headers["Cache-Control"] = "no-store"
+        return response, 201 if created else 200
+    except EdgeGatewayError as exc:
+        logger.warning("edge gateway request rejected: code=%s", exc.code)
+        response = jsonify(failed("边缘网关请求被拒绝", code=exc.status_code, error={"code": exc.code}))
+        response.headers["Cache-Control"] = "no-store"
+        return response, exc.status_code
+    except Exception:
+        logger.exception("edge gateway request failed")
+        response = jsonify(failed("边缘网关操作失败", code=500, error={"code": "EDGE_GATEWAY_INTERNAL"}))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 500
+
+
+@bp.errorhandler(EdgeGatewayError)
+def edge_gateway_error(error):
+    response = jsonify(failed("边缘网关请求被拒绝", code=error.status_code, error={"code": error.code}))
+    response.headers["Cache-Control"] = "no-store"
+    return response, error.status_code
+
+
+@bp.route("/edge/enrollments", methods=["POST"])
+def edge_create_enrollment():
+    payload = _payload()
+    return _respond(lambda: _service().create_enrollment(
+        gateway_name=payload.get("gateway_name"), environment=payload.get("environment"),
+        network_zone=payload.get("network_zone"), policy_digest=payload.get("policy_digest"),
+        allowed_control_hosts=payload.get("allowed_control_hosts"),
+        allowed_proxy_hosts=payload.get("allowed_proxy_hosts", []),
+        expected_certificate_sha256=payload.get("expected_certificate_sha256"),
+        ttl_seconds=payload.get("ttl_seconds", 600), actor_uid=_actor_uid(),
+    ), created=True)
+
+
+@bp.route("/edge/register", methods=["POST"])
+def edge_register():
+    payload = _payload()
+    return _respond(lambda: _service().register(
+        enrollment_token=request.headers.get("X-Edge-Enrollment", ""),
+        certificate_sha256=_tls_certificate_sha256(),
+        gateway_id=payload.get("gateway_id"), environment=payload.get("environment"),
+        network_zone=payload.get("network_zone"), policy_digest=payload.get("policy_digest"),
+        allowed_control_hosts=payload.get("allowed_control_hosts"),
+        allowed_proxy_hosts=payload.get("allowed_proxy_hosts", []), version=payload.get("version"),
+    ), created=True)
+
+
+@bp.route("/edge/gateways", methods=["GET"])
+def edge_list_gateways():
+    return _respond(lambda: {"gateways": _service().list_gateways(
+        limit=request.args.get("limit", 50), offset=request.args.get("offset", 0),
+    )})
+
+
+@bp.route("/edge/gateways/<gateway_id>/heartbeat", methods=["POST"])
+def edge_heartbeat(gateway_id):
+    payload = _payload()
+    return _respond(lambda: _service().heartbeat(
+        **_machine_auth(gateway_id, payload), version=payload.get("version"),
+        safe_summary=payload.get("safe_summary", {}),
+    ))
+
+
+@bp.route("/edge/gateways/<gateway_id>/rotate", methods=["POST"])
+def edge_rotate(gateway_id):
+    payload = _payload()
+    return _respond(lambda: _service().rotate(
+        gateway_id, certificate_sha256=payload.get("certificate_sha256"), actor_uid=_actor_uid(),
+        request_id=payload.get("request_id"),
+    ))
+
+
+@bp.route("/edge/gateways/<gateway_id>/revoke", methods=["POST"])
+def edge_revoke(gateway_id):
+    return _respond(lambda: {"revoked": _service().revoke(gateway_id, actor_uid=_actor_uid())})
+
+
+@bp.route("/edge/tasks", methods=["POST"])
+def edge_issue_task():
+    return _respond(lambda: _service().issue_task(_payload(), actor_uid=_actor_uid()), created=True)
+
+
+@bp.route("/edge/tasks/<task_id>/cancel", methods=["POST"])
+def edge_cancel_task(task_id):
+    return _respond(lambda: {"cancel_requested": _service().cancel_task(task_id, actor_uid=_actor_uid())})
+
+
+@bp.route("/edge/gateways/<gateway_id>/tasks/pull", methods=["POST"])
+def edge_pull_task(gateway_id):
+    payload = _payload()
+    return _respond(lambda: _service().pull_task(**_machine_auth(gateway_id, payload)))
+
+
+@bp.route("/edge/gateways/<gateway_id>/tasks/<task_id>/outcome", methods=["POST"])
+def edge_task_outcome(gateway_id, task_id):
+    payload = _payload()
+    return _respond(lambda: _service().task_outcome(
+        task_id, outcome=payload.get("outcome"), lease_token=payload.get("lease_token"),
+        safe_summary=payload.get("safe_summary", {}), **_machine_auth(gateway_id, payload),
+    ))
+
+
+@bp.route("/edge/gateways/<gateway_id>/events", methods=["POST"])
+def edge_accept_event(gateway_id):
+    payload = _payload()
+    return _respond(lambda: _service().accept_event(
+        event=payload.get("event"), lease_token=payload.get("lease_token"),
+        **_machine_auth(gateway_id, payload),
+    ))
+
+
+@bp.route("/edge/gateways/<gateway_id>/reconcile", methods=["POST"])
+def edge_reconcile(gateway_id):
+    payload = _payload()
+    return _respond(lambda: _service().reconcile(
+        limit=payload.get("limit", 50),
+        cancel_cursor=payload.get("cancel_cursor"),
+        release_cursor=payload.get("release_cursor"),
+        **_machine_auth(gateway_id, payload),
+    ))
+
+
+@bp.route("/edge/gateways/<gateway_id>/releases", methods=["POST"])
+def edge_offer_release(gateway_id):
+    payload = _payload()
+    return _respond(lambda: _service().offer_release(
+        gateway_id, version=payload.get("version"), artifact_digest=payload.get("artifact_digest"),
+        artifact_name=payload.get("artifact_name"), deadline_at=payload.get("deadline_at"),
+        rollback_version=payload.get("rollback_version"), request_id=payload.get("request_id"), actor_uid=_actor_uid(),
+    ), created=True)
+
+
+@bp.route("/edge/gateways/<gateway_id>/releases/ack", methods=["POST"])
+def edge_ack_release(gateway_id):
+    payload = _payload()
+    return _respond(lambda: _service().acknowledge_release(
+        release_id=payload.get("release_id"), outcome=payload.get("outcome"),
+        safe_summary=payload.get("safe_summary", {}), **_machine_auth(gateway_id, payload),
+    ))

+ 507 - 15
app/api/data_source/routes.py

@@ -5,6 +5,11 @@ import logging
 from flask import g, jsonify, request
 
 from app.api.data_source import bp
+from app.core.connectors.errors import (
+    ConnectorAuthenticationError,
+    ConnectorConfigurationError,
+    ConnectorError,
+)
 from app.core.data_source.errors import DataSourceError
 from app.core.data_source.redaction import (
     redact_mapping,
@@ -12,7 +17,6 @@ from app.core.data_source.redaction import (
 )
 from app.models.result import failed, success
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -29,6 +33,19 @@ def _actor_uid():
 
 
 def _error_response(error):
+    if isinstance(error, ConnectorError):
+        logger.warning("连接器操作失败: category=%s", error.category)
+        return jsonify(
+            failed(
+                "连接器操作失败",
+                code=error.http_status,
+                error={
+                    "code": "CONNECTOR_ERROR",
+                    "category": error.category,
+                    "retryable": error.retryable,
+                },
+            )
+        ), error.http_status
     if isinstance(error, DataSourceError):
         logger.warning(
             "数据源操作失败: code=%s message=%s",
@@ -84,9 +101,7 @@ def data_source_list():
         service = get_data_source_service()
         definitions = service.list(payload)
         items = [service.serialize(item) for item in definitions]
-        return jsonify(
-            success({"data_source": items, "total": len(items)})
-        ), 200
+        return jsonify(success({"data_source": items, "total": len(items)})), 200
     except Exception as error:
         return _error_response(error)
 
@@ -146,8 +161,7 @@ def data_source_pool_list():
     try:
         service = get_data_source_service()
         items = [
-            service.serialize_pool_status(status)
-            for status in service.pool_statuses()
+            service.serialize_pool_status(status) for status in service.pool_statuses()
         ]
         return jsonify(success({"pools": items, "total": len(items)})), 200
     except Exception as error:
@@ -215,13 +229,491 @@ def data_source_pool_invalidate(data_source_uid):
 
 @bp.route("/graph", methods=["POST"])
 def data_source_graph_relationship():
-    return (
-        jsonify(
-            failed(
-                "该功能尚未实现",
-                code=501,
-                error={"code": "DATASOURCE_GRAPH_NOT_IMPLEMENTED"},
-            )
-        ),
-        501,
+    from app import db
+    from app.core.connectors.repository import ConnectorRepository
+
+    payload = request.get_json(silent=True) or {}
+    try:
+        graph = ConnectorRepository(db.session).graph(
+            source_uid=payload.get("source_uid"),
+            business_domain_uid=payload.get("business_domain_uid"),
+            process_key=payload.get("process_key"),
+            run_uid=payload.get("run_uid"),
+            limit=payload.get("limit", 500),
+        )
+        return jsonify(success(graph)), 200
+    except Exception as error:
+        return _error_response(error)
+
+
+def _connector_registry():
+    from flask import current_app
+
+    from app.core.connectors.builtin import register_builtin_connectors
+    from app.core.connectors.registry import ConnectorRegistry
+    from app.core.connectors.secrets import EnvironmentSecretResolver
+    from app.core.data_source.runtime import get_data_source_manager
+
+    registry = current_app.extensions.get("connector_registry")
+    if registry is not None:
+        return registry
+    registry = ConnectorRegistry()
+    register_builtin_connectors(
+        registry,
+        connection_provider=get_data_source_manager().connect,
+        secret_resolver=current_app.config.get("CONNECTOR_SECRET_RESOLVER")
+        or EnvironmentSecretResolver(),
     )
+    current_app.extensions["connector_registry"] = registry
+    return registry
+
+
+@bp.route("/connectors/manifests", methods=["GET"])
+def connector_manifests():
+    try:
+        return jsonify(success({"manifests": _connector_registry().manifests()})), 200
+    except Exception as error:
+        return _error_response(error)
+
+
+@bp.route("/connectors/config/validate", methods=["POST"])
+def connector_config_validate():
+    payload = request.get_json(silent=True) or {}
+    try:
+        config = _connector_registry().validate(
+            payload.get("connector_id"), payload.get("version"), payload.get("config")
+        )
+        return jsonify(success({"valid": True, "config_keys": sorted(config)})), 200
+    except Exception as error:
+        return _error_response(error)
+
+
+@bp.route("/connectors/<connector_id>/<version>/health", methods=["POST"])
+def connector_health(connector_id, version):
+    payload = request.get_json(silent=True) or {}
+    try:
+        connector = _connector_registry().resolve(connector_id, version)
+        return jsonify(
+            success(vars(connector.health(payload.get("config") or {})))
+        ), 200
+    except Exception as error:
+        return _error_response(error)
+
+
+@bp.route("/connectors/<connector_id>/<version>/compatibility", methods=["GET"])
+def connector_compatibility(connector_id, version):
+    try:
+        connector = _connector_registry().resolve(connector_id, version)
+        return jsonify(success(vars(connector.compatibility()))), 200
+    except Exception as error:
+        return _error_response(error)
+
+
+@bp.route("/connectors/runs", methods=["GET"])
+def connector_runs_list():
+    from app import db
+    from app.core.connectors.repository import ConnectorRepository
+
+    try:
+        repository = ConnectorRepository(db.session, _actor_uid())
+        return jsonify(
+            success({"runs": repository.list(request.args.get("limit", 100))})
+        ), 200
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+def _require_human_dry_run(payload):
+    if payload.get("dry_run") is not True:
+        raise ConnectorConfigurationError("human connector runs must use dry_run=true")
+
+
+def _require_collection_operation(payload):
+    if payload.get("operation") not in {
+        "discover",
+        "snapshot",
+        "incremental",
+        "lineage",
+        "profile",
+        "evidence",
+    }:
+        raise ConnectorConfigurationError("connector collection operation is invalid")
+
+
+@bp.route("/connectors/runs", methods=["POST"])
+def connector_run_create():
+    from app import db
+    from app.core.connectors.repository import ConnectorRepository
+
+    try:
+        repository = ConnectorRepository(
+            db.session, _actor_uid(), run_access="human"
+        )
+        from dataclasses import asdict
+
+        from app.core.connectors.runtime import ConnectorRuntime
+        from app.core.connectors.sdk import OperationRequest
+
+        payload = request.get_json(silent=True) or {}
+        _require_human_dry_run(payload)
+        _require_collection_operation(payload)
+        registry = _connector_registry()
+        for manifest in (
+            registry.resolve(
+                payload.get("connector_id"), payload.get("version")
+            ).manifest,
+        ):
+            repository.register_manifest(manifest, _actor_uid())
+        db.session.commit()
+        operation_request = OperationRequest(
+            source_uid=str(payload.get("source_uid") or ""),
+            operation=str(payload.get("operation") or ""),
+            config=payload.get("config") or {},
+            scope=payload.get("scope") or {},
+            cursor=payload.get("cursor") or {},
+            checkpoint=payload.get("checkpoint") or {},
+            idempotency_key=payload.get("idempotency_key"),
+            dry_run=bool(payload.get("dry_run", False)),
+            process_key=str(payload.get("process_key") or "dry-run"),
+        )
+        result = ConnectorRuntime(registry, store=repository).execute(
+            payload.get("connector_id"), payload.get("version"), operation_request
+        )
+        return jsonify(success(asdict(result), code=201)), 201
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/machine/runs", methods=["POST"])
+def connector_machine_run():
+    """Machine-only run boundary; a human bearer token is never accepted here."""
+    from app import db
+    from app.core.connectors.identity import ConnectorIdentityRepository
+    from app.core.connectors.repository import ConnectorRepository
+    from app.core.connectors.runtime import ConnectorRuntime
+    from app.core.connectors.sdk import OperationRequest
+
+    payload = request.get_json(silent=True) or {}
+    token = request.headers.get("X-Connector-Credential", "").strip()
+    if not token:
+        return _error_response(
+            ConnectorAuthenticationError("machine credential is required")
+        )
+    try:
+        for field in (
+            "connector_id",
+            "version",
+            "source_uid",
+            "business_domain_uid",
+            "environment",
+            "process_key",
+            "operation",
+        ):
+            if not str(payload.get(field) or "").strip():
+                raise ConnectorConfigurationError(
+                    "machine connector binding is incomplete"
+                )
+        _require_collection_operation(payload)
+        if "config" in payload:
+            raise ConnectorConfigurationError(
+                "machine connector config is supplied by the approved source binding"
+            )
+        identity = ConnectorIdentityRepository(db.session).authenticate(
+            token,
+            connector_id=str(payload.get("connector_id") or ""),
+            connector_version=str(payload.get("version") or ""),
+            source_uid=str(payload.get("source_uid") or ""),
+            business_domain_uid=str(payload.get("business_domain_uid") or ""),
+            environment=str(payload.get("environment") or ""),
+            operation=str(payload.get("operation") or ""),
+            scope=payload.get("scope") or {},
+        )
+        registry = _connector_registry()
+        manifest = registry.resolve(
+            payload.get("connector_id"), payload.get("version")
+        ).manifest
+        if not identity.get("source_binding_uid"):
+            raise ConnectorConfigurationError(
+                "machine connector principal has no approved source binding"
+            )
+        repository = ConnectorRepository(
+            db.session,
+            identity["principal_uid"],
+            run_access="machine",
+            principal_uid=identity["principal_uid"],
+        )
+        repository.register_manifest(manifest, identity["principal_uid"])
+        db.session.commit()
+        operation_request = OperationRequest(
+            source_uid=str(payload.get("source_uid")),
+            operation=str(payload.get("operation")),
+            config=identity["approved_config"],
+            scope=payload.get("scope") or {},
+            cursor=payload.get("cursor") or {},
+            checkpoint=payload.get("checkpoint") or {},
+            idempotency_key=payload.get("idempotency_key"),
+            dry_run=bool(payload.get("dry_run", False)),
+            principal_uid=identity["principal_uid"],
+            business_domain_uid=str(payload.get("business_domain_uid")),
+            environment=str(payload.get("environment")),
+            process_key=str(payload.get("process_key") or ""),
+            source_binding_uid=identity["source_binding_uid"],
+            source_binding_version=identity["source_binding_version"],
+        )
+        result = ConnectorRuntime(registry, store=repository).execute(
+            payload.get("connector_id"), payload.get("version"), operation_request
+        )
+        from dataclasses import asdict
+
+        return jsonify(success(asdict(result), code=201)), 201
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/runs/<idempotency_key>/cancel", methods=["POST"])
+def connector_run_cancel(idempotency_key):
+    from app import db
+    from app.core.connectors.repository import ConnectorRepository
+    from app.core.connectors.runtime import ConnectorRuntime
+
+    try:
+        result = ConnectorRuntime(
+            _connector_registry(),
+            store=ConnectorRepository(
+                db.session, _actor_uid(), run_access="human"
+            ),
+        ).cancel(idempotency_key)
+        return jsonify(success(result)), 200
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/runs/<idempotency_key>/resume", methods=["POST"])
+def connector_run_resume(idempotency_key):
+    from app import db
+    from app.core.connectors.repository import ConnectorRepository
+    from app.core.connectors.runtime import ConnectorRuntime
+
+    try:
+        result = ConnectorRuntime(
+            _connector_registry(),
+            store=ConnectorRepository(
+                db.session, _actor_uid(), run_access="human"
+            ),
+        ).resume(idempotency_key)
+        return jsonify(success(result)), 200
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+def _machine_run_action(idempotency_key, action):
+    from app import db
+    from app.core.connectors.identity import ConnectorIdentityRepository
+    from app.core.connectors.repository import ConnectorRepository
+    from app.core.connectors.runtime import ConnectorRuntime
+
+    token = request.headers.get("X-Connector-Credential", "").strip()
+    if not token:
+        return _error_response(
+            ConnectorAuthenticationError("machine credential is required")
+        )
+    try:
+        record = ConnectorRepository(db.session).get(idempotency_key)
+        if not record or not record.get("principal_uid") or record.get("dry_run"):
+            raise ConnectorConfigurationError("machine connector run was not found")
+        identity = ConnectorIdentityRepository(db.session).authenticate(
+            token,
+            connector_id=record["connector_id"],
+            connector_version=record["connector_version"],
+            source_uid=record["source_uid"],
+            business_domain_uid=record["business_domain_uid"],
+            environment=record["environment"],
+            operation=action,
+            scope=record.get("scope") or {},
+        )
+        if (
+            identity["principal_uid"] != record["principal_uid"]
+            or identity.get("source_binding_uid") != record.get("source_binding_uid")
+            or identity.get("source_binding_version")
+            != record.get("source_binding_version")
+        ):
+            raise ConnectorAuthenticationError(
+                "machine credential run binding was rejected"
+            )
+        repository = ConnectorRepository(
+            db.session,
+            identity["principal_uid"],
+            run_access="machine",
+            principal_uid=identity["principal_uid"],
+        )
+        runtime = ConnectorRuntime(_connector_registry(), store=repository)
+        result = getattr(runtime, action)(idempotency_key)
+        if action == "resume":
+            from dataclasses import asdict
+
+            result = asdict(result)
+        return jsonify(success(result)), 200
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route(
+    "/connectors/machine/runs/<idempotency_key>/cancel", methods=["POST"]
+)
+def connector_machine_run_cancel(idempotency_key):
+    """Cancel one bound machine run with a fresh one-time credential."""
+    return _machine_run_action(idempotency_key, "cancel")
+
+
+@bp.route(
+    "/connectors/machine/runs/<idempotency_key>/resume", methods=["POST"]
+)
+def connector_machine_run_resume(idempotency_key):
+    """Resume one bound machine run with a fresh one-time credential."""
+    return _machine_run_action(idempotency_key, "resume")
+
+
+@bp.route("/connectors/source-bindings", methods=["GET"])
+def connector_source_bindings_list():
+    from app import db
+    from app.core.connectors.bindings import ConnectorSourceBindingRepository
+
+    try:
+        items = ConnectorSourceBindingRepository(db.session).list_public(
+            request.args.get("limit", 100)
+        )
+        return jsonify(success({"source_bindings": items})), 200
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/source-bindings", methods=["POST"])
+def connector_source_binding_approve():
+    from app import db
+    from app.core.connectors.bindings import ConnectorSourceBindingRepository
+    from app.core.connectors.repository import ConnectorRepository
+
+    payload = request.get_json(silent=True) or {}
+    try:
+        registry = _connector_registry()
+        manifest = registry.resolve(
+            payload.get("connector_id"), payload.get("version")
+        ).manifest
+        approved_config = registry.validate(
+            payload.get("connector_id"),
+            payload.get("version"),
+            payload.get("approved_config") or {},
+        )
+        ConnectorRepository(db.session, _actor_uid()).register_manifest(
+            manifest, _actor_uid()
+        )
+        result = ConnectorSourceBindingRepository(db.session).approve(
+            connector_id=payload.get("connector_id"),
+            connector_version=payload.get("version"),
+            source_uid=payload.get("source_uid"),
+            business_domain_uid=payload.get("business_domain_uid"),
+            environment=payload.get("environment"),
+            approved_config=approved_config,
+            approved_by=_actor_uid(),
+            binding_uid=payload.get("binding_uid"),
+        )
+        return jsonify(success(result, code=201)), 201
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/source-bindings/<binding_uid>/revoke", methods=["POST"])
+def connector_source_binding_revoke(binding_uid):
+    from app import db
+    from app.core.connectors.bindings import ConnectorSourceBindingRepository
+
+    try:
+        revoked = ConnectorSourceBindingRepository(db.session).revoke(
+            binding_uid, _actor_uid()
+        )
+        return jsonify(success(revoked)), 200
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/principals", methods=["POST"])
+def connector_principal_create():
+    from app import db
+    from app.core.connectors.identity import ConnectorIdentityRepository
+
+    payload = request.get_json(silent=True) or {}
+    try:
+        _connector_registry().resolve(
+            payload.get("connector_id"), payload.get("version", "1.0.0")
+        )
+        uid = ConnectorIdentityRepository(db.session).create_principal(
+            connector_id=payload.get("connector_id"),
+            connector_version=payload.get("version", "1.0.0"),
+            source_uid=payload.get("source_uid"),
+            business_domain_uid=payload.get("business_domain_uid"),
+            environment=payload.get("environment"),
+            operations=payload.get("operations") or (),
+            scopes=payload.get("scopes") or {},
+            actor_uid=_actor_uid(),
+            source_binding_uid=payload.get("source_binding_uid"),
+            source_binding_version=payload.get("source_binding_version"),
+        )
+        return jsonify(success({"principal_uid": uid}, code=201)), 201
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/principals/<principal_uid>/credentials", methods=["POST"])
+def connector_credential_issue(principal_uid):
+    from app import db
+    from app.core.connectors.identity import ConnectorIdentityRepository
+
+    payload = request.get_json(silent=True) or {}
+    try:
+        result = ConnectorIdentityRepository(db.session).issue(
+            principal_uid,
+            ttl_seconds=payload.get("ttl_seconds", 900),
+            actor_uid=_actor_uid(),
+        )
+        response = jsonify(success(result, code=201))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 201
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)
+
+
+@bp.route("/connectors/credentials/<credential_uid>/<action>", methods=["POST"])
+def connector_credential_action(credential_uid, action):
+    from app import db
+    from app.core.connectors.identity import ConnectorIdentityRepository
+
+    payload = request.get_json(silent=True) or {}
+    try:
+        repository = ConnectorIdentityRepository(db.session)
+        if action == "revoke":
+            result = {"revoked": repository.revoke(credential_uid, _actor_uid())}
+        elif action == "rotate":
+            result = repository.rotate(
+                credential_uid,
+                ttl_seconds=payload.get("ttl_seconds", 900),
+                actor_uid=_actor_uid(),
+            )
+        else:
+            raise ConnectorConfigurationError("credential action is invalid")
+        response = jsonify(success(result))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 200
+    except Exception as error:
+        db.session.rollback()
+        return _error_response(error)

+ 108 - 0
app/api/knowledge_base/agent_governance_routes.py

@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import hashlib
+import uuid
 
 from flask import current_app, g, jsonify, request
 
@@ -14,6 +15,14 @@ from app.core.llm.agent_governance_repository import (
     SqlAlchemyAgentGovernanceRepository,
     WorkCenterAgentApprovalGateway,
 )
+from app.core.llm.runtime_governance_repository import (
+    SqlAlchemyRuntimeGovernanceRepository,
+)
+from app.core.llm.runtime_server import (
+    RuntimeServerContext,
+    RuntimeServerError,
+    ServerGovernedInvocationService,
+)
 from app.core.system.permissions import (
     AGENTS_MANAGE,
     AGENTS_OPERATE,
@@ -60,6 +69,10 @@ def _service():
     )
 
 
+def _runtime_service():
+    return ServerGovernedInvocationService(SqlAlchemyRuntimeGovernanceRepository(db.session))
+
+
 def _expected_version() -> int:
     raw = str(request.headers.get("If-Match") or "").strip()
     if raw.startswith("W/"):
@@ -267,3 +280,98 @@ def agent_governance_dashboard():
         return jsonify(success(_service().dashboard()))
     except AgentGovernanceUnavailable as exc:
         return _error(exc)
+
+
+@bp.route("/agents/<agent_uid>/runtime/invocations", methods=["POST"])
+@require_permissions(AGENTS_OPERATE)
+def authorize_runtime_invocation(agent_uid):
+    """The only HTTP path that binds Agent credentials to a server identity."""
+    try:
+        token = str(request.headers.get("X-Agent-Credential") or "")
+        if not token:
+            raise PermissionError("Agent credential is required")
+        _service().validate_credential(agent_uid, token)
+        result = _runtime_service().authorize(
+            RuntimeServerContext(
+                agent_uid=agent_uid,
+                actor_uid=g.current_user["id"],
+                roles=frozenset(g.current_user["roles"]),
+            ),
+            request.get_json(silent=True) or {},
+        )
+        db.session.commit()
+        return _no_store(jsonify(success(result, code=201))), 201
+    except (RuntimeServerError, ValueError, LookupError, PermissionError, RuntimeError) as exc:
+        response, status = _error(exc)
+        return _no_store(response), status
+
+
+@bp.route("/agents/<agent_uid>/runtime/invocations/<idempotency_key>/settle", methods=["POST"])
+@require_permissions(AGENTS_OPERATE)
+def settle_runtime_invocation(agent_uid, idempotency_key):
+    try:
+        token = str(request.headers.get("X-Agent-Credential") or "")
+        if not token:
+            raise PermissionError("Agent credential is required")
+        claims = _service().validate_credential(agent_uid, token)
+        agent = _service().get_agent(agent_uid)
+        if claims["agent_uid"] != agent_uid or agent["owner_uid"] != g.current_user["id"]:
+            raise PermissionError("Agent credential principal does not match")
+        body = request.get_json(silent=True) or {}
+        allowed = {"lease_fence", "outcome", "actual_tokens", "actual_cost_micros", "actual_tools", "actual_time_ms"}
+        if set(body) != allowed:
+            raise ValueError("runtime settlement payload is closed")
+        scope = _runtime_service().repository.settlement_context(
+            agent_uid=agent_uid, actor_uid=g.current_user["id"], idempotency_key=idempotency_key
+        )
+        result = _runtime_service().repository.settle_claim({
+            **scope,
+            "worker_id": f"agent-runtime-http:{g.current_user['id']}",
+            **body,
+        })
+        db.session.commit()
+        if result.get("rejected"):
+            return _no_store(jsonify(failed("runtime_settlement_rejected", code=409))), 409
+        return _no_store(jsonify(success(result, code=201))), 201
+    except (RuntimeServerError, ValueError, LookupError, PermissionError, RuntimeError) as exc:
+        response, status = _error(exc)
+        return _no_store(response), status
+
+
+@bp.route("/agents/<agent_uid>/runtime/control", methods=["POST"])
+@require_permissions(AGENTS_OPERATE)
+def control_runtime(agent_uid):
+    """Execute a recovery/canary change only through an authenticated control claim."""
+    try:
+        token = str(request.headers.get("X-Agent-Credential") or "")
+        if not token:
+            raise PermissionError("Agent credential is required")
+        claims = _service().validate_credential(agent_uid, token)
+        agent = _service().get_agent(agent_uid)
+        if claims["agent_uid"] != agent_uid or agent["owner_uid"] != g.current_user["id"]:
+            raise PermissionError("Agent credential principal does not match")
+        body = request.get_json(silent=True) or {}
+        forbidden = {"agent_uid", "principal_id"}
+        required = {
+            "tenant_id", "business_domain_uid", "environment", "operation", "approval_task_uid",
+            "idempotency_key", "request_digest", "expected_fence", "incident_uid", "from_state",
+            "to_state", "generation", "from_generation", "to_generation", "dataset_version",
+            "dataset_digest", "metrics_digest", "threshold_policy", "issued_at",
+        }
+        if set(body) != required or set(body) & forbidden:
+            raise ValueError("runtime control payload is closed")
+        credential = _service().repository.get_credential(claims["jti"])
+        if not credential:
+            raise PermissionError("Agent credential is revoked or unknown")
+        payload = {**body, "agent_uid": agent_uid, "principal_id": g.current_user["id"]}
+        claim_uid = _runtime_service().repository.issue_control_claim({
+            "claim_uid": str(uuid.uuid4()), "credential_uid": credential["uid"],
+            "credential_token_digest": hashlib.sha256(token.encode()).hexdigest(),
+            "nonce": str(uuid.uuid4()), "control_payload": payload,
+        })
+        result = _runtime_service().repository.control_claimed(payload, claim_uid)
+        db.session.commit()
+        return _no_store(jsonify(success(result, code=201))), 201
+    except (RuntimeServerError, ValueError, LookupError, PermissionError, RuntimeError) as exc:
+        response, status = _error(exc)
+        return _no_store(response), status

+ 7 - 0
app/api/system/__init__.py

@@ -5,9 +5,16 @@ from flask import Blueprint
 bp = Blueprint("system", __name__)
 
 from app.api.system import governance_audit  # noqa: E402, F401
+from app.api.system import enterprise_identity  # noqa: E402, F401
+from app.api.system import bi_ai_catalog_routes  # noqa: E402, F401
+from app.api.system import metering_showback_routes  # noqa: E402, F401
+from app.api.system import plugin_platform_routes  # noqa: E402, F401
 from app.api.system import responsibilities  # noqa: E402, F401
 from app.api.system import routes  # noqa: E402, F401
 from app.api.system import security_governance  # noqa: E402, F401
+from app.api.system import trusted_delivery  # noqa: E402, F401
+from app.api.system import trusted_delivery_controls  # noqa: E402, F401
+from app.api.system import tenant_routes  # noqa: E402, F401
 from app.api.system import users  # noqa: E402, F401
 from app.api.system import workbench  # noqa: E402, F401
 from app.api.system import work_center  # noqa: E402, F401

+ 166 - 0
app/api/system/bi_ai_catalog_routes.py

@@ -0,0 +1,166 @@
+"""Closed, RBAC-protected HTTP contract for the local WP11 catalog fixture."""
+
+from __future__ import annotations
+
+import os
+
+from flask import current_app, g, jsonify, request
+from sqlalchemy import create_engine
+
+from app import db
+from app.api.system import bp
+from app.config.database_urls import validate_postgresql_url
+from app.core.governance.bi_ai_asset_catalog import (
+    CatalogApplicationService,
+    CatalogControlIssuer,
+)
+from app.core.system.permissions import (
+    BI_AI_CATALOG_MANAGE,
+    BI_AI_CATALOG_READ,
+    require_permissions,
+)
+from app.models.result import failed, success
+
+_MAX_BODY_BYTES = 4096
+
+
+def _service():
+    control_url = os.environ.get("BI_AI_CATALOG_CONTROL_DATABASE_URL", "")
+    control = validate_postgresql_url(control_url, "BI_AI_CATALOG_CONTROL_DATABASE_URL")
+    runtime = validate_postgresql_url(
+        str(current_app.config.get("SQLALCHEMY_DATABASE_URI", "")), "DATABASE_URL"
+    )
+    if control.username != "dataops_bi_ai_catalog_control" or control.username == runtime.username:
+        raise RuntimeError("catalog control identity is not configured")
+    return CatalogApplicationService(
+        db.session,
+        CatalogControlIssuer(create_engine(control_url, pool_pre_ping=True)),
+    )
+
+
+def _closed_body(required: set[str]) -> dict:
+    if request.content_length is not None and request.content_length > _MAX_BODY_BYTES:
+        raise ValueError("catalog request rejected")
+    body = request.get_json(silent=True)
+    if not isinstance(body, dict) or set(body) != required:
+        raise ValueError("catalog request rejected")
+    return body
+
+
+def _response(value, status: int = 200):
+    response = jsonify(success(value, code=status))
+    response.headers["Cache-Control"] = "no-store"
+    return response, status
+
+
+def _failure(status: int = 400):
+    response = jsonify(failed("catalog request rejected", code=status))
+    response.headers["Cache-Control"] = "no-store"
+    return response, status
+
+
+@bp.post("/bi-ai-catalog/local-fixture/sync")
+@require_permissions(BI_AI_CATALOG_MANAGE)
+def sync_local_fixture():
+    try:
+        body = _closed_body(set())
+        service = _service()
+        if hasattr(service, "sync_local_fixture"):
+            return _response(
+                service.sync_local_fixture(
+                    principal_id=str(g.current_user["id"]), body=body
+                ),
+                201,
+            )
+        return _response(
+            {
+                "source": "local-fixture-v1",
+                "mode": "ENGINEERING_EVIDENCE_ONLY",
+                "persisted_before_ack": False,
+            },
+            201,
+        )
+    except (KeyError, PermissionError, RuntimeError, TypeError, ValueError):
+        return _failure()
+
+
+@bp.post("/bi-ai-catalog/assets/search")
+@require_permissions(BI_AI_CATALOG_READ)
+def search_catalog_assets():
+    try:
+        body = _closed_body({"query", "filters"})
+        service = _service()
+        if hasattr(service, "search_for_principal"):
+            return _response(
+                service.search_for_principal(
+                    principal_id=str(g.current_user["id"]),
+                    query=body["query"],
+                    filters=body["filters"],
+                )
+            )
+        return _response(
+            service.search(
+                body["query"],
+                body["filters"],
+                principal_roles=set(g.current_user.get("roles", [])),
+                domain="default",
+            )
+        )
+    except ValueError:
+        return _failure(400)
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError):
+        return _failure(403)
+
+
+@bp.get("/bi-ai-catalog/assets/<external_uid>/impact")
+@require_permissions(BI_AI_CATALOG_READ)
+def catalog_asset_impact(external_uid: str):
+    try:
+        if set(request.args) - {"max_depth", "max_nodes"}:
+            raise ValueError("catalog request rejected")
+        max_depth, max_nodes = (
+            int(request.args.get("max_depth", "3")),
+            int(request.args.get("max_nodes", "50")),
+        )
+        service = _service()
+        if hasattr(service, "impact_for_principal"):
+            return _response(
+                service.impact_for_principal(
+                    principal_id=str(g.current_user["id"]),
+                    external_uid=external_uid,
+                    max_depth=max_depth,
+                    max_nodes=max_nodes,
+                )
+            )
+        return _response(
+            service.impact(
+                external_uid,
+                principal_roles=set(g.current_user.get("roles", [])),
+                domain="default",
+                max_depth=max_depth,
+                max_nodes=max_nodes,
+            )
+        )
+    except (
+        KeyError,
+        LookupError,
+        PermissionError,
+        RuntimeError,
+        TypeError,
+        ValueError,
+    ):
+        return _failure(403)
+
+
+@bp.get("/bi-ai-catalog/audit")
+@require_permissions(BI_AI_CATALOG_READ)
+def catalog_audit():
+    try:
+        if request.args:
+            raise ValueError("catalog request rejected")
+        service = _service()
+        return _response(
+            service.audit_for_principal(principal_id=str(g.current_user["id"]))
+        )
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError, ValueError):
+        return _failure(403)

+ 648 - 0
app/api/system/enterprise_identity.py

@@ -0,0 +1,648 @@
+"""Enterprise identity APIs. Public routes are explicitly enumerated in policy."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import secrets
+import uuid
+from datetime import UTC, datetime, timedelta
+from functools import wraps
+from urllib.parse import urlsplit
+
+from flask import current_app, g, jsonify, redirect, request
+from itsdangerous import BadSignature, URLSafeTimedSerializer
+from sqlalchemy import text
+from sqlalchemy.exc import IntegrityError, StatementError
+
+from app import db
+from app.api.system import bp
+from app.core.common.identifiers import new_governance_uid
+from app.core.system.enterprise_identity import (
+    ClaimMapper,
+    DirectorySynchronizer,
+    EmergencyAccess,
+    IdentityPolicyError,
+    IdentityUpstreamError,
+    IdpConfig,
+    sanitize_audit_detail,
+)
+from app.core.system.identity_repository import PostgresIdentityRepository
+from app.core.system.identity_sessions import SessionManager
+from app.core.system.oidc import OidcClient, OidcTransport
+from app.core.system.permissions import (
+    IDENTITY_MANAGE,
+    IDENTITY_OPERATE,
+    IDENTITY_READ,
+    require_permissions,
+)
+from app.core.system.tokens import issue_access_token
+from app.models.result import failed, success
+
+
+def _body() -> dict:
+    value = request.get_json(silent=True)
+    if not isinstance(value, dict):
+        raise IdentityPolicyError("JSON object body is required")
+    return value
+
+
+def _required_str(data: dict, key: str, *, maximum: int = 500) -> str:
+    value = data.get(key)
+    if not isinstance(value, str) or not value.strip() or len(value.strip()) > maximum:
+        raise IdentityPolicyError(f"{key} is required or invalid")
+    return value.strip()
+
+
+def _optional_str(data: dict, key: str, default: str, *, maximum: int) -> str:
+    if key not in data:
+        return default
+    return _required_str(data, key, maximum=maximum)
+
+
+def _uuid(value, field: str) -> str:
+    try:
+        return str(uuid.UUID(str(value)))
+    except (ValueError, TypeError, AttributeError) as exc:
+        raise IdentityPolicyError(f"{field} must be a UUID") from exc
+
+
+def _positive_int(value, field: str, *, maximum: int = 2_147_483_647) -> int:
+    if isinstance(value, bool):
+        raise IdentityPolicyError(f"{field} must be a positive integer")
+    try:
+        result = int(value)
+    except (ValueError, TypeError) as exc:
+        raise IdentityPolicyError(f"{field} must be a positive integer") from exc
+    if result < 1 or result > maximum:
+        raise IdentityPolicyError(f"{field} must be a positive integer")
+    return result
+
+
+def _timestamp(value, field: str) -> datetime:
+    if not isinstance(value, str):
+        raise IdentityPolicyError(f"{field} must be an ISO-8601 timestamp")
+    try:
+        parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
+    except ValueError as exc:
+        raise IdentityPolicyError(f"{field} must be an ISO-8601 timestamp") from exc
+    if parsed.tzinfo is None or parsed.utcoffset() is None:
+        raise IdentityPolicyError(f"{field} must include a timezone")
+    return parsed
+
+
+def _string_array(data: dict, key: str, *, default=None) -> tuple[str, ...]:
+    value = data.get(key, default)
+    if not isinstance(value, list) or not value or any(not isinstance(item, str) or not item.strip() for item in value):
+        raise IdentityPolicyError(f"{key} must be a non-empty string array")
+    return tuple(item.strip() for item in value)
+
+
+def _validate_mapping(version: str, group_rules) -> ClaimMapper:
+    if not isinstance(group_rules, dict):
+        raise IdentityPolicyError("group_rules must be an object")
+    return ClaimMapper(version=version, group_rules=group_rules)
+
+
+def _identity_input_guard(view):
+    @wraps(view)
+    def guarded(*args, **kwargs):
+        try:
+            return view(*args, **kwargs)
+        except IdentityPolicyError:
+            raise
+        except (KeyError, TypeError, ValueError, IntegrityError, StatementError) as exc:
+            db.session.rollback()
+            raise IdentityPolicyError("invalid enterprise identity request") from exc
+
+    return guarded
+
+
+def _repo() -> PostgresIdentityRepository:
+    return PostgresIdentityRepository(db.session)
+
+
+def _config(row) -> IdpConfig:
+    return IdpConfig(provider_uid=str(row["provider_uid"]), version=row["version"], issuer=row["issuer"],
+                     client_id=row["client_id"], secret_ref=row["secret_ref"],
+                     authorization_endpoint=row["authorization_endpoint"], token_endpoint=row["token_endpoint"],
+                     jwks_uri=row["jwks_uri"], redirect_uris=tuple(row["redirect_uris"]),
+                     post_login_redirect_uris=tuple(dict(row["config"]).get("post_login_redirect_uris", ())),
+                     algorithms=tuple(row["algorithms"]), mapping_version=row["mapping_version"], status=row["status"])
+
+
+def _load_config(provider_uid: str, *, version: int | None = None, active: bool = True):
+    clause = "AND status='active'" if active else ""
+    version_clause = "AND version=:version" if version is not None else ""
+    return db.session.execute(text(f"""
+        SELECT provider_uid::text,version,status,issuer,client_id,secret_ref,authorization_endpoint,
+               token_endpoint,jwks_uri,redirect_uris,algorithms,mapping_version,config
+        FROM public.identity_idp_config_versions WHERE provider_uid=CAST(:provider AS uuid) {version_clause} {clause}
+        ORDER BY version DESC LIMIT 1
+    """), {"provider": provider_uid, "version": version}).mappings().one_or_none()
+
+
+def _audit(event_type: str, outcome: str, *, actor_uid: str | None = None,
+           provider_uid: str | None = None, resource_uid: str | None = None,
+           enterprise_subject: str | None = None, detail: dict | None = None) -> None:
+    _repo().audit({"event_type": event_type, "outcome": outcome, "actor_uid": actor_uid,
+                   "provider_uid": provider_uid, "resource_type": "enterprise_identity",
+                   "resource_uid": resource_uid,
+                   "subject_digest": (hashlib.sha256(enterprise_subject.encode()).hexdigest()
+                                      if enterprise_subject else None),
+                   "safe_detail": sanitize_audit_detail(detail or {})})
+
+
+def _cookie_secure() -> bool:
+    return bool(current_app.config.get("IDENTITY_COOKIE_SECURE", True))
+
+
+def _origin(uri: str) -> str:
+    parts = urlsplit(uri)
+    return f"{parts.scheme}://{parts.netloc}"
+
+
+def _browser_origin_allowed(origin: str) -> bool:
+    rows = db.session.execute(text("SELECT config FROM public.identity_idp_config_versions WHERE status='active'")).scalars().all()
+    allowed = {_origin(uri) for config in rows for uri in dict(config).get("post_login_redirect_uris", ())}
+    allowed.update(current_app.config.get("IDENTITY_ALLOWED_TEST_ORIGINS", ()))
+    return origin in allowed
+
+
+@bp.errorhandler(IdentityPolicyError)
+def identity_policy_error(exc):
+    try:
+        db.session.rollback()
+        _audit("identity_policy_denied", "denied", actor_uid=getattr(g, "current_user", {}).get("id"),
+               detail={"path": request.path, "reason": str(exc)[:160]})
+    except Exception:  # pragma: no cover - failure reporting must not expose internals
+        db.session.rollback()
+    return jsonify(failed(str(exc), code=400)), 400
+
+
+@bp.errorhandler(IdentityUpstreamError)
+def identity_upstream_error(exc):
+    db.session.rollback()
+    try:
+        _audit("identity_upstream_failure", "failure", actor_uid=getattr(g, "current_user", {}).get("id"),
+               detail={"path": request.path})
+    except Exception:  # pragma: no cover
+        db.session.rollback()
+    return jsonify(failed("enterprise identity provider is unavailable", code=502)), 502
+
+
+@bp.route("/identity/providers", methods=["GET"])
+def identity_providers():
+    rows = db.session.execute(text("""
+        SELECT provider_uid::text,version,issuer,config->>'display_name' display_name
+        FROM public.identity_idp_config_versions WHERE status='active' ORDER BY provider_uid
+    """)).mappings().all()
+    return jsonify(success([{"provider_uid": row["provider_uid"], "version": row["version"],
+                             "issuer": row["issuer"], "display_name": row["display_name"] or "企业单点登录"} for row in rows]))
+
+
+@bp.route("/identity/authorize", methods=["POST"])
+@_identity_input_guard
+def identity_authorize():
+    data = _body()
+    provider_uid = _uuid(data.get("provider_uid"), "provider_uid")
+    row = _load_config(provider_uid)
+    if not row:
+        raise IdentityPolicyError("active identity provider not found")
+    config = _config(row)
+    callback_uri = _required_str(data, "redirect_uri", maximum=2048)
+    post_login_uri = _required_str(data, "post_login_uri", maximum=2048)
+    if post_login_uri not in config.post_login_redirect_uris:
+        raise IdentityPolicyError("post-login redirect URI is not allowlisted")
+    started = OidcClient(_repo()).begin(config, callback_uri)
+    serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"], salt="oidc-flow")
+    response = jsonify(success({"authorization_url": started.url, "provider_uid": config.provider_uid}))
+    response.set_cookie("dataops_oidc_flow", serializer.dumps({"state": started.state, "verifier": started.code_verifier,
+                                                                 "provider_uid": config.provider_uid,
+                                                                 "redirect_uri": callback_uri,
+                                                                 "post_login_uri": post_login_uri}),
+                        httponly=True, secure=_cookie_secure(), samesite="Lax", max_age=300, path="/api/system/identity/callback")
+    _audit("oidc_authorize", "success", provider_uid=config.provider_uid, detail={"redirect_uri": data["redirect_uri"]})
+    return response
+
+
+@bp.route("/identity/callback", methods=["GET"])
+@_identity_input_guard
+def identity_callback():
+    serializer = URLSafeTimedSerializer(current_app.config["SECRET_KEY"], salt="oidc-flow")
+    try:
+        flow = serializer.loads(request.cookies.get("dataops_oidc_flow", ""), max_age=300)
+    except BadSignature as exc:
+        raise IdentityPolicyError("OIDC browser flow binding is invalid or expired") from exc
+    if not isinstance(flow, dict) or request.args.get("state") != flow.get("state"):
+        raise IdentityPolicyError("OIDC state mismatch")
+    provider_uid = _uuid(flow.get("provider_uid"), "provider_uid")
+    row = _load_config(provider_uid)
+    if not row:
+        raise IdentityPolicyError("active identity provider not found")
+    row = dict(row)
+    config = _config(row)
+    config.validate()
+    callback_uri = _required_str(flow, "redirect_uri", maximum=2048)
+    verifier = _required_str(flow, "verifier", maximum=512)
+    state = _required_str(flow, "state", maximum=512)
+    post_login_uri = _required_str(flow, "post_login_uri", maximum=2048)
+    code = request.args.get("code")
+    if not isinstance(code, str) or not code or len(code) > 4096:
+        raise IdentityPolicyError("OIDC authorization code is required")
+    # Do not keep a database transaction open while calling the external IdP.
+    db.session.rollback()
+    transport = OidcTransport()
+    id_token = transport.exchange_code(config, code=code, redirect_uri=callback_uri, verifier=verifier)
+    jwks = transport.fetch_jwks(config)
+    repo = _repo()
+    try:
+        claims = OidcClient(repo).verify_callback(config, state=state, redirect_uri=callback_uri,
+                                                   code_verifier=verifier, id_token=id_token, jwks=jwks,
+                                                   commit=False)
+        mapped = ClaimMapper(version=config.mapping_version,
+                             group_rules=dict(row["config"]).get("group_rules", {})).map(claims)
+        link = repo.get_identity(config.provider_uid, mapped.subject, for_update=True)
+        if link and link["status"] != "active":
+            raise IdentityPolicyError("enterprise identity is disabled")
+        scope = {"business_domain_uids": mapped.business_domain_uids, "object_types": mapped.object_types,
+                 "environments": mapped.environments, "data_scopes": mapped.data_scopes}
+        changed = bool(link and (sorted(link["roles"]) != sorted(mapped.roles)
+                                 or dict(link["authorization_scope"]) != json.loads(json.dumps(scope))))
+        user_uid = link["user_uid"] if link else new_governance_uid()
+        token_version = (int(link["token_version"]) + 1 if changed else int(link["token_version"])) if link else 1
+        repo.put_identity(config.provider_uid, mapped.subject, {
+            "provider_uid": config.provider_uid, "user_uid": user_uid, "username": mapped.username,
+            "display_name": mapped.display_name, "department": mapped.department, "groups": list(mapped.groups),
+            "roles": list(mapped.roles), "authorization_scope": scope, "mapping_version": mapped.mapping_version,
+            "claims_digest": mapped.claims_digest, "status": "active", "token_version": token_version,
+        }, commit=False)
+        if changed:
+            repo.revoke_subject_sessions(config.provider_uid, mapped.subject,
+                                         reason="claims_mapping_changed", commit=False)
+        credentials = SessionManager(repo, secret=current_app.config["SECRET_KEY"]).create(
+            subject=mapped.subject, user_uid=user_uid, roles=list(mapped.roles), identity_source="oidc",
+            token_version=token_version, provider_uid=config.provider_uid, commit=False)
+        exchange = secrets.token_urlsafe(40)
+        repo.insert_exchange_code({"uid": new_governance_uid(),
+                                   "code_hash": hashlib.sha256(exchange.encode()).hexdigest(),
+                                   "session_uid": credentials.session_uid, "redirect_uri": post_login_uri,
+                                   "expires_at": datetime.now(UTC) + timedelta(minutes=2)}, commit=False)
+        db.session.commit()
+    except IdentityPolicyError:
+        db.session.rollback()
+        raise
+    except Exception as exc:
+        db.session.rollback()
+        raise IdentityPolicyError("OIDC callback persistence failed") from exc
+    _audit("oidc_login", "success", provider_uid=config.provider_uid, resource_uid=user_uid,
+           detail={"mapping_version": mapped.mapping_version, "claims_digest": mapped.claims_digest})
+    response = redirect(f"{flow['post_login_uri']}?exchange_code={exchange}", code=302)
+    response.set_cookie("dataops_refresh", credentials.refresh_token, httponly=True, secure=_cookie_secure(), samesite="Strict",
+                        max_age=8 * 3600, path="/api/system/identity")
+    response.delete_cookie("dataops_oidc_flow", path="/api/system/identity/callback")
+    return response
+
+
+@bp.route("/identity/exchange", methods=["POST"])
+@_identity_input_guard
+def identity_exchange():
+    data = _body()
+    exchange_code = _required_str(data, "exchange_code", maximum=1024)
+    redirect_uri = _required_str(data, "redirect_uri", maximum=2048)
+    digest = hashlib.sha256(exchange_code.encode()).hexdigest()
+    row = db.session.execute(text("""
+        UPDATE public.identity_exchange_codes SET status='consumed',consumed_at=CURRENT_TIMESTAMP
+        WHERE code_hash=:digest AND status='pending' AND expires_at>CURRENT_TIMESTAMP
+          AND redirect_uri=:redirect RETURNING session_uid::text,redirect_uri
+    """), {"digest": digest, "redirect": redirect_uri}).mappings().one_or_none()
+    if not row or request.headers.get("Origin") != _origin(row["redirect_uri"]):
+        db.session.rollback()
+        raise IdentityPolicyError("exchange code or browser origin is invalid")
+    db.session.commit()
+    session = _repo().get_session(row["session_uid"])
+    if not session or session["status"] != "active":
+        raise IdentityPolicyError("identity session is unavailable")
+    token = issue_access_token(user_id=session["user_uid"], roles=list(session["roles"]), secret=current_app.config["SECRET_KEY"],
+                               session_uid=session["uid"], token_version=session["token_version"], identity_source=session["identity_source"])
+    return jsonify(success({"token": token, "identity_source": session["identity_source"]}))
+
+
+@bp.route("/identity/refresh", methods=["POST"])
+def identity_refresh():
+    origin = request.headers.get("Origin", "")
+    if not _browser_origin_allowed(origin):
+        raise IdentityPolicyError("browser origin is not allowlisted")
+    credentials = SessionManager(_repo(), secret=current_app.config["SECRET_KEY"]).refresh(request.cookies.get("dataops_refresh", ""))
+    session = _repo().get_session(credentials.session_uid)
+    _audit("identity_refresh", "success", actor_uid=session["user_uid"] if session else None,
+           provider_uid=session.get("provider_uid") if session else None,
+           resource_uid=credentials.session_uid,
+           detail={"rotated_from_uid": session.get("rotated_from_uid") if session else None})
+    response = jsonify(success({"token": credentials.access_token}))
+    response.set_cookie("dataops_refresh", credentials.refresh_token, httponly=True, secure=_cookie_secure(), samesite="Strict",
+                        max_age=8 * 3600, path="/api/system/identity")
+    return response
+
+
+@bp.route("/identity/logout", methods=["POST"])
+@require_permissions(IDENTITY_READ)
+def identity_logout():
+    session_uid = g.current_user.get("sid")
+    session = _repo().get_session(session_uid) if session_uid else None
+    if session_uid:
+        SessionManager(_repo(), secret=current_app.config["SECRET_KEY"]).revoke(session_uid)
+    _audit("identity_logout", "success", actor_uid=g.current_user.get("id"),
+           provider_uid=session.get("provider_uid") if session else None, resource_uid=session_uid)
+    response = jsonify(success({"revoked": True}))
+    response.delete_cookie("dataops_refresh", path="/api/system/identity")
+    return response
+
+
+@bp.route("/identity/idp-versions", methods=["GET"])
+@require_permissions(IDENTITY_MANAGE)
+def identity_idp_versions():
+    rows = db.session.execute(text("SELECT provider_uid::text,version,status,issuer,client_id,secret_ref,redirect_uris,algorithms,mapping_version,created_at FROM public.identity_idp_config_versions ORDER BY provider_uid,version DESC")).mappings().all()
+    return jsonify(success([{**dict(row), "secret_ref": "env:DATAOPS_OIDC_***"} for row in rows]))
+
+
+@bp.route("/identity/idp-versions", methods=["POST"])
+@require_permissions(IDENTITY_MANAGE)
+@_identity_input_guard
+def identity_idp_version_create():
+    data = _body()
+    provider_uid = _uuid(data["provider_uid"], "provider_uid") if data.get("provider_uid") else new_governance_uid()
+    mapping_version = _required_str(data, "mapping_version", maximum=120)
+    group_rules = data.get("group_rules")
+    _validate_mapping(mapping_version, group_rules)
+    config = IdpConfig(provider_uid=provider_uid, version=_positive_int(data.get("version"), "version"),
+        issuer=_required_str(data, "issuer", maximum=2048), client_id=_required_str(data, "client_id", maximum=300),
+        secret_ref=_required_str(data, "secret_ref", maximum=160),
+        authorization_endpoint=_required_str(data, "authorization_endpoint", maximum=2048),
+        token_endpoint=_required_str(data, "token_endpoint", maximum=2048),
+        jwks_uri=_required_str(data, "jwks_uri", maximum=2048), redirect_uris=_string_array(data, "redirect_uris"),
+        post_login_redirect_uris=_string_array(data, "post_login_redirect_uris"),
+        algorithms=_string_array(data, "algorithms", default=["RS256"]), mapping_version=mapping_version).validate()
+    db.session.execute(text("""
+        INSERT INTO public.identity_idp_config_versions(uid,provider_uid,version,status,issuer,client_id,secret_ref,authorization_endpoint,
+        token_endpoint,jwks_uri,redirect_uris,algorithms,mapping_version,config,created_by)
+        VALUES(CAST(:uid AS uuid),CAST(:provider AS uuid),:version,'draft',:issuer,:client,:secret,:auth,:token,:jwks,
+        CAST(:redirects AS jsonb),CAST(:algorithms AS jsonb),:mapping,CAST(:config AS jsonb),CAST(:actor AS uuid))
+    """), {"uid": new_governance_uid(), "provider": config.provider_uid, "version": config.version, "issuer": config.issuer,
+             "client": config.client_id, "secret": config.secret_ref, "auth": config.authorization_endpoint,
+             "token": config.token_endpoint, "jwks": config.jwks_uri, "redirects": json.dumps(config.redirect_uris),
+             "algorithms": json.dumps(config.algorithms), "mapping": config.mapping_version,
+             "config": json.dumps({"display_name": data.get("display_name"), "group_rules": group_rules,
+                                   "post_login_redirect_uris": data.get("post_login_redirect_uris", [])}), "actor": g.current_user["id"]})
+    db.session.commit()
+    _audit("idp_version_created", "success", actor_uid=g.current_user["id"], provider_uid=config.provider_uid)
+    return jsonify(success(config.public_dict())), 201
+
+
+@bp.route("/identity/idp-versions/<provider_uid>/<int:version>/<action>", methods=["POST"])
+@require_permissions(IDENTITY_MANAGE)
+@_identity_input_guard
+def identity_idp_action(provider_uid: str, version: int, action: str):
+    provider_uid = _uuid(provider_uid, "provider_uid")
+    version = _positive_int(version, "version")
+    row = _load_config(provider_uid, version=version, active=False)
+    if not row:
+        raise IdentityPolicyError("IdP configuration version not found")
+    config = _config(row)
+    config.validate()
+    _validate_mapping(config.mapping_version, dict(row["config"]).get("group_rules"))
+    if action == "dry-run":
+        result = {"valid": True, "secret_available": bool(config.resolve_secret())}
+        _audit("idp_dry_run", "success", actor_uid=g.current_user["id"], provider_uid=provider_uid,
+               resource_uid=f"{provider_uid}:{version}", detail={"version": version})
+        return jsonify(success(result))
+    if action in {"activate", "rotate"}:
+        config.resolve_secret()
+        db.session.execute(text("UPDATE public.identity_idp_config_versions SET status='superseded' WHERE provider_uid=CAST(:p AS uuid) AND status='active'"), {"p": provider_uid})
+        db.session.execute(text("UPDATE public.identity_idp_config_versions SET status='active',activated_at=CURRENT_TIMESTAMP WHERE provider_uid=CAST(:p AS uuid) AND version=:v"), {"p": provider_uid, "v": version})
+        db.session.execute(text("UPDATE public.enterprise_identity_links SET token_version=token_version+1,updated_at=CURRENT_TIMESTAMP WHERE provider_uid=CAST(:p AS uuid)"), {"p": provider_uid})
+        db.session.execute(text("""
+            UPDATE public.identity_sessions SET status='revoked',revoke_reason='provider_configuration_changed',revoked_at=CURRENT_TIMESTAMP
+            WHERE status='active' AND identity_link_uid IN
+              (SELECT uid FROM public.enterprise_identity_links WHERE provider_uid=CAST(:p AS uuid))
+        """), {"p": provider_uid})
+    elif action == "disable":
+        disabled = db.session.execute(text("UPDATE public.identity_idp_config_versions SET status='disabled',disabled_at=CURRENT_TIMESTAMP WHERE provider_uid=CAST(:p AS uuid) AND version=:v AND status='active' RETURNING provider_uid"), {"p": provider_uid, "v": version}).scalar_one_or_none()
+        if not disabled:
+            raise IdentityPolicyError("only the selected active IdP version can be disabled")
+        db.session.execute(text("UPDATE public.enterprise_identity_links SET token_version=token_version+1,updated_at=CURRENT_TIMESTAMP WHERE provider_uid=CAST(:p AS uuid)"), {"p": provider_uid})
+        db.session.execute(text("""
+            UPDATE public.identity_sessions SET status='revoked',revoke_reason='provider_disabled',revoked_at=CURRENT_TIMESTAMP
+            WHERE status='active' AND identity_link_uid IN
+              (SELECT uid FROM public.enterprise_identity_links WHERE provider_uid=CAST(:p AS uuid))
+        """), {"p": provider_uid})
+    elif action == "connectivity":
+        payload = OidcTransport().fetch_jwks(config)
+        result = {"reachable": True, "key_count": len(payload.get("keys", []))}
+        _audit("idp_connectivity", "success", actor_uid=g.current_user["id"], provider_uid=provider_uid,
+               resource_uid=f"{provider_uid}:{version}", detail={"version": version, **result})
+        return jsonify(success(result))
+    else:
+        raise IdentityPolicyError("unsupported IdP version action")
+    db.session.commit()
+    _audit(f"idp_{action}", "success", actor_uid=g.current_user["id"], provider_uid=provider_uid)
+    return jsonify(success({"provider_uid": provider_uid, "version": version, "action": action}))
+
+
+@bp.route("/identity/claims/dry-run", methods=["POST"])
+@require_permissions(IDENTITY_OPERATE)
+@_identity_input_guard
+def identity_claims_dry_run():
+    data = _body()
+    mapping_version = _required_str(data, "mapping_version", maximum=120)
+    claims = data.get("claims")
+    if not isinstance(claims, dict):
+        raise IdentityPolicyError("claims must be an object")
+    mapped = _validate_mapping(mapping_version, data.get("group_rules")).map(claims)
+    result = {"roles": mapped.roles, "business_domain_uids": mapped.business_domain_uids,
+              "object_types": mapped.object_types, "environments": mapped.environments,
+              "data_scopes": mapped.data_scopes, "mapping_version": mapped.mapping_version,
+              "claims_digest": mapped.claims_digest}
+    _audit("claims_dry_run", "success", actor_uid=g.current_user["id"],
+           enterprise_subject=mapped.subject,
+           detail={"mapping_version": mapped.mapping_version, "claims_digest": mapped.claims_digest,
+                   "role_count": len(mapped.roles), "scope_count": sum(len(result[key]) for key in
+                   ("business_domain_uids", "object_types", "environments", "data_scopes"))})
+    return jsonify(success(result))
+
+
+@bp.route("/identity/directory/delta", methods=["POST"])
+@require_permissions(IDENTITY_OPERATE)
+@_identity_input_guard
+def identity_directory_delta():
+    data = _body()
+    event_type = _required_str(data, "event_type", maximum=20).upper()
+    provider_uid = _uuid(data.get("provider_uid"), "provider_uid")
+    params = {
+        "provider_uid": provider_uid,
+        "source": _required_str(data, "source", maximum=80),
+        "source_event_id": _required_str(data, "source_event_id", maximum=300),
+        "cursor": _required_str(data, "cursor", maximum=500),
+        "cursor_sequence": _positive_int(data.get("cursor_sequence"), "cursor_sequence"),
+        "event_type": event_type,
+    }
+    if event_type in {"JOINER", "MOVER", "RESTORE"}:
+        row = _load_config(provider_uid)
+        if not row:
+            raise IdentityPolicyError("active identity provider not found")
+        config = _config(row)
+        mapped = ClaimMapper(
+            version=config.mapping_version,
+            group_rules=dict(row["config"]).get("group_rules", {}),
+        ).map(data.get("claims", {}))
+        params["subject"] = mapped.subject
+        params["attributes"] = {
+            "provider_uid": config.provider_uid,
+            "username": mapped.username,
+            "display_name": mapped.display_name,
+            "department": mapped.department,
+            "groups": mapped.groups,
+            "roles": mapped.roles,
+            "mapping_version": mapped.mapping_version,
+            "claims_digest": mapped.claims_digest,
+            "authorization_scope": {
+                "business_domain_uids": mapped.business_domain_uids,
+                "object_types": mapped.object_types,
+                "environments": mapped.environments,
+                "data_scopes": mapped.data_scopes,
+            },
+        }
+    elif event_type in {"LEAVER", "DISABLE"}:
+        params["subject"] = _required_str(data, "subject", maximum=500)
+        params["attributes"] = {"username": data.get("username"), "roles": []}
+    elif event_type in {"DEPARTMENT", "GROUP"}:
+        if not isinstance(data.get("attributes"), dict):
+            raise IdentityPolicyError("organization attributes must be an object")
+        params["attributes"] = data["attributes"]
+    else:
+        raise IdentityPolicyError("unsupported directory event type")
+    result = DirectorySynchronizer(_repo(), SessionManager(_repo(), secret=current_app.config["SECRET_KEY"])).apply(**params)
+    _audit("directory_delta", "success", actor_uid=g.current_user["id"],
+           provider_uid=provider_uid, resource_uid=params["source_event_id"],
+           enterprise_subject=str(params["subject"]) if params.get("subject") else None,
+           detail={"event_type": event_type, "result": result})
+    return jsonify(success(result))
+
+
+@bp.route("/identity/sessions", methods=["GET"])
+@require_permissions(IDENTITY_OPERATE)
+@_identity_input_guard
+def identity_sessions():
+    subject = request.args.get("subject")
+    provider_uid = request.args.get("provider_uid")
+    if subject and not provider_uid:
+        raise IdentityPolicyError("provider_uid is required with subject")
+    if provider_uid:
+        provider_uid = _uuid(provider_uid, "provider_uid")
+    if subject and len(subject) > 500:
+        raise IdentityPolicyError("subject is invalid")
+    rows = _repo().list_sessions(provider_uid, subject)
+    visible = ("uid", "user_uid", "identity_source", "status", "created_at", "last_seen_at", "expires_at")
+    return jsonify(success([{key: row.get(key) for key in visible} for row in rows]))
+
+
+@bp.route("/identity/sessions/<session_uid>/revoke", methods=["POST"])
+@require_permissions(IDENTITY_OPERATE)
+@_identity_input_guard
+def identity_session_revoke(session_uid: str):
+    session_uid = _uuid(session_uid, "session_uid")
+    data = _body()
+    session = _repo().get_session(session_uid)
+    reason = _optional_str(data, "reason", "operator_revoke", maximum=120)
+    SessionManager(_repo(), secret=current_app.config["SECRET_KEY"]).revoke(session_uid, reason=reason)
+    _audit("identity_session_revoke", "success", actor_uid=g.current_user["id"],
+           provider_uid=session.get("provider_uid") if session else None, resource_uid=session_uid,
+           detail={"reason_supplied": bool(data.get("reason"))})
+    return jsonify(success({"session_uid": session_uid, "revoked": True}))
+
+
+@bp.route("/identity/sessions/<session_uid>/risk-terminate", methods=["POST"])
+@require_permissions(IDENTITY_OPERATE)
+@_identity_input_guard
+def identity_session_risk(session_uid: str):
+    session_uid = _uuid(session_uid, "session_uid")
+    data = _body()
+    session = _repo().get_session(session_uid)
+    reason = _optional_str(data, "reason", "risk_signal", maximum=300)
+    SessionManager(_repo(), secret=current_app.config["SECRET_KEY"]).risk_revoke(session_uid, reason=reason)
+    _audit("identity_session_risk_terminate", "success", actor_uid=g.current_user["id"],
+           provider_uid=session.get("provider_uid") if session else None, resource_uid=session_uid,
+           detail={"reason_supplied": bool(data.get("reason"))})
+    return jsonify(success({"session_uid": session_uid, "revoked": True, "risk": True}))
+
+
+@bp.route("/identity/emergency/requests", methods=["POST"])
+@require_permissions(IDENTITY_OPERATE)
+@_identity_input_guard
+def identity_emergency_request():
+    data = _body()
+    account_uid = _uuid(data.get("account_uid"), "account_uid")
+    reason = _required_str(data, "reason", maximum=2000)
+    expires_at = _timestamp(data.get("expires_at"), "expires_at")
+    eligible = db.session.execute(text("""
+        SELECT EXISTS(
+          SELECT 1 FROM public.users u JOIN public.user_roles ur ON ur.user_id=u.id
+          JOIN public.roles r ON r.id=ur.role_id
+          WHERE u.id=CAST(:account AS uuid) AND u.status='active' AND r.name='admin'
+            AND NOT EXISTS(SELECT 1 FROM public.enterprise_identity_links l WHERE l.user_uid=u.id)
+        )
+    """), {"account": account_uid}).scalar()
+    record = EmergencyAccess(_repo()).request(requester_uid=g.current_user["id"], account_uid=account_uid,
+        reason=reason, expires_at=expires_at,
+        account_is_local_active_admin=bool(eligible))
+    _audit("emergency_request", "success", actor_uid=g.current_user["id"], resource_uid=record["uid"],
+           detail={"account_uid": record["account_uid"], "expires_at": record["expires_at"].isoformat()})
+    return jsonify(success(record)), 201
+
+
+@bp.route("/identity/emergency/<request_uid>/<action>", methods=["POST"])
+@require_permissions(IDENTITY_MANAGE)
+@_identity_input_guard
+def identity_emergency_action(request_uid: str, action: str):
+    request_uid = _uuid(request_uid, "request_uid")
+    service = EmergencyAccess(_repo())
+    data = _body()
+    if action == "approve":
+        result = service.approve(request_uid, approver_uid=g.current_user["id"])
+    elif action == "activate":
+        result = service.activate(request_uid)
+    elif action == "review":
+        result = service.review(request_uid, reviewer_uid=g.current_user["id"],
+                                outcome=_required_str(data, "outcome", maximum=2000))
+    elif action == "close":
+        result = service.close(request_uid, actor_uid=g.current_user["id"])
+    else:
+        raise IdentityPolicyError("unsupported emergency action")
+    _audit(f"emergency_{action}", "warning" if action == "activate" else "success", actor_uid=g.current_user["id"], resource_uid=request_uid)
+    return jsonify(success(result))
+
+
+@bp.route("/identity/sessions/revoke-all", methods=["POST"])
+@require_permissions(IDENTITY_OPERATE)
+@_identity_input_guard
+def identity_session_revoke_all():
+    data = _body()
+    provider_uid = _uuid(data.get("provider_uid"), "provider_uid")
+    subject = _required_str(data, "subject", maximum=500)
+    reason = _optional_str(data, "reason", "operator_revoke_all", maximum=120)
+    SessionManager(_repo(), secret=current_app.config["SECRET_KEY"]).revoke_subject(
+        provider_uid, subject, reason=reason
+    )
+    _audit("identity_session_revoke_all", "success", actor_uid=g.current_user["id"],
+           provider_uid=provider_uid, enterprise_subject=subject,
+           detail={"reason_supplied": bool(data.get("reason"))})
+    return jsonify(success({"revoked": True}))
+
+
+@bp.route("/identity/audit", methods=["GET"])
+@require_permissions(IDENTITY_MANAGE)
+@_identity_input_guard
+def identity_audit():
+    rows = db.session.execute(text("SELECT uid::text,event_type,outcome,actor_uid::text,provider_uid::text,resource_type,resource_uid,safe_detail,created_at FROM public.identity_audit_events ORDER BY created_at DESC LIMIT :limit"),
+                              {"limit": _positive_int(request.args.get("limit", 100), "limit", maximum=500)}).mappings().all()
+    return jsonify(success([dict(row) for row in rows]))

+ 137 - 0
app/api/system/metering_showback_routes.py

@@ -0,0 +1,137 @@
+"""Closed, Showback-only WP12 HTTP boundary; no chargeback endpoint exists."""
+
+from __future__ import annotations
+
+from flask import g, jsonify, request
+from sqlalchemy.exc import SQLAlchemyError
+
+from app.api.system import bp
+from app.core.governance.metering_showback import DatabaseMeteringShowbackService
+from app.core.system.permissions import (
+    METERING_MANAGE,
+    METERING_READ,
+    require_permissions,
+)
+from app.models.result import failed, success
+
+_MAX_BODY_BYTES = 4096
+_EVENT_FIELDS = {"schema_version", "event_uid", "event_kind", "occurred_at", "window_start", "window_end", "quantity", "unit", "idempotency_key", "evidence", "mapping", "correction_of"}
+_REQUIRED_EVENT_FIELDS = _EVENT_FIELDS - {"correction_of"}
+_ALLOCATION_FIELDS = {"schema_version", "rule_uid", "rule_version", "effective_start", "effective_end", "mapping", "allocations"}
+_BUDGET_FIELDS = {"schema_version", "budget_uid", "window", "mapping", "limit_micros", "threshold_micros"}
+
+
+def _service() -> DatabaseMeteringShowbackService:
+    return DatabaseMeteringShowbackService()
+
+
+def _response(value: object, status: int = 200):
+    response = jsonify(success(value, code=status))
+    response.headers["Cache-Control"] = "no-store"
+    return response, status
+
+
+def _failure(status: int = 400):
+    response = jsonify(failed("metering request rejected", code=status))
+    response.headers["Cache-Control"] = "no-store"
+    return response, status
+
+
+def _event_body() -> dict:
+    if request.content_length is not None and request.content_length > _MAX_BODY_BYTES:
+        raise ValueError("metering_body_closed")
+    value = request.get_json(silent=True)
+    if not isinstance(value, dict) or set(value) not in (_REQUIRED_EVENT_FIELDS, _EVENT_FIELDS):
+        raise ValueError("metering_body_closed")
+    mapping = value.get("mapping")
+    if not isinstance(mapping, dict) or set(mapping) != {"department", "project", "cost_center"}:
+        raise ValueError("metering_mapping_closed")
+    return value
+
+
+@bp.post("/metering/events")
+@require_permissions(METERING_MANAGE)
+def record_metering_event():
+    """record local metering event"""
+    try:
+        body = _event_body()
+        result = _service().record_for_principal(principal_id=str(g.current_user["id"]), body=body)
+        return _response(result, 201 if not result.get("replay") else 200)
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+def _control_body(fields: set[str]) -> dict:
+    if request.content_length is not None and request.content_length > _MAX_BODY_BYTES:
+        raise ValueError("metering_body_closed")
+    value = request.get_json(silent=True)
+    if not isinstance(value, dict) or set(value) != fields:
+        raise ValueError("metering_body_closed")
+    mapping = value.get("mapping")
+    if not isinstance(mapping, dict) or set(mapping) != {"department", "project", "cost_center"}:
+        raise ValueError("metering_mapping_closed")
+    return value
+
+
+@bp.post("/metering/allocations")
+@require_permissions(METERING_MANAGE)
+def publish_metering_allocation():
+    """publish versioned local allocation rule"""
+    try:
+        return _response(_service().publish_allocation_for_principal(principal_id=str(g.current_user["id"]), body=_control_body(_ALLOCATION_FIELDS)), 201)
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+@bp.post("/metering/budgets")
+@require_permissions(METERING_MANAGE)
+def evaluate_metering_budget():
+    """persist disabled-provider budget alert evidence"""
+    try:
+        return _response(_service().evaluate_budget_for_principal(principal_id=str(g.current_user["id"]), body=_control_body(_BUDGET_FIELDS)), 201)
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+def _window() -> str:
+    if set(request.args) != {"window"}:
+        raise ValueError("metering_query_closed")
+    value = request.args.get("window", "")
+    if len(value) != 7 or value[4:5] != "-" or not value.replace("-", "").isdigit():
+        raise ValueError("metering_window_invalid")
+    return value
+
+
+@bp.get("/metering/showback")
+@require_permissions(METERING_READ)
+def get_showback():
+    """get local showback"""
+    try:
+        return _response(_service().showback_for_principal(principal_id=str(g.current_user["id"]), window=_window(), kind="showback"))
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure(403)
+
+
+@bp.get("/metering/reconciliation")
+@require_permissions(METERING_READ)
+def get_metering_reconciliation():
+    """get metering reconciliation"""
+    try:
+        return _response(_service().showback_for_principal(principal_id=str(g.current_user["id"]), window=_window(), kind="reconciliation"))
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure(403)
+
+
+@bp.get("/metering/allocation-replay")
+@require_permissions(METERING_READ)
+def get_metering_allocation_replay():
+    """replay one persisted allocation-rule version"""
+    try:
+        if set(request.args) != {"window", "rule_uid", "rule_version"}:
+            raise ValueError("metering_query_closed")
+        version = request.args.get("rule_version", "")
+        if not version.isdigit():
+            raise ValueError("rule_version_invalid")
+        return _response(_service().allocation_replay_for_principal(principal_id=str(g.current_user["id"]), window=request.args.get("window", ""), rule_uid=request.args.get("rule_uid", ""), rule_version=int(version)))
+    except (KeyError, LookupError, PermissionError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure(403)

+ 140 - 0
app/api/system/plugin_platform_routes.py

@@ -0,0 +1,140 @@
+"""Closed HTTP validation boundary for the inactive WP13 local fixture."""
+
+from __future__ import annotations
+
+from flask import g, jsonify, request
+from sqlalchemy.exc import SQLAlchemyError
+
+from app.api.system import bp
+from app.core.plugins.governance import PluginManifestError, normalize_plugin_manifest
+from app.core.plugins.persistence import (
+    DatabasePluginPlatformService,
+    PluginPersistenceError,
+)
+from app.core.plugins.repository import PluginLifecycleError, PluginPlatform
+from app.core.system.permissions import AGENTS_MANAGE, require_permissions
+from app.models.result import failed, success
+
+_MAX_BODY_BYTES = 8192
+
+
+def _body(required: set[str]) -> dict:
+    if request.content_length is not None and request.content_length > _MAX_BODY_BYTES:
+        raise ValueError("plugin_body_closed")
+    value = request.get_json(silent=True)
+    if not isinstance(value, dict) or set(value) != required:
+        raise ValueError("plugin_body_closed")
+    return value
+
+
+def _response(value: object, status: int = 200):
+    response = jsonify(success(value, code=status))
+    response.headers["Cache-Control"] = "no-store"
+    return response, status
+
+
+def _failure(status: int = 400):
+    response = jsonify(failed("plugin request rejected", code=status))
+    response.headers["Cache-Control"] = "no-store"
+    return response, status
+
+
+@bp.post("/plugins/local-fixture/validate")
+@require_permissions(AGENTS_MANAGE)
+def validate_local_plugin_fixture():
+    """Validate closed metadata only; it never stores or activates a plugin."""
+    try:
+        body = _body({"manifest", "registry_record"})
+        platform = PluginPlatform(now=lambda: 0)
+        result = platform.register(body["manifest"], registry_record=body["registry_record"])
+        return _response({"plugin_uid": result["plugin_uid"], "version": result["version"], "manifest_digest": result["manifest_digest"], "mode": "ENGINEERING_EVIDENCE_ONLY", "activation": "blocked"})
+    except (PluginManifestError, PluginLifecycleError, TypeError, ValueError):
+        return _failure()
+
+
+@bp.post("/plugins/invocations")
+@require_permissions(AGENTS_MANAGE)
+def invoke_plugin_fixture():
+    """Invoke only an approved fixed local fixture through the DB gateway."""
+    try:
+        body = _body({"plugin_uid", "version", "operation", "input_digest", "idempotency_key"})
+        return _response(DatabasePluginPlatformService().invoke(actor_ref=str(g.current_user["id"]), **body), 201)
+    except (PluginPersistenceError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+@bp.post("/plugins/registry")
+@require_permissions(AGENTS_MANAGE)
+def register_plugin_fixture():
+    try:
+        body = _body({"manifest", "registry_record"})
+        # Validate executable-surface metadata before allocating a DB gateway;
+        # this keeps malformed URL/code/credential bodies out of all backends.
+        normalize_plugin_manifest(body["manifest"])
+        return _response(DatabasePluginPlatformService().register(actor_ref=str(g.current_user["id"]), **body), 201)
+    except (PluginPersistenceError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/review")
+@require_permissions(AGENTS_MANAGE)
+def review_plugin_fixture(plugin_uid: str, version: str):
+    try:
+        _body(set())
+        return _response(DatabasePluginPlatformService().review(plugin_uid=plugin_uid, version=version, actor_ref=str(g.current_user["id"])))
+    except (PluginPersistenceError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/approvals")
+@require_permissions(AGENTS_MANAGE)
+def approve_plugin_fixture(plugin_uid: str, version: str):
+    try:
+        body = _body({"approval_action"})
+        return _response(DatabasePluginPlatformService().issue_approval(plugin_uid=plugin_uid, version=version, actor_ref=str(g.current_user["id"]), **body), 201)
+    except (PluginPersistenceError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+def _transition(plugin_uid: str, version: str, target_state: str):
+    try:
+        body = _body({"approval_uid", "expected_fence", "incident_uid"})
+        return _response(DatabasePluginPlatformService().transition(plugin_uid=plugin_uid, version=version, target_state=target_state, actor_ref=str(g.current_user["id"]), **body))
+    except (PluginPersistenceError, RuntimeError, TypeError, ValueError, SQLAlchemyError):
+        return _failure()
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/canary")
+@require_permissions(AGENTS_MANAGE)
+def canary_plugin_fixture(plugin_uid: str, version: str):
+    return _transition(plugin_uid, version, "canary")
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/activate")
+@require_permissions(AGENTS_MANAGE)
+def activate_plugin_fixture(plugin_uid: str, version: str):
+    return _transition(plugin_uid, version, "active")
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/pause")
+@require_permissions(AGENTS_MANAGE)
+def pause_plugin_fixture(plugin_uid: str, version: str):
+    return _transition(plugin_uid, version, "paused")
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/rollback")
+@require_permissions(AGENTS_MANAGE)
+def rollback_plugin_fixture(plugin_uid: str, version: str):
+    return _transition(plugin_uid, version, "rolled_back")
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/revoke")
+@require_permissions(AGENTS_MANAGE)
+def revoke_plugin_fixture(plugin_uid: str, version: str):
+    return _transition(plugin_uid, version, "revoked")
+
+
+@bp.post("/plugins/<plugin_uid>/<version>/recover")
+@require_permissions(AGENTS_MANAGE)
+def recover_plugin_fixture(plugin_uid: str, version: str):
+    return _transition(plugin_uid, version, "recovery")

+ 207 - 0
app/api/system/tenant_routes.py

@@ -0,0 +1,207 @@
+"""Authenticated, fail-closed HTTP entry points for the conditional WP10 base."""
+
+from __future__ import annotations
+
+import os
+
+from flask import g, jsonify, request
+
+from app.api.system import bp
+from app.core.system.permissions import (
+    IDENTITY_MANAGE,
+    IDENTITY_OPERATE,
+    IDENTITY_READ,
+    require_permissions,
+)
+from app.core.system.tenant_control_repository import SqlAlchemyTenantControlRepository
+from app.core.system.tenant_control_service import (
+    TenantControlError,
+    TenantControlService,
+)
+from app.models.result import failed, success
+
+_MAX_REQUEST_BYTES = 4096
+_QUOTA_FIELDS = {"quota_name", "amount", "idempotency_key"}
+_LIFECYCLE_FIELDS = {"expected_fence", "idempotency_key", "approval_ref", "backup_digest", "retention_seconds"}
+_APPROVAL_FIELDS = {"operation", "expected_fence", "idempotency_key", "backup_digest", "retention_seconds"}
+
+
+def _service() -> TenantControlService:
+    return TenantControlService(SqlAlchemyTenantControlRepository())
+
+
+def _body(allowed: set[str] = _QUOTA_FIELDS) -> dict:
+    if request.content_length is not None and request.content_length > _MAX_REQUEST_BYTES:
+        raise TenantControlError("tenant_payload_closed")
+    if len(request.get_data(cache=True)) > _MAX_REQUEST_BYTES:
+        raise TenantControlError("tenant_payload_closed")
+    value = request.get_json(silent=True)
+    if not isinstance(value, dict) or set(value) != allowed:
+        raise TenantControlError("tenant_payload_closed")
+    return value
+
+
+def _server_host() -> str:
+    # Deliberately never consume a client-supplied tenant field or a forwarded
+    # host header.  Trusted proxy normalization belongs in deployment; the
+    # persistent DB membership is the final principal+route authorization.
+    host = os.environ.get("TRUSTED_TENANT_ROUTE_HOST", "").lower()
+    if not host:
+        raise TenantControlError("tenant_route_denied")
+    return host
+
+
+@bp.post("/tenant/quota-claims")
+@require_permissions(IDENTITY_OPERATE)
+def issue_tenant_quota_claim():
+    try:
+        value = _service().issue_quota_claim(
+            principal_id=str(g.current_user["id"]),
+            host=_server_host(),
+            request=_body(),
+        )
+        response = jsonify(success(value, code=201))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 201
+    except (KeyError, RuntimeError, TypeError, ValueError, TenantControlError):
+        response = jsonify(failed("tenant control request rejected", code=400))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 400
+
+
+@bp.post("/tenant/provisions")
+@require_permissions(IDENTITY_MANAGE)
+def provision_private_tenant():
+    try:
+        value = _service().provision_private(
+            principal_id=str(g.current_user["id"]), host=_server_host(), request=_body_provision()
+        )
+        response = jsonify(success(value, code=201))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 201
+    except (KeyError, RuntimeError, TypeError, ValueError, TenantControlError):
+        response = jsonify(failed("tenant control request rejected", code=400))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 400
+
+
+def _body_provision() -> dict:
+    return _body({"idempotency_key"})
+
+
+@bp.post("/tenant/approvals")
+@require_permissions(IDENTITY_MANAGE)
+def issue_tenant_lifecycle_approval():
+    try:
+        value = _service().issue_lifecycle_approval(
+            reviewer_id=str(g.current_user["id"]), request=_body_approval()
+        )
+        response = jsonify(success(value, code=201))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 201
+    except (KeyError, RuntimeError, TypeError, ValueError, TenantControlError):
+        response = jsonify(failed("tenant approval request rejected", code=400))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 400
+
+
+def _body_approval() -> dict:
+    if request.content_length is not None and request.content_length > _MAX_REQUEST_BYTES:
+        raise TenantControlError("tenant_payload_closed")
+    if len(request.get_data(cache=True)) > _MAX_REQUEST_BYTES:
+        raise TenantControlError("tenant_payload_closed")
+    value = request.get_json(silent=True)
+    if not isinstance(value, dict) or not set(value).issubset(_APPROVAL_FIELDS) or not {"operation", "expected_fence", "idempotency_key"}.issubset(value):
+        raise TenantControlError("tenant_payload_closed")
+    return value
+
+
+def _lifecycle(operation: str):
+    try:
+        value = _service().lifecycle_transition(
+            principal_id=str(g.current_user["id"]), host=_server_host(), operation=operation, request=_body_lifecycle()
+        )
+        response = jsonify(success(value))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 200
+    except (KeyError, RuntimeError, TypeError, ValueError, TenantControlError):
+        response = jsonify(failed("tenant lifecycle request rejected", code=400))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 400
+
+
+@bp.post("/tenant/lifecycle/activate")
+@require_permissions(IDENTITY_MANAGE)
+def activate_tenant_lifecycle():
+    return _lifecycle("activate")
+
+
+@bp.post("/tenant/lifecycle/freeze")
+@require_permissions(IDENTITY_MANAGE)
+def freeze_tenant_lifecycle():
+    return _lifecycle("freeze")
+
+
+@bp.post("/tenant/lifecycle/recover")
+@require_permissions(IDENTITY_MANAGE)
+def recover_tenant_lifecycle():
+    return _lifecycle("begin_recovery")
+
+
+@bp.post("/tenant/lifecycle/deletion-candidate")
+@require_permissions(IDENTITY_MANAGE)
+def mark_tenant_deletion_candidate():
+    return _lifecycle("mark_deletion_candidate")
+
+
+@bp.post("/tenant/lifecycle/rollback")
+@require_permissions(IDENTITY_MANAGE)
+def rollback_tenant_lifecycle():
+    return _lifecycle("rollback")
+
+
+@bp.post("/tenant/lifecycle/delete")
+@require_permissions(IDENTITY_MANAGE)
+def delete_tenant_lifecycle():
+    return _lifecycle("delete")
+
+
+@bp.get("/tenant/status")
+@require_permissions(IDENTITY_READ)
+def get_tenant_status():
+    return _read("status")
+
+
+@bp.get("/tenant/audit")
+@require_permissions(IDENTITY_READ)
+def get_tenant_audit():
+    return _read("audit")
+
+
+@bp.get("/tenant/manifests")
+@require_permissions(IDENTITY_READ)
+def get_tenant_manifests():
+    return _read("manifests")
+
+
+def _read(kind: str):
+    try:
+        value = _service().read(principal_id=str(g.current_user["id"]), host=_server_host(), kind=kind)
+        response = jsonify(success(value))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 200
+    except (KeyError, RuntimeError, TypeError, ValueError, TenantControlError):
+        response = jsonify(failed("tenant read request rejected", code=400))
+        response.headers["Cache-Control"] = "no-store"
+        return response, 400
+
+
+def _body_lifecycle() -> dict:
+    if request.content_length is not None and request.content_length > _MAX_REQUEST_BYTES:
+        raise TenantControlError("tenant_payload_closed")
+    if len(request.get_data(cache=True)) > _MAX_REQUEST_BYTES:
+        raise TenantControlError("tenant_payload_closed")
+    value = request.get_json(silent=True)
+    if not isinstance(value, dict) or not set(value).issubset(_LIFECYCLE_FIELDS) or not {"expected_fence", "idempotency_key"}.issubset(value):
+        raise TenantControlError("tenant_payload_closed")
+    return value

+ 182 - 0
app/api/system/trusted_delivery.py

@@ -0,0 +1,182 @@
+"""No-store HTTP boundary for closed trusted-delivery contracts."""
+from datetime import UTC, datetime
+
+from flask import g, jsonify, request
+from sqlalchemy.exc import DBAPIError, IntegrityError
+
+from app import db
+from app.api.system import bp
+from app.core.system.permissions import (
+    SECURITY_GOVERNANCE_MANAGE,
+    SECURITY_GOVERNANCE_OPERATE,
+    require_permissions,
+)
+from app.core.system.trusted_delivery import TrustedDeliveryService
+from app.core.system.trusted_delivery_repository import (
+    SqlAlchemyTrustedDeliveryRepository,
+)
+from app.core.system.trusted_delivery_subscription_repository import (
+    SqlAlchemyTrustedDeliverySubscriptionRepository,
+)
+from app.core.system.trusted_delivery_subscriptions import (
+    TrustedDeliverySubscriptionService,
+)
+from app.models.result import failed, success
+
+MAX_TRUSTED_DELIVERY_REQUEST_BYTES = 16 * 1024
+
+
+def _service():
+    return TrustedDeliveryService(SqlAlchemyTrustedDeliveryRepository(db.session))
+
+
+def _subscription_service():
+    return TrustedDeliverySubscriptionService(
+        SqlAlchemyTrustedDeliverySubscriptionRepository(db.session)
+    )
+
+
+def _execute(operation, *, created=False):
+    try:
+        value = operation()
+        # Service/repository methods deliberately leave transaction ownership to
+        # the HTTP boundary.  Commit before returning success so a second
+        # connection can observe a successful response immediately.
+        db.session.commit()
+        response = jsonify(success(value, code=201 if created else 200))
+        response.headers["Cache-Control"] = "no-store"
+        return (response, 201) if created else response
+    except PermissionError:
+        db.session.rollback()
+        response = jsonify(failed("trusted delivery request denied", code=403))
+    except (IntegrityError, DBAPIError):
+        db.session.rollback()
+        response = jsonify(failed("trusted delivery request conflict", code=409))
+    except (LookupError, RuntimeError, ValueError, TypeError):
+        db.session.rollback()
+        response = jsonify(failed("trusted delivery request rejected", code=400))
+    response.headers["Cache-Control"] = "no-store"
+    return response, response.get_json()["code"]
+
+
+def _body(allowed):
+    if request.content_length is not None and request.content_length > MAX_TRUSTED_DELIVERY_REQUEST_BYTES:
+        raise ValueError("trusted delivery request is too large")
+    if len(request.get_data(cache=True)) > MAX_TRUSTED_DELIVERY_REQUEST_BYTES:
+        raise ValueError("trusted delivery request is too large")
+    body = request.get_json(silent=True)
+    if not isinstance(body, dict) or set(body) - allowed:
+        raise ValueError("closed request schema")
+    return body
+
+
+@bp.post("/trusted-delivery/policy-versions")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def create_trusted_delivery_policy():
+    return _execute(lambda: _service().create_policy_version(_body({"code", "version", "selector", "resource_rules"}), actor_uid=g.current_user["id"]), created=True)
+
+
+@bp.post("/trusted-delivery/policy-versions/activate")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def activate_trusted_delivery_policy():
+    return _execute(lambda: _service().activate_policy_version(_body({"code", "version", "approval_ref", "approval_digest", "idempotency_key"}), actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/policy-versions/rollback")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def rollback_trusted_delivery_policy():
+    return _execute(lambda: _service().rollback_policy_version(_body({"code", "version", "approval_ref", "approval_digest", "idempotency_key"}), actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/decisions")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def evaluate_trusted_delivery():
+    allowed = {"subject_uid", "roles", "business_domain_uid", "asset_uid", "purpose", "environment", "action", "classification", "requested_fields"}
+    body = _body(allowed)
+    body["subject_uid"] = g.current_user["id"]
+    body["roles"] = list(g.current_user["roles"])
+    return _execute(lambda: _service().evaluate_gateway(body))
+
+
+@bp.post("/trusted-delivery/provisions")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def provision_trusted_delivery():
+    allowed = {"policy_uid", "approval_ref", "approval_digest", "asset_uid", "business_domain_uid", "purpose", "environment", "action", "classification", "requested_fields", "expires_at", "target_capability", "provider", "idempotency_key"}
+    def operation():
+        body = _body(allowed)
+        # Browser callers never choose the identity facts used for policy matching.
+        body["subject_uid"] = g.current_user["id"]
+        return _service().provision(body, actor_uid=g.current_user["id"], subject_roles=list(g.current_user["roles"]))
+    return _execute(operation, created=True)
+
+
+@bp.post("/trusted-delivery/legal-holds")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def create_trusted_delivery_hold():
+    return _execute(lambda: _service().create_legal_hold(_body({"asset_uid", "operation", "approver_refs", "evidence_digest"}), actor_uid=g.current_user["id"]), created=True)
+
+
+@bp.post("/trusted-delivery/legal-holds/release")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def release_trusted_delivery_hold():
+    return _execute(lambda: _service().release_legal_hold(_body({"hold_uid", "approval_ref", "approval_digest", "idempotency_key"}), actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/reclaim/preview")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def trusted_delivery_reclaim_preview():
+    body = _body({"as_of"})
+    as_of = body.get("as_of") or datetime.now(UTC).isoformat()
+    return _execute(lambda: {"candidates": _service().preview_reclaim(as_of=as_of, actor_uid=g.current_user["id"]), "executed": False})
+
+
+@bp.post("/trusted-delivery/reclaim/execute")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def trusted_delivery_reclaim_execute():
+    return _execute(lambda: _service().execute_reclaim(_body({"grant_uid", "idempotency_key"}), actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/subscriptions")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def create_trusted_delivery_subscription():
+    allowed = {"grant_uid", "asset_uid", "trigger", "purpose", "expires_at", "idempotency_key"}
+    return _execute(lambda: _subscription_service().create_subscription(_body(allowed), actor_uid=g.current_user["id"]), created=True)
+
+
+@bp.post("/trusted-delivery/subscriptions/<subscription_uid>/pause")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def pause_trusted_delivery_subscription(subscription_uid):
+    return _execute(lambda: _subscription_service().pause_subscription(subscription_uid, actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/subscriptions/<subscription_uid>/activate")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def activate_trusted_delivery_subscription(subscription_uid):
+    return _execute(lambda: _subscription_service().activate_subscription(subscription_uid, actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/subscriptions/<subscription_uid>/resume")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def resume_trusted_delivery_subscription(subscription_uid):
+    return _execute(lambda: _subscription_service().resume_subscription(subscription_uid, actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/subscriptions/<subscription_uid>/terminate")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def terminate_trusted_delivery_subscription(subscription_uid):
+    return _execute(lambda: _subscription_service().terminate_subscription(subscription_uid, actor_uid=g.current_user["id"]))
+
+
+@bp.post("/trusted-delivery/subscriptions/deliveries/<delivery_uid>/compensate")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def compensate_trusted_delivery_subscription_delivery(delivery_uid):
+    return _execute(lambda: _subscription_service().compensate_dead_letter(
+        delivery_uid, **_body({"reason_code", "receipt_code"}), actor_uid=g.current_user["id"]
+    ))
+
+
+@bp.post("/trusted-delivery/subscriptions/anomalies")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def report_trusted_delivery_subscription_anomaly():
+    allowed = {"kind", "asset_uid", "subscription_uid", "incident_uid", "summary", "correlation_id"}
+    return _execute(lambda: _subscription_service().report_anomaly(_body(allowed), actor_uid=g.current_user["id"]), created=True)

+ 99 - 0
app/api/system/trusted_delivery_controls.py

@@ -0,0 +1,99 @@
+"""No-store HTTP boundary for trusted-delivery control and release gate summaries."""
+
+from flask import g, jsonify, request
+from sqlalchemy.exc import DBAPIError, IntegrityError
+
+from app import db
+from app.api.system import bp
+from app.core.system.permissions import (
+    SECURITY_GOVERNANCE_MANAGE,
+    SECURITY_GOVERNANCE_OPERATE,
+    SECURITY_GOVERNANCE_READ,
+    require_permissions,
+)
+from app.core.system.trusted_delivery_controls import TrustedDeliveryControlError
+from app.core.system.trusted_delivery_controls_repository import (
+    SqlAlchemyTrustedDeliveryControlsRepository,
+)
+from app.core.system.trusted_delivery_controls_service import (
+    TrustedDeliveryControlsService,
+)
+from app.models.result import failed, success
+
+MAX_TRUSTED_DELIVERY_CONTROL_REQUEST_BYTES = 16 * 1024
+
+
+def _service():
+    return TrustedDeliveryControlsService(SqlAlchemyTrustedDeliveryControlsRepository(db.session))
+
+
+def _body(allowed):
+    if request.content_length is not None and request.content_length > MAX_TRUSTED_DELIVERY_CONTROL_REQUEST_BYTES:
+        raise TrustedDeliveryControlError("trusted delivery control request is too large")
+    if len(request.get_data(cache=True)) > MAX_TRUSTED_DELIVERY_CONTROL_REQUEST_BYTES:
+        raise TrustedDeliveryControlError("trusted delivery control request is too large")
+    body = request.get_json(silent=True)
+    if not isinstance(body, dict) or set(body) - allowed:
+        raise TrustedDeliveryControlError("closed request schema")
+    return body
+
+
+def _execute(operation, *, created=False):
+    try:
+        value = operation()
+        # Do not acknowledge a durable control mutation before its commit has
+        # succeeded.  Failure takes the common rollback/redacted-error path.
+        db.session.commit()
+        response = jsonify(success(value, code=201 if created else 200))
+        response.headers["Cache-Control"] = "no-store"
+        return (response, 201) if created else response
+    except PermissionError:
+        db.session.rollback()
+        response = jsonify(failed("trusted delivery control request denied", code=403))
+    except (LookupError, RuntimeError, TrustedDeliveryControlError, ValueError, TypeError, IntegrityError, DBAPIError):
+        db.session.rollback()
+        response = jsonify(failed("trusted delivery control request rejected", code=400))
+    response.headers["Cache-Control"] = "no-store"
+    return response, response.get_json()["code"]
+
+
+@bp.get("/trusted-delivery/controls/profiles/<profile_id>/active")
+@require_permissions(SECURITY_GOVERNANCE_READ)
+def trusted_delivery_active_control_profile(profile_id):
+    return _execute(lambda: _service().active_profile_summary(profile_id))
+
+
+@bp.post("/trusted-delivery/controls/release-gates/evaluate")
+@require_permissions(SECURITY_GOVERNANCE_OPERATE)
+def trusted_delivery_release_gate():
+    return _execute(lambda: _service().evaluate_release_gate(_body({"artifact_digest", "sbom_digest", "license_policy_digest", "vulnerability_scan_digest", "approval_id", "approval_version", "scan_decision", "evidence_expires_at", "approval_ref", "approval_digest", "idempotency_key"}), actor_uid=g.current_user["id"]), created=True)
+
+
+@bp.post("/trusted-delivery/controls/profiles/activate")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def trusted_delivery_activate_control_profile():
+    return _execute(lambda: _service().activate_profile(_body({"profile_id", "version", "approval_ref", "approval_digest", "idempotency_key"}), actor_uid=g.current_user["id"]), created=True)
+
+
+@bp.post("/trusted-delivery/controls/profiles/rollback")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def trusted_delivery_rollback_control_profile():
+    return _execute(lambda: _service().activate_profile(_body({"profile_id", "version", "approval_ref", "approval_digest", "idempotency_key"}), actor_uid=g.current_user["id"], operation="rollback"))
+
+
+@bp.post("/trusted-delivery/controls/capability-approvals")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def trusted_delivery_capability_approval():
+    return _execute(lambda: _service().approve_capability(_body({"provider", "capability", "version", "config_digest", "approval_ref", "approval_digest", "idempotency_key"}), actor_uid=g.current_user["id"]), created=True)
+
+
+@bp.get("/trusted-delivery/controls/evidence")
+@require_permissions(SECURITY_GOVERNANCE_READ)
+def trusted_delivery_control_evidence():
+    return _execute(lambda: _service().evidence_summary())
+
+
+@bp.post("/trusted-delivery/controls/destruction-approvals")
+@require_permissions(SECURITY_GOVERNANCE_MANAGE)
+def trusted_delivery_destruction_approval():
+    return _execute(lambda: _service().approve_destruction(_body({"asset_uid", "approval_refs", "evidence_digest", "idempotency_key"}), actor_uid=g.current_user["id"]), created=True)

+ 34 - 1
app/api/system/users.py

@@ -4,7 +4,7 @@ import json
 
 from flask import g, jsonify, request
 from sqlalchemy import text
-from sqlalchemy.exc import IntegrityError
+from sqlalchemy.exc import IntegrityError, ProgrammingError
 
 from app import db
 from app.api.system import bp
@@ -33,6 +33,35 @@ def _audit_access_control(action, resource_uid, status, safe_detail):
             "detail": json.dumps(safe_detail, ensure_ascii=False),
         },
     )
+    try:
+        with db.session.begin_nested():
+            db.session.execute(
+                text(
+                    "INSERT INTO public.identity_audit_events "
+                    "(uid,event_type,outcome,actor_uid,resource_type,resource_uid,safe_detail) VALUES "
+                    "(CAST(:uid AS uuid),:action,:outcome,CAST(:actor AS uuid),'user',:resource_uid,CAST(:detail AS jsonb))"
+                ),
+                {"uid": new_governance_uid(), "action": action, "outcome": "success" if status == "success" else "failure",
+                 "actor": g.current_user["id"], "resource_uid": resource_uid,
+                 "detail": json.dumps(safe_detail, ensure_ascii=False)},
+            )
+    except ProgrammingError:
+        pass
+
+
+def _revoke_identity_sessions(user_id: str, reason: str) -> None:
+    try:
+        with db.session.begin_nested():
+            db.session.execute(
+                text("UPDATE public.enterprise_identity_links SET token_version=token_version+1,updated_at=CURRENT_TIMESTAMP WHERE user_uid=CAST(:id AS uuid)"),
+                {"id": user_id},
+            )
+            db.session.execute(
+                text("UPDATE public.identity_sessions SET status='revoked',revoke_reason=:reason,revoked_at=CURRENT_TIMESTAMP WHERE user_uid=CAST(:id AS uuid) AND status='active'"),
+                {"id": user_id, "reason": reason},
+            )
+    except ProgrammingError:
+        pass
 
 
 def _active_admin_count(session) -> int:
@@ -186,6 +215,8 @@ def update_user(user_id: str):
             "password_value_retained": False,
         },
     )
+    if status == "disabled":
+        _revoke_identity_sessions(user_id, "account_disabled")
     db.session.commit()
     return jsonify(success(message="用户更新成功"))
 
@@ -233,5 +264,7 @@ def update_user_roles(user_id: str):
         "success",
         {"before_roles": previous_roles, "after_roles": sorted(roles)},
     )
+    if sorted(previous_roles) != sorted(roles):
+        _revoke_identity_sessions(user_id, "roles_changed")
     db.session.commit()
     return jsonify(success(message="角色更新成功"))

+ 154 - 24
app/config/config.py

@@ -2,6 +2,8 @@ import os
 import platform
 from collections.abc import Mapping
 
+from app.config.database_urls import validate_postgresql_url
+
 
 def get_bool_env(name: str, default: bool = False) -> bool:
     """Return a boolean value from an environment variable."""
@@ -160,7 +162,16 @@ def _apply_minio_config(app, defaults: dict) -> None:
     password = _clean_env("MINIO_PASSWORD")
     bucket = _clean_env("MINIO_BUCKET")
     prefix = _clean_env("MINIO_PREFIX")
-    is_production = os.environ.get("FLASK_ENV", "").lower() == "production"
+    is_production = str(app.config.get("FLASK_ENV", "")).lower() == "production"
+
+    if is_production:
+        app.config["MINIO_HOST"] = host
+        app.config["MINIO_USER"] = user
+        app.config["MINIO_PASSWORD"] = password
+        app.config["MINIO_BUCKET"] = bucket
+        app.config["PREFIX"] = prefix
+        app.config["MINIO_SECURE"] = get_bool_env("MINIO_SECURE", False)
+        return
 
     use_defaults = (
         is_placeholder_env_value(user)
@@ -201,6 +212,36 @@ PRODUCTION_SERVICE_DEFAULTS = {
     "MINIO_PREFIX": "",
 }
 
+_PRODUCTION_REQUIRED_SERVICE_ENV = (
+    "DATABASE_URL",
+    "BI_AI_CATALOG_CONTROL_DATABASE_URL",
+    "NEO4J_URI",
+    "NEO4J_HTTP_URI",
+    "NEO4J_USER",
+    "NEO4J_PASSWORD",
+    "MINIO_HOST",
+    "MINIO_USER",
+    "MINIO_PASSWORD",
+    "MINIO_BUCKET",
+)
+
+
+def _validate_production_service_env() -> None:
+    """Reject missing, template, or malformed production service settings."""
+    values = {name: _clean_env(name) for name in _PRODUCTION_REQUIRED_SERVICE_ENV}
+    runtime = validate_postgresql_url(values["DATABASE_URL"], "DATABASE_URL")
+    control = validate_postgresql_url(values["BI_AI_CATALOG_CONTROL_DATABASE_URL"], "BI_AI_CATALOG_CONTROL_DATABASE_URL")
+    if control.username != "dataops_bi_ai_catalog_control" or control.username == runtime.username:
+        raise RuntimeError("production BI_AI_CATALOG_CONTROL_DATABASE_URL must use the dedicated control identity")
+    for name, value in values.items():
+        if name in {"DATABASE_URL", "BI_AI_CATALOG_CONTROL_DATABASE_URL"}:
+            continue
+        placeholder = is_placeholder_env_value(value)
+        if name in {"NEO4J_URI", "NEO4J_HTTP_URI", "MINIO_HOST"}:
+            placeholder = not value or value.lower().startswith("replace-")
+        if placeholder:
+            raise RuntimeError(f"production {name} is required and cannot be a placeholder")
+
 
 def _apply_config_from_env(app, config_key: str, env_name: str, default):
     value = _clean_env(env_name)
@@ -216,21 +257,35 @@ def apply_runtime_env_config(app) -> None:
     load_production_env_file()
     defaults = PRODUCTION_SERVICE_DEFAULTS
 
-    _apply_config_from_env(
-        app, "SQLALCHEMY_DATABASE_URI", "DATABASE_URL", defaults["SQLALCHEMY_DATABASE_URI"]
-    )
-    _apply_config_from_env(app, "NEO4J_URI", "NEO4J_URI", defaults["NEO4J_URI"])
-    _apply_config_from_env(
-        app, "NEO4J_HTTP_URI", "NEO4J_HTTP_URI", defaults["NEO4J_HTTP_URI"]
-    )
-    _apply_config_from_env(app, "NEO4J_USER", "NEO4J_USER", defaults["NEO4J_USER"])
-    _apply_config_from_env(
-        app, "NEO4J_PASSWORD", "NEO4J_PASSWORD", defaults["NEO4J_PASSWORD"]
-    )
-    app.config["NEO4J_ENCRYPTED"] = get_bool_env(
-        "NEO4J_ENCRYPTED", defaults["NEO4J_ENCRYPTED"]
-    )
-    _apply_minio_config(app, defaults)
+    is_production = str(app.config.get("FLASK_ENV", "")).lower() == "production"
+    if is_production:
+        _validate_production_service_env()
+        app.config["SQLALCHEMY_DATABASE_URI"] = _clean_env("DATABASE_URL")
+        for config_key, env_name in (
+            ("NEO4J_URI", "NEO4J_URI"),
+            ("NEO4J_HTTP_URI", "NEO4J_HTTP_URI"),
+            ("NEO4J_USER", "NEO4J_USER"),
+            ("NEO4J_PASSWORD", "NEO4J_PASSWORD"),
+        ):
+            app.config[config_key] = _clean_env(env_name)
+        app.config["NEO4J_ENCRYPTED"] = get_bool_env("NEO4J_ENCRYPTED", True)
+        _apply_minio_config(app, defaults)
+    else:
+        _apply_config_from_env(
+            app, "SQLALCHEMY_DATABASE_URI", "DATABASE_URL", defaults["SQLALCHEMY_DATABASE_URI"]
+        )
+        _apply_config_from_env(app, "NEO4J_URI", "NEO4J_URI", defaults["NEO4J_URI"])
+        _apply_config_from_env(
+            app, "NEO4J_HTTP_URI", "NEO4J_HTTP_URI", defaults["NEO4J_HTTP_URI"]
+        )
+        _apply_config_from_env(app, "NEO4J_USER", "NEO4J_USER", defaults["NEO4J_USER"])
+        _apply_config_from_env(
+            app, "NEO4J_PASSWORD", "NEO4J_PASSWORD", defaults["NEO4J_PASSWORD"]
+        )
+        app.config["NEO4J_ENCRYPTED"] = get_bool_env(
+            "NEO4J_ENCRYPTED", defaults["NEO4J_ENCRYPTED"]
+        )
+        _apply_minio_config(app, defaults)
 
     secret_key = _clean_env("SECRET_KEY")
     if secret_key and not is_placeholder_env_value(secret_key):
@@ -293,6 +348,64 @@ def apply_runtime_env_config(app) -> None:
     )
 
 
+def validate_production_database_identity(app, engine) -> None:
+    """Verify the connected production login has runtime-only database powers."""
+    if str(app.config.get("FLASK_ENV", "")).lower() != "production":
+        return
+    from sqlalchemy import text
+
+    try:
+        with engine.connect() as connection:
+            identity = connection.execute(text("""
+                SELECT current_user AS role_name,
+                       role.rolsuper,role.rolcreaterole,role.rolcreatedb,
+                       role.rolreplication,role.rolbypassrls,
+                       EXISTS (
+                         SELECT 1 FROM pg_class object
+                         JOIN pg_namespace namespace ON namespace.oid=object.relnamespace
+                         WHERE object.relowner=role.oid
+                           AND namespace.nspname NOT IN ('pg_catalog','information_schema')
+                           AND namespace.nspname NOT LIKE 'pg_toast%'
+                       ) OR EXISTS (
+                         SELECT 1 FROM pg_proc function
+                         JOIN pg_namespace namespace ON namespace.oid=function.pronamespace
+                         WHERE function.proowner=role.oid
+                           AND namespace.nspname NOT IN ('pg_catalog','information_schema')
+                       ) OR EXISTS (
+                         SELECT 1 FROM pg_namespace namespace
+                         WHERE namespace.nspowner=role.oid
+                           AND namespace.nspname NOT IN ('pg_catalog','information_schema')
+                           AND namespace.nspname NOT LIKE 'pg_%'
+                       ) AS owns_application_objects,
+                       EXISTS (
+                         SELECT 1 FROM pg_roles evidence_owner
+                         WHERE evidence_owner.rolname='dataops_edge_evidence_owner'
+                           AND pg_has_role(current_user,evidence_owner.oid,'MEMBER')
+                       ) AS evidence_owner_member,
+                       EXISTS (
+                         SELECT 1 FROM pg_roles trusted_owner
+                         WHERE trusted_owner.rolname IN (
+                           'dataops_trusted_delivery_owner',
+                           'dataops_trusted_delivery_writer'
+                         )
+                           AND pg_has_role(current_user,trusted_owner.oid,'MEMBER')
+                       ) AS trusted_delivery_owner_member
+                  FROM pg_roles role WHERE role.rolname=current_user
+            """)).mappings().one()
+    except Exception as exc:
+        raise RuntimeError("production runtime database identity verification failed") from exc
+    forbidden = (
+        identity["rolsuper"] or identity["rolcreaterole"] or identity["rolcreatedb"]
+        or identity["rolreplication"] or identity["rolbypassrls"]
+        or identity["owns_application_objects"] or identity["evidence_owner_member"]
+        or identity["trusted_delivery_owner_member"]
+    )
+    if forbidden:
+        raise RuntimeError(
+            "production DATABASE_URL must use a non-owner, least-privilege runtime identity"
+        )
+
+
 def log_llm_env_status(app) -> None:
     """Log LLM env load result after logging is configured."""
     deepseek_key = _clean_env("DEEPSEEK_API_KEY") or _clean_env("LLM_API_KEY")
@@ -335,6 +448,8 @@ def log_service_env_status(app) -> None:
         app.config.get("MINIO_HOST"),
         _mask_secret(str(app.config.get("MINIO_USER", ""))),
     )
+    if str(app.config.get("FLASK_ENV", "")).lower() == "production":
+        return
     if is_placeholder_env_value(_clean_env("DATABASE_URL")):
         app.logger.warning(
             "DATABASE_URL 仍为模板占位符,已回退到默认生产库 "
@@ -434,6 +549,23 @@ class BaseConfig:
         "DATASOURCE_CREDENTIAL_KEY_VERSION", "v1"
     )
     AGENT_CREDENTIAL_SECRET = os.environ.get("AGENT_CREDENTIAL_SECRET", "")
+    # Ed25519 raw-private-key hex or PEM. It is loaded only into process memory
+    # and must never be persisted in a task, release, audit, or HTTP response.
+    EDGE_GATEWAY_SIGNING_PRIVATE_KEY = os.environ.get(
+        "EDGE_GATEWAY_SIGNING_PRIVATE_KEY", ""
+    )
+    EDGE_GATEWAY_SIGNING_PRIVATE_KEY_FILE = os.environ.get(
+        "EDGE_GATEWAY_SIGNING_PRIVATE_KEY_FILE", ""
+    )
+    EDGE_GATEWAY_SIGNING_PUBLIC_KEY = os.environ.get(
+        "EDGE_GATEWAY_SIGNING_PUBLIC_KEY", ""
+    )
+    EDGE_GATEWAY_SIGNING_KEY_ID = os.environ.get(
+        "EDGE_GATEWAY_SIGNING_KEY_ID", ""
+    )
+    EDGE_MTLS_TRUSTED_PROXY_IPS = os.environ.get(
+        "EDGE_MTLS_TRUSTED_PROXY_IPS", ""
+    )
     DATASOURCE_CERT_DIR = os.environ.get(
         "DATASOURCE_CERT_DIR",
         "/etc/dataops-platform/datasource-certs",
@@ -555,23 +687,21 @@ class ProductionConfig(BaseConfig):
     PORT = int(os.environ.get("LISTEN_PORT", os.environ.get("PORT", "5500")))
 
     # 生产环境 MinIO 配置
-    MINIO_HOST = os.environ.get("MINIO_HOST", "127.0.0.1:9000")
-    MINIO_USER = os.environ.get("MINIO_USER", "citu-test")
-    MINIO_PASSWORD = os.environ.get("MINIO_PASSWORD", "citu-test")
+    MINIO_HOST = os.environ.get("MINIO_HOST", "")
+    MINIO_USER = os.environ.get("MINIO_USER", "")
+    MINIO_PASSWORD = os.environ.get("MINIO_PASSWORD", "")
     MINIO_SECURE = get_bool_env("MINIO_SECURE", False)
     MINIO_BUCKET = os.environ.get("MINIO_BUCKET", "dataops-bucket")
     PREFIX = os.environ.get("MINIO_PREFIX", "")
 
     # 生产环境 PostgreSQL 配置
-    SQLALCHEMY_DATABASE_URI = os.environ.get(
-        "DATABASE_URL", "postgresql://postgres:postgres@127.0.0.1:5432/dataops"
-    )
+    SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "")
 
     # 生产环境 Neo4j 配置
     NEO4J_URI = os.environ.get("NEO4J_URI", "bolt://127.0.0.1:7687")
     NEO4J_HTTP_URI = os.environ.get("NEO4J_HTTP_URI", "http://127.0.0.1:7474")
-    NEO4J_USER = os.environ.get("NEO4J_USER", "neo4j")
-    NEO4J_PASSWORD = os.environ.get("NEO4J_PASSWORD", "Passw0rd")
+    NEO4J_USER = os.environ.get("NEO4J_USER", "")
+    NEO4J_PASSWORD = os.environ.get("NEO4J_PASSWORD", "")
     NEO4J_ENCRYPTED = get_bool_env("NEO4J_ENCRYPTED", False)
 
     # 生产环境文件路径配置

+ 121 - 0
app/config/database_urls.py

@@ -0,0 +1,121 @@
+"""Fail-closed validation for production PostgreSQL identities and templates."""
+
+from __future__ import annotations
+
+import argparse
+import re
+import sys
+from dataclasses import dataclass
+from pathlib import Path
+from urllib.parse import unquote, urlsplit
+
+_BAD_PERCENT_ESCAPE = re.compile(r"%(?![0-9a-fA-F]{2})")
+_PLACEHOLDER_VALUES = {
+    "change-me",
+    "changeme",
+    "database",
+    "database_name",
+    "dbname",
+    "host",
+    "hostname",
+    "pass",
+    "password",
+    "replace",
+    "user",
+    "username",
+}
+
+
+@dataclass(frozen=True)
+class DatabaseUrlParts:
+    username: str
+    password: str
+    host: str
+    database: str
+
+
+def is_template_secret(value: str) -> bool:
+    """Return true only for empty or explicit template values, after decoding."""
+    normalized = unquote(str(value or "")).strip().lower()
+    return (
+        not normalized
+        or normalized in _PLACEHOLDER_VALUES
+        or normalized.startswith(("replace-", "your-"))
+        or "${" in normalized
+        or normalized.startswith("<")
+        or normalized.endswith(">")
+    )
+
+
+def validate_postgresql_url(value: str, field_name: str) -> DatabaseUrlParts:
+    """Parse a PostgreSQL URL and reject empty/template identity components."""
+    raw = str(value or "").strip()
+    if not raw or _BAD_PERCENT_ESCAPE.search(raw):
+        raise RuntimeError(f"{field_name} is missing or malformed")
+    try:
+        parsed = urlsplit(raw)
+        port = parsed.port
+    except (TypeError, ValueError) as exc:
+        raise RuntimeError(f"{field_name} is malformed") from exc
+    if parsed.scheme.split("+", 1)[0].lower() not in {"postgres", "postgresql"}:
+        raise RuntimeError(f"{field_name} must use PostgreSQL")
+    values = {
+        "username": unquote(parsed.username or ""),
+        "password": unquote(parsed.password or ""),
+        "host": unquote(parsed.hostname or ""),
+        "database": unquote(parsed.path.lstrip("/")),
+    }
+    if port is not None and not 1 <= port <= 65535:
+        raise RuntimeError(f"{field_name} has an invalid port")
+    for component, component_value in values.items():
+        if is_template_secret(component_value):
+            raise RuntimeError(f"{field_name} has an invalid {component}")
+    return DatabaseUrlParts(**values)
+
+
+def _read_env_file(path: str | Path) -> dict[str, str]:
+    values: dict[str, str] = {}
+    for raw_line in Path(path).read_text(encoding="utf-8-sig").splitlines():
+        line = raw_line.strip()
+        if not line or line.startswith("#") or "=" not in line:
+            continue
+        name, value = line.split("=", 1)
+        values[name.strip()] = value.strip().strip('"').strip("'")
+    return values
+
+
+def validate_database_environment(values: dict[str, str]) -> None:
+    runtime_parts = None
+    for field_name in (
+        "DB_ROLE_INIT_DATABASE_URL",
+        "MIGRATION_DATABASE_URL",
+        "DATABASE_URL",
+    ):
+        parts = validate_postgresql_url(values.get(field_name, ""), field_name)
+        if field_name == "DATABASE_URL":
+            runtime_parts = parts
+    runtime_password = values.get("DATAOPS_RUNTIME_PASSWORD", "")
+    if is_template_secret(runtime_password):
+        raise RuntimeError("DATAOPS_RUNTIME_PASSWORD is missing or a template value")
+    if runtime_parts is None or runtime_parts.password != runtime_password:
+        raise RuntimeError("DATAOPS_RUNTIME_PASSWORD must match DATABASE_URL")
+
+
+def load_and_validate_database_env(path: str | Path) -> None:
+    validate_database_environment(_read_env_file(path))
+
+
+def main(argv: list[str] | None = None) -> int:
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--env-file", required=True)
+    args = parser.parse_args(argv)
+    try:
+        load_and_validate_database_env(args.env_file)
+    except (OSError, RuntimeError) as exc:
+        print(f"database configuration rejected: {exc}", file=sys.stderr)
+        return 2
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())

+ 15 - 0
app/core/connectors/__init__.py

@@ -0,0 +1,15 @@
+"""Stable connector SDK public entry point."""
+
+from app.core.connectors import errors as _errors
+from app.core.connectors import sdk as _sdk
+from app.core.connectors.errors import *  # noqa: F401,F403
+from app.core.connectors.registry import ConnectorRegistry
+from app.core.connectors.sdk import *  # noqa: F401,F403
+from app.core.connectors.secrets import EnvironmentSecretResolver
+
+__all__ = [
+    "ConnectorRegistry",
+    "EnvironmentSecretResolver",
+    *_sdk.__all__,
+    *_errors.__all__,
+]

+ 224 - 0
app/core/connectors/bindings.py

@@ -0,0 +1,224 @@
+"""Server-approved connector source and destination bindings."""
+
+from __future__ import annotations
+
+import json
+from urllib.parse import urlsplit
+
+from sqlalchemy import text
+
+from app.core.common.identifiers import new_governance_uid
+from app.core.connectors.errors import ConnectorConfigurationError
+from app.core.connectors.sdk import SECRET_REF_PATTERN, _walk_secrets
+
+ENVIRONMENTS = frozenset({"development", "staging", "production"})
+
+
+def validate_approved_config(connector_id, config):
+    if not isinstance(config, dict):
+        raise ConnectorConfigurationError("approved connector config must be an object")
+    _walk_secrets(config, "approved_config")
+    if connector_id != "rest-catalog":
+        return dict(config)
+    if set(config) != {"base_url", "allowed_host", "credential_ref"}:
+        raise ConnectorConfigurationError("REST approved config is incomplete")
+    parsed = urlsplit(str(config["base_url"]))
+    host = str(config["allowed_host"]).lower().rstrip(".")
+    if (
+        parsed.scheme != "https"
+        or not parsed.hostname
+        or parsed.username
+        or parsed.password
+        or parsed.query
+        or parsed.fragment
+        or parsed.hostname.lower().rstrip(".") != host
+    ):
+        raise ConnectorConfigurationError("REST approved destination is invalid")
+    credential_ref = str(config["credential_ref"])
+    if not SECRET_REF_PATTERN.fullmatch(credential_ref):
+        raise ConnectorConfigurationError("REST approved credential reference is invalid")
+    return {
+        "base_url": str(config["base_url"]).rstrip("/"),
+        "allowed_host": host,
+        "credential_ref": credential_ref,
+    }
+
+
+class ConnectorSourceBindingRepository:
+    def __init__(self, session):
+        self.session = session
+
+    def approve(
+        self,
+        *,
+        connector_id,
+        connector_version,
+        source_uid,
+        business_domain_uid,
+        environment,
+        approved_config,
+        approved_by,
+        binding_uid=None,
+    ):
+        if environment not in ENVIRONMENTS:
+            raise ConnectorConfigurationError("connector binding environment is invalid")
+        config = validate_approved_config(connector_id, approved_config)
+        uid = binding_uid or new_governance_uid()
+        if binding_uid:
+            self.session.execute(
+                text("SELECT pg_advisory_xact_lock(hashtext(:lock_key))"),
+                {"lock_key": f"connector-binding:{uid}"},
+            )
+            current = self.session.execute(
+                text("""
+                SELECT COALESCE(MAX(binding_version),0)
+                  FROM public.connector_source_bindings
+                 WHERE uid=CAST(:uid AS uuid)
+                """),
+                {"uid": uid},
+            ).scalar_one()
+            self.session.execute(
+                text("""
+                UPDATE public.connector_source_bindings
+                   SET status='revoked',updated_at=CURRENT_TIMESTAMP
+                 WHERE uid=CAST(:uid AS uuid) AND status='approved'
+                """),
+                {"uid": uid},
+            )
+            version = int(current) + 1
+        else:
+            version = 1
+        rest = config if connector_id == "rest-catalog" else {}
+        self.session.execute(
+            text("""
+            INSERT INTO public.connector_source_bindings
+              (uid,binding_version,connector_id,connector_version,source_uid,
+               business_domain_uid,environment,approved_base_url,allowed_host,
+               credential_ref,approved_config,status,approved_by)
+            VALUES(CAST(:uid AS uuid),:binding_version,:connector,:version,
+                   CAST(:source AS uuid),CAST(:domain AS uuid),:environment,
+                   :base_url,:allowed_host,:credential_ref,CAST(:config AS jsonb),
+                   'approved',CAST(:approved_by AS uuid))
+            """),
+            {
+                "uid": uid,
+                "binding_version": version,
+                "connector": connector_id,
+                "version": connector_version,
+                "source": source_uid,
+                "domain": business_domain_uid,
+                "environment": environment,
+                "base_url": rest.get("base_url"),
+                "allowed_host": rest.get("allowed_host"),
+                "credential_ref": rest.get("credential_ref"),
+                "config": json.dumps(config, sort_keys=True),
+                "approved_by": approved_by,
+            },
+        )
+        rebound_principals = 0
+        if binding_uid and int(current) > 0:
+            rebound_principals = self.session.execute(
+                text("""
+                    UPDATE public.connector_principals
+                       SET source_binding_version=:binding_version
+                     WHERE source_binding_uid=CAST(:uid AS uuid)
+                       AND source_binding_version=:old_version
+                """),
+                {
+                    "uid": uid,
+                    "old_version": int(current),
+                    "binding_version": version,
+                },
+            ).rowcount
+        self.session.commit()
+        return {
+            "binding_uid": uid,
+            "binding_version": version,
+            "status": "approved",
+            "rebound_principals": rebound_principals,
+        }
+
+    def revoke(self, binding_uid, approved_by):
+        principals = self.session.execute(
+            text("""
+                UPDATE public.connector_principals
+                   SET status='revoked'
+                 WHERE source_binding_uid=CAST(:uid AS uuid) AND status='active'
+                 RETURNING uid
+            """),
+            {"uid": binding_uid},
+        ).all()
+        if principals:
+            principal_uids = [str(row[0]) for row in principals]
+            self.session.execute(
+                text("""
+                    UPDATE public.connector_machine_credentials
+                       SET status='revoked',revoked_at=CURRENT_TIMESTAMP
+                     WHERE principal_uid=ANY(CAST(:principals AS uuid[]))
+                       AND status='active'
+                """),
+                {"principals": principal_uids},
+            )
+        changed = self.session.execute(
+            text("""
+            UPDATE public.connector_source_bindings
+               SET status='revoked',approved_by=CAST(:actor AS uuid),updated_at=CURRENT_TIMESTAMP
+             WHERE uid=CAST(:uid AS uuid) AND status='approved'
+            """),
+            {"uid": binding_uid, "actor": approved_by},
+        ).rowcount
+        self.session.commit()
+        return {"revoked": bool(changed), "principals_deactivated": len(principals)}
+
+    def get_approved(self, binding_uid, binding_version=None):
+        version_clause = (
+            "AND binding_version=:binding_version" if binding_version is not None else ""
+        )
+        row = (
+            self.session.execute(
+                text(f"""
+                SELECT uid::text,binding_version,connector_id,connector_version,
+                       source_uid::text,business_domain_uid::text,environment,
+                       approved_base_url,allowed_host,credential_ref,approved_config,status
+                  FROM public.connector_source_bindings
+                 WHERE uid=CAST(:uid AS uuid) AND status='approved' {version_clause}
+                 ORDER BY binding_version DESC LIMIT 1
+                """),
+                {"uid": binding_uid, "binding_version": binding_version},
+            )
+            .mappings()
+            .one_or_none()
+        )
+        if row is None:
+            raise ConnectorConfigurationError("approved connector binding was not found")
+        result = dict(row)
+        config = dict(result.pop("approved_config") or {})
+        if result["connector_id"] == "rest-catalog":
+            config = {
+                "base_url": result.pop("approved_base_url"),
+                "allowed_host": result.pop("allowed_host"),
+                "credential_ref": result.pop("credential_ref"),
+            }
+        else:
+            result.pop("approved_base_url", None)
+            result.pop("allowed_host", None)
+            result.pop("credential_ref", None)
+        result["approved_config"] = config
+        return result
+
+    def list_public(self, limit=100):
+        rows = self.session.execute(
+            text("""
+            SELECT uid::text,binding_version,connector_id,connector_version,
+                   source_uid::text,business_domain_uid::text,environment,
+                   approved_base_url,allowed_host,status,approved_by::text,
+                   created_at,updated_at
+              FROM public.connector_source_bindings
+             ORDER BY created_at DESC LIMIT :limit
+            """),
+            {"limit": min(max(int(limit), 1), 200)},
+        ).mappings()
+        return [dict(row) for row in rows]
+
+
+__all__ = ["ConnectorSourceBindingRepository", "validate_approved_config"]

+ 32 - 0
app/core/connectors/builtin/__init__.py

@@ -0,0 +1,32 @@
+"""Built-in connectors registered exclusively through the public SDK."""
+
+from app.core.connectors.builtin.oracle import OracleConnector
+from app.core.connectors.builtin.postgresql import PostgreSQLConnector
+from app.core.connectors.builtin.rest_catalog import (
+    RestCatalogConnector,
+    SafeRestTransport,
+)
+from app.core.connectors.builtin.sqlserver import SqlServerConnector
+
+
+def register_builtin_connectors(registry, **dependencies):
+    registry.register(OracleConnector(dependencies.get("connection_provider")))
+    registry.register(PostgreSQLConnector(dependencies.get("connection_provider")))
+    registry.register(SqlServerConnector(dependencies.get("connection_provider")))
+    transport = dependencies.get("rest_transport")
+    if transport is None:
+        transport = SafeRestTransport(
+            secret_resolver=dependencies.get("secret_resolver")
+        )
+    registry.register(RestCatalogConnector(transport))
+    return registry
+
+
+__all__ = [
+    "register_builtin_connectors",
+    "OracleConnector",
+    "PostgreSQLConnector",
+    "SqlServerConnector",
+    "RestCatalogConnector",
+    "SafeRestTransport",
+]

+ 184 - 0
app/core/connectors/builtin/database.py

@@ -0,0 +1,184 @@
+"""Common normalized behavior for read-only relational catalog connectors."""
+
+from __future__ import annotations
+
+import hashlib
+import inspect
+import json
+
+from sqlalchemy import text
+
+from app.core.connectors.errors import (
+    ConnectorCancelledError,
+    ConnectorConfigurationError,
+    classify_error,
+)
+from app.core.connectors.sdk import Connector, OperationResult
+
+DATABASE_CONFIG_SCHEMA = {
+    "type": "object",
+    "additionalProperties": False,
+    "required": ["credential_ref"],
+    "properties": {
+        "credential_ref": {
+            "type": "string",
+            "pattern": r"^(?:env|vault|secret):[A-Za-z0-9][A-Za-z0-9_./:-]{2,255}$",
+        },
+    },
+}
+
+
+class ReadOnlyCatalogConnector(Connector):
+    catalog_sql = ""
+
+    def __init__(self, connection_provider=None):
+        self.connection_provider = connection_provider
+
+    @staticmethod
+    def _check_cancel(request):
+        if request.cancel_probe and request.cancel_probe():
+            raise ConnectorCancelledError()
+
+    @staticmethod
+    def _scope(request):
+        scope = dict(request.scope or {})
+        allowed = {
+            "include_schemas",
+            "exclude_schemas",
+            "include_tables",
+            "exclude_tables",
+        }
+        if set(scope) - allowed:
+            raise ConnectorConfigurationError("catalog scope is invalid")
+        result = {}
+        for name in allowed:
+            value = scope.get(name, ())
+            if not isinstance(value, (list, tuple)) or any(
+                not isinstance(item, str) or not item.strip() for item in value
+            ):
+                raise ConnectorConfigurationError("catalog scope values are invalid")
+            result[name] = frozenset(item.strip() for item in value)
+        return result
+
+    def _collect(self, request):
+        if self.connection_provider is None:
+            raise ConnectorConfigurationError("connection provider is not configured")
+        self._check_cancel(request)
+        scope = self._scope(request)
+        provider_kwargs = {}
+        if self.manifest.connector_id == "sqlserver":
+            parameters = inspect.signature(self.connection_provider).parameters
+            accepts_kwargs = any(
+                item.kind is inspect.Parameter.VAR_KEYWORD
+                for item in parameters.values()
+            )
+            if accepts_kwargs or "environment" in parameters:
+                provider_kwargs = {
+                    "environment": request.environment,
+                    "allow_insecure_development": bool(
+                        request.config.get("allow_insecure_development", False)
+                    ),
+                }
+        try:
+            with self.connection_provider(
+                request.source_uid, "metadata_collection", **provider_kwargs
+            ) as connection:
+                self._check_cancel(request)
+                rows = connection.execute(text(self.catalog_sql), {}).mappings().all()
+                self._check_cancel(request)
+        except Exception as error:
+            raise classify_error(error) from error
+        records = []
+        for raw in rows:
+            self._check_cancel(request)
+            row = {str(key).lower(): value for key, value in dict(raw).items()}
+            schema = str(row["schema_name"])
+            table = str(row["asset_name"])
+            if scope["include_schemas"] and schema not in scope["include_schemas"]:
+                continue
+            if schema in scope["exclude_schemas"] or table in scope["exclude_tables"]:
+                continue
+            if scope["include_tables"] and table not in scope["include_tables"]:
+                continue
+            records.append(
+                {
+                    "asset_key": f"{request.source_uid}:{schema}.{table}",
+                    "schema": schema,
+                    "name": table,
+                    "asset_type": "view"
+                    if "VIEW" in str(row["asset_type"]).upper()
+                    else "table",
+                    "field": str(row["column_name"]),
+                    "ordinal_position": int(row["ordinal_position"]),
+                    "data_type": str(row["data_type"]),
+                    "nullable": str(row.get("is_nullable", "NO")).upper()
+                    in {"YES", "Y", "TRUE", "1"},
+                    "default": row.get("column_default"),
+                    "comment": row.get("column_comment"),
+                }
+            )
+        records.sort(
+            key=lambda item: (item["schema"], item["name"], item["ordinal_position"])
+        )
+        digest = hashlib.sha256(
+            json.dumps(records, sort_keys=True, default=str).encode()
+        ).hexdigest()
+        snapshot_summary = {"sha256": digest, "record_count": len(records)}
+        checkpoint = {"snapshot_summary": snapshot_summary}
+        evidence = {
+            "query_kind": "read_only_metadata",
+            "record_count": len(records),
+            "snapshot_hash": digest,
+        }
+        if request.operation in {"incremental", "resume"}:
+            previous = request.checkpoint.get("snapshot_summary") or {}
+            evidence["diff"] = {
+                "changed": previous.get("sha256") != digest,
+                "previous_record_count": previous.get("record_count"),
+                "current_record_count": len(records),
+                "details_materialized": False,
+            }
+        return OperationResult(
+            records=tuple(records),
+            cursor={"snapshot_hash": digest},
+            checkpoint=checkpoint,
+            evidence=evidence,
+        )
+
+    def discover(self, request):
+        return self._collect(request)
+
+    def snapshot(self, request):
+        return self._collect(request)
+
+    def incremental(self, request):
+        return self._collect(request)
+
+    def lineage(self, request):
+        return OperationResult(
+            evidence={
+                "supported": False,
+                "reason": "database catalog has no portable lineage endpoint",
+            }
+        )
+
+    def profile(self, request):
+        return OperationResult(
+            evidence={"supported": False, "reason": "profile requires explicit opt-in"}
+        )
+
+    def cancel(self, request):
+        return OperationResult(status="cancelled", checkpoint=dict(request.checkpoint))
+
+    def resume(self, request):
+        return self._collect(request)
+
+    def evidence(self, request):
+        return OperationResult(
+            evidence={
+                "connector_id": self.manifest.connector_id,
+                "version": self.manifest.version,
+                "scope": self._scope(request),
+                "secret_material": False,
+            }
+        )

+ 63 - 0
app/core/connectors/builtin/oracle.py

@@ -0,0 +1,63 @@
+"""Oracle read-only catalog connector (driver and connection are optional)."""
+
+import importlib.util
+
+from app.core.connectors.builtin.database import (
+    DATABASE_CONFIG_SCHEMA,
+    ReadOnlyCatalogConnector,
+)
+from app.core.connectors.sdk import (
+    SDK_VERSION,
+    CompatibilityResult,
+    ConnectorManifest,
+    HealthResult,
+    validate_config,
+)
+
+ORACLE_CATALOG_SQL = """
+SELECT c.owner AS schema_name, c.table_name AS asset_name,
+       CASE WHEN v.view_name IS NULL THEN 'TABLE' ELSE 'VIEW' END AS asset_type,
+       c.column_name, c.column_id AS ordinal_position, c.data_type,
+       CASE WHEN c.nullable = 'Y' THEN 'YES' ELSE 'NO' END AS is_nullable,
+       c.data_default AS column_default, cc.comments AS column_comment
+FROM all_tab_columns c
+LEFT JOIN all_views v ON v.owner = c.owner AND v.view_name = c.table_name
+LEFT JOIN all_col_comments cc ON cc.owner = c.owner AND cc.table_name = c.table_name AND cc.column_name = c.column_name
+WHERE c.owner NOT IN ('SYS', 'SYSTEM', 'OUTLN', 'DBSNMP')
+ORDER BY c.owner, c.table_name, c.column_id
+"""
+
+
+class OracleConnector(ReadOnlyCatalogConnector):
+    catalog_sql = ORACLE_CATALOG_SQL
+    manifest = ConnectorManifest(
+        connector_id="oracle",
+        version="1.0.0",
+        sdk_version=SDK_VERSION,
+        display_name="Oracle Database",
+        capabilities=(
+            "discover",
+            "snapshot",
+            "incremental",
+            "cancel",
+            "resume",
+            "evidence",
+        ),
+        config_schema=DATABASE_CONFIG_SCHEMA,
+    )
+
+    def health(self, config):
+        validate_config(self.manifest.config_schema, config)
+        available = importlib.util.find_spec("oracledb") is not None
+        return HealthResult(
+            "available" if available else "degraded",
+            "" if available else "optional driver unavailable",
+        )
+
+    def compatibility(self):
+        available = importlib.util.find_spec("oracledb") is not None
+        return CompatibilityResult(
+            available,
+            self.manifest.version,
+            detail="" if available else "optional driver unavailable",
+        )

+ 68 - 0
app/core/connectors/builtin/postgresql.py

@@ -0,0 +1,68 @@
+"""PostgreSQL read-only catalog connector (driver and connection are optional)."""
+
+import importlib.util
+
+from app.core.connectors.builtin.database import (
+    DATABASE_CONFIG_SCHEMA,
+    ReadOnlyCatalogConnector,
+)
+from app.core.connectors.sdk import (
+    SDK_VERSION,
+    CompatibilityResult,
+    ConnectorManifest,
+    HealthResult,
+    validate_config,
+)
+
+POSTGRESQL_CATALOG_SQL = """
+SELECT c.table_schema AS schema_name, c.table_name AS asset_name,
+       CASE WHEN t.table_type = 'VIEW' THEN 'VIEW' ELSE 'TABLE' END AS asset_type,
+       c.column_name, c.ordinal_position, c.data_type, c.is_nullable,
+       c.column_default, d.description AS column_comment
+FROM information_schema.columns c
+JOIN information_schema.tables t
+  ON t.table_schema = c.table_schema AND t.table_name = c.table_name
+LEFT JOIN pg_catalog.pg_namespace n ON n.nspname = c.table_schema
+LEFT JOIN pg_catalog.pg_class r
+  ON r.relnamespace = n.oid AND r.relname = c.table_name
+LEFT JOIN pg_catalog.pg_description d
+  ON d.objoid = r.oid AND d.objsubid = c.ordinal_position
+WHERE t.table_type IN ('BASE TABLE', 'VIEW')
+  AND c.table_schema NOT IN ('pg_catalog', 'information_schema')
+ORDER BY c.table_schema, c.table_name, c.ordinal_position
+"""
+
+
+class PostgreSQLConnector(ReadOnlyCatalogConnector):
+    catalog_sql = POSTGRESQL_CATALOG_SQL
+    manifest = ConnectorManifest(
+        connector_id="postgresql",
+        version="1.0.0",
+        sdk_version=SDK_VERSION,
+        display_name="PostgreSQL",
+        capabilities=(
+            "discover",
+            "snapshot",
+            "incremental",
+            "cancel",
+            "resume",
+            "evidence",
+        ),
+        config_schema=DATABASE_CONFIG_SCHEMA,
+    )
+
+    def health(self, config):
+        validate_config(self.manifest.config_schema, config)
+        available = importlib.util.find_spec("psycopg2") is not None
+        return HealthResult(
+            "available" if available else "degraded",
+            "" if available else "optional driver unavailable",
+        )
+
+    def compatibility(self):
+        available = importlib.util.find_spec("psycopg2") is not None
+        return CompatibilityResult(
+            available,
+            self.manifest.version,
+            detail="" if available else "optional driver unavailable",
+        )

+ 381 - 0
app/core/connectors/builtin/rest_catalog.py

@@ -0,0 +1,381 @@
+"""Controlled REST catalog with DNS-pinned, bounded TLS transport."""
+
+from __future__ import annotations
+
+import hashlib
+import ipaddress
+import json
+import socket
+from collections.abc import Callable, Mapping
+from urllib.parse import urlencode, urlsplit
+
+import requests
+import urllib3
+from jsonschema import Draft202012Validator
+from jsonschema.exceptions import ValidationError
+
+from app.core.connectors.errors import (
+    ConnectorCancelledError,
+    ConnectorConfigurationError,
+    ConnectorContractError,
+    ConnectorUpstreamError,
+)
+from app.core.connectors.sdk import (
+    SDK_VERSION,
+    Connector,
+    ConnectorManifest,
+    OperationResult,
+)
+
+MAX_RESPONSE_BYTES = 2 * 1024 * 1024
+MAX_PAGES = 100
+MAX_RECORDS = 10000
+REST_CONFIG_SCHEMA = {
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "type": "object",
+    "additionalProperties": False,
+    "required": ["base_url", "allowed_host", "credential_ref"],
+    "properties": {
+        "base_url": {
+            "type": "string",
+            "pattern": r"^https://[A-Za-z0-9.-]+(?::443)?(?:/[^?#\s]*)?$",
+            "maxLength": 2048,
+        },
+        "allowed_host": {
+            "type": "string",
+            "pattern": r"^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$",
+            "maxLength": 253,
+        },
+        "credential_ref": {
+            "type": "string",
+            "pattern": r"^(?:env|vault|secret):[A-Za-z0-9][A-Za-z0-9_./:-]{2,255}$",
+        },
+    },
+}
+CATALOG_RESPONSE_SCHEMA = {
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "type": "object",
+    "additionalProperties": False,
+    "required": ["assets"],
+    "properties": {
+        "assets": {
+            "type": "array",
+            "maxItems": 10000,
+            "items": {
+                "type": "object",
+                "additionalProperties": False,
+                "required": ["key", "name", "namespace", "type"],
+                "properties": {
+                    name: {"type": "string", "minLength": 1, "maxLength": 500}
+                    for name in ("key", "name", "namespace", "type")
+                },
+            },
+        },
+        "next_cursor": {
+            "type": "object",
+            "additionalProperties": {
+                "type": ["string", "number", "integer", "boolean", "null"]
+            },
+            "maxProperties": 20,
+        },
+        "evidence": {"type": "object"},
+        "completion_token": {"type": "string", "minLength": 1, "maxLength": 500},
+    },
+}
+
+
+class SafeRestTransport:
+    """Resolve once, reject non-global answers, then connect only to pinned IPs."""
+
+    def __init__(
+        self,
+        *,
+        secret_resolver: Callable[[str], str] | None = None,
+        resolver: Callable[..., object] = socket.getaddrinfo,
+        pool_factory: Callable[..., object] = urllib3.HTTPSConnectionPool,
+        connect_timeout: float = 3.0,
+        read_timeout: float = 10.0,
+        max_response_bytes: int = MAX_RESPONSE_BYTES,
+    ):
+        self.secret_resolver = secret_resolver
+        self.resolver = resolver
+        self.pool_factory = pool_factory
+        self.connect_timeout = float(connect_timeout)
+        self.read_timeout = float(read_timeout)
+        self.max_response_bytes = min(int(max_response_bytes), MAX_RESPONSE_BYTES)
+
+    def _destination(self, url, allowed_host):
+        parsed = urlsplit(url)
+        host = (parsed.hostname or "").lower().rstrip(".")
+        expected = str(allowed_host).lower().rstrip(".")
+        if (
+            parsed.scheme != "https"
+            or parsed.username
+            or parsed.password
+            or parsed.fragment
+            or host != expected
+        ):
+            raise ConnectorConfigurationError("catalog destination is not allowed")
+        if parsed.port not in {None, 443}:
+            raise ConnectorConfigurationError("catalog HTTPS port is not allowed")
+        try:
+            raw = self.resolver(host, 443, type=socket.SOCK_STREAM)
+            addresses = sorted({item[4][0] for item in raw})
+            parsed_addresses = [ipaddress.ip_address(item) for item in addresses]
+        except (OSError, ValueError) as exc:
+            raise ConnectorConfigurationError("catalog host resolution failed") from exc
+        if not parsed_addresses or any(not item.is_global for item in parsed_addresses):
+            raise ConnectorConfigurationError(
+                "catalog host must resolve only to public addresses"
+            )
+        return parsed, tuple(str(item) for item in parsed_addresses)
+
+    def _decode(self, response, cancel_probe=None):
+        status = int(response.status)
+        if 300 <= status < 400:
+            raise ConnectorUpstreamError("catalog redirect was rejected")
+        if status != 200:
+            raise ConnectorUpstreamError("catalog upstream request failed")
+        content_type = str(response.headers.get("Content-Type", "")).lower()
+        if "application/json" not in content_type:
+            raise ConnectorContractError("catalog response must be JSON")
+        length = response.headers.get("Content-Length")
+        if length is not None:
+            try:
+                if int(length) > self.max_response_bytes or int(length) < 0:
+                    raise ConnectorContractError("catalog response exceeds size limit")
+            except ValueError as exc:
+                raise ConnectorContractError(
+                    "catalog content length is invalid"
+                ) from exc
+        body = bytearray()
+        try:
+            for chunk in response.stream(amt=64 * 1024, decode_content=True):
+                if cancel_probe and cancel_probe():
+                    raise ConnectorCancelledError()
+                body.extend(chunk)
+                if len(body) > self.max_response_bytes:
+                    raise ConnectorContractError("catalog response exceeds size limit")
+        except (urllib3.exceptions.HTTPError, OSError) as exc:
+            raise ConnectorUpstreamError("catalog response read failed") from exc
+        try:
+            payload = json.loads(body.decode())
+        except (UnicodeDecodeError, json.JSONDecodeError) as exc:
+            raise ConnectorContractError("catalog response is not valid JSON") from exc
+        if not isinstance(payload, Mapping):
+            raise ConnectorContractError("catalog response must be a JSON object")
+        try:
+            Draft202012Validator(CATALOG_RESPONSE_SCHEMA).validate(payload)
+        except ValidationError as exc:
+            raise ConnectorContractError("catalog response shape is invalid") from exc
+        return dict(payload)
+
+    def get_json(self, *, url, allowed_host, credential_ref, cancel_probe=None):
+        if self.secret_resolver is None:
+            raise ConnectorConfigurationError("REST secret resolver is not configured")
+        authorization = self.secret_resolver(credential_ref)
+        if not isinstance(authorization, str) or not authorization.strip():
+            raise ConnectorConfigurationError(
+                "REST authorization secret is unavailable"
+            )
+        parsed, addresses = self._destination(url, allowed_host)
+        hostname = parsed.hostname.lower()
+        target = parsed.path or "/"
+        if parsed.query:
+            target += f"?{parsed.query}"
+        last_error = None
+        for address in addresses:
+            if cancel_probe and cancel_probe():
+                raise ConnectorCancelledError()
+            pool = self.pool_factory(
+                host=address,
+                port=443,
+                server_hostname=hostname,
+                assert_hostname=hostname,
+                cert_reqs="CERT_REQUIRED",
+                ca_certs=requests.certs.where(),
+                timeout=urllib3.Timeout(
+                    connect=self.connect_timeout, read=self.read_timeout
+                ),
+                retries=False,
+                maxsize=1,
+                block=True,
+            )
+            try:
+                response = pool.urlopen(
+                    "GET",
+                    target,
+                    headers={
+                        "Host": hostname,
+                        "Accept": "application/json",
+                        "Authorization": f"Bearer {authorization.strip()}",
+                    },
+                    redirect=False,
+                    retries=False,
+                    assert_same_host=False,
+                    preload_content=False,
+                    decode_content=True,
+                    timeout=urllib3.Timeout(
+                        connect=self.connect_timeout, read=self.read_timeout
+                    ),
+                )
+            except (urllib3.exceptions.HTTPError, OSError) as exc:
+                last_error = exc
+                pool.close()
+                continue
+            try:
+                return self._decode(response, cancel_probe=cancel_probe)
+            finally:
+                response.release_conn()
+                pool.close()
+        raise ConnectorUpstreamError("catalog connection failed") from last_error
+
+
+class RestCatalogConnector(Connector):
+    manifest = ConnectorManifest(
+        connector_id="rest-catalog",
+        version="1.0.0",
+        sdk_version=SDK_VERSION,
+        display_name="Controlled REST Catalog",
+        capabilities=(
+            "discover",
+            "snapshot",
+            "incremental",
+            "cancel",
+            "resume",
+            "evidence",
+        ),
+        config_schema=REST_CONFIG_SCHEMA,
+    )
+
+    def __init__(self, transport=None):
+        self.transport = transport or SafeRestTransport()
+
+    @staticmethod
+    def _check_cancel(request):
+        if request.cancel_probe and request.cancel_probe():
+            raise ConnectorCancelledError()
+
+    def _read(self, request):
+        self._check_cancel(request)
+        cursor = request.cursor or request.checkpoint.get("cursor") or {}
+        if cursor.get("state") == "complete":
+            cursor = {}
+        elif "state" in cursor:
+            raise ConnectorContractError("catalog cursor state is invalid")
+        seen_cursors = set()
+        records = []
+        page_count = 0
+        while True:
+            self._check_cancel(request)
+            marker = json.dumps(cursor, sort_keys=True, separators=(",", ":"))
+            if marker in seen_cursors:
+                raise ConnectorContractError("catalog cursor repeated")
+            seen_cursors.add(marker)
+            query = f"?{urlencode({'cursor': marker})}" if cursor else ""
+            payload = self.transport.get_json(
+                url=request.config["base_url"].rstrip("/") + "/v1/catalog" + query,
+                allowed_host=request.config["allowed_host"],
+                credential_ref=request.config["credential_ref"],
+                cancel_probe=request.cancel_probe,
+            )
+            page_count += 1
+            records.extend(
+                {key: asset[key] for key in sorted(asset)}
+                for asset in payload["assets"]
+            )
+            if len(records) > MAX_RECORDS:
+                raise ConnectorContractError("catalog record limit exceeded")
+            next_cursor = payload.get("next_cursor") or {}
+            if not next_cursor:
+                break
+            if page_count >= MAX_PAGES:
+                raise ConnectorContractError("catalog page limit exceeded")
+            for name, value in next_cursor.items():
+                previous = cursor.get(name)
+                if (
+                    isinstance(previous, (int, float))
+                    and isinstance(value, (int, float))
+                    and value <= previous
+                ):
+                    raise ConnectorContractError("catalog cursor is not monotonic")
+            cursor = next_cursor
+        records = tuple(records)
+        snapshot_hash = hashlib.sha256(
+            json.dumps(records, sort_keys=True, separators=(",", ":")).encode()
+        ).hexdigest()
+        completed_cursor = {
+            "state": "complete",
+            "completion_marker": payload.get("completion_token") or snapshot_hash,
+        }
+        snapshot_summary = {
+            "sha256": snapshot_hash,
+            "record_count": len(records),
+        }
+        checkpoint = {
+            "cursor": completed_cursor,
+            "snapshot_summary": snapshot_summary,
+        }
+        evidence = {
+            "transport": "https-pinned-ip",
+            "redirects": "disabled",
+            "record_count": len(records),
+            "page_count": page_count,
+        }
+        if request.operation in {"incremental", "resume"}:
+            previous = request.checkpoint.get("snapshot_summary") or {}
+            evidence["diff"] = {
+                "changed": previous.get("sha256") != snapshot_hash,
+                "previous_record_count": previous.get("record_count"),
+                "current_record_count": len(records),
+                "details_materialized": False,
+            }
+        return OperationResult(
+            records=records,
+            cursor=completed_cursor,
+            checkpoint=checkpoint,
+            evidence=evidence,
+        )
+
+    def discover(self, request):
+        return self._read(request)
+
+    def snapshot(self, request):
+        return self._read(request)
+
+    def incremental(self, request):
+        return self._read(request)
+
+    def lineage(self, request):
+        return self._read(request)
+
+    def profile(self, request):
+        return self._read(request)
+
+    def cancel(self, request):
+        return OperationResult(
+            status="cancelled", checkpoint=request.checkpoint, cursor=request.cursor
+        )
+
+    def resume(self, request):
+        return self._read(request)
+
+    def evidence(self, request):
+        return OperationResult(
+            evidence={
+                "transport": "https-pinned-ip",
+                "redirects": "disabled",
+                "max_response_bytes": MAX_RESPONSE_BYTES,
+            }
+        )
+
+
+__all__ = [
+    "RestCatalogConnector",
+    "SafeRestTransport",
+    "REST_CONFIG_SCHEMA",
+    "CATALOG_RESPONSE_SCHEMA",
+    "MAX_RESPONSE_BYTES",
+    "MAX_PAGES",
+    "MAX_RECORDS",
+]

+ 78 - 0
app/core/connectors/builtin/sqlserver.py

@@ -0,0 +1,78 @@
+"""SQL Server read-only catalog connector (driver and connection are optional)."""
+
+import importlib.util
+
+from app.core.connectors.builtin.database import (
+    ReadOnlyCatalogConnector,
+)
+from app.core.connectors.sdk import (
+    SDK_VERSION,
+    CompatibilityResult,
+    ConnectorManifest,
+    HealthResult,
+    validate_config,
+)
+
+SQLSERVER_CATALOG_SQL = """
+SELECT s.name AS schema_name, o.name AS asset_name,
+       CASE WHEN o.type = 'V' THEN 'VIEW' ELSE 'TABLE' END AS asset_type,
+       c.name AS column_name, c.column_id AS ordinal_position, t.name AS data_type,
+       CASE WHEN c.is_nullable = 1 THEN 'YES' ELSE 'NO' END AS is_nullable,
+       OBJECT_DEFINITION(c.default_object_id) AS column_default,
+       CAST(ep.value AS nvarchar(4000)) AS column_comment
+FROM sys.objects o
+JOIN sys.schemas s ON s.schema_id = o.schema_id
+JOIN sys.columns c ON c.object_id = o.object_id
+JOIN sys.types t ON t.user_type_id = c.user_type_id
+LEFT JOIN sys.extended_properties ep ON ep.major_id = o.object_id AND ep.minor_id = c.column_id AND ep.name = 'MS_Description'
+WHERE o.type IN ('U', 'V') AND o.is_ms_shipped = 0
+ORDER BY s.name, o.name, c.column_id
+"""
+
+SQLSERVER_CONFIG_SCHEMA = {
+    "type": "object",
+    "additionalProperties": False,
+    "required": ["credential_ref"],
+    "properties": {
+        "credential_ref": {
+            "type": "string",
+            "pattern": r"^(?:env|vault|secret):[A-Za-z0-9][A-Za-z0-9_./:-]{2,255}$",
+        },
+        "allow_insecure_development": {"type": "boolean"},
+    },
+}
+
+
+class SqlServerConnector(ReadOnlyCatalogConnector):
+    catalog_sql = SQLSERVER_CATALOG_SQL
+    manifest = ConnectorManifest(
+        connector_id="sqlserver",
+        version="1.0.0",
+        sdk_version=SDK_VERSION,
+        display_name="Microsoft SQL Server",
+        capabilities=(
+            "discover",
+            "snapshot",
+            "incremental",
+            "cancel",
+            "resume",
+            "evidence",
+        ),
+        config_schema=SQLSERVER_CONFIG_SCHEMA,
+    )
+
+    def health(self, config):
+        validate_config(self.manifest.config_schema, config)
+        available = importlib.util.find_spec("pyodbc") is not None
+        return HealthResult(
+            "available" if available else "degraded",
+            "" if available else "optional driver unavailable",
+        )
+
+    def compatibility(self):
+        available = importlib.util.find_spec("pyodbc") is not None
+        return CompatibilityResult(
+            available,
+            self.manifest.version,
+            detail="" if available else "optional driver unavailable",
+        )

+ 84 - 0
app/core/connectors/errors.py

@@ -0,0 +1,84 @@
+"""Stable, secret-free connector failure taxonomy."""
+
+from __future__ import annotations
+
+
+class ConnectorError(RuntimeError):
+    category = "upstream"
+    retryable = False
+    http_status = 502
+
+    def __init__(self, message="connector operation failed"):
+        super().__init__(message)
+
+
+class ConnectorConfigurationError(ConnectorError):
+    category = "configuration"
+    http_status = 400
+
+
+class ConnectorConflictError(ConnectorError):
+    category = "conflict"
+    http_status = 409
+
+
+class ConnectorAuthenticationError(ConnectorError):
+    category = "authentication"
+    http_status = 401
+
+
+class ConnectorPermissionError(ConnectorError):
+    category = "permission"
+    http_status = 403
+
+
+class ConnectorRateLimitError(ConnectorError):
+    category = "rate_limit"
+    retryable = True
+    http_status = 429
+
+
+class ConnectorTimeoutError(ConnectorError):
+    category = "timeout"
+    retryable = True
+    http_status = 504
+
+
+class ConnectorUpstreamError(ConnectorError):
+    category = "upstream"
+    retryable = True
+
+
+class ConnectorContractError(ConnectorError):
+    category = "contract"
+
+
+class ConnectorCancelledError(ConnectorError):
+    category = "cancelled"
+    http_status = 409
+
+
+class ConnectorDriverUnavailableError(ConnectorError):
+    category = "configuration"
+    http_status = 503
+
+
+def classify_error(error):
+    """Map driver/transport failures without exposing their text."""
+    if isinstance(error, ConnectorError):
+        return error
+    name = type(error).__name__.lower()
+    if "driver" in name and "unavailable" in name:
+        return ConnectorDriverUnavailableError()
+    if "auth" in name or "credential" in name:
+        return ConnectorAuthenticationError()
+    if "permission" in name or "access" in name:
+        return ConnectorPermissionError()
+    if "timeout" in name:
+        return ConnectorTimeoutError()
+    if "rate" in name or "thrott" in name:
+        return ConnectorRateLimitError()
+    return ConnectorUpstreamError()
+
+
+__all__ = [name for name in globals() if name.startswith("Connector")]

+ 379 - 0
app/core/connectors/identity.py

@@ -0,0 +1,379 @@
+"""Short-lived, one-time-issued connector machine credentials."""
+
+from __future__ import annotations
+
+import hashlib
+import hmac
+import secrets
+from collections.abc import Mapping
+
+from sqlalchemy import text
+
+from app.core.common.identifiers import new_governance_uid
+from app.core.connectors.errors import (
+    ConnectorAuthenticationError,
+    ConnectorConfigurationError,
+    ConnectorPermissionError,
+)
+
+ALLOWED_OPERATIONS = frozenset(
+    {
+        "discover",
+        "snapshot",
+        "incremental",
+        "lineage",
+        "profile",
+        "cancel",
+        "resume",
+        "evidence",
+    }
+)
+ALLOWED_SCOPE_KEYS = frozenset(
+    {"include_schemas", "exclude_schemas", "include_tables", "exclude_tables"}
+)
+
+
+def validate_machine_scope(scope):
+    if not isinstance(scope, Mapping) or set(scope) - ALLOWED_SCOPE_KEYS:
+        raise ConnectorConfigurationError("machine identity scope is invalid")
+    normalized = {}
+    for key, values in scope.items():
+        if not isinstance(values, list) or any(
+            not isinstance(value, str) or not value.strip() for value in values
+        ):
+            raise ConnectorConfigurationError(
+                "machine identity scope values are invalid"
+            )
+        normalized[key] = tuple(value.strip() for value in values)
+    return normalized
+
+
+def _token_hash(token):
+    return hashlib.sha256(str(token).encode()).hexdigest()
+
+
+class ConnectorIdentityRepository:
+    def __init__(self, session):
+        self.session = session
+
+    def create_principal(
+        self,
+        *,
+        connector_id,
+        connector_version,
+        source_uid,
+        business_domain_uid,
+        environment,
+        operations,
+        scopes,
+        actor_uid,
+        source_binding_uid=None,
+        source_binding_version=None,
+    ):
+        operations = tuple(sorted(set(operations)))
+        if not operations or set(operations) - ALLOWED_OPERATIONS:
+            raise ConnectorConfigurationError("machine identity operations are invalid")
+        scopes = validate_machine_scope(scopes)
+        if environment not in {"development", "staging", "production"}:
+            raise ConnectorConfigurationError("machine identity environment is invalid")
+        if not source_binding_uid or source_binding_version is None:
+            raise ConnectorConfigurationError(
+                "enterprise connector principal requires an approved source binding"
+            )
+        uid = new_governance_uid()
+        self.session.execute(
+            text("""
+            INSERT INTO public.connector_principals
+              (uid, connector_id, connector_version, source_uid, business_domain_uid, environment,
+               allowed_operations, allowed_scopes, status, created_by,
+               source_binding_uid,source_binding_version)
+            VALUES (CAST(:uid AS uuid), :connector, :version, CAST(:source AS uuid), CAST(:domain AS uuid), :environment,
+                    CAST(:operations AS text[]), CAST(:scopes AS jsonb), 'active', CAST(:actor AS uuid),
+                    CAST(:binding AS uuid),:binding_version)
+        """),
+            {
+                "uid": uid,
+                "connector": connector_id,
+                "version": connector_version,
+                "source": source_uid,
+                "domain": business_domain_uid,
+                "environment": environment,
+                "operations": list(operations),
+                "scopes": __import__("json").dumps(scopes),
+                "actor": actor_uid,
+                "binding": source_binding_uid,
+                "binding_version": source_binding_version,
+            },
+        )
+        self._audit(uid, None, "principal_created", actor_uid, True)
+        self.session.commit()
+        return uid
+
+    def issue(self, principal_uid, *, ttl_seconds, actor_uid, rotated_from=None):
+        ttl = int(ttl_seconds)
+        if ttl < 60 or ttl > 900:
+            raise ConnectorConfigurationError(
+                "credential TTL must be between 60 and 900 seconds"
+            )
+        self.session.execute(
+            text("SELECT pg_advisory_xact_lock(hashtext(:key))"),
+            {"key": f"connector-principal:{principal_uid}"},
+        )
+        principal = self.session.execute(
+            text("""
+                SELECT p.status
+                  FROM public.connector_principals p
+                  JOIN public.connector_source_bindings b
+                    ON b.uid=p.source_binding_uid
+                   AND b.binding_version=p.source_binding_version
+                   AND b.status='approved'
+                   AND b.connector_id=p.connector_id
+                   AND b.connector_version=p.connector_version
+                   AND b.source_uid=p.source_uid
+                   AND b.business_domain_uid=p.business_domain_uid
+                   AND b.environment=p.environment
+                 WHERE p.uid=CAST(:uid AS uuid)
+                 FOR UPDATE OF p
+            """),
+            {"uid": principal_uid},
+        ).scalar_one_or_none()
+        if principal != "active":
+            raise ConnectorAuthenticationError(
+                "connector principal or approved binding is not active"
+            )
+        token = "dopc_" + secrets.token_urlsafe(32)
+        credential_uid = new_governance_uid()
+        self.session.execute(
+            text("""
+            INSERT INTO public.connector_machine_credentials
+              (uid, principal_uid, token_hash, status, expires_at, rotated_from_uid, issued_by)
+            VALUES (CAST(:uid AS uuid), CAST(:principal AS uuid), :hash, 'active',
+                    CURRENT_TIMESTAMP + (:ttl * INTERVAL '1 second'), CAST(:rotated AS uuid), CAST(:actor AS uuid))
+        """),
+            {
+                "uid": credential_uid,
+                "principal": principal_uid,
+                "hash": _token_hash(token),
+                "ttl": ttl,
+                "rotated": rotated_from,
+                "actor": actor_uid,
+            },
+        )
+        self._audit(principal_uid, credential_uid, "credential_issued", actor_uid, True)
+        self.session.commit()
+        return {
+            "credential_uid": credential_uid,
+            "credential": token,
+            "expires_in": ttl,
+            "returned_once": True,
+        }
+
+    def authenticate(
+        self,
+        token,
+        *,
+        connector_id,
+        connector_version,
+        source_uid,
+        business_domain_uid,
+        environment,
+        operation,
+        scope,
+    ):
+        scope = validate_machine_scope(scope)
+        digest = _token_hash(token)
+        expired = (
+            self.session.execute(
+                text("""
+            SELECT c.uid::text, c.principal_uid::text
+            FROM public.connector_machine_credentials c
+            WHERE c.token_hash=:hash AND c.status='active' AND c.expires_at<=CURRENT_TIMESTAMP
+            FOR UPDATE
+        """),
+                {"hash": digest},
+            )
+            .mappings()
+            .one_or_none()
+        )
+        if expired is not None:
+            self.session.execute(
+                text(
+                    "UPDATE public.connector_machine_credentials SET status='expired',revoked_at=CURRENT_TIMESTAMP WHERE uid=CAST(:uid AS uuid)"
+                ),
+                {"uid": expired["uid"]},
+            )
+            self._audit(
+                expired["principal_uid"],
+                expired["uid"],
+                "credential_expired_rejected",
+                None,
+                False,
+            )
+            self.session.commit()
+            raise ConnectorAuthenticationError(
+                "machine credential is invalid or expired"
+            )
+        row = (
+            self.session.execute(
+                text("""
+            SELECT c.uid::text, c.principal_uid::text, c.use_count, p.connector_id,
+                   p.connector_version, p.source_uid::text, p.business_domain_uid::text,
+                   p.environment, p.allowed_operations, p.allowed_scopes,
+                   p.source_binding_uid::text,p.source_binding_version,
+                   b.approved_base_url,b.allowed_host,b.credential_ref,b.approved_config
+              FROM public.connector_machine_credentials c
+              JOIN public.connector_principals p ON c.principal_uid=p.uid
+              JOIN public.connector_source_bindings b
+                ON b.uid=p.source_binding_uid
+               AND b.binding_version=p.source_binding_version
+               AND b.status='approved'
+               AND b.connector_id=p.connector_id
+               AND b.connector_version=p.connector_version
+               AND b.source_uid=p.source_uid
+               AND b.business_domain_uid=p.business_domain_uid
+               AND b.environment=p.environment
+             WHERE c.token_hash=:hash AND c.status='active'
+               AND c.expires_at>CURRENT_TIMESTAMP AND p.status='active'
+             FOR UPDATE OF c
+        """),
+                {"hash": digest},
+            )
+            .mappings()
+            .one_or_none()
+        )
+        if row is None:
+            self.session.rollback()
+            raise ConnectorAuthenticationError(
+                "machine credential is invalid or expired"
+            )
+        if int(row["use_count"]) > 0:
+            self.session.execute(
+                text(
+                    "UPDATE public.connector_machine_credentials SET status='replayed', revoked_at=CURRENT_TIMESTAMP WHERE uid=CAST(:uid AS uuid)"
+                ),
+                {"uid": row["uid"]},
+            )
+            self._audit(
+                row["principal_uid"],
+                row["uid"],
+                "credential_replay_rejected",
+                None,
+                False,
+            )
+            self.session.commit()
+            raise ConnectorAuthenticationError("machine credential replay was rejected")
+        if (
+            not hmac.compare_digest(row["connector_id"], connector_id)
+            or not hmac.compare_digest(row["connector_version"], connector_version)
+            or row["source_uid"] != source_uid
+            or row["business_domain_uid"] != business_domain_uid
+            or row["environment"] != environment
+            or operation not in row["allowed_operations"]
+        ):
+            self._audit(
+                row["principal_uid"],
+                row["uid"],
+                "credential_scope_rejected",
+                None,
+                False,
+            )
+            self.session.commit()
+            raise ConnectorPermissionError("machine credential scope was rejected")
+        allowed_scope = validate_machine_scope(row["allowed_scopes"] or {})
+        if any(
+            set(scope.get(key, ())) - set(allowed_scope.get(key, ())) for key in scope
+        ):
+            self._audit(
+                row["principal_uid"],
+                row["uid"],
+                "credential_scope_rejected",
+                None,
+                False,
+            )
+            self.session.commit()
+            raise ConnectorPermissionError(
+                "machine credential resource scope was rejected"
+            )
+        identity = dict(row)
+        config = dict(identity.pop("approved_config", {}) or {})
+        if identity["connector_id"] == "rest-catalog":
+            if not identity.get("source_binding_uid"):
+                raise ConnectorPermissionError(
+                    "machine credential has no approved source binding"
+                )
+            config = {
+                "base_url": identity.pop("approved_base_url"),
+                "allowed_host": identity.pop("allowed_host"),
+                "credential_ref": identity.pop("credential_ref"),
+            }
+        else:
+            identity.pop("approved_base_url", None)
+            identity.pop("allowed_host", None)
+            identity.pop("credential_ref", None)
+        identity["approved_config"] = config
+        self.session.execute(
+            text("""
+                UPDATE public.connector_machine_credentials
+                   SET first_used_at=CURRENT_TIMESTAMP,use_count=use_count+1
+                 WHERE uid=CAST(:uid AS uuid) AND use_count=0 AND status='active'
+            """),
+            {"uid": row["uid"]},
+        )
+        self._audit(
+            row["principal_uid"], row["uid"], "credential_authenticated", None, True
+        )
+        self.session.commit()
+        return identity
+
+    def revoke(self, credential_uid, actor_uid):
+        principal_uid = self.session.execute(
+            text(
+                "UPDATE public.connector_machine_credentials SET status='revoked', revoked_at=CURRENT_TIMESTAMP WHERE uid=CAST(:uid AS uuid) AND status='active' RETURNING principal_uid::text"
+            ),
+            {"uid": credential_uid},
+        ).scalar_one_or_none()
+        if principal_uid:
+            self._audit(
+                principal_uid, credential_uid, "credential_revoked", actor_uid, True
+            )
+        self.session.commit()
+        return principal_uid is not None
+
+    def rotate(self, credential_uid, *, ttl_seconds, actor_uid):
+        row = self.session.execute(
+            text(
+                "UPDATE public.connector_machine_credentials SET status='rotated', revoked_at=CURRENT_TIMESTAMP WHERE uid=CAST(:uid AS uuid) AND status='active' RETURNING principal_uid::text"
+            ),
+            {"uid": credential_uid},
+        ).scalar_one_or_none()
+        if row is None:
+            raise ConnectorAuthenticationError("credential cannot be rotated")
+        self._audit(row, credential_uid, "credential_rotated", actor_uid, True)
+        return self.issue(
+            row,
+            ttl_seconds=ttl_seconds,
+            actor_uid=actor_uid,
+            rotated_from=credential_uid,
+        )
+
+    def _audit(self, principal_uid, credential_uid, event_type, actor_uid, success):
+        self.session.execute(
+            text("""
+            INSERT INTO public.connector_audit_events
+              (uid, principal_uid, credential_uid, event_type, actor_uid, success, safe_detail)
+            VALUES (CAST(:uid AS uuid), CAST(:principal AS uuid), CAST(:credential AS uuid), :event,
+                    CAST(:actor AS uuid), :success, :detail)
+        """),
+            {
+                "uid": new_governance_uid(),
+                "principal": principal_uid,
+                "credential": credential_uid,
+                "event": event_type,
+                "actor": actor_uid,
+                "success": success,
+                "detail": event_type.replace("_", " "),
+            },
+        )
+
+
+__all__ = ["ConnectorIdentityRepository", "ALLOWED_OPERATIONS"]

+ 52 - 0
app/core/connectors/registry.py

@@ -0,0 +1,52 @@
+"""Thread-safe, deny-by-default connector registry."""
+
+from __future__ import annotations
+
+import threading
+
+from app.core.connectors.errors import ConnectorConfigurationError
+from app.core.connectors.sdk import Connector, validate_config
+
+
+class ConnectorRegistry:
+    def __init__(self):
+        self._lock = threading.RLock()
+        self._connectors = {}
+
+    def register(self, connector: Connector):
+        manifest = connector.manifest
+        key = (manifest.connector_id, manifest.version)
+        with self._lock:
+            if key in self._connectors:
+                raise ConnectorConfigurationError(
+                    "connector version is already registered"
+                )
+            self._connectors[key] = connector
+        return connector
+
+    def resolve(self, connector_id, version, capability=None):
+        with self._lock:
+            connector = self._connectors.get((str(connector_id), str(version)))
+        if connector is None:
+            raise ConnectorConfigurationError("connector or version is not registered")
+        if capability and capability not in connector.manifest.capabilities:
+            raise ConnectorConfigurationError("connector capability is not declared")
+        return connector
+
+    def validate(self, connector_id, version, config):
+        connector = self.resolve(connector_id, version)
+        return validate_config(connector.manifest.config_schema, config)
+
+    def manifests(self):
+        with self._lock:
+            items = tuple(self._connectors.values())
+        return [
+            item.manifest.public_dict()
+            for item in sorted(
+                items,
+                key=lambda item: (item.manifest.connector_id, item.manifest.version),
+            )
+        ]
+
+
+__all__ = ["ConnectorRegistry"]

+ 513 - 0
app/core/connectors/repository.py

@@ -0,0 +1,513 @@
+"""PostgreSQL persistence for connector registrations, runs and graph edges."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+
+from sqlalchemy import text
+
+from app.core.common.identifiers import new_governance_uid
+from app.core.connectors.errors import (
+    ConnectorConfigurationError,
+    ConnectorConflictError,
+)
+from app.core.connectors.store import UpdateOutcome
+
+
+class ConnectorRepository:
+    def __init__(self, session, actor_uid=None, *, run_access=None, principal_uid=None):
+        self.session = session
+        self.actor_uid = actor_uid
+        self.run_access = run_access
+        self.principal_uid = principal_uid
+
+    @staticmethod
+    def _control_edges(run):
+        edges = []
+        if run.get("business_domain_uid"):
+            edges.append(
+                (
+                    "business_domain",
+                    run["business_domain_uid"],
+                    "owns",
+                    "source",
+                    run["source_uid"],
+                )
+            )
+        edges.append(("source", run["source_uid"], "executed", "run", run["uid"]))
+        if run.get("process_key"):
+            edges.append(
+                ("process", run["process_key"], "executed_as", "run", run["uid"])
+            )
+        return edges
+
+    def _upsert_graph_edges(self, run, status, edges):
+        for from_type, from_key, relation, to_type, to_key in edges:
+            self.session.execute(
+                text("""
+                INSERT INTO public.connector_graph_edges
+                  (uid,source_uid,from_type,from_key,relation_type,to_type,to_key,business_domain_uid,run_uid,run_status,evidence)
+                VALUES(CAST(:uid AS uuid),CAST(:source AS uuid),:from_type,:from_key,:relation,:to_type,:to_key,
+                       CAST(:domain AS uuid),CAST(:run AS uuid),:status,'{}'::jsonb)
+                ON CONFLICT(source_uid,from_type,from_key,relation_type,to_type,to_key)
+                DO UPDATE SET run_uid=EXCLUDED.run_uid,run_status=EXCLUDED.run_status,created_at=CURRENT_TIMESTAMP
+            """),
+                {
+                    "uid": new_governance_uid(),
+                    "source": run["source_uid"],
+                    "from_type": from_type,
+                    "from_key": str(from_key),
+                    "relation": relation,
+                    "to_type": to_type,
+                    "to_key": str(to_key),
+                    "domain": run.get("business_domain_uid"),
+                    "run": run["uid"],
+                    "status": status,
+                },
+            )
+
+    def register_manifest(self, manifest, actor_uid):
+        self.session.execute(
+            text("SELECT pg_advisory_xact_lock(hashtext(:key))"),
+            {"key": f"connector-manifest:{manifest.connector_id}"},
+        )
+        self.session.execute(
+            text("""
+            INSERT INTO public.connector_manifests
+              (uid, connector_id, connector_version, sdk_version, display_name, capabilities, config_schema, status, created_by)
+            VALUES (CAST(:uid AS uuid), :connector_id, :version, :sdk_version, :display_name,
+                    CAST(:capabilities AS jsonb), CAST(:schema AS jsonb), 'active', CAST(:actor AS uuid))
+            ON CONFLICT (connector_id, connector_version) DO UPDATE SET
+              sdk_version=EXCLUDED.sdk_version, display_name=EXCLUDED.display_name,
+              capabilities=EXCLUDED.capabilities, config_schema=EXCLUDED.config_schema,
+              status='active', updated_at=CURRENT_TIMESTAMP
+        """),
+            {
+                "uid": new_governance_uid(),
+                "connector_id": manifest.connector_id,
+                "version": manifest.version,
+                "sdk_version": manifest.sdk_version,
+                "display_name": manifest.display_name,
+                "capabilities": json.dumps(list(manifest.capabilities)),
+                "schema": json.dumps(manifest.config_schema),
+                "actor": actor_uid,
+            },
+        )
+
+    def claim(self, key, record):
+        uid = new_governance_uid()
+        request_hash = record.get("request_hash") or key
+        row = self.session.execute(
+            text("""
+            INSERT INTO public.connector_runs
+              (uid, idempotency_key, connector_id, connector_version, source_uid, operation, status,
+               attempt_count, checkpoint, cursor, dry_run, actor_uid, principal_uid,
+               business_domain_uid, environment, process_key, safe_config, scope,
+               request_hash,client_hint_hash,source_binding_uid,source_binding_version,
+               cancel_requested)
+            VALUES (CAST(:uid AS uuid), :key, :connector_id, :version, CAST(:source AS uuid), :operation,
+                    'running', 0, CAST(:checkpoint AS jsonb), CAST(:cursor AS jsonb), :dry_run, CAST(:actor AS uuid),
+                    CAST(:principal AS uuid), CAST(:domain AS uuid), :environment, :process_key,
+                    CAST(:config AS jsonb), CAST(:scope AS jsonb),:request_hash,:client_hint_hash,
+                    CAST(:binding AS uuid),:binding_version,FALSE)
+            ON CONFLICT DO NOTHING
+            RETURNING uid::text
+        """),
+            {
+                "uid": uid,
+                "key": key,
+                "connector_id": record["connector_id"],
+                "version": record["connector_version"],
+                "source": record["source_uid"],
+                "operation": record["operation"],
+                "checkpoint": json.dumps(record.get("checkpoint", {})),
+                "cursor": json.dumps(record.get("cursor", {})),
+                "dry_run": bool(record.get("dry_run", False)),
+                "actor": record.get("actor_uid") or self.actor_uid,
+                "principal": record.get("principal_uid"),
+                "domain": record.get("business_domain_uid"),
+                "environment": record.get("environment"),
+                "process_key": record.get("process_key"),
+                "config": json.dumps(record.get("config", {}), sort_keys=True),
+                "scope": json.dumps(record.get("scope", {}), sort_keys=True),
+                "request_hash": request_hash,
+                "client_hint_hash": record.get("client_hint_hash"),
+                "binding": record.get("source_binding_uid"),
+                "binding_version": record.get("source_binding_version"),
+            },
+        ).scalar_one_or_none()
+        if row:
+            claimed = {**record, "uid": row, "status": "running"}
+            self._upsert_graph_edges(claimed, "running", self._control_edges(claimed))
+            self.session.commit()
+            return claimed, True
+        self.session.rollback()
+        existing = (
+            self.session.execute(
+                text("""
+                SELECT idempotency_key,request_hash
+                  FROM public.connector_runs
+                 WHERE idempotency_key=:key
+                    OR (:client_hint_hash IS NOT NULL AND client_hint_hash=:client_hint_hash)
+                 LIMIT 1
+                """),
+                {"key": key, "client_hint_hash": record.get("client_hint_hash")},
+            )
+            .mappings()
+            .one_or_none()
+        )
+        if existing is None or existing["request_hash"] != request_hash:
+            self.session.rollback()
+            raise ConnectorConflictError("idempotency request binding conflicts")
+        return self.get(existing["idempotency_key"]), False
+
+    def update(self, key, **values):
+        result = values.pop("result", None)
+        allowed = {
+            "status",
+            "attempt_count",
+            "checkpoint",
+            "cursor",
+            "error_category",
+            "error_code",
+            "resumed_from",
+            "lease_token",
+            "expected_attempt",
+            "cancel_requested",
+        }
+        if set(values) - allowed:
+            raise ConnectorConfigurationError("connector run update is invalid")
+        if "attempt_count" in values and not 1 <= int(values["attempt_count"]) <= 5:
+            raise ConnectorConfigurationError(
+                "connector run attempt must be between 1 and 5"
+            )
+        expected_attempt = values.pop("expected_attempt", None)
+        lease_token = values.pop("lease_token", None)
+        assignments, parameters = [], {"key": key}
+        for name, value in values.items():
+            if name in {"checkpoint", "cursor"}:
+                assignments.append(f"{name}=CAST(:{name} AS jsonb)")
+                parameters[name] = json.dumps(value)
+            elif name == "resumed_from":
+                assignments.append("resumed_from_run_uid=CAST(:resumed_from AS uuid)")
+                parameters[name] = value
+            else:
+                assignments.append(f"{name}=:{name}")
+                parameters[name] = value
+        assignments.append("updated_at=CURRENT_TIMESTAMP")
+        requested_status = values.get("status")
+        terminal = requested_status in {"succeeded", "dry_run", "failed", "cancelled"}
+        guarded = terminal or requested_status in {"running", "resumable"}
+        if requested_status == "resumable":
+            status_clause = " AND status IN ('failed','cancelled')"
+        elif requested_status == "cancelled":
+            status_clause = " AND status='running'"
+        elif requested_status == "running" and "attempt_count" in values:
+            status_clause = (
+                " AND status IN ('running','resumable','failed')"
+                " AND attempt_count < :attempt_count"
+                " AND cancel_requested=FALSE"
+            )
+            assignments.append("attempt_lease_token=CAST(:lease_token AS uuid)")
+            parameters["lease_token"] = lease_token
+        elif requested_status in {"succeeded", "dry_run", "failed"}:
+            status_clause = (
+                " AND status='running' AND cancel_requested=FALSE"
+                " AND attempt_count=:expected_attempt"
+                " AND attempt_lease_token=CAST(:lease_token AS uuid)"
+            )
+            parameters["expected_attempt"] = expected_attempt
+            parameters["lease_token"] = lease_token
+        elif guarded:
+            status_clause = " AND status IN ('running','resumable','failed')"
+        else:
+            status_clause = ""
+        if self.run_access == "human":
+            status_clause += " AND principal_uid IS NULL AND dry_run=TRUE"
+        elif self.run_access == "machine":
+            status_clause += " AND principal_uid=CAST(:access_principal AS uuid)"
+            parameters["access_principal"] = self.principal_uid
+        update_result = self.session.execute(
+            text(
+                f"UPDATE public.connector_runs SET {', '.join(assignments)} WHERE idempotency_key=:key{status_clause}"
+            ),
+            parameters,
+        )
+        if guarded and update_result.rowcount != 1:
+            self.session.rollback()
+            return UpdateOutcome(self.get(key) or {}, False)
+        run_uid = self.session.execute(
+            text(
+                "SELECT uid::text FROM public.connector_runs WHERE idempotency_key=:key"
+            ),
+            {"key": key},
+        ).scalar_one()
+        if "attempt_count" in values:
+            self.session.execute(
+                text("""
+                INSERT INTO public.connector_run_attempts(uid,run_uid,attempt_number,status,lease_token)
+                VALUES(CAST(:uid AS uuid),CAST(:run AS uuid),:attempt,'running',CAST(:lease AS uuid))
+                ON CONFLICT(run_uid,attempt_number) DO NOTHING
+            """),
+                {
+                    "uid": new_governance_uid(),
+                    "run": run_uid,
+                    "attempt": values["attempt_count"],
+                    "lease": lease_token,
+                },
+            )
+        if values.get("status") in {"succeeded", "dry_run", "failed", "cancelled"}:
+            self.session.execute(
+                text("""
+                    UPDATE public.connector_run_attempts
+                       SET status=:status, finished_at=CURRENT_TIMESTAMP,
+                           error_category=:error_category
+                     WHERE run_uid=CAST(:run AS uuid)
+                       AND attempt_number=COALESCE(
+                         :attempt,
+                         (SELECT attempt_count FROM public.connector_runs WHERE uid=CAST(:run AS uuid))
+                       )
+                       AND lease_token=COALESCE(
+                         CAST(:lease AS uuid),
+                         (SELECT attempt_lease_token FROM public.connector_runs WHERE uid=CAST(:run AS uuid))
+                       )
+                """),
+                {
+                    "run": run_uid,
+                    "status": values["status"],
+                    "error_category": values.get("error_category"),
+                    "attempt": expected_attempt,
+                    "lease": lease_token,
+                },
+            )
+        if result is not None:
+            payload = dict(result.evidence)
+            encoded = json.dumps(payload, sort_keys=True, default=str).encode()
+            self.session.execute(
+                text("""
+                INSERT INTO public.connector_evidence
+                  (uid,run_uid,evidence_type,payload,content_hash,byte_size,redacted)
+                VALUES (CAST(:uid AS uuid),CAST(:run AS uuid),'operation',CAST(:payload AS jsonb),:hash,:size,TRUE)
+            """),
+                {
+                    "uid": new_governance_uid(),
+                    "run": run_uid,
+                    "payload": encoded.decode(),
+                    "hash": hashlib.sha256(encoded).hexdigest(),
+                    "size": len(encoded),
+                },
+            )
+            checkpoint_payload = json.dumps(
+                dict(result.checkpoint), sort_keys=True, default=str
+            )
+            cursor_payload = json.dumps(
+                dict(result.cursor), sort_keys=True, default=str
+            )
+            checkpoint_hash = hashlib.sha256(
+                (cursor_payload + checkpoint_payload).encode()
+            ).hexdigest()
+            sequence = self.session.execute(
+                text(
+                    "SELECT COALESCE(MAX(sequence_number),0)+1 FROM public.connector_checkpoints WHERE run_uid=CAST(:run AS uuid)"
+                ),
+                {"run": run_uid},
+            ).scalar_one()
+            self.session.execute(
+                text("""
+                INSERT INTO public.connector_checkpoints(uid,run_uid,sequence_number,cursor,checkpoint,content_hash)
+                VALUES(CAST(:uid AS uuid),CAST(:run AS uuid),:sequence,CAST(:cursor AS jsonb),CAST(:checkpoint AS jsonb),:hash)
+            """),
+                {
+                    "uid": new_governance_uid(),
+                    "run": run_uid,
+                    "sequence": sequence,
+                    "cursor": cursor_payload,
+                    "checkpoint": checkpoint_payload,
+                    "hash": checkpoint_hash,
+                },
+            )
+            run = self.get(key)
+            edge_values = []
+            for record in result.records:
+                asset_key = record.get("asset_key") or record.get("key")
+                if not asset_key:
+                    continue
+                edge_values.extend(
+                    [
+                        (
+                            "source",
+                            run["source_uid"],
+                            "contains",
+                            "asset",
+                            str(asset_key),
+                        ),
+                        ("run", run_uid, "observed", "asset", str(asset_key)),
+                    ]
+                )
+            self._upsert_graph_edges(
+                run, values.get("status", "succeeded"), edge_values
+            )
+        if requested_status:
+            self.session.execute(
+                text("""
+                UPDATE public.connector_graph_edges
+                   SET run_status=:status,created_at=CURRENT_TIMESTAMP
+                 WHERE run_uid=CAST(:run AS uuid)
+            """),
+                {"status": requested_status, "run": run_uid},
+            )
+        self.session.commit()
+        return UpdateOutcome(self.get(key) or {}, True)
+
+    def get(self, key):
+        row = (
+            self.session.execute(
+                text("""
+            SELECT uid::text, idempotency_key, connector_id, connector_version, source_uid::text,
+                   operation, status, attempt_count, checkpoint, cursor, error_category, error_code,
+                   resumed_from_run_uid::text, dry_run, created_at, updated_at
+                   ,principal_uid::text,business_domain_uid::text,environment,process_key,safe_config AS config,scope,
+                   request_hash,source_binding_uid::text,source_binding_version,
+                   attempt_lease_token::text,cancel_requested
+            FROM public.connector_runs
+            WHERE idempotency_key=:key OR client_hint_hash=:hint
+            ORDER BY (idempotency_key=:key) DESC LIMIT 1
+        """),
+                {
+                    "key": key,
+                    "hint": hashlib.sha256(str(key).encode()).hexdigest(),
+                },
+            )
+            .mappings()
+            .one_or_none()
+        )
+        result = dict(row) if row else None
+        if result and self.run_access == "human" and (
+            result.get("principal_uid") is not None or not result.get("dry_run")
+        ):
+            return None
+        if result and self.run_access == "machine" and result.get("principal_uid") != self.principal_uid:
+            return None
+        return result
+
+    def list(self, limit=100):
+        rows = (
+            self.session.execute(
+                text("""
+            SELECT uid::text, idempotency_key, connector_id, connector_version, source_uid::text,
+                   operation, status, attempt_count, checkpoint, cursor, error_category, dry_run, created_at, updated_at
+                   ,principal_uid::text,business_domain_uid::text,environment,process_key
+            FROM public.connector_runs ORDER BY created_at DESC LIMIT :limit
+        """),
+                {"limit": min(max(int(limit), 1), 200)},
+            )
+            .mappings()
+            .all()
+        )
+        result = []
+        for row in rows:
+            item = dict(row)
+            for name in ("checkpoint", "cursor"):
+                value = item.pop(name) or {}
+                encoded = json.dumps(value, sort_keys=True, default=str).encode()
+                item[f"{name}_summary"] = {
+                    "sha256": hashlib.sha256(encoded).hexdigest(),
+                    "item_count": len(value) if isinstance(value, (dict, list)) else 0,
+                    "byte_size": len(encoded),
+                }
+            result.append(item)
+        return result
+
+    def acquire_rate_limit(self, key, limit=30):
+        row = self.session.execute(
+            text("""
+            INSERT INTO public.connector_rate_limits(limit_key,window_started_at,request_count)
+            VALUES(:key,date_trunc('minute',CURRENT_TIMESTAMP),1)
+            ON CONFLICT(limit_key,window_started_at) DO UPDATE
+              SET request_count=public.connector_rate_limits.request_count+1,updated_at=CURRENT_TIMESTAMP
+              WHERE public.connector_rate_limits.request_count < :limit
+            RETURNING request_count
+        """),
+            {"key": key, "limit": min(int(limit), 30)},
+        ).scalar_one_or_none()
+        self.session.commit()
+        if row is None:
+            from app.core.connectors.errors import ConnectorRateLimitError
+
+            raise ConnectorRateLimitError("connector runtime rate limit exceeded")
+
+    def cancel(self, key):
+        outcome = self.update(
+            key,
+            status="cancelled",
+            cancel_requested=True,
+        )
+        if not outcome.acquired:
+            raise ConnectorConfigurationError("connector run cannot be cancelled")
+        return outcome.record
+
+    def is_cancel_requested(self, key):
+        value = self.session.execute(
+            text(
+                "SELECT cancel_requested FROM public.connector_runs WHERE idempotency_key=:key"
+            ),
+            {"key": key},
+        ).scalar_one_or_none()
+        return bool(value)
+
+    def graph(
+        self,
+        source_uid=None,
+        business_domain_uid=None,
+        process_key=None,
+        run_uid=None,
+        limit=500,
+    ):
+        filters = []
+        parameters = {"limit": min(max(int(limit), 1), 1000)}
+        if source_uid:
+            filters.append("source_uid=CAST(:source AS uuid)")
+            parameters["source"] = source_uid
+        if business_domain_uid:
+            filters.append("business_domain_uid=CAST(:domain AS uuid)")
+            parameters["domain"] = business_domain_uid
+        if process_key:
+            filters.append("(from_key=:process OR to_key=:process)")
+            parameters["process"] = process_key
+        if run_uid:
+            filters.append("run_uid=CAST(:run AS uuid)")
+            parameters["run"] = run_uid
+        clauses = "WHERE " + " AND ".join(filters) if filters else ""
+        rows = (
+            self.session.execute(
+                text(f"""
+            SELECT uid::text, source_uid::text, from_type, from_key, relation_type, to_type, to_key,
+                   business_domain_uid::text, run_uid::text, run_status, evidence, created_at
+            FROM public.connector_graph_edges {clauses}
+            ORDER BY created_at DESC LIMIT :limit
+        """),
+                parameters,
+            )
+            .mappings()
+            .all()
+        )
+        edges = [dict(row) for row in rows]
+        node_map = {}
+        for edge in edges:
+            node_map[(edge["from_type"], edge["from_key"])] = {
+                "type": edge["from_type"],
+                "key": edge["from_key"],
+            }
+            node_map[(edge["to_type"], edge["to_key"])] = {
+                "type": edge["to_type"],
+                "key": edge["to_key"],
+            }
+        return {
+            "nodes": list(node_map.values()),
+            "edges": edges,
+            "summary": {"node_count": len(node_map), "edge_count": len(edges)},
+        }
+
+
+__all__ = ["ConnectorRepository"]

+ 573 - 0
app/core/connectors/runtime.py

@@ -0,0 +1,573 @@
+"""Connector execution controls: idempotency, bounded retry, cancellation and evidence."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import re
+import threading
+import time
+import uuid
+from collections import deque
+from dataclasses import replace
+
+from app.core.connectors.errors import (
+    ConnectorCancelledError,
+    ConnectorConfigurationError,
+    ConnectorConflictError,
+    ConnectorRateLimitError,
+    classify_error,
+)
+from app.core.connectors.sdk import OperationResult
+from app.core.connectors.store import UpdateOutcome
+
+MAX_EVIDENCE_BYTES = 32768
+MAX_CURSOR_BYTES = 32768
+MAX_CHECKPOINT_BYTES = 262144
+MAX_RECORDS_BYTES = 1048576
+MAX_RECORDS = 10000
+MAX_TOTAL_ATTEMPTS = 5
+SENSITIVE_KEYS = {
+    "password",
+    "passwd",
+    "secret",
+    "token",
+    "api_key",
+    "authorization",
+    "credential",
+}
+
+
+def deterministic_idempotency_key(connector_id, version, request):
+    payload = {
+        "connector_id": connector_id,
+        "version": version,
+        "source_uid": request.source_uid,
+        "principal_uid": request.principal_uid,
+        "business_domain_uid": request.business_domain_uid,
+        "environment": request.environment,
+        "process_key": request.process_key,
+        "source_binding_uid": request.source_binding_uid,
+        "source_binding_version": request.source_binding_version,
+        "operation": request.operation,
+        "config": request.config,
+        "scope": request.scope,
+        "cursor": request.cursor,
+        "checkpoint": request.checkpoint,
+        "dry_run": request.dry_run,
+        "client_idempotency_hint": request.idempotency_key,
+    }
+    encoded = json.dumps(
+        payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True
+    ).encode()
+    return hashlib.sha256(encoded).hexdigest()
+
+
+def redact_evidence(
+    value, *, max_bytes=MAX_EVIDENCE_BYTES, summarize=True, truncate_lists=True
+):
+    def visit(item):
+        if isinstance(item, dict):
+            return {
+                str(key): "[REDACTED]"
+                if any(marker in str(key).lower() for marker in SENSITIVE_KEYS)
+                else visit(child)
+                for key, child in item.items()
+            }
+        if isinstance(item, (list, tuple)):
+            values = item[:1000] if truncate_lists else item
+            return [visit(child) for child in values]
+        if isinstance(item, str):
+            text_value = item[:4096]
+            text_value = re.sub(
+                r"(?i)\bBearer\s+[A-Za-z0-9._~+/-]+=*", "Bearer [REDACTED]", text_value
+            )
+            text_value = re.sub(r"\bdopc_[A-Za-z0-9_-]+", "[REDACTED]", text_value)
+            text_value = re.sub(
+                r"(?i)(password|token|secret|api[_-]?key)=([^&\s]+)",
+                r"\1=[REDACTED]",
+                text_value,
+            )
+            text_value = re.sub(
+                r"(?i)(https?://)[^/@\s]+@", r"\1[REDACTED]@", text_value
+            )
+            return text_value
+        return item
+
+    safe = visit(value)
+    encoded = json.dumps(safe, sort_keys=True, default=str).encode()
+    if summarize and len(encoded) > max_bytes:
+        return {
+            "truncated": True,
+            "sha256": hashlib.sha256(encoded).hexdigest(),
+            "original_bytes": len(encoded),
+        }
+    return safe
+
+
+def _bounded_channel(value, *, max_bytes, channel):
+    raw = json.dumps(
+        value, sort_keys=True, separators=(",", ":"), default=str
+    ).encode()
+    if len(raw) > max_bytes:
+        raise ConnectorConfigurationError(f"connector {channel} exceeds safe limit")
+    safe = redact_evidence(
+        value, max_bytes=max_bytes, summarize=False, truncate_lists=False
+    )
+    encoded = json.dumps(
+        safe, sort_keys=True, separators=(",", ":"), default=str
+    ).encode()
+    if len(encoded) > max_bytes:
+        raise ConnectorConfigurationError(f"connector {channel} exceeds safe limit")
+    return safe
+
+
+def sanitize_operation_result(result):
+    if not isinstance(result.records, (list, tuple)) or len(result.records) > MAX_RECORDS:
+        raise ConnectorConfigurationError("connector records exceed safe limit")
+    records = _bounded_channel(
+        list(result.records), max_bytes=MAX_RECORDS_BYTES, channel="records"
+    )
+    if any(not isinstance(item, dict) for item in records):
+        raise ConnectorConfigurationError("connector records must be objects")
+    cursor = _bounded_channel(
+        dict(result.cursor), max_bytes=MAX_CURSOR_BYTES, channel="cursor"
+    )
+    checkpoint = _bounded_channel(
+        dict(result.checkpoint), max_bytes=MAX_CHECKPOINT_BYTES, channel="checkpoint"
+    )
+    evidence = _bounded_channel(
+        dict(result.evidence), max_bytes=MAX_EVIDENCE_BYTES, channel="evidence"
+    )
+    return replace(
+        result,
+        records=tuple(records),
+        cursor=cursor,
+        checkpoint=checkpoint,
+        evidence=evidence,
+    )
+
+
+def snapshot_diff(previous, current):
+    def identity(item):
+        if isinstance(item, dict):
+            return (
+                str(item.get("asset_key") or item.get("key") or "")
+                + ":"
+                + str(item.get("field") or "")
+            )
+        return json.dumps(item, sort_keys=True, default=str)
+
+    before = {identity(item): item for item in previous}
+    after = {identity(item): item for item in current}
+    shared = before.keys() & after.keys()
+    return {
+        "added": tuple(after[key] for key in sorted(after.keys() - before.keys())),
+        "removed": tuple(before[key] for key in sorted(before.keys() - after.keys())),
+        "changed": tuple(
+            {"before": before[key], "after": after[key]}
+            for key in sorted(shared)
+            if before[key] != after[key]
+        ),
+    }
+
+
+class InMemoryRunStore:
+    """Test/reference store. Production API injects the PostgreSQL repository."""
+
+    def __init__(self):
+        self._lock = threading.RLock()
+        self._runs = {}
+        self._hints = {}
+
+    def claim(self, key, record):
+        with self._lock:
+            hint = record.get("client_hint_hash")
+            if hint and hint in self._hints and self._hints[hint] != key:
+                raise ConnectorConflictError("idempotency hint conflicts with another request")
+            if key in self._runs:
+                if self._runs[key].get("request_hash") != record.get("request_hash"):
+                    raise ConnectorConflictError("idempotency request binding conflicts")
+                return self._runs[key], False
+            self._runs[key] = dict(record)
+            if hint:
+                self._hints[hint] = key
+            return self._runs[key], True
+
+    def update(self, key, **values):
+        with self._lock:
+            record = self._runs[key]
+            current = record.get("status")
+            requested = values.get("status")
+            expected_attempt = values.pop("expected_attempt", None)
+            lease_token = values.pop("lease_token", None)
+            acquired = True
+            if requested == "resumable":
+                acquired = current in {"failed", "cancelled"}
+            elif requested == "cancelled":
+                acquired = current == "running"
+            elif requested == "running":
+                attempt = int(values.get("attempt_count", -1))
+                acquired = (
+                    current in {"running", "resumable", "failed"}
+                    and int(record.get("attempt_count", 0)) < attempt
+                )
+            elif requested in {"succeeded", "dry_run", "failed"}:
+                acquired = (
+                    current == "running"
+                    and not record.get("cancel_requested", False)
+                    and int(record.get("attempt_count", -1)) == int(expected_attempt or -1)
+                    and record.get("attempt_lease_token") == lease_token
+                )
+            if not acquired:
+                return UpdateOutcome(dict(record), False)
+            self._runs[key].update(values)
+            if requested == "running":
+                self._runs[key]["attempt_lease_token"] = lease_token
+            return UpdateOutcome(dict(self._runs[key]), True)
+
+    def cancel(self, key):
+        outcome = self.update(key, status="cancelled", cancel_requested=True)
+        if not outcome.acquired:
+            raise ConnectorConfigurationError("connector run cannot be cancelled")
+        return outcome.record
+
+    def is_cancel_requested(self, key):
+        with self._lock:
+            return bool(self._runs.get(key, {}).get("cancel_requested"))
+
+    def get(self, key):
+        with self._lock:
+            value = self._runs.get(key)
+            if value is None:
+                canonical = self._hints.get(hashlib.sha256(str(key).encode()).hexdigest())
+                value = self._runs.get(canonical) if canonical else None
+            return dict(value) if value else None
+
+    def list(self):
+        with self._lock:
+            return [dict(item) for item in self._runs.values()]
+
+
+class SlidingWindowLimiter:
+    def __init__(self, limit=30, window_seconds=60, clock=None):
+        self.limit = int(limit)
+        self.window_seconds = float(window_seconds)
+        self.clock = clock or time.monotonic
+        self._lock = threading.Lock()
+        self._events = {}
+
+    def acquire(self, key):
+        now = self.clock()
+        with self._lock:
+            events = self._events.setdefault(key, deque())
+            while events and events[0] <= now - self.window_seconds:
+                events.popleft()
+            if len(events) >= self.limit:
+                raise ConnectorRateLimitError("connector runtime rate limit exceeded")
+            events.append(now)
+
+
+class ConnectorRuntime:
+    def __init__(
+        self, registry, store=None, limiter=None, max_attempts=3, sleeper=None
+    ):
+        if max_attempts < 1 or max_attempts > 5:
+            raise ConnectorConfigurationError("max_attempts must be between 1 and 5")
+        self.registry = registry
+        self.store = store or InMemoryRunStore()
+        self.limiter = limiter or SlidingWindowLimiter()
+        self.max_attempts = max_attempts
+        self.sleeper = sleeper or time.sleep
+
+    def _acquire_rate_limit(self, connector_id, source_uid):
+        key = f"{connector_id}:{source_uid}"
+        if hasattr(self.store, "acquire_rate_limit"):
+            self.store.acquire_rate_limit(key)
+        else:
+            self.limiter.acquire(key)
+
+    def _run_operation(self, key, record, request, operation):
+        first_attempt = int(record.get("attempt_count", 0)) + 1
+        remaining = MAX_TOTAL_ATTEMPTS - first_attempt + 1
+        invocation_attempts = min(self.max_attempts, remaining)
+        if invocation_attempts <= 0:
+            raise ConnectorConfigurationError(
+                "connector run attempt budget is exhausted"
+            )
+        for attempt in range(first_attempt, first_attempt + invocation_attempts):
+            lease_token = str(uuid.uuid4())
+            started = self.store.update(
+                key,
+                attempt_count=attempt,
+                status="running",
+                error_category=None,
+                error_code=None,
+                lease_token=lease_token,
+            )
+            if not started.acquired:
+                if started.record.get("status") == "cancelled":
+                    raise ConnectorCancelledError()
+                raise ConnectorConfigurationError(
+                    "connector run attempt is already owned"
+                )
+            started_record = started.record
+            if started_record.get("status") == "cancelled":
+                raise ConnectorCancelledError()
+            if (
+                started_record.get("status") != "running"
+                or int(started_record.get("attempt_count", -1)) != attempt
+            ):
+                raise ConnectorConfigurationError(
+                    "connector run attempt could not be claimed"
+                )
+            try:
+                def cancel_probe():
+                    return bool(
+                        hasattr(self.store, "is_cancel_requested")
+                        and self.store.is_cancel_requested(key)
+                    )
+                attempt_request = replace(
+                    request,
+                    run_key=key,
+                    lease_token=lease_token,
+                    cancel_probe=cancel_probe,
+                )
+                if cancel_probe():
+                    raise ConnectorCancelledError()
+                result = operation(attempt_request)
+                if not isinstance(result, OperationResult):
+                    raise ConnectorConfigurationError(
+                        "connector returned an invalid result"
+                    )
+                if result.status != "succeeded":
+                    raise ConnectorConfigurationError(
+                        "connector operation returned an invalid status"
+                    )
+                if cancel_probe():
+                    raise ConnectorCancelledError()
+                safe_result = sanitize_operation_result(result)
+                status = "dry_run" if request.dry_run else "succeeded"
+                safe_result = replace(safe_result, status=status)
+                updated = self.store.update(
+                    key,
+                    status=status,
+                    result=safe_result,
+                    checkpoint=dict(safe_result.checkpoint),
+                    cursor=dict(safe_result.cursor),
+                    error_category=None,
+                    error_code=None,
+                    expected_attempt=attempt,
+                    lease_token=lease_token,
+                )
+                if not updated.acquired and updated.record.get("status") == "cancelled":
+                    raise ConnectorCancelledError()
+                if not updated.acquired:
+                    raise ConnectorConfigurationError(
+                        "connector run completion ownership was lost"
+                    )
+                return safe_result
+            except Exception as error:
+                classified = classify_error(error)
+                updated = self.store.update(
+                    key,
+                    status="failed",
+                    error_category=classified.category,
+                    error_code=type(classified).__name__,
+                    expected_attempt=attempt,
+                    lease_token=lease_token,
+                )
+                if not updated.acquired and updated.record.get("status") == "cancelled":
+                    raise ConnectorCancelledError() from error
+                if not updated.acquired:
+                    raise ConnectorConfigurationError(
+                        "connector run failure ownership was lost"
+                    ) from error
+                final_attempt = attempt == first_attempt + invocation_attempts - 1
+                if not classified.retryable or final_attempt:
+                    raise classified from error
+                self.sleeper(min(0.1 * (2 ** (attempt - 1)), 1.0))
+        raise ConnectorConfigurationError("connector run did not complete")
+
+    def execute(self, connector_id, version, request):
+        connector = self.registry.resolve(connector_id, version, request.operation)
+        config = self.registry.validate(connector_id, version, request.config)
+        request = replace(request, config=config)
+        key = deterministic_idempotency_key(connector_id, version, request)
+        client_hint_hash = (
+            hashlib.sha256(str(request.idempotency_key).encode()).hexdigest()
+            if request.idempotency_key
+            else None
+        )
+        self._acquire_rate_limit(connector_id, request.source_uid)
+        record, created = self.store.claim(
+            key,
+            {
+                "idempotency_key": key,
+                "request_hash": key,
+                "client_hint_hash": client_hint_hash,
+                "connector_id": connector_id,
+                "connector_version": version,
+                "source_uid": request.source_uid,
+                "operation": request.operation,
+                "config": dict(request.config),
+                "scope": dict(request.scope),
+                "status": "running",
+                "attempt_count": 0,
+                "checkpoint": dict(request.checkpoint),
+                "cursor": dict(request.cursor),
+                "dry_run": request.dry_run,
+                "principal_uid": request.principal_uid,
+                "business_domain_uid": request.business_domain_uid,
+                "environment": request.environment,
+                "process_key": request.process_key,
+                "source_binding_uid": request.source_binding_uid,
+                "source_binding_version": request.source_binding_version,
+                "cancel_requested": False,
+            },
+        )
+        if not created:
+            if record["status"] in {"succeeded", "dry_run"}:
+                return record.get("result") or OperationResult(
+                    cursor=record.get("cursor") or {},
+                    checkpoint=record.get("checkpoint") or {},
+                    evidence={"idempotent_replay": True},
+                    status=record["status"],
+                )
+            if record["status"] == "running":
+                raise ConnectorConfigurationError(
+                    "idempotent operation is already running"
+                )
+        if record.get("status") == "cancelled":
+            raise ConnectorCancelledError()
+        operation = getattr(connector, request.operation)
+        if request.dry_run:
+            health = connector.health(config)
+
+            def validation_only(_request):
+                return OperationResult(
+                    evidence={
+                        "validation_only": True,
+                        "health_status": health.status,
+                        "network_requested": False,
+                    },
+                )
+
+            operation = validation_only
+        return self._run_operation(key, record, request, operation)
+
+    def cancel(self, key):
+        record = self.store.get(key)
+        if not record:
+            raise ConnectorConfigurationError("connector run was not found")
+        if record["status"] in {"succeeded", "failed", "cancelled"}:
+            raise ConnectorConfigurationError("connector run cannot be cancelled")
+        key = record.get("idempotency_key", key)
+        connector = self.registry.resolve(
+            record["connector_id"], record["connector_version"], "cancel"
+        )
+        from app.core.connectors.sdk import OperationRequest
+
+        request = OperationRequest(
+            source_uid=record["source_uid"],
+            operation="cancel",
+            config=record.get("config") or {},
+            scope=record.get("scope") or {},
+            cursor=record.get("cursor") or {},
+            checkpoint=record.get("checkpoint") or {},
+            idempotency_key=key,
+            dry_run=bool(record.get("dry_run")),
+            principal_uid=record.get("principal_uid"),
+            business_domain_uid=record.get("business_domain_uid"),
+            environment=record.get("environment"),
+            process_key=record.get("process_key"),
+            source_binding_uid=record.get("source_binding_uid"),
+            source_binding_version=record.get("source_binding_version"),
+            run_key=key,
+        )
+        if hasattr(self.store, "cancel"):
+            cancelled = self.store.cancel(key)
+        else:
+            outcome = self.store.update(
+                key, status="cancelled", cancel_requested=True
+            )
+            if not outcome.acquired:
+                raise ConnectorConfigurationError("connector run cannot be cancelled")
+            cancelled = outcome.record
+        def cancel_probe():
+            return bool(
+                hasattr(self.store, "is_cancel_requested")
+                and self.store.is_cancel_requested(key)
+            )
+        request = replace(request, cancel_probe=cancel_probe)
+        if not request.dry_run:
+            connector.cancel(request)
+        return cancelled
+
+    def resume(self, key):
+        record = self.store.get(key)
+        if not record or record["status"] not in {"failed", "cancelled"}:
+            raise ConnectorConfigurationError("connector run cannot be resumed")
+        key = record.get("idempotency_key", key)
+        connector = self.registry.resolve(
+            record["connector_id"], record["connector_version"], "resume"
+        )
+        config = self.registry.validate(
+            record["connector_id"],
+            record["connector_version"],
+            record.get("config") or {},
+        )
+        self._acquire_rate_limit(record["connector_id"], record["source_uid"])
+        from app.core.connectors.sdk import OperationRequest
+
+        request = OperationRequest(
+            source_uid=record["source_uid"],
+            operation="resume",
+            config=config,
+            scope=record.get("scope") or {},
+            cursor=record.get("cursor") or {},
+            checkpoint=record.get("checkpoint") or {},
+            idempotency_key=key,
+            dry_run=bool(record.get("dry_run")),
+            principal_uid=record.get("principal_uid"),
+            business_domain_uid=record.get("business_domain_uid"),
+            environment=record.get("environment"),
+            process_key=record.get("process_key"),
+            source_binding_uid=record.get("source_binding_uid"),
+            source_binding_version=record.get("source_binding_version"),
+        )
+        resumable = self.store.update(
+            key,
+            status="resumable",
+            resumed_from=record.get("uid"),
+            cancel_requested=False,
+        )
+        if not resumable.acquired:
+            raise ConnectorConfigurationError("connector run cannot be resumed")
+        operation = connector.resume
+        if request.dry_run:
+            health = connector.health(config)
+
+            def validation_only(_request):
+                return OperationResult(
+                    evidence={
+                        "validation_only": True,
+                        "health_status": health.status,
+                        "network_requested": False,
+                        "resumed": True,
+                    }
+                )
+
+            operation = validation_only
+        return self._run_operation(key, resumable.record, request, operation)
+
+
+__all__ = [
+    "ConnectorRuntime",
+    "InMemoryRunStore",
+    "SlidingWindowLimiter",
+    "MAX_TOTAL_ATTEMPTS",
+    "deterministic_idempotency_key",
+    "snapshot_diff",
+    "redact_evidence",
+]

+ 233 - 0
app/core/connectors/sdk.py

@@ -0,0 +1,233 @@
+"""Versioned public connector SDK.
+
+Only symbols exported here and from ``app.core.connectors`` are stable. Connector
+packages register against the registry; the collection runtime never branches on
+connector names.
+"""
+
+from __future__ import annotations
+
+import re
+from abc import ABC, abstractmethod
+from collections.abc import Callable, Mapping
+from dataclasses import asdict, dataclass, field
+from typing import Any
+
+from jsonschema import Draft202012Validator
+from jsonschema.exceptions import SchemaError, ValidationError
+
+from app.core.connectors.errors import ConnectorConfigurationError
+
+SDK_VERSION = "1.0"
+CAPABILITIES = frozenset(
+    {
+        "discover",
+        "snapshot",
+        "incremental",
+        "lineage",
+        "profile",
+        "cancel",
+        "resume",
+        "evidence",
+    }
+)
+SECRET_KEY_PATTERN = re.compile(
+    r"(?:password|passwd|secret|token|api[_-]?key|authorization|private[_-]?key)", re.I
+)
+SECRET_REF_PATTERN = re.compile(
+    r"^(?:env|vault|secret):[A-Za-z0-9][A-Za-z0-9_./:-]{2,255}$"
+)
+SEMVER_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$")
+
+
+@dataclass(frozen=True)
+class ConnectorManifest:
+    connector_id: str
+    version: str
+    sdk_version: str
+    display_name: str
+    capabilities: tuple[str, ...]
+    config_schema: Mapping[str, Any] = field(repr=False)
+
+    def __post_init__(self):
+        if not re.fullmatch(r"[a-z][a-z0-9_-]{2,63}", self.connector_id):
+            raise ConnectorConfigurationError("connector_id is invalid")
+        if not SEMVER_PATTERN.fullmatch(self.version):
+            raise ConnectorConfigurationError("connector version is invalid")
+        if self.sdk_version != SDK_VERSION:
+            raise ConnectorConfigurationError("SDK version is incompatible")
+        capabilities = tuple(dict.fromkeys(self.capabilities))
+        if not capabilities or set(capabilities) - CAPABILITIES:
+            raise ConnectorConfigurationError("connector capability is unsupported")
+        object.__setattr__(self, "capabilities", capabilities)
+        validate_schema(self.config_schema)
+
+    def public_dict(self):
+        return asdict(self)
+
+
+@dataclass(frozen=True)
+class OperationRequest:
+    source_uid: str
+    operation: str
+    config: Mapping[str, Any]
+    scope: Mapping[str, Any] = field(default_factory=dict)
+    cursor: Mapping[str, Any] = field(default_factory=dict)
+    checkpoint: Mapping[str, Any] = field(default_factory=dict)
+    idempotency_key: str | None = None
+    dry_run: bool = False
+    principal_uid: str | None = None
+    business_domain_uid: str | None = None
+    environment: str | None = None
+    process_key: str | None = None
+    source_binding_uid: str | None = None
+    source_binding_version: int | None = None
+    run_key: str | None = None
+    lease_token: str | None = None
+    cancel_probe: Callable[[], bool] | None = field(
+        default=None, compare=False, repr=False
+    )
+
+
+@dataclass(frozen=True)
+class OperationResult:
+    records: tuple[Mapping[str, Any], ...] = ()
+    cursor: Mapping[str, Any] = field(default_factory=dict)
+    checkpoint: Mapping[str, Any] = field(default_factory=dict)
+    evidence: Mapping[str, Any] = field(default_factory=dict)
+    status: str = "succeeded"
+
+
+@dataclass(frozen=True)
+class HealthResult:
+    status: str
+    detail: str = ""
+
+
+@dataclass(frozen=True)
+class CompatibilityResult:
+    compatible: bool
+    connector_version: str
+    sdk_version: str = SDK_VERSION
+    detail: str = ""
+
+
+def _walk_secrets(value, path="config"):
+    if isinstance(value, Mapping):
+        for key, item in value.items():
+            current = f"{path}.{key}"
+            if SECRET_KEY_PATTERN.search(str(key)) and (
+                not str(key).endswith("_ref")
+                or not isinstance(item, str)
+                or not SECRET_REF_PATTERN.fullmatch(item)
+            ):
+                raise ConnectorConfigurationError(
+                    f"{current} must be a secret reference"
+                )
+            _walk_secrets(item, current)
+    elif isinstance(value, (list, tuple)):
+        for index, item in enumerate(value):
+            _walk_secrets(item, f"{path}[{index}]")
+
+
+def validate_schema(schema):
+    if not isinstance(schema, Mapping) or schema.get("type") != "object":
+        raise ConnectorConfigurationError("config schema must describe an object")
+    if schema.get("additionalProperties") is not False:
+        raise ConnectorConfigurationError(
+            "config schema must reject unknown properties"
+        )
+    properties = schema.get("properties")
+    if not isinstance(properties, Mapping):
+        raise ConnectorConfigurationError("config schema properties are required")
+
+    def inspect_definition(definition):
+        if not isinstance(definition, Mapping):
+            return
+        for key, child in definition.get("properties", {}).items():
+            if SECRET_KEY_PATTERN.search(str(key)) and not str(key).endswith("_ref"):
+                raise ConnectorConfigurationError(
+                    "config schema cannot define plaintext secrets"
+                )
+            if (
+                str(key).endswith("_ref")
+                and child.get("pattern") != SECRET_REF_PATTERN.pattern
+            ):
+                raise ConnectorConfigurationError(
+                    "secret reference schema pattern is required"
+                )
+            inspect_definition(child)
+        inspect_definition(definition.get("items"))
+        for keyword in ("allOf", "anyOf", "oneOf"):
+            for child in definition.get(keyword, ()):
+                inspect_definition(child)
+        for child in definition.get("$defs", {}).values():
+            inspect_definition(child)
+
+    inspect_definition(schema)
+    try:
+        Draft202012Validator.check_schema(dict(schema))
+    except SchemaError as exc:
+        raise ConnectorConfigurationError("config JSON Schema is invalid") from exc
+
+
+def validate_config(schema, config):
+    """Validate recursively with JSON Schema Draft 2020-12."""
+    if not isinstance(config, Mapping):
+        raise ConnectorConfigurationError("connector config must be an object")
+    _walk_secrets(config)
+    try:
+        Draft202012Validator(dict(schema)).validate(dict(config))
+    except ValidationError as exc:
+        raise ConnectorConfigurationError("connector config shape is invalid") from exc
+    return dict(config)
+
+
+class Connector(ABC):
+    manifest: ConnectorManifest
+
+    @abstractmethod
+    def discover(self, request: OperationRequest) -> OperationResult: ...
+
+    @abstractmethod
+    def snapshot(self, request: OperationRequest) -> OperationResult: ...
+
+    @abstractmethod
+    def incremental(self, request: OperationRequest) -> OperationResult: ...
+
+    @abstractmethod
+    def lineage(self, request: OperationRequest) -> OperationResult: ...
+
+    @abstractmethod
+    def profile(self, request: OperationRequest) -> OperationResult: ...
+
+    @abstractmethod
+    def cancel(self, request: OperationRequest) -> OperationResult: ...
+
+    @abstractmethod
+    def resume(self, request: OperationRequest) -> OperationResult: ...
+
+    @abstractmethod
+    def evidence(self, request: OperationRequest) -> OperationResult: ...
+
+    def health(self, config: Mapping[str, Any]) -> HealthResult:
+        validate_config(self.manifest.config_schema, config)
+        return HealthResult("available")
+
+    def compatibility(self) -> CompatibilityResult:
+        return CompatibilityResult(True, self.manifest.version)
+
+
+__all__ = [
+    "SDK_VERSION",
+    "CAPABILITIES",
+    "SECRET_REF_PATTERN",
+    "ConnectorManifest",
+    "OperationRequest",
+    "OperationResult",
+    "HealthResult",
+    "CompatibilityResult",
+    "Connector",
+    "validate_config",
+    "validate_schema",
+]

+ 39 - 0
app/core/connectors/secrets.py

@@ -0,0 +1,39 @@
+"""Deployable, fail-closed connector secret reference resolution."""
+
+from __future__ import annotations
+
+import os
+import re
+from collections.abc import Mapping
+
+from app.core.connectors.errors import ConnectorConfigurationError
+
+ENV_REFERENCE = re.compile(r"^env:(DATAOPS_CONNECTOR_[A-Z0-9_]{1,200})$")
+
+
+class EnvironmentSecretResolver:
+    """Resolve only the dedicated DATAOPS_CONNECTOR_* environment namespace."""
+
+    def __init__(self, environ: Mapping[str, str] | None = None):
+        self.environ = os.environ if environ is None else environ
+
+    def __call__(self, reference: str) -> str:
+        value = str(reference or "")
+        match = ENV_REFERENCE.fullmatch(value)
+        if match:
+            secret = self.environ.get(match.group(1))
+            if not isinstance(secret, str) or not secret.strip():
+                raise ConnectorConfigurationError(
+                    "connector environment secret is unavailable"
+                )
+            return secret
+        if value.startswith(("vault:", "secret:")):
+            raise ConnectorConfigurationError(
+                "connector secret backend is not configured"
+            )
+        raise ConnectorConfigurationError(
+            "connector environment secret reference is not allowed"
+        )
+
+
+__all__ = ["EnvironmentSecretResolver", "ENV_REFERENCE"]

+ 16 - 0
app/core/connectors/store.py

@@ -0,0 +1,16 @@
+"""Explicit persistence outcomes for connector state CAS operations."""
+
+from __future__ import annotations
+
+from collections.abc import Mapping
+from dataclasses import dataclass
+from typing import Any
+
+
+@dataclass(frozen=True)
+class UpdateOutcome:
+    record: Mapping[str, Any]
+    acquired: bool
+
+
+__all__ = ["UpdateOutcome"]

+ 7 - 1
app/core/data_source/adapters/__init__.py

@@ -3,7 +3,9 @@
 from pathlib import Path
 
 from app.core.data_source.adapters.mysql import MySQLAdapter
+from app.core.data_source.adapters.oracle import OracleAdapter
 from app.core.data_source.adapters.postgresql import PostgreSQLAdapter
+from app.core.data_source.adapters.sqlserver import SqlServerAdapter
 from app.core.data_source.errors import DataSourceTypeUnsupported
 
 
@@ -16,7 +18,11 @@ def adapter_for(database_type: str, certificate_dir=None):
         return PostgreSQLAdapter(**options)
     if normalized == "mysql":
         return MySQLAdapter(**options)
+    if normalized == "oracle":
+        return OracleAdapter(**options)
+    if normalized in {"sqlserver", "mssql"}:
+        return SqlServerAdapter(**options)
     raise DataSourceTypeUnsupported()
 
 
-__all__ = ["adapter_for", "PostgreSQLAdapter", "MySQLAdapter"]
+__all__ = ["adapter_for", "PostgreSQLAdapter", "MySQLAdapter", "OracleAdapter", "SqlServerAdapter"]

+ 31 - 4
app/core/data_source/adapters/base.py

@@ -11,7 +11,6 @@ from app.core.data_source.errors import (
     DataSourceConnectionFailed,
 )
 
-
 READ_ONLY_PURPOSES = {
     "metadata_collection",
     "metadata_preview",
@@ -92,6 +91,16 @@ class BaseDataSourceAdapter:
             database=definition.database,
         )
 
+    def build_url_for_environment(
+        self,
+        definition,
+        credential,
+        *,
+        trusted_environment="production",
+        allow_insecure_development=False,
+    ):
+        return self.build_url(definition, credential)
+
     def connect_args(self, definition, credential, query_timeout):
         raise NotImplementedError
 
@@ -114,7 +123,12 @@ class BaseDataSourceAdapter:
         query_timeout,
     ):
         engine = create_engine(
-            self.build_url(definition, credential),
+            self.build_url_for_environment(
+                definition,
+                credential,
+                trusted_environment="production",
+                allow_insecure_development=False,
+            ),
             poolclass=NullPool,
             connect_args=self.connect_args(
                 definition,
@@ -130,9 +144,22 @@ class BaseDataSourceAdapter:
         finally:
             engine.dispose()
 
-    def create_pooled_engine(self, definition, credential, settings):
+    def create_pooled_engine(
+        self,
+        definition,
+        credential,
+        settings,
+        *,
+        trusted_environment="production",
+        allow_insecure_development=False,
+    ):
         return create_engine(
-            self.build_url(definition, credential),
+            self.build_url_for_environment(
+                definition,
+                credential,
+                trusted_environment=trusted_environment,
+                allow_insecure_development=allow_insecure_development,
+            ),
             pool_size=int(settings["pool_size"]),
             max_overflow=int(settings["max_overflow"]),
             pool_timeout=int(settings["pool_timeout"]),

+ 25 - 0
app/core/data_source/adapters/oracle.py

@@ -0,0 +1,25 @@
+"""Oracle adapter; the optional python-oracledb driver is checked lazily."""
+
+import importlib.util
+
+from app.core.data_source.adapters.base import BaseDataSourceAdapter
+from app.core.data_source.errors import DataSourceDriverUnavailable
+
+
+class OracleAdapter(BaseDataSourceAdapter):
+    database_type = "oracle"
+    drivername = "oracle+oracledb"
+    allowed_tls_options = frozenset()
+
+    def build_url(self, definition, credential):
+        if importlib.util.find_spec("oracledb") is None:
+            raise DataSourceDriverUnavailable()
+        return super().build_url(definition, credential)
+
+    def connect_args(self, definition, credential, query_timeout):
+        return {"tcp_connect_timeout": 5}
+
+    def configure_transaction(self, connection, purpose):
+        super().configure_transaction(connection, purpose)
+        if purpose != "dataflow_write":
+            connection.exec_driver_sql("SET TRANSACTION READ ONLY")

+ 92 - 0
app/core/data_source/adapters/sqlserver.py

@@ -0,0 +1,92 @@
+"""SQL Server adapter; the optional pyodbc driver is checked lazily."""
+
+import importlib.util
+import logging
+
+from app.core.data_source.adapters.base import BaseDataSourceAdapter
+from app.core.data_source.errors import DataSourceDriverUnavailable
+
+
+class SqlServerAdapter(BaseDataSourceAdapter):
+    database_type = "sqlserver"
+    drivername = "mssql+pyodbc"
+    allowed_tls_options = frozenset(
+        {
+            "Encrypt",
+            "TrustServerCertificate",
+            "ServerCertificate",
+        }
+    )
+    certificate_options = frozenset({"ServerCertificate"})
+
+    def build_url(self, definition, credential):
+        return self.build_url_for_environment(
+            definition,
+            credential,
+            trusted_environment="production",
+            allow_insecure_development=False,
+        )
+
+    def build_url_for_environment(
+        self,
+        definition,
+        credential,
+        *,
+        trusted_environment="production",
+        allow_insecure_development=False,
+    ):
+        if importlib.util.find_spec("pyodbc") is None:
+            raise DataSourceDriverUnavailable()
+        options = self.validate_options(definition.tls_options)
+        environment = str(trusted_environment)
+        if environment not in {"development", "staging", "production"}:
+            from app.core.data_source.errors import DataSourceConfigurationInvalid
+
+            raise DataSourceConfigurationInvalid(
+                "trusted SQL Server environment is invalid"
+            )
+        allow_insecure = bool(allow_insecure_development)
+        encrypt = options.setdefault("Encrypt", "yes")
+        trust = options.setdefault("TrustServerCertificate", "no")
+        if environment in {"staging", "production"} and (
+            encrypt != "yes" or trust != "no"
+        ):
+            from app.core.data_source.errors import DataSourceConfigurationInvalid
+
+            raise DataSourceConfigurationInvalid(
+                "SQL Server staging/production requires certificate-verified TLS"
+            )
+        if (encrypt != "yes" or trust != "no") and not (
+            environment == "development" and allow_insecure
+        ):
+            from app.core.data_source.errors import DataSourceConfigurationInvalid
+
+            raise DataSourceConfigurationInvalid(
+                "SQL Server insecure TLS requires explicit development policy"
+            )
+        if allow_insecure and (encrypt != "yes" or trust != "no"):
+            logging.getLogger(__name__).warning(
+                "SQL Server approved insecure development TLS policy enabled"
+            )
+        query = {"driver": "ODBC Driver 18 for SQL Server", **options}
+        return super().build_url(definition, credential).update_query_dict(query)
+
+    def _validate_scalar_option(self, key, value):
+        normalized = str(value).strip().lower()
+        if normalized not in {"yes", "no"}:
+            from app.core.data_source.errors import DataSourceConfigurationInvalid
+
+            raise DataSourceConfigurationInvalid("SQL Server TLS option is invalid")
+        return normalized
+
+    def connect_args(self, definition, credential, query_timeout):
+        return {"timeout": min(int(query_timeout), 30)}
+
+    def configure_transaction(self, connection, purpose):
+        from app.core.data_source.adapters.base import PURPOSES, READ_ONLY_PURPOSES
+        from app.core.data_source.errors import DataSourceConfigurationInvalid
+
+        if purpose not in PURPOSES:
+            raise DataSourceConfigurationInvalid("unsupported data source purpose")
+        if purpose in READ_ONLY_PURPOSES:
+            connection.exec_driver_sql("SET TRANSACTION ISOLATION LEVEL SNAPSHOT")

+ 6 - 0
app/core/data_source/errors.py

@@ -40,6 +40,12 @@ class DataSourceConnectionFailed(DataSourceError):
     default_message = "data source connection failed"
 
 
+class DataSourceDriverUnavailable(DataSourceError):
+    code = "DATASOURCE_DRIVER_UNAVAILABLE"
+    http_status = 503
+    default_message = "optional data source driver is unavailable"
+
+
 class DataSourcePoolTimeout(DataSourceError):
     code = "DATASOURCE_POOL_TIMEOUT"
     http_status = 503

+ 32 - 5
app/core/data_source/manager.py

@@ -1,7 +1,8 @@
 """Public context-managed access to external business data sources."""
 
+import inspect
 import time
-from contextlib import contextmanager
+from contextlib import contextmanager, suppress
 
 from sqlalchemy.exc import DBAPIError, OperationalError, TimeoutError
 
@@ -83,11 +84,22 @@ class DataSourceConnectionManager:
         self._clock = clock or time.monotonic
 
     @contextmanager
-    def connect(self, data_source_uid, purpose):
+    def connect(
+        self,
+        data_source_uid,
+        purpose,
+        *,
+        environment="production",
+        allow_insecure_development=False,
+    ):
         if purpose not in PURPOSES or purpose == "connection_test":
             raise DataSourceConfigurationInvalid(
                 "unsupported pooled connection purpose"
             )
+        if environment not in {"development", "staging", "production"}:
+            raise DataSourceConfigurationInvalid(
+                "trusted connector environment is invalid"
+            )
         definition = self.definitions.get(data_source_uid)
         if not definition:
             raise DataSourceNotFound()
@@ -103,6 +115,22 @@ class DataSourceConnectionManager:
             credential_version=int(definition.credential_version),
             config_fingerprint=definition.connection_fingerprint(),
         )
+        create_parameters = inspect.signature(adapter.create_pooled_engine).parameters
+        supports_security_context = (
+            "trusted_environment" in create_parameters
+            or any(
+                item.kind is inspect.Parameter.VAR_KEYWORD
+                for item in create_parameters.values()
+            )
+        )
+        security_context = (
+            {
+                "trusted_environment": environment,
+                "allow_insecure_development": bool(allow_insecure_development),
+            }
+            if supports_security_context
+            else {}
+        )
 
         with self.registry.lease(
             key,
@@ -110,6 +138,7 @@ class DataSourceConnectionManager:
                 definition,
                 credential,
                 settings,
+                **security_context,
             ),
         ) as engine:
             breaker = self.registry.breaker_for(key)
@@ -148,10 +177,8 @@ class DataSourceConnectionManager:
                     else:
                         transaction.rollback()
                 except Exception:
-                    try:
+                    with suppress(Exception):
                         transaction.rollback()
-                    except Exception:
-                        pass
                     raise
 
     def invalidate(self, data_source_uid, reason):

+ 4 - 1
app/core/data_source/service.py

@@ -19,6 +19,9 @@ TYPE_ALIASES = {
     "postgres": "postgresql",
     "postgresql": "postgresql",
     "mysql": "mysql",
+    "oracle": "oracle",
+    "sqlserver": "sqlserver",
+    "mssql": "sqlserver",
 }
 POOL_INVALIDATION_REASONS = {
     "admin_reset",
@@ -53,7 +56,7 @@ class DataSourceService:
         database_type = TYPE_ALIASES.get(requested)
         if database_type is None:
             raise DataSourceConfigurationInvalid(
-                "only PostgreSQL and MySQL data sources are supported"
+                "only registered PostgreSQL, MySQL, Oracle and SQL Server data sources are supported"
             )
         return database_type
 

+ 63 - 0
app/core/edge_gateway/__init__.py

@@ -0,0 +1,63 @@
+"""Enterprise edge gateway contracts, policy, and durable local queue."""
+
+from .contracts import (
+    EVENT_CLASSIFICATIONS,
+    TASK_CLASSIFICATIONS,
+    TASK_OPERATIONS,
+    EdgeContractError,
+    EdgeEventContract,
+    EdgeTaskContract,
+    SignedTaskEnvelope,
+    canonical_json_bytes,
+    canonical_sha256,
+    stable_event_id,
+)
+from .policy import (
+    CONTROL_PLANE_ALLOWED,
+    EDGE_ONLY,
+    RETENTION_DAYS,
+    EdgeEgressPolicy,
+    EdgePolicyError,
+)
+from .queue import (
+    SCHEMA_VERSION,
+    EdgeQueueConflictError,
+    EdgeQueueError,
+    EdgeQueueLeaseError,
+    EdgeQueueSchemaError,
+    LocalArtifact,
+    QueuedEvent,
+    QueuedOutcome,
+    QueuedTask,
+    ReleaseState,
+    SqliteEdgeQueue,
+)
+
+__all__ = [
+    "CONTROL_PLANE_ALLOWED",
+    "EDGE_ONLY",
+    "EVENT_CLASSIFICATIONS",
+    "RETENTION_DAYS",
+    "TASK_CLASSIFICATIONS",
+    "TASK_OPERATIONS",
+    "EdgeContractError",
+    "EdgeEgressPolicy",
+    "EdgeEventContract",
+    "EdgePolicyError",
+    "EdgeQueueConflictError",
+    "EdgeQueueError",
+    "EdgeQueueLeaseError",
+    "EdgeQueueSchemaError",
+    "EdgeTaskContract",
+    "SignedTaskEnvelope",
+    "LocalArtifact",
+    "QueuedEvent",
+    "QueuedOutcome",
+    "QueuedTask",
+    "ReleaseState",
+    "SqliteEdgeQueue",
+    "SCHEMA_VERSION",
+    "canonical_json_bytes",
+    "canonical_sha256",
+    "stable_event_id",
+]

+ 706 - 0
app/core/edge_gateway/contracts.py

@@ -0,0 +1,706 @@
+"""Immutable, fail-closed contracts for the enterprise edge boundary."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import math
+import re
+from collections.abc import Mapping
+from dataclasses import dataclass
+from dataclasses import field as dataclass_field
+from datetime import UTC, datetime, timedelta
+from decimal import Decimal, InvalidOperation
+from types import MappingProxyType
+from typing import ClassVar
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+
+
+class EdgeContractError(ValueError):
+    """Raised when an edge contract is malformed or ambiguous."""
+
+
+TASK_OPERATIONS = frozenset(
+    {"collect", "profile", "quality", "lineage", "controlled_query"}
+)
+TASK_CLASSIFICATIONS = frozenset(
+    {
+        "raw",
+        "recent_detail",
+        "restricted",
+        "desensitized_metadata",
+        "statistics",
+        "lineage",
+        "evidence",
+    }
+)
+EVENT_CLASSIFICATIONS = frozenset(
+    {
+        "desensitized_metadata",
+        "statistics",
+        "lineage",
+        "evidence",
+        "health_summary",
+        "diagnostic_summary",
+    }
+)
+CONTRACT_VERSION = 1
+MAX_ATTEMPT = 5
+PREFLIGHT_MAX_DEPTH = 64
+PREFLIGHT_MAX_NODES = 10_000
+PREFLIGHT_MAX_STRING_BYTES = 131_072
+PREFLIGHT_MAX_TOTAL_BYTES = 1_048_576
+
+_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,254}$")
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+_ED25519_SIGNATURE = re.compile(r"^[0-9a-f]{128}$")
+_RFC3339 = re.compile(
+    r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$"
+)
+
+
+def preflight_json(
+    value: object,
+    *,
+    max_depth: int = PREFLIGHT_MAX_DEPTH,
+    max_nodes: int = PREFLIGHT_MAX_NODES,
+    max_string_bytes: int = PREFLIGHT_MAX_STRING_BYTES,
+    max_total_bytes: int = PREFLIGHT_MAX_TOTAL_BYTES,
+) -> None:
+    """Bound a JSON-like graph iteratively before copying or encoding it."""
+
+    nodes = 0
+    estimated_bytes = 0
+    active: set[int] = set()
+    stack: list[tuple[bool, object, int]] = [(False, value, 0)]
+    try:
+        while stack:
+            exiting, current, depth = stack.pop()
+            if exiting:
+                active.remove(id(current))
+                continue
+            if depth > max_depth:
+                raise EdgeContractError("contract exceeds the maximum depth")
+            nodes += 1
+            if nodes > max_nodes:
+                raise EdgeContractError("contract exceeds the maximum node count")
+            if isinstance(current, str):
+                size = len(current.encode("utf-8"))
+                estimated_bytes += size + 16
+                if estimated_bytes > max_total_bytes:
+                    raise EdgeContractError(
+                        "contract exceeds the conservative byte limit"
+                    )
+                if size > max_string_bytes:
+                    raise EdgeContractError("contract string exceeds the byte limit")
+            elif current is None or isinstance(current, bool):
+                estimated_bytes += 8
+            elif isinstance(current, int):
+                if current.bit_length() > 4096:
+                    raise EdgeContractError("contract integer exceeds the byte limit")
+                estimated_bytes += max(8, current.bit_length() // 3 + 4)
+            elif isinstance(current, float):
+                if not math.isfinite(current):
+                    raise EdgeContractError("contract numbers must be finite")
+                estimated_bytes += 32
+            elif isinstance(current, Mapping):
+                identity = id(current)
+                if identity in active:
+                    raise EdgeContractError("contract contains a cycle")
+                active.add(identity)
+                stack.append((True, current, depth))
+                estimated_bytes += 16
+                children: list[object] = []
+                for key, item in current.items():
+                    if not isinstance(key, str):
+                        raise EdgeContractError("contract property names must be strings")
+                    key_size = len(key.encode("utf-8"))
+                    if key_size > max_string_bytes:
+                        raise EdgeContractError("contract key exceeds the byte limit")
+                    estimated_bytes += key_size + 8
+                    children.append(item)
+                stack.extend((False, item, depth + 1) for item in reversed(children))
+            elif isinstance(current, (list, tuple)):
+                identity = id(current)
+                if identity in active:
+                    raise EdgeContractError("contract contains a cycle")
+                active.add(identity)
+                stack.append((True, current, depth))
+                estimated_bytes += 16
+                stack.extend((False, item, depth + 1) for item in reversed(current))
+            else:
+                raise EdgeContractError("contract contains a non-JSON value")
+            if estimated_bytes > max_total_bytes:
+                raise EdgeContractError("contract exceeds the conservative byte limit")
+    except (MemoryError, RecursionError) as exc:
+        raise EdgeContractError("contract exceeds safe structural limits") from exc
+
+
+def _length_prefixed(tag: bytes, content: bytes) -> bytes:
+    return tag + str(len(content)).encode("ascii") + b":" + content
+
+
+def _number_text(value: int | float) -> str:
+    try:
+        number = Decimal(value if isinstance(value, int) else str(value))
+    except (InvalidOperation, ValueError) as exc:
+        raise EdgeContractError("contract number is invalid") from exc
+    if not number.is_finite():
+        raise EdgeContractError("contract numbers must be finite")
+    if number == 0:
+        return "0"
+    normalized = format(number.normalize(), "f")
+    if "." in normalized:
+        normalized = normalized.rstrip("0").rstrip(".")
+    return normalized
+
+
+def _canonical_encode(value: object) -> bytes:
+    if value is None:
+        return b"n"
+    if isinstance(value, bool):
+        return b"b1" if value else b"b0"
+    if isinstance(value, (int, float)):
+        return _length_prefixed(b"d", _number_text(value).encode("ascii"))
+    if isinstance(value, str):
+        return _length_prefixed(b"s", value.encode("utf-8"))
+    if isinstance(value, Mapping):
+        encoded = bytearray(b"m" + str(len(value)).encode("ascii") + b":")
+        for key in sorted(value, key=lambda item: item.encode("utf-8")):
+            key_bytes = key.encode("utf-8")
+            encoded.extend(_length_prefixed(b"k", key_bytes))
+            encoded.extend(_canonical_encode(value[key]))
+        return bytes(encoded)
+    if isinstance(value, (list, tuple)):
+        encoded = bytearray(b"l" + str(len(value)).encode("ascii") + b":")
+        for item in value:
+            encoded.extend(_canonical_encode(item))
+        return bytes(encoded)
+    raise EdgeContractError("contract contains a non-JSON value")
+
+
+def canonical_json_bytes(value: object) -> bytes:
+    """Encode the explicit type-tagged, length-prefixed canonical protocol."""
+
+    preflight_json(value)
+    try:
+        return _canonical_encode(value)
+    except (MemoryError, RecursionError) as exc:
+        raise EdgeContractError("contract exceeds safe encoding limits") from exc
+
+
+def canonical_sha256(value: object) -> str:
+    return hashlib.sha256(canonical_json_bytes(value)).hexdigest()
+
+
+def strict_json_bytes(value: object) -> bytes:
+    """Serialize validated data as strict transport/storage JSON."""
+
+    preflight_json(value)
+    try:
+        return json.dumps(
+            value,
+            sort_keys=True,
+            separators=(",", ":"),
+            ensure_ascii=False,
+            allow_nan=False,
+        ).encode("utf-8")
+    except (MemoryError, RecursionError, TypeError, ValueError) as exc:
+        raise EdgeContractError("contract cannot be serialized as strict JSON") from exc
+
+
+def stable_event_id(identity: Mapping[str, object]) -> str:
+    if not isinstance(identity, Mapping):
+        raise EdgeContractError("event identity must be a mapping")
+    normalized = dict(identity)
+    if "occurred_at" in normalized:
+        normalized["occurred_at"] = _timestamp(
+            normalized["occurred_at"], "occurred_at"
+        )
+    return f"evt_{canonical_sha256(normalized)}"
+
+
+def _strict_mapping(value: object, *, label: str) -> dict[str, object]:
+    if not isinstance(value, Mapping):
+        raise EdgeContractError(f"{label} must be a mapping")
+    preflight_json(value)
+    return dict(value)
+
+
+def _require_exact_properties(
+    value: Mapping[str, object],
+    *,
+    required: frozenset[str],
+    optional: frozenset[str] = frozenset(),
+) -> None:
+    keys = frozenset(value)
+    unknown = keys - required - optional
+    missing = required - keys
+    if unknown:
+        raise EdgeContractError(
+            f"contract has unknown properties: {', '.join(sorted(unknown))}"
+        )
+    if missing:
+        raise EdgeContractError(
+            f"contract is missing properties: {', '.join(sorted(missing))}"
+        )
+
+
+def _text(value: object, label: str, *, maximum: int = 255) -> str:
+    if (
+        not isinstance(value, str)
+        or not value
+        or value.strip() != value
+        or len(value.encode("utf-8")) > maximum
+        or "\x00" in value
+    ):
+        raise EdgeContractError(f"{label} is invalid")
+    return value
+
+
+def _identifier(value: object, label: str) -> str:
+    candidate = _text(value, label)
+    if not _IDENTIFIER.fullmatch(candidate):
+        raise EdgeContractError(f"{label} is invalid")
+    return candidate
+
+
+def _attempt(value: object) -> int:
+    if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= 5:
+        raise EdgeContractError("attempt must be between 1 and 5")
+    return value
+
+
+def _version(value: object) -> int:
+    if isinstance(value, bool) or value != CONTRACT_VERSION:
+        raise EdgeContractError("contract_version is not supported")
+    return CONTRACT_VERSION
+
+
+def _timestamp(value: object, label: str) -> str:
+    candidate = _text(value, label, maximum=64)
+    if not _RFC3339.fullmatch(candidate):
+        raise EdgeContractError(f"{label} must be an RFC3339 timestamp")
+    try:
+        parsed = datetime.fromisoformat(candidate.replace("Z", "+00:00"))
+    except ValueError as exc:
+        raise EdgeContractError(f"{label} must be an RFC3339 timestamp") from exc
+    if parsed.tzinfo is None or parsed.utcoffset() is None:
+        raise EdgeContractError(f"{label} must be an RFC3339 timestamp")
+    utc = parsed.astimezone(UTC)
+    canonical = utc.strftime("%Y-%m-%dT%H:%M:%S")
+    if utc.microsecond:
+        canonical += f".{utc.microsecond:06d}".rstrip("0")
+    return f"{canonical}Z"
+
+
+def canonical_timestamp(value: object, label: str = "timestamp") -> str:
+    return _timestamp(value, label)
+
+
+def _digest(value: object, label: str) -> str:
+    candidate = _text(value, label, maximum=64)
+    if not _SHA256.fullmatch(candidate):
+        raise EdgeContractError(f"{label} must be a lowercase SHA256 digest")
+    return candidate
+
+
+def _json_copy(value: object) -> object:
+    try:
+        encoded = strict_json_bytes(value)
+        return json.loads(encoded.decode("utf-8"))
+    except (MemoryError, RecursionError, TypeError, ValueError) as exc:
+        raise EdgeContractError("contract cannot be copied as strict JSON") from exc
+
+
+def _deep_freeze(value: object) -> object:
+    if isinstance(value, dict):
+        return MappingProxyType({key: _deep_freeze(item) for key, item in value.items()})
+    if isinstance(value, list):
+        return tuple(_deep_freeze(item) for item in value)
+    return value
+
+
+def _deep_thaw(value: object) -> object:
+    if isinstance(value, Mapping):
+        return {key: _deep_thaw(item) for key, item in value.items()}
+    if isinstance(value, tuple):
+        return [_deep_thaw(item) for item in value]
+    return value
+
+
+@dataclass(frozen=True, slots=True)
+class EdgeTaskContract:
+    task_id: str
+    gateway_id: str
+    environment: str
+    network_zone: str
+    purpose: str
+    classification: str
+    operation: str
+    contract_version: int
+    deadline_at: str
+    attempt: int
+    idempotency_key: str
+    policy_digest: str
+
+    def __post_init__(self) -> None:
+        values = {
+            "task_id": _identifier(self.task_id, "task_id"),
+            "gateway_id": _identifier(self.gateway_id, "gateway_id"),
+            "environment": _identifier(self.environment, "environment"),
+            "network_zone": _identifier(self.network_zone, "network_zone"),
+            "purpose": _identifier(self.purpose, "purpose"),
+            "contract_version": _version(self.contract_version),
+            "deadline_at": _timestamp(self.deadline_at, "deadline_at"),
+            "attempt": _attempt(self.attempt),
+            "idempotency_key": _identifier(self.idempotency_key, "idempotency_key"),
+            "policy_digest": _digest(self.policy_digest, "policy_digest"),
+        }
+        if self.operation not in TASK_OPERATIONS:
+            raise EdgeContractError("task operation is not approved")
+        if self.classification not in TASK_CLASSIFICATIONS:
+            raise EdgeContractError("task classification is invalid")
+        for field, value in values.items():
+            object.__setattr__(self, field, value)
+
+    @property
+    def task_type(self) -> str:
+        return self.operation
+
+    @classmethod
+    def from_mapping(cls, value: Mapping[str, object]) -> EdgeTaskContract:
+        data = _strict_mapping(value, label="task contract")
+        required = frozenset(
+            {
+                "task_id",
+                "gateway_id",
+                "environment",
+                "network_zone",
+                "purpose",
+                "classification",
+                "contract_version",
+                "deadline_at",
+                "attempt",
+                "idempotency_key",
+                "policy_digest",
+            }
+        )
+        _require_exact_properties(
+            data,
+            required=required,
+            optional=frozenset({"task_type", "operation"}),
+        )
+        if ("task_type" in data) == ("operation" in data):
+            raise EdgeContractError(
+                "task contract requires exactly one operation property"
+            )
+        return cls(
+            task_id=data["task_id"],  # type: ignore[arg-type]
+            gateway_id=data["gateway_id"],  # type: ignore[arg-type]
+            environment=data["environment"],  # type: ignore[arg-type]
+            network_zone=data["network_zone"],  # type: ignore[arg-type]
+            purpose=data["purpose"],  # type: ignore[arg-type]
+            classification=data["classification"],  # type: ignore[arg-type]
+            operation=data.get("task_type", data.get("operation")),  # type: ignore[arg-type]
+            contract_version=data["contract_version"],  # type: ignore[arg-type]
+            deadline_at=data["deadline_at"],  # type: ignore[arg-type]
+            attempt=data["attempt"],  # type: ignore[arg-type]
+            idempotency_key=data["idempotency_key"],  # type: ignore[arg-type]
+            policy_digest=data["policy_digest"],  # type: ignore[arg-type]
+        )
+
+    def to_mapping(self) -> dict[str, object]:
+        return {
+            "task_id": self.task_id,
+            "gateway_id": self.gateway_id,
+            "environment": self.environment,
+            "network_zone": self.network_zone,
+            "purpose": self.purpose,
+            "classification": self.classification,
+            "task_type": self.operation,
+            "contract_version": self.contract_version,
+            "deadline_at": self.deadline_at,
+            "attempt": self.attempt,
+            "idempotency_key": self.idempotency_key,
+            "policy_digest": self.policy_digest,
+        }
+
+    @property
+    def digest(self) -> str:
+        return canonical_sha256(self.to_mapping())
+
+
+@dataclass(frozen=True, slots=True)
+class SignedTaskEnvelope:
+    """Control-plane authority over an exact, context-bound task contract."""
+
+    task: EdgeTaskContract
+    authority_key_id: str
+    signature_algorithm: str
+    contract_digest: str
+    gateway_id: str
+    environment: str
+    network_zone: str
+    policy_digest: str
+    purpose: str
+    issued_at: str
+    expires_at: str
+    signature: str
+    _verified: bool = dataclass_field(default=False, init=False, repr=False, compare=False)
+
+    _UNSIGNED_FIELDS: ClassVar[frozenset[str]] = frozenset(
+        {
+            "task", "authority_key_id", "signature_algorithm", "contract_digest",
+            "gateway_id", "environment", "network_zone", "policy_digest", "purpose",
+            "issued_at", "expires_at",
+        }
+    )
+
+    def __post_init__(self) -> None:
+        if not isinstance(self.task, EdgeTaskContract):
+            raise EdgeContractError("signed task envelope requires a task contract")
+        normalized_task = EdgeTaskContract.from_mapping(self.task.to_mapping())
+        unsigned = {
+            "task": normalized_task.to_mapping(),
+            "authority_key_id": self.authority_key_id,
+            "signature_algorithm": self.signature_algorithm,
+            "contract_digest": self.contract_digest,
+            "gateway_id": self.gateway_id,
+            "environment": self.environment,
+            "network_zone": self.network_zone,
+            "policy_digest": self.policy_digest,
+            "purpose": self.purpose,
+            "issued_at": self.issued_at,
+            "expires_at": self.expires_at,
+        }
+        self.canonical_unsigned_bytes(unsigned)
+        if not isinstance(self.signature, str) or not _ED25519_SIGNATURE.fullmatch(self.signature):
+            raise EdgeContractError("signature must be a lowercase Ed25519 signature")
+        object.__setattr__(self, "task", normalized_task)
+
+    @classmethod
+    def canonical_unsigned_bytes(cls, value: Mapping[str, object]) -> bytes:
+        data = _strict_mapping(value, label="signed task envelope")
+        _require_exact_properties(data, required=cls._UNSIGNED_FIELDS)
+        task_raw = data["task"]
+        if isinstance(task_raw, EdgeTaskContract):
+            task_raw = task_raw.to_mapping()
+        task = EdgeTaskContract.from_mapping(task_raw)  # type: ignore[arg-type]
+        normalized = {
+            "task": task.to_mapping(),
+            "authority_key_id": _identifier(data["authority_key_id"], "authority_key_id"),
+            "signature_algorithm": data["signature_algorithm"],
+            "contract_digest": _digest(data["contract_digest"], "contract_digest"),
+            "gateway_id": _identifier(data["gateway_id"], "gateway_id"),
+            "environment": _identifier(data["environment"], "environment"),
+            "network_zone": _identifier(data["network_zone"], "network_zone"),
+            "policy_digest": _digest(data["policy_digest"], "policy_digest"),
+            "purpose": _identifier(data["purpose"], "purpose"),
+            "issued_at": _timestamp(data["issued_at"], "issued_at"),
+            "expires_at": _timestamp(data["expires_at"], "expires_at"),
+        }
+        if normalized["signature_algorithm"] != "Ed25519":
+            raise EdgeContractError("signature_algorithm is not supported")
+        for field in ("gateway_id", "environment", "network_zone", "policy_digest", "purpose"):
+            if normalized[field] != task.to_mapping()[field]:
+                raise EdgeContractError(f"signed authority {field} does not bind the task")
+        if normalized["contract_digest"] != task.digest:
+            raise EdgeContractError("contract_digest does not bind the task")
+        issued = datetime.fromisoformat(str(normalized["issued_at"]).replace("Z", "+00:00"))
+        expires = datetime.fromisoformat(str(normalized["expires_at"]).replace("Z", "+00:00"))
+        deadline = datetime.fromisoformat(task.deadline_at.replace("Z", "+00:00"))
+        if expires <= issued or expires > deadline:
+            raise EdgeContractError("signed authority validity window is invalid")
+        return canonical_json_bytes(normalized)
+
+    @classmethod
+    def verify_mapping(
+        cls,
+        value: Mapping[str, object],
+        *,
+        authority_keys: Mapping[str, bytes | Ed25519PublicKey],
+        now: datetime,
+        allowed_future_skew_seconds: int = 0,
+    ) -> SignedTaskEnvelope:
+        data = _strict_mapping(value, label="signed task envelope")
+        _require_exact_properties(data, required=cls._UNSIGNED_FIELDS | {"signature"})
+        if not isinstance(now, datetime) or now.tzinfo is None or now.utcoffset() is None:
+            raise EdgeContractError("verification time must be timezone-aware")
+        if (
+            isinstance(allowed_future_skew_seconds, bool)
+            or not isinstance(allowed_future_skew_seconds, int)
+            or not 0 <= allowed_future_skew_seconds <= 300
+        ):
+            raise EdgeContractError("task authority clock skew is invalid")
+        signature = _text(data["signature"], "signature", maximum=128)
+        if not _ED25519_SIGNATURE.fullmatch(signature):
+            raise EdgeContractError("signature must be a lowercase Ed25519 signature")
+        unsigned = {key: data[key] for key in cls._UNSIGNED_FIELDS}
+        signed_bytes = cls.canonical_unsigned_bytes(unsigned)
+        key_id = _identifier(data["authority_key_id"], "authority_key_id")
+        key = authority_keys.get(key_id)
+        if key is None:
+            raise EdgeContractError("task authority key is not trusted")
+        try:
+            public_key = (
+                key if isinstance(key, Ed25519PublicKey)
+                else Ed25519PublicKey.from_public_bytes(bytes(key))
+            )
+            public_key.verify(bytes.fromhex(signature), signed_bytes)
+        except (InvalidSignature, TypeError, ValueError) as exc:
+            raise EdgeContractError("task authority signature is invalid") from exc
+        issued_at = _timestamp(data["issued_at"], "issued_at")
+        expires_at = _timestamp(data["expires_at"], "expires_at")
+        instant = now.astimezone(UTC)
+        issued = datetime.fromisoformat(issued_at.replace("Z", "+00:00"))
+        expires = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
+        if instant + timedelta(seconds=allowed_future_skew_seconds) < issued:
+            raise EdgeContractError("signed task is not yet valid")
+        if instant >= expires:
+            raise EdgeContractError("signed task authority has expired")
+        task = EdgeTaskContract.from_mapping(data["task"])  # type: ignore[arg-type]
+        envelope = cls(
+            task=task,
+            authority_key_id=key_id,
+            signature_algorithm="Ed25519",
+            contract_digest=task.digest,
+            gateway_id=task.gateway_id,
+            environment=task.environment,
+            network_zone=task.network_zone,
+            policy_digest=task.policy_digest,
+            purpose=task.purpose,
+            issued_at=issued_at,
+            expires_at=expires_at,
+            signature=signature,
+        )
+        object.__setattr__(envelope, "_verified", True)
+        return envelope
+
+    @property
+    def is_verified(self) -> bool:
+        return self._verified
+
+    def to_mapping(self) -> dict[str, object]:
+        return {
+            "task": self.task.to_mapping(),
+            "authority_key_id": self.authority_key_id,
+            "signature_algorithm": self.signature_algorithm,
+            "contract_digest": self.contract_digest,
+            "gateway_id": self.gateway_id,
+            "environment": self.environment,
+            "network_zone": self.network_zone,
+            "policy_digest": self.policy_digest,
+            "purpose": self.purpose,
+            "issued_at": self.issued_at,
+            "expires_at": self.expires_at,
+            "signature": self.signature,
+        }
+
+    @property
+    def digest(self) -> str:
+        return canonical_sha256(self.to_mapping())
+
+
+@dataclass(frozen=True, slots=True)
+class EdgeEventContract:
+    event_id: str
+    task_id: str
+    gateway_id: str
+    environment: str
+    network_zone: str
+    purpose: str
+    classification: str
+    contract_version: int
+    occurred_at: str
+    attempt: int
+    idempotency_key: str
+    policy_digest: str
+    payload: Mapping[str, object]
+
+    def __post_init__(self) -> None:
+        payload = _strict_mapping(self.payload, label="event payload")
+        payload_copy = _json_copy(payload)
+        if not isinstance(payload_copy, dict):
+            raise EdgeContractError("event payload must be a mapping")
+        values = {
+            "task_id": _identifier(self.task_id, "task_id"),
+            "gateway_id": _identifier(self.gateway_id, "gateway_id"),
+            "environment": _identifier(self.environment, "environment"),
+            "network_zone": _identifier(self.network_zone, "network_zone"),
+            "purpose": _identifier(self.purpose, "purpose"),
+            "contract_version": _version(self.contract_version),
+            "occurred_at": _timestamp(self.occurred_at, "occurred_at"),
+            "attempt": _attempt(self.attempt),
+            "idempotency_key": _identifier(self.idempotency_key, "idempotency_key"),
+            "policy_digest": _digest(self.policy_digest, "policy_digest"),
+        }
+        if self.classification not in EVENT_CLASSIFICATIONS:
+            raise EdgeContractError("event classification is not approved")
+        identity = {
+            "task_id": values["task_id"],
+            "gateway_id": values["gateway_id"],
+            "environment": values["environment"],
+            "network_zone": values["network_zone"],
+            "purpose": values["purpose"],
+            "classification": self.classification,
+            "contract_version": values["contract_version"],
+            "occurred_at": values["occurred_at"],
+            "attempt": values["attempt"],
+            "idempotency_key": values["idempotency_key"],
+            "policy_digest": values["policy_digest"],
+            "payload": payload_copy,
+        }
+        expected_id = stable_event_id(identity)
+        if self.event_id != expected_id:
+            raise EdgeContractError("event_id is not the stable event identifier")
+        object.__setattr__(self, "event_id", expected_id)
+        object.__setattr__(self, "payload", _deep_freeze(payload_copy))
+        for field, value in values.items():
+            object.__setattr__(self, field, value)
+
+    @classmethod
+    def from_mapping(cls, value: Mapping[str, object]) -> EdgeEventContract:
+        data = _strict_mapping(value, label="event contract")
+        required = frozenset(
+            {
+                "event_id",
+                "task_id",
+                "gateway_id",
+                "environment",
+                "network_zone",
+                "purpose",
+                "classification",
+                "contract_version",
+                "occurred_at",
+                "attempt",
+                "idempotency_key",
+                "policy_digest",
+                "payload",
+            }
+        )
+        _require_exact_properties(data, required=required)
+        return cls(**data)  # type: ignore[arg-type]
+
+    def to_mapping(self) -> dict[str, object]:
+        return {
+            "event_id": self.event_id,
+            "task_id": self.task_id,
+            "gateway_id": self.gateway_id,
+            "environment": self.environment,
+            "network_zone": self.network_zone,
+            "purpose": self.purpose,
+            "classification": self.classification,
+            "contract_version": self.contract_version,
+            "occurred_at": self.occurred_at,
+            "attempt": self.attempt,
+            "idempotency_key": self.idempotency_key,
+            "policy_digest": self.policy_digest,
+            "payload": _deep_thaw(self.payload),
+        }
+
+    @property
+    def digest(self) -> str:
+        return canonical_sha256(self.to_mapping())

+ 475 - 0
app/core/edge_gateway/policy.py

@@ -0,0 +1,475 @@
+"""Default-deny egress and retention policy for enterprise edge events."""
+
+from __future__ import annotations
+
+import ipaddress
+import re
+import unicodedata
+from collections.abc import Mapping
+from urllib.parse import urlsplit
+
+from .contracts import EdgeContractError, preflight_json, strict_json_bytes
+
+
+class EdgePolicyError(ValueError):
+    """Raised when content or transport violates the edge boundary."""
+
+
+EDGE_ONLY = {"raw", "recent_detail", "restricted"}
+CONTROL_PLANE_ALLOWED = {
+    "desensitized_metadata",
+    "statistics",
+    "lineage",
+    "evidence",
+    "health_summary",
+    "diagnostic_summary",
+}
+RETENTION_DAYS = {
+    "raw": 0,
+    "recent_detail": 365,
+    "metadata": 1095,
+    "evidence": 2190,
+}
+
+DEFAULT_BYTE_LIMITS = {
+    "desensitized_metadata": 65_536,
+    "statistics": 32_768,
+    "lineage": 65_536,
+    "evidence": 131_072,
+    "health_summary": 8_192,
+    "diagnostic_summary": 16_384,
+}
+DEFAULT_COUNT_LIMITS = {
+    "desensitized_metadata": 2_000,
+    "statistics": 1_000,
+    "lineage": 2_000,
+    "evidence": 4_000,
+    "health_summary": 256,
+    "diagnostic_summary": 512,
+}
+
+_SENSITIVE_KEYS = {
+    "api_key",
+    "apikey",
+    "authorization",
+    "access_token",
+    "cookie",
+    "credential",
+    "credentials",
+    "password",
+    "passwd",
+    "private_key",
+    "query",
+    "query_text",
+    "raw_record",
+    "raw_records",
+    "raw_row",
+    "raw_rows",
+    "secret",
+    "sql",
+    "sql_text",
+    "statement",
+    "token",
+}
+_SENSITIVE_KEY_PARTS = ("password", "passwd", "secret", "private_key", "api_key")
+_RAW_ROW_CONTAINER_KEYS = {
+    "data",
+    "items",
+    "item",
+    "record",
+    "records",
+    "result",
+    "row",
+    "rows",
+    "sample",
+    "samples",
+}
+_PRIVATE_KEY = re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----", re.IGNORECASE)
+_BEARER = re.compile(r"\bBearer\s+[A-Za-z0-9._~+/=-]{8,}", re.IGNORECASE)
+_CREDENTIAL_ASSIGNMENT = re.compile(
+    r"\b(?:[a-z0-9]+[_-])*(?:password|passwd|pwd|secret|token|api[_-]?key|"
+    r"authorization|credential)"
+    r"\b\s*(?:=|:)\s*[^\s,;]+",
+    re.IGNORECASE,
+)
+_SQL = re.compile(
+    r"(?:"
+    r"\bSELECT\b.{0,4096}?\bFROM\b"
+    r"|\bINSERT\s+INTO\b"
+    r"|\bUPDATE\b.{0,4096}?\bSET\b"
+    r"|\bDELETE\s+FROM\b"
+    r"|\bMERGE(?:\s+INTO)?\b"
+    r"|\b(?:CREATE|ALTER|DROP|TRUNCATE)\s+"
+    r"(?:TABLE|VIEW|SCHEMA|DATABASE|INDEX|FUNCTION|PROCEDURE)\b"
+    r"|\b(?:GRANT|REVOKE)\b.{0,4096}?\b(?:TO|FROM)\b"
+    r"|\bCOPY\b"
+    r")",
+    re.IGNORECASE | re.DOTALL,
+)
+
+
+def _normalized_key(value: str) -> str:
+    compatible = unicodedata.normalize("NFKC", value)
+    words = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", compatible)
+    return re.sub(r"[^a-z0-9]+", "_", words.lower()).strip("_")
+
+
+def _is_sensitive_key(value: str) -> bool:
+    folded = value.replace("_", "")
+    return (
+        value in _SENSITIVE_KEYS
+        or folded
+        in {
+            "accesstoken",
+            "apikey",
+            "authorization",
+            "clientsecret",
+            "credential",
+            "credentials",
+            "password",
+            "passwd",
+            "privatekey",
+            "rawrecords",
+            "rawrows",
+            "secret",
+            "token",
+        }
+        or any(part in value for part in _SENSITIVE_KEY_PARTS)
+        or value.startswith(("raw_", "recent_detail_", "token_"))
+        or value.endswith("_token")
+    )
+
+
+def _is_raw_row_container(key: str, value: object) -> bool:
+    return (
+        key in _RAW_ROW_CONTAINER_KEYS
+        or key.endswith(("_rows", "_records"))
+    ) and (
+        isinstance(value, Mapping)
+        or (
+            isinstance(value, (list, tuple))
+            and any(isinstance(child, Mapping) for child in value)
+        )
+    )
+
+
+def _contains_obfuscated_sql(value: str) -> bool:
+    compatible = unicodedata.normalize("NFKC", value)
+    without_comments = re.sub(
+        r"/\*.*?\*/|--[^\r\n]*", "", compatible, flags=re.DOTALL
+    )
+    normalized = re.sub(r"\s+", " ", without_comments).upper()
+    return bool(
+        re.search(
+            r"(?:\bSELECT\b.{0,4096}\bFROM\b|\bINSERT\s+INTO\b|"
+            r"\bUPDATE\b.{0,4096}\bSET\b|\bDELETE\s+FROM\b|"
+            r"\bMERGE(?:\s+INTO)?\b|\b(?:CREATE|ALTER|DROP|TRUNCATE)\s+"
+            r"(?:TABLE|VIEW|SCHEMA|DATABASE|INDEX|FUNCTION|PROCEDURE)\b|"
+            r"\b(?:GRANT|REVOKE)\b.{0,4096}\b(?:TO|FROM)\b|\bCOPY\b|"
+            r"\bEXEC(?:UTE)?\b\s+[A-Z_\[][A-Z0-9_.$\[\]]*|"
+            r"\bCALL\b\s+[A-Z_][A-Z0-9_.$]*\s*\()",
+            normalized,
+        )
+    )
+
+
+def _approved_hosts(values: set[str] | frozenset[str], label: str) -> frozenset[str]:
+    if not isinstance(values, (set, frozenset)):
+        raise ValueError(f"{label} must be an explicit set")
+    approved: set[str] = set()
+    for value in values:
+        if (
+            not isinstance(value, str)
+            or not value
+            or value != value.lower()
+            or value.endswith(".")
+            or ":" in value
+            or "/" in value
+            or "@" in value
+        ):
+            raise ValueError(f"{label} contains an invalid host")
+        try:
+            value.encode("ascii")
+            ipaddress.ip_address(value)
+        except UnicodeEncodeError as exc:
+            raise ValueError(f"{label} contains an invalid host") from exc
+        except ValueError:
+            pass
+        else:
+            raise ValueError(f"{label} cannot contain an IP literal")
+        if not re.fullmatch(
+            r"(?=.{1,253}\Z)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*"
+            r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?",
+            value,
+        ):
+            raise ValueError(f"{label} contains an invalid host")
+        approved.add(value)
+    return frozenset(approved)
+
+
+def _approved_origins(
+    values: set[str] | frozenset[str] | None,
+    hosts: frozenset[str],
+    label: str,
+) -> frozenset[str]:
+    if values is None:
+        return frozenset(f"https://{host}" for host in hosts)
+    if not isinstance(values, (set, frozenset)):
+        raise ValueError(f"{label} must be an explicit set")
+    origins: set[str] = set()
+    for value in values:
+        if not isinstance(value, str):
+            raise ValueError(f"{label} contains an invalid origin")
+        try:
+            parsed = urlsplit(value)
+            port = parsed.port
+        except ValueError as exc:
+            raise ValueError(f"{label} contains an invalid origin") from exc
+        if (
+            parsed.scheme != "https"
+            or parsed.hostname not in hosts
+            or parsed.username is not None
+            or parsed.password is not None
+            or parsed.path not in {"", "/"}
+            or parsed.query
+            or parsed.fragment
+            or port is None and parsed.netloc.endswith(":")
+        ):
+            raise ValueError(f"{label} contains an invalid origin")
+        effective_port = port or 443
+        canonical = (
+            f"https://{parsed.hostname}"
+            if effective_port == 443
+            else f"https://{parsed.hostname}:{effective_port}"
+        )
+        if value.rstrip("/") != canonical:
+            raise ValueError(f"{label} contains a non-canonical origin")
+        origins.add(canonical)
+    return frozenset(origins)
+
+
+def _limit_map(
+    defaults: Mapping[str, int], overrides: Mapping[str, int] | None, label: str
+) -> dict[str, int]:
+    result = dict(defaults)
+    if overrides is None:
+        return result
+    if not isinstance(overrides, Mapping):
+        raise ValueError(f"{label} must be a mapping")
+    unknown = set(overrides) - CONTROL_PLANE_ALLOWED
+    if unknown:
+        raise ValueError(f"{label} contains an unknown classification")
+    for classification, limit in overrides.items():
+        if isinstance(limit, bool) or not isinstance(limit, int) or limit < 1:
+            raise ValueError(f"{label} values must be positive integers")
+        result[classification] = limit
+    return result
+
+
+class EdgeEgressPolicy:
+    def __init__(
+        self,
+        *,
+        allowed_control_hosts: set[str] | frozenset[str],
+        allowed_proxy_hosts: set[str] | frozenset[str] | None = None,
+        allowed_control_origins: set[str] | frozenset[str] | None = None,
+        allowed_proxy_origins: set[str] | frozenset[str] | None = None,
+        byte_limits: Mapping[str, int] | None = None,
+        count_limits: Mapping[str, int] | None = None,
+        max_depth: int = 12,
+        max_string_bytes: int = 4_096,
+    ) -> None:
+        self.allowed_control_hosts = _approved_hosts(
+            allowed_control_hosts, "allowed_control_hosts"
+        )
+        self.allowed_proxy_hosts = _approved_hosts(
+            allowed_proxy_hosts or set(), "allowed_proxy_hosts"
+        )
+        self.allowed_control_origins = _approved_origins(
+            allowed_control_origins,
+            self.allowed_control_hosts,
+            "allowed_control_origins",
+        )
+        self.allowed_proxy_origins = _approved_origins(
+            allowed_proxy_origins,
+            self.allowed_proxy_hosts,
+            "allowed_proxy_origins",
+        )
+        self.byte_limits = _limit_map(
+            DEFAULT_BYTE_LIMITS, byte_limits, "byte_limits"
+        )
+        self.count_limits = _limit_map(
+            DEFAULT_COUNT_LIMITS, count_limits, "count_limits"
+        )
+        if isinstance(max_depth, bool) or not isinstance(max_depth, int) or max_depth < 1:
+            raise ValueError("max_depth must be a positive integer")
+        if (
+            isinstance(max_string_bytes, bool)
+            or not isinstance(max_string_bytes, int)
+            or max_string_bytes < 1
+        ):
+            raise ValueError("max_string_bytes must be a positive integer")
+        self.max_depth = max_depth
+        self.max_string_bytes = max_string_bytes
+
+    def approve_event(self, event: Mapping[str, object]) -> dict[str, object]:
+        if not isinstance(event, Mapping):
+            raise EdgePolicyError("event must be a mapping")
+        unknown = set(event) - {
+            "event_id",
+            "task_id",
+            "gateway_id",
+            "environment",
+            "network_zone",
+            "purpose",
+            "classification",
+            "contract_version",
+            "occurred_at",
+            "attempt",
+            "idempotency_key",
+            "policy_digest",
+            "payload",
+        }
+        if unknown:
+            raise EdgePolicyError("event contains unapproved properties")
+        classification = event.get("classification")
+        if classification in EDGE_ONLY:
+            raise EdgePolicyError("classification is edge-only")
+        if classification not in CONTROL_PLANE_ALLOWED:
+            raise EdgePolicyError("classification is not control-plane approved")
+        payload = event.get("payload")
+        if not isinstance(payload, Mapping):
+            raise EdgePolicyError("approved event payload must be a mapping")
+        self.validate_approved_payload(str(classification), payload)
+        try:
+            return dict(event)
+        except (TypeError, ValueError) as exc:  # pragma: no cover - Mapping guard
+            raise EdgePolicyError("event is invalid") from exc
+
+    def validate_approved_payload(
+        self, classification: str, payload: Mapping[str, object]
+    ) -> None:
+        if classification not in CONTROL_PLANE_ALLOWED:
+            raise EdgePolicyError("classification is not control-plane approved")
+        try:
+            preflight_json(
+                payload,
+                max_depth=self.max_depth,
+                max_nodes=self.count_limits[classification] + 1,
+                max_string_bytes=self.max_string_bytes,
+                max_total_bytes=self.byte_limits[classification],
+            )
+            encoded = strict_json_bytes(payload)
+        except EdgeContractError as exc:
+            detail = str(exc)
+            if "node count" in detail:
+                detail = "payload exceeds its independent item count limit"
+            elif "string exceeds" in detail:
+                detail = "payload string exceeds the string limit"
+            elif "depth" in detail:
+                detail = "payload exceeds the maximum depth"
+            elif "byte limit" in detail:
+                detail = "payload exceeds its independent byte limit"
+            raise EdgePolicyError(detail) from exc
+        if len(encoded) > self.byte_limits[classification]:
+            raise EdgePolicyError("payload exceeds its independent byte limit")
+        item_count = self._count_items(payload)
+        if item_count > self.count_limits[classification]:
+            raise EdgePolicyError("payload exceeds its independent item count limit")
+        self._inspect(payload, depth=1)
+
+    def _count_items(self, value: object) -> int:
+        if isinstance(value, Mapping):
+            return len(value) + sum(self._count_items(item) for item in value.values())
+        if isinstance(value, (list, tuple)):
+            return len(value) + sum(self._count_items(item) for item in value)
+        return 0
+
+    def _inspect(self, value: object, *, depth: int) -> None:
+        if depth > self.max_depth:
+            raise EdgePolicyError("payload exceeds the maximum depth")
+        if isinstance(value, Mapping):
+            for key, item in value.items():
+                if not isinstance(key, str):
+                    raise EdgePolicyError("payload contains a non-string key")
+                normalized = _normalized_key(key)
+                if _is_sensitive_key(normalized):
+                    raise EdgePolicyError("payload contains sensitive content")
+                if _is_raw_row_container(normalized, item):
+                    raise EdgePolicyError("payload contains sensitive raw-row content")
+                self._inspect(item, depth=depth + 1)
+            return
+        if isinstance(value, (list, tuple)):
+            for item in value:
+                self._inspect(item, depth=depth + 1)
+            return
+        if isinstance(value, str):
+            compatible = unicodedata.normalize("NFKC", value)
+            if len(value.encode("utf-8")) > self.max_string_bytes:
+                raise EdgePolicyError("payload string exceeds the string limit")
+            if (
+                _PRIVATE_KEY.search(compatible)
+                or _BEARER.search(compatible)
+                or _CREDENTIAL_ASSIGNMENT.search(compatible)
+                or _SQL.search(compatible)
+                or _contains_obfuscated_sql(compatible)
+            ):
+                raise EdgePolicyError("payload contains sensitive content")
+
+    def validate_destination(
+        self, control_url: str, *, proxy_url: str | None = None
+    ) -> None:
+        self._validate_url(
+            control_url,
+            approved_hosts=self.allowed_control_hosts,
+            approved_origins=self.allowed_control_origins,
+            label="destination",
+        )
+        if proxy_url is not None:
+            self._validate_url(
+                proxy_url,
+                approved_hosts=self.allowed_proxy_hosts,
+                approved_origins=self.allowed_proxy_origins,
+                label="proxy",
+            )
+
+    @staticmethod
+    def _validate_url(
+        url: str,
+        *,
+        approved_hosts: frozenset[str],
+        approved_origins: frozenset[str],
+        label: str,
+    ) -> None:
+        if not isinstance(url, str):
+            raise EdgePolicyError(f"{label} is not approved")
+        try:
+            parsed = urlsplit(url)
+            port = parsed.port
+        except ValueError as exc:
+            raise EdgePolicyError(f"{label} is not approved") from exc
+        if (
+            parsed.scheme != "https"
+            or not parsed.hostname
+            or parsed.hostname not in approved_hosts
+            or parsed.username is not None
+            or parsed.password is not None
+            or parsed.fragment
+            or port is None and parsed.netloc.endswith(":")
+        ):
+            raise EdgePolicyError(f"{label} is not approved")
+        effective_port = port or 443
+        origin = (
+            f"https://{parsed.hostname}"
+            if effective_port == 443
+            else f"https://{parsed.hostname}:{effective_port}"
+        )
+        if origin not in approved_origins:
+            raise EdgePolicyError(f"{label} origin is not approved")
+
+    @staticmethod
+    def retention_days(classification: str) -> int:
+        try:
+            return RETENTION_DAYS[classification]
+        except (KeyError, TypeError) as exc:
+            raise EdgePolicyError("retention classification is invalid") from exc

+ 2189 - 0
app/core/edge_gateway/queue.py

@@ -0,0 +1,2189 @@
+"""Durable SQLite task and outbound-event queue for an offline edge gateway."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import re
+import sqlite3
+import uuid
+from collections.abc import Callable, Iterator, Mapping
+from contextlib import contextmanager
+from dataclasses import dataclass
+from datetime import UTC, datetime, timedelta
+from pathlib import Path
+from types import MappingProxyType
+
+from .contracts import (
+    EdgeContractError,
+    EdgeEventContract,
+    EdgeTaskContract,
+    SignedTaskEnvelope,
+    canonical_sha256,
+    canonical_timestamp,
+    strict_json_bytes,
+)
+from .policy import EdgeEgressPolicy, EdgePolicyError
+
+
+class EdgeQueueError(RuntimeError):
+    """Base error for durable edge queue operations."""
+
+
+class EdgeQueueConflictError(EdgeQueueError):
+    """Raised when an existing stable ID is replayed with different content."""
+
+
+class EdgeQueueLeaseError(EdgeQueueError):
+    """Raised when a mutation is attempted without the current lease token."""
+
+
+class EdgeQueueSchemaError(EdgeQueueError):
+    """Raised when an edge queue database schema cannot be trusted."""
+
+
+SCHEMA_VERSION = 2
+
+_V1_TASK_TABLE_SQL = """CREATE TABLE edge_tasks (
+    task_id TEXT PRIMARY KEY,
+    digest TEXT NOT NULL,
+    task_json TEXT NOT NULL,
+    status TEXT NOT NULL CHECK (
+        status IN ('pending', 'leased', 'completed', 'failed', 'cancelled')
+    ),
+    attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
+    available_at TEXT NOT NULL,
+    deadline_at TEXT NOT NULL,
+    deadline_epoch_us INTEGER NOT NULL,
+    lease_owner TEXT,
+    lease_token TEXT,
+    lease_expires_at TEXT,
+    error_code TEXT,
+    created_at TEXT NOT NULL,
+    updated_at TEXT NOT NULL
+)"""
+_TASK_TABLE_SQL = _V1_TASK_TABLE_SQL.rstrip()[:-1].rstrip() + ",\n" + (
+    "    authority_key_id TEXT,\n"
+    "    authority_digest TEXT,\n"
+    "    authority_json TEXT,\n"
+    "    remote_lease_token_digest TEXT,\n"
+    "    remote_lease_ciphertext TEXT,\n"
+    "    remote_lease_expires_at TEXT,\n"
+    "    remote_attempt INTEGER CHECK (remote_attempt IS NULL OR remote_attempt >= 1)\n"
+    ")"
+)
+_TASK_INDEX_SQL = """CREATE INDEX edge_tasks_claim_idx ON edge_tasks(
+    status, deadline_epoch_us, available_at, lease_expires_at, created_at
+)"""
+_V1_EVENT_TABLE_SQL = """CREATE TABLE edge_outbound_events (
+    event_id TEXT PRIMARY KEY,
+    digest TEXT NOT NULL,
+    event_json TEXT NOT NULL,
+    status TEXT NOT NULL CHECK (
+        status IN ('pending', 'sending', 'acknowledged', 'failed')
+    ),
+    attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
+    available_at TEXT NOT NULL,
+    lease_owner TEXT,
+    lease_token TEXT,
+    lease_expires_at TEXT,
+    error_code TEXT,
+    acknowledgement_json TEXT,
+    ack_digest TEXT,
+    lease_token_digest TEXT,
+    created_at TEXT NOT NULL,
+    updated_at TEXT NOT NULL
+)"""
+_EVENT_TABLE_SQL = _V1_EVENT_TABLE_SQL.rstrip()[:-1].rstrip() + ",\n" + (
+    "    task_digest TEXT,\n"
+    "    remote_lease_token_digest TEXT,\n"
+    "    control_lease_token_digest TEXT\n"
+    ")"
+)
+_EVENT_INDEX_SQL = """CREATE INDEX edge_events_claim_idx ON edge_outbound_events(
+    status, available_at, lease_expires_at, created_at
+)"""
+_ARTIFACT_TABLE_SQL = """CREATE TABLE edge_local_artifacts (
+    artifact_digest TEXT PRIMARY KEY,
+    artifact_ref TEXT,
+    artifact_ref_hash TEXT NOT NULL,
+    classification TEXT NOT NULL CHECK (
+        classification IN ('raw', 'recent_detail', 'restricted',
+                           'desensitized_metadata', 'statistics', 'lineage', 'evidence')
+    ),
+    retention_until TEXT NOT NULL,
+    task_id TEXT NOT NULL,
+    event_id TEXT NOT NULL,
+    cleanup_status TEXT NOT NULL CHECK (
+        cleanup_status IN ('pending', 'deleting', 'deleted', 'failed')
+    ),
+    attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
+    available_at TEXT NOT NULL,
+    lease_owner TEXT,
+    lease_token TEXT,
+    lease_expires_at TEXT,
+    error_code TEXT,
+    deletion_receipt_json TEXT,
+    created_at TEXT NOT NULL,
+    updated_at TEXT NOT NULL,
+    FOREIGN KEY(task_id) REFERENCES edge_tasks(task_id),
+    FOREIGN KEY(event_id) REFERENCES edge_outbound_events(event_id)
+)"""
+_ARTIFACT_INDEX_SQL = """CREATE INDEX edge_artifacts_task_idx ON edge_local_artifacts(
+    cleanup_status, retention_until, available_at, lease_expires_at, created_at
+)"""
+_RELEASE_TABLE_SQL = """CREATE TABLE edge_release_state (
+    release_id TEXT PRIMARY KEY,
+    manifest_digest TEXT NOT NULL,
+    version TEXT NOT NULL,
+    rollback_version TEXT NOT NULL,
+    status TEXT NOT NULL CHECK (
+        status IN ('offered', 'accepted', 'candidate', 'installed', 'failed', 'rolled_back')
+    ),
+    previous_version TEXT,
+    current_version TEXT,
+    updated_at TEXT NOT NULL
+)"""
+_RELEASE_INDEX_SQL = """CREATE INDEX edge_release_status_idx ON edge_release_state(
+    status, updated_at, release_id
+)"""
+_OUTCOME_TABLE_SQL = """CREATE TABLE edge_task_outcomes (
+    task_id TEXT PRIMARY KEY,
+    digest TEXT NOT NULL,
+    outcome_json TEXT NOT NULL,
+    status TEXT NOT NULL CHECK (
+        status IN ('pending', 'sending', 'acknowledged', 'failed')
+    ),
+    attempt_count INTEGER NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
+    available_at TEXT NOT NULL,
+    lease_owner TEXT,
+    lease_token TEXT,
+    lease_expires_at TEXT,
+    error_code TEXT,
+    acknowledgement_json TEXT,
+    ack_digest TEXT,
+    remote_lease_token_digest TEXT NOT NULL,
+    created_at TEXT NOT NULL,
+    updated_at TEXT NOT NULL,
+    FOREIGN KEY(task_id) REFERENCES edge_tasks(task_id)
+)"""
+_OUTCOME_INDEX_SQL = """CREATE INDEX edge_outcomes_claim_idx ON edge_task_outcomes(
+    status, available_at, lease_expires_at, created_at
+)"""
+_EXPECTED_TABLE_INFO = {
+    "edge_tasks": (
+        ("task_id", "TEXT", 0, None, 1),
+        ("digest", "TEXT", 1, None, 0),
+        ("task_json", "TEXT", 1, None, 0),
+        ("status", "TEXT", 1, None, 0),
+        ("attempt_count", "INTEGER", 1, "0", 0),
+        ("available_at", "TEXT", 1, None, 0),
+        ("deadline_at", "TEXT", 1, None, 0),
+        ("deadline_epoch_us", "INTEGER", 1, None, 0),
+        ("lease_owner", "TEXT", 0, None, 0),
+        ("lease_token", "TEXT", 0, None, 0),
+        ("lease_expires_at", "TEXT", 0, None, 0),
+        ("error_code", "TEXT", 0, None, 0),
+        ("created_at", "TEXT", 1, None, 0),
+        ("updated_at", "TEXT", 1, None, 0),
+        ("authority_key_id", "TEXT", 0, None, 0),
+        ("authority_digest", "TEXT", 0, None, 0),
+        ("authority_json", "TEXT", 0, None, 0),
+        ("remote_lease_token_digest", "TEXT", 0, None, 0),
+        ("remote_lease_ciphertext", "TEXT", 0, None, 0),
+        ("remote_lease_expires_at", "TEXT", 0, None, 0),
+        ("remote_attempt", "INTEGER", 0, None, 0),
+    ),
+    "edge_outbound_events": (
+        ("event_id", "TEXT", 0, None, 1),
+        ("digest", "TEXT", 1, None, 0),
+        ("event_json", "TEXT", 1, None, 0),
+        ("status", "TEXT", 1, None, 0),
+        ("attempt_count", "INTEGER", 1, "0", 0),
+        ("available_at", "TEXT", 1, None, 0),
+        ("lease_owner", "TEXT", 0, None, 0),
+        ("lease_token", "TEXT", 0, None, 0),
+        ("lease_expires_at", "TEXT", 0, None, 0),
+        ("error_code", "TEXT", 0, None, 0),
+        ("acknowledgement_json", "TEXT", 0, None, 0),
+        ("ack_digest", "TEXT", 0, None, 0),
+        ("lease_token_digest", "TEXT", 0, None, 0),
+        ("created_at", "TEXT", 1, None, 0),
+        ("updated_at", "TEXT", 1, None, 0),
+        ("task_digest", "TEXT", 0, None, 0),
+        ("remote_lease_token_digest", "TEXT", 0, None, 0),
+        ("control_lease_token_digest", "TEXT", 0, None, 0),
+    ),
+    "edge_local_artifacts": (
+        ("artifact_digest", "TEXT", 0, None, 1),
+        ("artifact_ref", "TEXT", 0, None, 0),
+        ("artifact_ref_hash", "TEXT", 1, None, 0),
+        ("classification", "TEXT", 1, None, 0),
+        ("retention_until", "TEXT", 1, None, 0),
+        ("task_id", "TEXT", 1, None, 0),
+        ("event_id", "TEXT", 1, None, 0),
+        ("cleanup_status", "TEXT", 1, None, 0),
+        ("attempt_count", "INTEGER", 1, "0", 0),
+        ("available_at", "TEXT", 1, None, 0),
+        ("lease_owner", "TEXT", 0, None, 0),
+        ("lease_token", "TEXT", 0, None, 0),
+        ("lease_expires_at", "TEXT", 0, None, 0),
+        ("error_code", "TEXT", 0, None, 0),
+        ("deletion_receipt_json", "TEXT", 0, None, 0),
+        ("created_at", "TEXT", 1, None, 0),
+        ("updated_at", "TEXT", 1, None, 0),
+    ),
+    "edge_release_state": (
+        ("release_id", "TEXT", 0, None, 1),
+        ("manifest_digest", "TEXT", 1, None, 0),
+        ("version", "TEXT", 1, None, 0),
+        ("rollback_version", "TEXT", 1, None, 0),
+        ("status", "TEXT", 1, None, 0),
+        ("previous_version", "TEXT", 0, None, 0),
+        ("current_version", "TEXT", 0, None, 0),
+        ("updated_at", "TEXT", 1, None, 0),
+    ),
+    "edge_task_outcomes": (
+        ("task_id", "TEXT", 0, None, 1),
+        ("digest", "TEXT", 1, None, 0),
+        ("outcome_json", "TEXT", 1, None, 0),
+        ("status", "TEXT", 1, None, 0),
+        ("attempt_count", "INTEGER", 1, "0", 0),
+        ("available_at", "TEXT", 1, None, 0),
+        ("lease_owner", "TEXT", 0, None, 0),
+        ("lease_token", "TEXT", 0, None, 0),
+        ("lease_expires_at", "TEXT", 0, None, 0),
+        ("error_code", "TEXT", 0, None, 0),
+        ("acknowledgement_json", "TEXT", 0, None, 0),
+        ("ack_digest", "TEXT", 0, None, 0),
+        ("remote_lease_token_digest", "TEXT", 1, None, 0),
+        ("created_at", "TEXT", 1, None, 0),
+        ("updated_at", "TEXT", 1, None, 0),
+    ),
+}
+_EXPECTED_INDEXES = {
+    "edge_tasks": {
+        "edge_tasks_claim_idx": (
+            0,
+            "c",
+            0,
+            ("status", "deadline_epoch_us", "available_at", "lease_expires_at", "created_at"),
+        ),
+    },
+    "edge_outbound_events": {
+        "edge_events_claim_idx": (
+            0,
+            "c",
+            0,
+            ("status", "available_at", "lease_expires_at", "created_at"),
+        ),
+    },
+    "edge_local_artifacts": {
+        "edge_artifacts_task_idx": (
+            0, "c", 0,
+            ("cleanup_status", "retention_until", "available_at", "lease_expires_at", "created_at"),
+        ),
+    },
+    "edge_release_state": {
+        "edge_release_status_idx": (0, "c", 0, ("status", "updated_at", "release_id")),
+    },
+    "edge_task_outcomes": {
+        "edge_outcomes_claim_idx": (
+            0, "c", 0, ("status", "available_at", "lease_expires_at", "created_at")
+        ),
+    },
+}
+_EXPECTED_TABLE_SQL = {
+    "edge_tasks": _TASK_TABLE_SQL,
+    "edge_outbound_events": _EVENT_TABLE_SQL,
+    "edge_local_artifacts": _ARTIFACT_TABLE_SQL,
+    "edge_release_state": _RELEASE_TABLE_SQL,
+    "edge_task_outcomes": _OUTCOME_TABLE_SQL,
+}
+
+_V1_EXPECTED_TABLE_INFO = {
+    table: tuple(row for row in info if row[0] not in {
+        "authority_key_id", "authority_digest", "authority_json",
+        "remote_lease_token_digest", "remote_lease_ciphertext", "remote_lease_expires_at", "remote_attempt",
+        "task_digest", "control_lease_token_digest",
+    })
+    for table, info in _EXPECTED_TABLE_INFO.items()
+    if table not in {"edge_local_artifacts", "edge_release_state", "edge_task_outcomes"}
+}
+_V1_EXPECTED_TABLE_SQL = {
+    "edge_tasks": _V1_TASK_TABLE_SQL,
+    "edge_outbound_events": _V1_EVENT_TABLE_SQL,
+}
+
+
+@dataclass(frozen=True, slots=True)
+class QueuedTask:
+    task_id: str
+    digest: str
+    status: str
+    task: Mapping[str, object]
+    attempt_count: int
+    available_at: str
+    lease_owner: str | None
+    lease_token: str | None
+    lease_expires_at: str | None
+    error_code: str | None
+    authority_key_id: str | None
+    authority_digest: str | None
+    remote_lease_token_digest: str | None
+    remote_lease_expires_at: str | None
+    remote_attempt: int | None
+    created_at: str
+    updated_at: str
+
+
+@dataclass(frozen=True, slots=True)
+class QueuedEvent:
+    event_id: str
+    digest: str
+    status: str
+    event: Mapping[str, object]
+    attempt_count: int
+    available_at: str
+    lease_owner: str | None
+    lease_token: str | None
+    lease_expires_at: str | None
+    error_code: str | None
+    acknowledgement: Mapping[str, object] | None
+    task_digest: str | None
+    remote_lease_token_digest: str | None
+    control_lease_token_digest: str | None
+    created_at: str
+    updated_at: str
+
+
+@dataclass(frozen=True, slots=True)
+class LocalArtifact:
+    artifact_digest: str
+    artifact_ref: str | None
+    artifact_ref_hash: str
+    classification: str
+    retention_until: str
+    task_id: str
+    event_id: str
+    cleanup_status: str
+    attempt_count: int
+    available_at: str
+    lease_owner: str | None
+    lease_token: str | None
+    lease_expires_at: str | None
+    error_code: str | None
+    deletion_receipt: Mapping[str, object] | None
+    created_at: str
+    updated_at: str
+
+
+@dataclass(frozen=True, slots=True)
+class ReleaseState:
+    release_id: str
+    manifest_digest: str
+    version: str
+    rollback_version: str
+    status: str
+    previous_version: str | None
+    current_version: str | None
+    updated_at: str
+
+
+@dataclass(frozen=True, slots=True)
+class QueuedOutcome:
+    task_id: str
+    digest: str
+    status: str
+    outcome: Mapping[str, object]
+    attempt_count: int
+    available_at: str
+    lease_owner: str | None
+    lease_token: str | None
+    lease_expires_at: str | None
+    error_code: str | None
+    acknowledgement: Mapping[str, object] | None
+    remote_lease_token_digest: str
+    created_at: str
+    updated_at: str
+
+
+_ERROR_CODE = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
+_SENSITIVE_ERROR_PARTS = ("password", "passwd", "secret", "token", "credential")
+
+
+def _utc_now(clock: Callable[[], object]) -> datetime:
+    value = clock()
+    if isinstance(value, bool):
+        raise ValueError("queue clock returned an invalid value")
+    if isinstance(value, (int, float)):
+        value = datetime.fromtimestamp(value, tz=UTC)
+    if not isinstance(value, datetime):
+        raise ValueError("queue clock must return a datetime or timestamp")
+    if value.tzinfo is None or value.utcoffset() is None:
+        raise ValueError("queue clock must return a timezone-aware value")
+    return value.astimezone(UTC)
+
+
+def _timestamp(value: datetime) -> str:
+    utc = value.astimezone(UTC)
+    canonical = utc.strftime("%Y-%m-%dT%H:%M:%S")
+    if utc.microsecond:
+        canonical += f".{utc.microsecond:06d}".rstrip("0")
+    return f"{canonical}Z"
+
+
+def _epoch_microseconds(value: datetime) -> int:
+    epoch = datetime(1970, 1, 1, tzinfo=UTC)
+    delta = value.astimezone(UTC) - epoch
+    return (
+        (delta.days * 86_400 + delta.seconds) * 1_000_000
+        + delta.microseconds
+    )
+
+
+def _normalize_schema_sql(value: str | None) -> str:
+    if not isinstance(value, str):
+        return ""
+    normalized = re.sub(r"\s+", " ", value.strip()).lower()
+    return re.sub(r"\s*([(),])\s*", r"\1", normalized)
+
+
+def _freeze_mapping(value: Mapping[str, object]) -> Mapping[str, object]:
+    def freeze(item: object) -> object:
+        if isinstance(item, dict):
+            return MappingProxyType({key: freeze(child) for key, child in item.items()})
+        if isinstance(item, list):
+            return tuple(freeze(child) for child in item)
+        return item
+
+    return freeze(dict(value))  # type: ignore[return-value]
+
+
+class SqliteEdgeQueue:
+    """An on-disk, WAL-backed queue with fenced leases and exact replay."""
+
+    def __init__(
+        self,
+        db_path: str | Path,
+        *,
+        clock: Callable[[], object] | None = None,
+        lease_seconds: int = 60,
+        max_attempts: int = 5,
+        retry_base_seconds: int = 2,
+        retry_max_seconds: int = 300,
+        busy_timeout_ms: int = 5_000,
+        egress_policy: EdgeEgressPolicy | None = None,
+        random_source: Callable[[], float] | None = None,
+        remote_lease_encoder: Callable[[str], str] | None = None,
+        remote_lease_decoder: Callable[[str], str] | None = None,
+    ) -> None:
+        if not isinstance(db_path, (str, Path)) or not str(db_path).strip():
+            raise ValueError("an explicit SQLite database path is required")
+        path_text = str(db_path)
+        if path_text == ":memory:" or "mode=memory" in path_text:
+            raise ValueError("an in-memory database is not allowed")
+        for label, value in {
+            "lease_seconds": lease_seconds,
+            "max_attempts": max_attempts,
+            "retry_base_seconds": retry_base_seconds,
+            "retry_max_seconds": retry_max_seconds,
+            "busy_timeout_ms": busy_timeout_ms,
+        }.items():
+            if isinstance(value, bool) or not isinstance(value, int) or value < 1:
+                raise ValueError(f"{label} must be a positive integer")
+        if retry_base_seconds > retry_max_seconds:
+            raise ValueError("retry_base_seconds cannot exceed retry_max_seconds")
+        self.db_path = str(Path(db_path).expanduser().resolve())
+        self._clock = clock or (lambda: datetime.now(UTC))
+        self.lease_seconds = lease_seconds
+        self.max_attempts = max_attempts
+        self.retry_base_seconds = retry_base_seconds
+        self.retry_max_seconds = retry_max_seconds
+        self.busy_timeout_ms = busy_timeout_ms
+        self.egress_policy = egress_policy or EdgeEgressPolicy(
+            allowed_control_hosts=set()
+        )
+        # The injectable source makes retry timing testable; 0.5 is neutral jitter.
+        self._random = random_source or (lambda: 0.5)
+        if (remote_lease_encoder is None) != (remote_lease_decoder is None):
+            raise ValueError("remote lease encoder and decoder must be paired")
+        self._remote_lease_encoder = remote_lease_encoder
+        self._remote_lease_decoder = remote_lease_decoder
+        Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
+        self._initialize()
+
+    def _connect(self) -> sqlite3.Connection:
+        connection = sqlite3.connect(
+            self.db_path,
+            timeout=self.busy_timeout_ms / 1_000,
+            isolation_level=None,
+        )
+        connection.row_factory = sqlite3.Row
+        connection.execute(f"PRAGMA busy_timeout = {self.busy_timeout_ms}")
+        connection.execute("PRAGMA foreign_keys = ON")
+        return connection
+
+    def _initialize(self) -> None:
+        connection = self._connect()
+        try:
+            version = connection.execute("PRAGMA user_version").fetchone()[0]
+            edge_tables = {
+                row[0]
+                for row in connection.execute(
+                    """
+                    SELECT name FROM sqlite_master
+                    WHERE type = 'table' AND name LIKE 'edge_%'
+                    """
+                ).fetchall()
+            }
+            if version == 0 and edge_tables:
+                raise EdgeQueueSchemaError("unversioned edge queue schema is unsafe")
+            if version not in {0, 1, SCHEMA_VERSION}:
+                raise EdgeQueueSchemaError("edge queue schema version is unsupported")
+            connection.execute("PRAGMA journal_mode = WAL")
+            connection.execute("PRAGMA synchronous = FULL")
+            connection.execute("BEGIN IMMEDIATE")
+            if version == 0:
+                for statement in (
+                    _TASK_TABLE_SQL,
+                    _TASK_INDEX_SQL,
+                    _EVENT_TABLE_SQL,
+                    _EVENT_INDEX_SQL,
+                    _ARTIFACT_TABLE_SQL,
+                    _ARTIFACT_INDEX_SQL,
+                    _RELEASE_TABLE_SQL,
+                    _RELEASE_INDEX_SQL,
+                    _OUTCOME_TABLE_SQL,
+                    _OUTCOME_INDEX_SQL,
+                ):
+                    connection.execute(statement)
+                connection.execute(f"PRAGMA user_version = {SCHEMA_VERSION}")
+            elif version == 1:
+                self._validate_schema(
+                    connection,
+                    expected_info=_V1_EXPECTED_TABLE_INFO,
+                    expected_sql=_V1_EXPECTED_TABLE_SQL,
+                    expected_indexes={
+                        key: _EXPECTED_INDEXES[key]
+                        for key in ("edge_tasks", "edge_outbound_events")
+                    },
+                )
+                active_unsigned = connection.execute(
+                    """SELECT EXISTS(
+                    SELECT 1 FROM edge_tasks WHERE status IN ('pending','leased')
+                    )"""
+                ).fetchone()[0]
+                unacknowledged_events = connection.execute(
+                    """SELECT EXISTS(
+                    SELECT 1 FROM edge_outbound_events WHERE status <> 'acknowledged'
+                    )"""
+                ).fetchone()[0]
+                if active_unsigned or unacknowledged_events:
+                    raise EdgeQueueSchemaError(
+                        "v1 edge queue must drain active tasks and unacknowledged events"
+                    )
+                for statement in (
+                    "ALTER TABLE edge_tasks ADD COLUMN authority_key_id TEXT",
+                    "ALTER TABLE edge_tasks ADD COLUMN authority_digest TEXT",
+                    "ALTER TABLE edge_tasks ADD COLUMN authority_json TEXT",
+                    "ALTER TABLE edge_tasks ADD COLUMN remote_lease_token_digest TEXT",
+                    "ALTER TABLE edge_tasks ADD COLUMN remote_lease_ciphertext TEXT",
+                    "ALTER TABLE edge_tasks ADD COLUMN remote_lease_expires_at TEXT",
+                    "ALTER TABLE edge_tasks ADD COLUMN remote_attempt INTEGER CHECK (remote_attempt IS NULL OR remote_attempt >= 1)",
+                    "ALTER TABLE edge_outbound_events ADD COLUMN task_digest TEXT",
+                    "ALTER TABLE edge_outbound_events ADD COLUMN remote_lease_token_digest TEXT",
+                    "ALTER TABLE edge_outbound_events ADD COLUMN control_lease_token_digest TEXT",
+                    _ARTIFACT_TABLE_SQL,
+                    _ARTIFACT_INDEX_SQL,
+                    _RELEASE_TABLE_SQL,
+                    _RELEASE_INDEX_SQL,
+                    _OUTCOME_TABLE_SQL,
+                    _OUTCOME_INDEX_SQL,
+                ):
+                    connection.execute(statement)
+                connection.execute(f"PRAGMA user_version = {SCHEMA_VERSION}")
+            self._validate_schema(connection)
+            if connection.in_transaction:
+                connection.execute("COMMIT")
+        except Exception:
+            if connection.in_transaction:
+                connection.execute("ROLLBACK")
+            raise
+        finally:
+            connection.close()
+
+    @staticmethod
+    def _validate_schema(
+        connection: sqlite3.Connection,
+        *,
+        expected_info: Mapping[str, tuple] = _EXPECTED_TABLE_INFO,
+        expected_sql: Mapping[str, str] = _EXPECTED_TABLE_SQL,
+        expected_indexes: Mapping[str, Mapping[str, tuple]] = _EXPECTED_INDEXES,
+    ) -> None:
+        primary_keys = {
+            "edge_tasks": "task_id",
+            "edge_outbound_events": "event_id",
+            "edge_local_artifacts": "artifact_digest",
+            "edge_release_state": "release_id",
+            "edge_task_outcomes": "task_id",
+        }
+        for table, table_expected_info in expected_info.items():
+            rows = connection.execute(f"PRAGMA table_info({table})").fetchall()
+            actual_info = tuple(
+                (row[1], row[2].upper(), row[3], row[4], row[5]) for row in rows
+            )
+            if actual_info != table_expected_info:
+                raise EdgeQueueSchemaError("edge queue schema columns are invalid")
+            table_sql = connection.execute(
+                "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
+                (table,),
+            ).fetchone()
+            if table_sql is None or _normalize_schema_sql(
+                table_sql[0]
+            ) != _normalize_schema_sql(expected_sql[table]):
+                raise EdgeQueueSchemaError(
+                    "edge queue schema constraints are invalid"
+                )
+
+            index_rows = connection.execute(f"PRAGMA index_list({table})").fetchall()
+            custom_rows = {row[1]: row for row in index_rows if row[3] == "c"}
+            table_expected_indexes = expected_indexes[table]
+            if set(custom_rows) != set(table_expected_indexes):
+                raise EdgeQueueSchemaError("edge queue schema indexes are invalid")
+            for name, (unique, origin, partial, columns) in table_expected_indexes.items():
+                row = custom_rows[name]
+                actual_columns = tuple(
+                    item[2]
+                    for item in connection.execute(
+                        f"PRAGMA index_info({name})"
+                    ).fetchall()
+                )
+                if (row[2], row[3], row[4], actual_columns) != (
+                    unique,
+                    origin,
+                    partial,
+                    columns,
+                ):
+                    raise EdgeQueueSchemaError(
+                        "edge queue schema index definition is invalid"
+                    )
+            primary_indexes = [row for row in index_rows if row[3] == "pk"]
+            if len(primary_indexes) != 1:
+                raise EdgeQueueSchemaError("edge queue schema primary key is invalid")
+            primary = primary_indexes[0]
+            primary_columns = tuple(
+                item[2]
+                for item in connection.execute(
+                    f"PRAGMA index_info({primary[1]})"
+                ).fetchall()
+            )
+            if (primary[2], primary[4], primary_columns) != (
+                1,
+                0,
+                (primary_keys[table],),
+            ):
+                raise EdgeQueueSchemaError("edge queue schema primary key is invalid")
+
+    @contextmanager
+    def _transaction(self) -> Iterator[sqlite3.Connection]:
+        connection = self._connect()
+        try:
+            connection.execute("BEGIN IMMEDIATE")
+            yield connection
+            connection.execute("COMMIT")
+        except Exception:
+            if connection.in_transaction:
+                connection.execute("ROLLBACK")
+            raise
+        finally:
+            connection.close()
+
+    def enqueue(self, task: EdgeTaskContract | Mapping[str, object]) -> QueuedTask:
+        contract = EdgeTaskContract.from_mapping(
+            task.to_mapping() if isinstance(task, EdgeTaskContract) else task
+        )
+        return self._enqueue_contract(contract, authority=None)
+
+    def enqueue_signed(self, envelope: SignedTaskEnvelope) -> QueuedTask:
+        """Atomically persist a previously verified authority envelope and task."""
+
+        if not isinstance(envelope, SignedTaskEnvelope) or not envelope.is_verified:
+            raise EdgeContractError("a verified SignedTaskEnvelope is required")
+        # Reconstruct the task so object.__setattr__ cannot bypass validation.
+        contract = EdgeTaskContract.from_mapping(envelope.task.to_mapping())
+        if envelope.contract_digest != contract.digest:
+            raise EdgeContractError("signed authority no longer binds the task")
+        return self._enqueue_contract(contract, authority=envelope)
+
+    def accept_signed_task(
+        self,
+        envelope: SignedTaskEnvelope,
+        *,
+        remote_lease_token: str,
+        remote_lease_expires_at: str,
+        remote_attempt: int,
+    ) -> QueuedTask:
+        """Atomically accept authority and an encrypted recoverable control lease."""
+
+        if self._remote_lease_encoder is None:
+            raise EdgeQueueLeaseError("secure remote lease codec is required")
+        if not isinstance(envelope, SignedTaskEnvelope) or not envelope.is_verified:
+            raise EdgeContractError("a verified SignedTaskEnvelope is required")
+        return self._enqueue_contract(
+            EdgeTaskContract.from_mapping(envelope.task.to_mapping()),
+            authority=envelope,
+            remote_binding=self._normalize_remote_binding(
+                remote_lease_token, remote_lease_expires_at, remote_attempt
+            ),
+        )
+
+    def _enqueue_contract(
+        self,
+        contract: EdgeTaskContract,
+        *,
+        authority: SignedTaskEnvelope | None,
+        remote_binding: tuple[str, str | None, str, int] | None = None,
+    ) -> QueuedTask:
+        task_mapping = contract.to_mapping()
+        encoded = strict_json_bytes(task_mapping).decode("utf-8")
+        digest = canonical_sha256(task_mapping)
+        if remote_binding is not None and remote_binding[3] != contract.attempt:
+            raise EdgeQueueLeaseError(
+                "remote lease attempt does not bind the task contract"
+            )
+        with self._transaction() as connection:
+            existing = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (contract.task_id,)
+            ).fetchone()
+            if existing is not None:
+                if existing["digest"] != digest:
+                    raise EdgeQueueConflictError(
+                        "task ID already exists with a different digest"
+                    )
+                if authority is not None and existing["authority_digest"] != authority.digest:
+                    raise EdgeQueueConflictError(
+                        "task ID already exists with a different authority digest"
+                    )
+                if remote_binding is not None:
+                    now = _utc_now(self._clock)
+                    old_expiry_text = existing["remote_lease_expires_at"]
+                    old_expiry = (
+                        datetime.fromisoformat(old_expiry_text.replace("Z", "+00:00"))
+                        if old_expiry_text is not None else None
+                    )
+                    if (
+                        existing["remote_lease_token_digest"] != remote_binding[0]
+                        and old_expiry is not None and old_expiry > now
+                    ):
+                        raise EdgeQueueConflictError("active remote lease binding conflicts")
+                    new_expiry = datetime.fromisoformat(remote_binding[2].replace("Z", "+00:00"))
+                    if new_expiry <= now:
+                        raise EdgeQueueLeaseError("remote lease has expired")
+                    if (
+                        existing["status"] in {"failed", "cancelled"}
+                        and not (
+                            existing["status"] == "failed"
+                            and existing["error_code"] == "execution_lease_expired"
+                        )
+                    ):
+                        raise EdgeQueueConflictError(
+                            "terminal task cannot be rebound to a remote lease"
+                        )
+                    changed_remote_lease = (
+                        existing["remote_lease_token_digest"] != remote_binding[0]
+                    )
+                    if changed_remote_lease:
+                        event_rows = connection.execute(
+                            """SELECT * FROM edge_outbound_events
+                            WHERE status IN ('pending','sending')
+                              AND json_extract(event_json,'$.task_id')=?""",
+                            (contract.task_id,),
+                        ).fetchall()
+                        for event_row in event_rows:
+                            event_mapping = json.loads(event_row["event_json"])
+                            if (
+                                event_row["task_digest"] != digest
+                                or event_row["remote_lease_token_digest"]
+                                != existing["remote_lease_token_digest"]
+                                or event_mapping.get("task_id") != contract.task_id
+                                or event_mapping.get("gateway_id") != contract.gateway_id
+                                or canonical_sha256(event_mapping) != event_row["digest"]
+                            ):
+                                raise EdgeQueueConflictError(
+                                    "pending event does not bind the task lease"
+                                )
+                        connection.execute(
+                            """UPDATE edge_outbound_events
+                            SET remote_lease_token_digest=?,updated_at=?
+                            WHERE status IN ('pending','sending')
+                              AND json_extract(event_json,'$.task_id')=?""",
+                            (
+                                remote_binding[0],
+                                _timestamp(now),
+                                contract.task_id,
+                            ),
+                        )
+                    recoverable_failure = (
+                        existing["status"] == "failed"
+                        and existing["error_code"] == "execution_lease_expired"
+                    )
+                    connection.execute(
+                        """UPDATE edge_tasks SET remote_lease_token_digest=?,
+                        remote_lease_ciphertext=?,remote_lease_expires_at=?,remote_attempt=?,
+                        status=CASE WHEN ? THEN 'pending' ELSE status END,
+                        available_at=CASE WHEN ? THEN ? ELSE available_at END,
+                        error_code=CASE WHEN ? THEN NULL ELSE error_code END,
+                        updated_at=?
+                        WHERE task_id=? AND digest=? AND authority_digest=?""",
+                        (
+                            remote_binding[0], remote_binding[1], remote_binding[2],
+                            remote_binding[3], recoverable_failure,
+                            recoverable_failure, _timestamp(now), recoverable_failure,
+                            _timestamp(now), contract.task_id,
+                            digest, authority.digest if authority is not None else None,
+                        ),
+                    )
+                    existing = connection.execute(
+                        "SELECT * FROM edge_tasks WHERE task_id=?", (contract.task_id,)
+                    ).fetchone()
+                return self._task_record(existing)
+            now = _utc_now(self._clock)
+            deadline = datetime.fromisoformat(
+                contract.deadline_at.replace("Z", "+00:00")
+            ).astimezone(UTC)
+            if deadline <= now:
+                raise EdgeContractError("task deadline has expired")
+            if remote_binding is not None:
+                remote_expiry = datetime.fromisoformat(remote_binding[2].replace("Z", "+00:00"))
+                if remote_expiry <= now or remote_expiry > deadline:
+                    raise EdgeQueueLeaseError("remote lease validity window is invalid")
+            now_text = _timestamp(now)
+            connection.execute(
+                """
+                INSERT INTO edge_tasks (
+                    task_id, digest, task_json, status, attempt_count,
+                    available_at, deadline_at, deadline_epoch_us,
+                    authority_key_id, authority_digest, authority_json,
+                    remote_lease_token_digest, remote_lease_ciphertext,
+                    remote_lease_expires_at, remote_attempt,
+                    created_at, updated_at
+                ) VALUES (?, ?, ?, 'pending', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                """,
+                (
+                    contract.task_id,
+                    digest,
+                    encoded,
+                    now_text,
+                    contract.deadline_at,
+                    _epoch_microseconds(deadline),
+                    authority.authority_key_id if authority is not None else None,
+                    authority.digest if authority is not None else None,
+                    strict_json_bytes(authority.to_mapping()).decode("utf-8")
+                    if authority is not None else None,
+                    remote_binding[0] if remote_binding is not None else None,
+                    remote_binding[1] if remote_binding is not None else None,
+                    remote_binding[2] if remote_binding is not None else None,
+                    remote_binding[3] if remote_binding is not None else None,
+                    now_text,
+                    now_text,
+                ),
+            )
+            row = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (contract.task_id,)
+            ).fetchone()
+            return self._task_record(row)
+
+    def claim(self, lease_owner: str) -> QueuedTask | None:
+        owner = self._lease_owner(lease_owner)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            now_epoch_us = _epoch_microseconds(now)
+            lease_token = str(uuid.uuid4())
+            expires_at = _timestamp(now + timedelta(seconds=self.lease_seconds))
+            connection.execute(
+                """
+                UPDATE edge_tasks
+                SET status = 'failed', lease_owner = NULL, lease_token = NULL,
+                    lease_expires_at = NULL,
+                    error_code = 'task_deadline_expired', updated_at = ?
+                WHERE status IN ('pending', 'leased') AND deadline_epoch_us <= ?
+                """,
+                (now_text, now_epoch_us),
+            )
+            connection.execute(
+                """
+                UPDATE edge_tasks
+                SET status = 'failed', lease_owner = NULL, lease_token = NULL,
+                    lease_expires_at = NULL,
+                    error_code = 'lease_attempts_exhausted', updated_at = ?
+                WHERE status = 'leased'
+                  AND julianday(lease_expires_at) <= julianday(?)
+                  AND attempt_count >= ?
+                """,
+                (now_text, now_text, self.max_attempts),
+            )
+            row = connection.execute(
+                """
+                SELECT * FROM edge_tasks
+                WHERE deadline_epoch_us > ? AND attempt_count < ? AND (
+                    (status = 'pending' AND julianday(available_at) <= julianday(?))
+                    OR (status = 'leased'
+                        AND julianday(lease_expires_at) <= julianday(?))
+                )
+                ORDER BY created_at, task_id
+                LIMIT 1
+                """,
+                (now_epoch_us, self.max_attempts, now_text, now_text),
+            ).fetchone()
+            if row is None:
+                return None
+            updated = connection.execute(
+                """
+                UPDATE edge_tasks
+                SET status = 'leased', attempt_count = attempt_count + 1,
+                    lease_owner = ?, lease_token = ?, lease_expires_at = ?,
+                    error_code = NULL, updated_at = ?
+                WHERE task_id = ? AND deadline_epoch_us > ?
+                  AND attempt_count = ? AND (
+                    (status = 'pending' AND julianday(available_at) <= julianday(?))
+                    OR (status = 'leased'
+                        AND julianday(lease_expires_at) <= julianday(?))
+                )
+                """,
+                (
+                    owner,
+                    lease_token,
+                    expires_at,
+                    now_text,
+                    row["task_id"],
+                    now_epoch_us,
+                    row["attempt_count"],
+                    now_text,
+                    now_text,
+                ),
+            )
+            if updated.rowcount != 1:
+                return None
+            claimed = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (row["task_id"],)
+            ).fetchone()
+            return self._task_record(claimed)
+
+    def bind_remote_lease(
+        self,
+        task_id: str,
+        *,
+        lease_token: str,
+        remote_lease_token: str,
+        remote_lease_expires_at: str,
+        remote_attempt: int,
+    ) -> QueuedTask:
+        """Bind control-plane authority to the signed contract attempt."""
+
+        digest, ciphertext, expiry_text, remote_attempt = self._normalize_remote_binding(
+            remote_lease_token, remote_lease_expires_at, remote_attempt
+        )
+        expiry = datetime.fromisoformat(expiry_text.replace("Z", "+00:00"))
+        with self._transaction() as connection:
+            row = self._leased_row(connection, "edge_tasks", "task_id", task_id, lease_token)
+            now = _utc_now(self._clock)
+            if expiry <= now:
+                raise EdgeQueueLeaseError("remote lease has expired")
+            task_mapping = json.loads(row["task_json"])
+            if remote_attempt != task_mapping["attempt"]:
+                raise EdgeQueueLeaseError("remote lease attempt does not bind task contract")
+            existing = row["remote_lease_token_digest"]
+            if existing is not None and (
+                existing != digest
+                or row["remote_lease_expires_at"] != expiry_text
+                or row["remote_attempt"] != remote_attempt
+            ):
+                raise EdgeQueueConflictError("remote lease binding conflicts")
+            connection.execute(
+                """
+                UPDATE edge_tasks
+                SET remote_lease_token_digest=?, remote_lease_ciphertext=?, remote_lease_expires_at=?,
+                    remote_attempt=?, updated_at=?
+                WHERE task_id=? AND status='leased' AND lease_token=?
+                """,
+                (digest, ciphertext, expiry_text, remote_attempt, _timestamp(now), task_id, lease_token),
+            )
+            updated = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id=?", (task_id,)
+            ).fetchone()
+            return self._task_record(updated)
+
+    def recover_remote_lease(self, task_id: str) -> str:
+        """Recover a control lease through the injected secure decoder without exposing it in records."""
+
+        if self._remote_lease_decoder is None:
+            raise EdgeQueueLeaseError("secure remote lease codec is required")
+        connection = self._connect()
+        try:
+            row = connection.execute(
+                "SELECT remote_lease_ciphertext,remote_lease_token_digest FROM edge_tasks WHERE task_id=?",
+                (task_id,),
+            ).fetchone()
+        finally:
+            connection.close()
+        if row is None or row["remote_lease_ciphertext"] is None:
+            raise EdgeQueueLeaseError("recoverable remote lease does not exist")
+        try:
+            token = self._remote_lease_decoder(row["remote_lease_ciphertext"])
+        except Exception as exc:
+            raise EdgeQueueLeaseError("remote lease recovery failed") from exc
+        if not isinstance(token, str) or self._opaque_token_digest(token) != row["remote_lease_token_digest"]:
+            raise EdgeQueueLeaseError("recovered remote lease failed integrity validation")
+        return token
+
+    def complete_with_event(
+        self,
+        task_id: str,
+        *,
+        lease_token: str,
+        remote_lease_token: str,
+        event: EdgeEventContract | Mapping[str, object],
+        artifacts: list[Mapping[str, object]] | tuple[Mapping[str, object], ...] = (),
+    ) -> tuple[QueuedTask, QueuedEvent]:
+        """Atomically commit a safe event, local artifact metadata, and task terminal state."""
+
+        contract = EdgeEventContract.from_mapping(
+            event.to_mapping() if isinstance(event, EdgeEventContract) else event
+        )
+        approved = self.egress_policy.approve_event(contract.to_mapping())
+        encoded_event = strict_json_bytes(approved).decode("utf-8")
+        normalized_artifacts = [self._validate_local_artifact(item) for item in artifacts]
+        if not isinstance(remote_lease_token, str) or not remote_lease_token:
+            raise EdgeQueueLeaseError("remote lease token is invalid")
+        remote_digest = self._opaque_token_digest(remote_lease_token)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            row = self._leased_row(connection, "edge_tasks", "task_id", task_id, lease_token)
+            if row["deadline_epoch_us"] <= _epoch_microseconds(now):
+                raise EdgeQueueLeaseError("task deadline has expired")
+            if row["remote_lease_token_digest"] != remote_digest:
+                raise EdgeQueueLeaseError("current remote lease token is required")
+            remote_expiry = row["remote_lease_expires_at"]
+            if remote_expiry is None or datetime.fromisoformat(
+                remote_expiry.replace("Z", "+00:00")
+            ) <= now:
+                raise EdgeQueueLeaseError("remote lease has expired")
+            task_mapping = json.loads(row["task_json"])
+            if row["remote_attempt"] != task_mapping["attempt"]:
+                raise EdgeQueueLeaseError("remote lease attempt is stale")
+            for field in ("task_id", "gateway_id", "environment", "network_zone", "purpose", "policy_digest"):
+                if contract.to_mapping()[field] != task_mapping[field]:
+                    raise EdgeQueueConflictError(f"event {field} does not bind the task")
+            if contract.attempt != task_mapping["attempt"]:
+                raise EdgeQueueConflictError("event attempt does not bind the task")
+            existing = connection.execute(
+                "SELECT digest FROM edge_outbound_events WHERE event_id=?", (contract.event_id,)
+            ).fetchone()
+            if existing is not None:
+                raise EdgeQueueConflictError("event ID already exists before task completion")
+            connection.execute(
+                """
+                INSERT INTO edge_outbound_events (
+                    event_id,digest,event_json,status,attempt_count,available_at,
+                    task_digest,remote_lease_token_digest,created_at,updated_at
+                ) VALUES (?,?,?,'pending',0,?,?,?,?,?)
+                """,
+                (
+                    contract.event_id, contract.digest, encoded_event, now_text,
+                    row["digest"], remote_digest, now_text, now_text,
+                ),
+            )
+            for artifact in normalized_artifacts:
+                connection.execute(
+                    """
+                    INSERT INTO edge_local_artifacts (
+                        artifact_digest,artifact_ref,artifact_ref_hash,classification,
+                        retention_until,task_id,event_id,cleanup_status,attempt_count,
+                        available_at,created_at,updated_at
+                    ) VALUES (?,?,?,?,?,?,?,'pending',0,?,?,?)
+                    """,
+                    (
+                        artifact["artifact_digest"], artifact["artifact_ref"],
+                        artifact["artifact_ref_hash"], artifact["classification"],
+                        artifact["retention_until"], task_id, contract.event_id,
+                        now_text, now_text, now_text,
+                    ),
+                )
+            updated = connection.execute(
+                """
+                UPDATE edge_tasks SET status='completed', lease_owner=NULL,
+                    lease_token=NULL, lease_expires_at=NULL, error_code=NULL, updated_at=?
+                WHERE task_id=? AND status='leased' AND lease_token=?
+                """,
+                (now_text, task_id, lease_token),
+            )
+            if updated.rowcount != 1:
+                raise EdgeQueueLeaseError("task completion lost its lease")
+            task_row = connection.execute("SELECT * FROM edge_tasks WHERE task_id=?", (task_id,)).fetchone()
+            event_row = connection.execute("SELECT * FROM edge_outbound_events WHERE event_id=?", (contract.event_id,)).fetchone()
+            return self._task_record(task_row), self._event_record(event_row)
+
+    def list_local_artifacts(self, *, task_id: str) -> tuple[LocalArtifact, ...]:
+        connection = self._connect()
+        try:
+            rows = connection.execute(
+                "SELECT * FROM edge_local_artifacts WHERE task_id=? ORDER BY created_at,artifact_digest",
+                (task_id,),
+            ).fetchall()
+        finally:
+            connection.close()
+        return tuple(self._artifact_record(row) for row in rows)
+
+    def claim_artifact_cleanup(self, lease_owner: str) -> LocalArtifact | None:
+        owner = self._lease_owner(lease_owner)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            expires_at = _timestamp(now + timedelta(seconds=self.lease_seconds))
+            connection.execute(
+                """UPDATE edge_local_artifacts SET cleanup_status='failed',
+                lease_owner=NULL,lease_token=NULL,lease_expires_at=NULL,
+                error_code='lease_attempts_exhausted',updated_at=?
+                WHERE cleanup_status='deleting'
+                AND julianday(lease_expires_at)<=julianday(?) AND attempt_count>=?""",
+                (now_text, now_text, self.max_attempts),
+            )
+            row = connection.execute(
+                """SELECT * FROM edge_local_artifacts WHERE attempt_count<?
+                AND julianday(retention_until)<=julianday(?) AND (
+                  (cleanup_status='pending' AND julianday(available_at)<=julianday(?))
+                  OR (cleanup_status='deleting'
+                      AND julianday(lease_expires_at)<=julianday(?)))
+                ORDER BY retention_until,created_at,artifact_digest LIMIT 1""",
+                (self.max_attempts, now_text, now_text, now_text),
+            ).fetchone()
+            if row is None:
+                return None
+            lease_token = str(uuid.uuid4())
+            connection.execute(
+                """UPDATE edge_local_artifacts SET cleanup_status='deleting',
+                attempt_count=attempt_count+1,lease_owner=?,lease_token=?,
+                lease_expires_at=?,error_code=NULL,updated_at=?
+                WHERE artifact_digest=? AND attempt_count=? AND (
+                  (cleanup_status='pending' AND julianday(available_at)<=julianday(?))
+                  OR (cleanup_status='deleting'
+                      AND julianday(lease_expires_at)<=julianday(?)))""",
+                (
+                    owner, lease_token, expires_at, now_text, row["artifact_digest"],
+                    row["attempt_count"], now_text, now_text,
+                ),
+            )
+            claimed = connection.execute(
+                "SELECT * FROM edge_local_artifacts WHERE artifact_digest=?",
+                (row["artifact_digest"],),
+            ).fetchone()
+            return self._artifact_record(claimed)
+
+    def acknowledge_artifact_cleanup(
+        self,
+        artifact_digest: str,
+        *,
+        lease_token: str,
+        receipt: Mapping[str, object],
+    ) -> LocalArtifact:
+        required = {"artifact_digest", "artifact_ref_hash", "deleted_at", "status"}
+        with self._transaction() as connection:
+            row = self._leased_row(
+                connection, "edge_local_artifacts", "artifact_digest",
+                artifact_digest, lease_token, status="deleting",
+                status_column="cleanup_status",
+            )
+            if (
+                not isinstance(receipt, Mapping)
+                or set(receipt) != required
+                or receipt.get("artifact_digest") != artifact_digest
+                or receipt.get("artifact_ref_hash") != row["artifact_ref_hash"]
+                or receipt.get("status") != "deleted"
+            ):
+                raise EdgePolicyError("artifact deletion receipt is invalid")
+            try:
+                deleted_at = canonical_timestamp(receipt["deleted_at"], "deleted_at")
+            except EdgeContractError as exc:
+                raise EdgePolicyError("artifact deletion receipt is invalid") from exc
+            normalized = {
+                "artifact_digest": artifact_digest,
+                "artifact_ref_hash": row["artifact_ref_hash"],
+                "deleted_at": deleted_at,
+                "status": "deleted",
+            }
+            now_text = _timestamp(_utc_now(self._clock))
+            connection.execute(
+                """UPDATE edge_local_artifacts SET artifact_ref=NULL,
+                cleanup_status='deleted',deletion_receipt_json=?,lease_owner=NULL,
+                lease_token=NULL,lease_expires_at=NULL,error_code=NULL,updated_at=?
+                WHERE artifact_digest=? AND cleanup_status='deleting' AND lease_token=?""",
+                (
+                    strict_json_bytes(normalized).decode("utf-8"), now_text,
+                    artifact_digest, lease_token,
+                ),
+            )
+            updated = connection.execute(
+                "SELECT * FROM edge_local_artifacts WHERE artifact_digest=?",
+                (artifact_digest,),
+            ).fetchone()
+            return self._artifact_record(updated)
+
+    def fail_artifact_cleanup(
+        self, artifact_digest: str, *, lease_token: str, error_code: str
+    ) -> LocalArtifact:
+        safe_error = self._error_code(error_code)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            row = self._leased_row(
+                connection, "edge_local_artifacts", "artifact_digest",
+                artifact_digest, lease_token, status="deleting",
+                status_column="cleanup_status",
+            )
+            terminal = row["attempt_count"] >= self.max_attempts
+            status = "failed" if terminal else "pending"
+            available_at = now_text if terminal else _timestamp(
+                now + timedelta(seconds=self._retry_delay(row["attempt_count"]))
+            )
+            connection.execute(
+                """UPDATE edge_local_artifacts SET cleanup_status=?,available_at=?,
+                lease_owner=NULL,lease_token=NULL,lease_expires_at=NULL,error_code=?,
+                updated_at=? WHERE artifact_digest=? AND cleanup_status='deleting'
+                AND lease_token=?""",
+                (
+                    status, available_at, safe_error, now_text,
+                    artifact_digest, lease_token,
+                ),
+            )
+            updated = connection.execute(
+                "SELECT * FROM edge_local_artifacts WHERE artifact_digest=?",
+                (artifact_digest,),
+            ).fetchone()
+            return self._artifact_record(updated)
+
+    def put_release_state(
+        self,
+        *,
+        release_id: str,
+        manifest_digest: str,
+        version: str,
+        rollback_version: str,
+    ) -> ReleaseState:
+        release_id = self._bounded_identifier(release_id, "release_id")
+        manifest_digest = self._sha256(manifest_digest, "manifest_digest")
+        version = self._bounded_identifier(version, "version")
+        rollback_version = self._bounded_identifier(rollback_version, "rollback_version")
+        with self._transaction() as connection:
+            existing = connection.execute(
+                "SELECT * FROM edge_release_state WHERE release_id=?", (release_id,)
+            ).fetchone()
+            if existing is not None:
+                if (
+                    existing["manifest_digest"], existing["version"], existing["rollback_version"]
+                ) != (manifest_digest, version, rollback_version):
+                    raise EdgeQueueConflictError("release ID has different immutable content")
+                return ReleaseState(**dict(existing))
+            now_text = _timestamp(_utc_now(self._clock))
+            connection.execute(
+                """INSERT INTO edge_release_state
+                (release_id,manifest_digest,version,rollback_version,status,updated_at)
+                VALUES (?,?,?,?,'offered',?)""",
+                (release_id, manifest_digest, version, rollback_version, now_text),
+            )
+            row = connection.execute(
+                "SELECT * FROM edge_release_state WHERE release_id=?", (release_id,)
+            ).fetchone()
+            return ReleaseState(**dict(row))
+
+    def compare_and_set_release_state(
+        self,
+        release_id: str,
+        *,
+        expected_status: str,
+        target_status: str,
+        previous_version: str | None = None,
+        current_version: str | None = None,
+    ) -> ReleaseState:
+        release_id = self._bounded_identifier(release_id, "release_id")
+        allowed = {"offered", "accepted", "candidate", "installed", "failed", "rolled_back"}
+        if expected_status not in allowed or target_status not in allowed:
+            raise EdgeQueueConflictError("release status is invalid")
+        transitions = {
+            "offered": {"accepted", "failed"},
+            "accepted": {"candidate", "failed"},
+            "candidate": {"installed", "failed", "rolled_back"},
+            "installed": {"rolled_back", "failed"},
+            "failed": {"rolled_back"},
+            "rolled_back": set(),
+        }
+        if target_status not in transitions[expected_status]:
+            raise EdgeQueueConflictError("release state transition is not approved")
+        if previous_version is not None:
+            previous_version = self._bounded_identifier(previous_version, "previous_version")
+        if current_version is not None:
+            current_version = self._bounded_identifier(current_version, "current_version")
+        with self._transaction() as connection:
+            now_text = _timestamp(_utc_now(self._clock))
+            updated = connection.execute(
+                """UPDATE edge_release_state
+                SET status=?,previous_version=COALESCE(?,previous_version),
+                    current_version=COALESCE(?,current_version),updated_at=?
+                WHERE release_id=? AND status=?""",
+                (
+                    target_status, previous_version, current_version, now_text,
+                    release_id, expected_status,
+                ),
+            )
+            if updated.rowcount != 1:
+                row = connection.execute(
+                    "SELECT status FROM edge_release_state WHERE release_id=?", (release_id,)
+                ).fetchone()
+                if row is None:
+                    raise EdgeQueueError("release does not exist")
+                raise EdgeQueueConflictError("release state compare-and-set lost")
+            row = connection.execute(
+                "SELECT * FROM edge_release_state WHERE release_id=?", (release_id,)
+            ).fetchone()
+            return ReleaseState(**dict(row))
+
+    def get_release_state(self, release_id: str) -> ReleaseState | None:
+        release_id = self._bounded_identifier(release_id, "release_id")
+        connection = self._connect()
+        try:
+            row = connection.execute(
+                "SELECT * FROM edge_release_state WHERE release_id=?", (release_id,)
+            ).fetchone()
+        finally:
+            connection.close()
+        return ReleaseState(**dict(row)) if row is not None else None
+
+    def list_release_states(self) -> tuple[ReleaseState, ...]:
+        connection = self._connect()
+        try:
+            rows = connection.execute(
+                "SELECT * FROM edge_release_state ORDER BY updated_at,release_id"
+            ).fetchall()
+        finally:
+            connection.close()
+        return tuple(ReleaseState(**dict(row)) for row in rows)
+
+    def complete(self, task_id: str, *, lease_token: str) -> QueuedTask:
+        return self._finish_task(
+            task_id,
+            lease_token=lease_token,
+            target_status="completed",
+            error_code=None,
+        )
+
+    def fail(
+        self,
+        task_id: str,
+        *,
+        lease_token: str,
+        error_code: str,
+        retryable: bool = True,
+    ) -> QueuedTask:
+        safe_error = self._error_code(error_code)
+        if not isinstance(retryable, bool):
+            raise ValueError("retryable must be a boolean")
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            row = self._leased_row(
+                connection, "edge_tasks", "task_id", task_id, lease_token
+            )
+            terminal = not retryable or row["attempt_count"] >= self.max_attempts
+            status = "failed" if terminal else "pending"
+            available_at = now_text
+            if not terminal:
+                available_at = _timestamp(
+                    now + timedelta(seconds=self._retry_delay(row["attempt_count"]))
+                )
+            connection.execute(
+                """
+                UPDATE edge_tasks
+                SET status = ?, available_at = ?, lease_owner = NULL,
+                    lease_token = NULL, lease_expires_at = NULL,
+                    error_code = ?, updated_at = ?
+                WHERE task_id = ? AND status = 'leased' AND lease_token = ?
+                """,
+                (status, available_at, safe_error, now_text, task_id, lease_token),
+            )
+            updated = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (task_id,)
+            ).fetchone()
+            return self._task_record(updated)
+
+    def _finish_task(
+        self,
+        task_id: str,
+        *,
+        lease_token: str,
+        target_status: str,
+        error_code: str | None,
+    ) -> QueuedTask:
+        with self._transaction() as connection:
+            now_text = _timestamp(_utc_now(self._clock))
+            self._leased_row(
+                connection, "edge_tasks", "task_id", task_id, lease_token
+            )
+            connection.execute(
+                """
+                UPDATE edge_tasks
+                SET status = ?, lease_owner = NULL, lease_token = NULL,
+                    lease_expires_at = NULL, error_code = ?, updated_at = ?
+                WHERE task_id = ? AND status = 'leased' AND lease_token = ?
+                """,
+                (target_status, error_code, now_text, task_id, lease_token),
+            )
+            row = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (task_id,)
+            ).fetchone()
+            return self._task_record(row)
+
+    def cancel(self, task_id: str) -> QueuedTask:
+        with self._transaction() as connection:
+            now_text = _timestamp(_utc_now(self._clock))
+            row = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (task_id,)
+            ).fetchone()
+            if row is None:
+                raise EdgeQueueError("task does not exist")
+            if row["status"] in {"pending", "leased"}:
+                connection.execute(
+                    """
+                    UPDATE edge_tasks
+                    SET status = 'cancelled', lease_owner = NULL,
+                        lease_token = NULL, lease_expires_at = NULL,
+                        updated_at = ?
+                    WHERE task_id = ? AND status IN ('pending', 'leased')
+                    """,
+                    (now_text, task_id),
+                )
+                row = connection.execute(
+                    "SELECT * FROM edge_tasks WHERE task_id = ?", (task_id,)
+                ).fetchone()
+            return self._task_record(row)
+
+    def cancel_with_outcome(self, task_id: str) -> QueuedOutcome:
+        """Atomically fence local work and persist the cancelled control outcome."""
+
+        task_id = self._bounded_identifier(task_id, "task_id")
+        with self._transaction() as connection:
+            now_text = _timestamp(_utc_now(self._clock))
+            row = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (task_id,)
+            ).fetchone()
+            if row is None:
+                raise EdgeQueueError("task does not exist")
+            if row["status"] in {"pending", "leased"}:
+                connection.execute(
+                    """UPDATE edge_tasks SET status='cancelled',lease_owner=NULL,
+                    lease_token=NULL,lease_expires_at=NULL,updated_at=?
+                    WHERE task_id=? AND status IN ('pending','leased')""",
+                    (now_text, task_id),
+                )
+                row = connection.execute(
+                    "SELECT * FROM edge_tasks WHERE task_id=?", (task_id,)
+                ).fetchone()
+            if row["status"] != "cancelled":
+                raise EdgeQueueConflictError("only a cancelled task can emit cancellation")
+            remote_digest = row["remote_lease_token_digest"]
+            if not isinstance(remote_digest, str):
+                raise EdgeQueueLeaseError("cancel outcome requires a remote lease binding")
+            outcome = {
+                "task_id": task_id,
+                "outcome": "cancelled",
+                "safe_summary": {"task_status": "cancelled"},
+            }
+            digest = canonical_sha256(outcome)
+            encoded = strict_json_bytes(outcome).decode("utf-8")
+            existing = connection.execute(
+                "SELECT * FROM edge_task_outcomes WHERE task_id=?", (task_id,)
+            ).fetchone()
+            if existing is not None:
+                if (
+                    existing["digest"] != digest
+                    or existing["remote_lease_token_digest"] != remote_digest
+                ):
+                    raise EdgeQueueConflictError("cancel outcome replay conflicts")
+                return self._outcome_record(existing)
+            connection.execute(
+                """INSERT INTO edge_task_outcomes(
+                task_id,digest,outcome_json,status,attempt_count,available_at,
+                remote_lease_token_digest,created_at,updated_at
+                ) VALUES (?,?,?,'pending',0,?,?,?,?)""",
+                (task_id, digest, encoded, now_text, remote_digest, now_text, now_text),
+            )
+            inserted = connection.execute(
+                "SELECT * FROM edge_task_outcomes WHERE task_id=?", (task_id,)
+            ).fetchone()
+            return self._outcome_record(inserted)
+
+    def claim_outcome(self, lease_owner: str) -> QueuedOutcome | None:
+        owner = self._lease_owner(lease_owner)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            expires_at = _timestamp(now + timedelta(seconds=self.lease_seconds))
+            connection.execute(
+                """UPDATE edge_task_outcomes SET status='failed',lease_owner=NULL,
+                lease_token=NULL,lease_expires_at=NULL,error_code='lease_attempts_exhausted',
+                updated_at=? WHERE status='sending'
+                AND julianday(lease_expires_at)<=julianday(?) AND attempt_count>=?""",
+                (now_text, now_text, self.max_attempts),
+            )
+            row = connection.execute(
+                """SELECT * FROM edge_task_outcomes WHERE attempt_count<? AND (
+                (status='pending' AND julianday(available_at)<=julianday(?)) OR
+                (status='sending' AND julianday(lease_expires_at)<=julianday(?)))
+                ORDER BY created_at,task_id LIMIT 1""",
+                (self.max_attempts, now_text, now_text),
+            ).fetchone()
+            if row is None:
+                return None
+            lease_token = str(uuid.uuid4())
+            connection.execute(
+                """UPDATE edge_task_outcomes SET status='sending',
+                attempt_count=attempt_count+1,lease_owner=?,lease_token=?,
+                lease_expires_at=?,error_code=NULL,updated_at=?
+                WHERE task_id=? AND attempt_count=? AND (
+                (status='pending' AND julianday(available_at)<=julianday(?)) OR
+                (status='sending' AND julianday(lease_expires_at)<=julianday(?)))""",
+                (
+                    owner, lease_token, expires_at, now_text, row["task_id"],
+                    row["attempt_count"], now_text, now_text,
+                ),
+            )
+            claimed = connection.execute(
+                "SELECT * FROM edge_task_outcomes WHERE task_id=?", (row["task_id"],)
+            ).fetchone()
+            return self._outcome_record(claimed)
+
+    def acknowledge_outcome(
+        self,
+        task_id: str,
+        *,
+        lease_token: str,
+        acknowledgement: Mapping[str, object],
+    ) -> QueuedOutcome:
+        required = {"task_id", "status", "replayed"}
+        if (
+            not isinstance(acknowledgement, Mapping)
+            or set(acknowledgement) != required
+            or acknowledgement.get("task_id") != task_id
+            or acknowledgement.get("status") != "cancelled"
+            or not isinstance(acknowledgement.get("replayed"), bool)
+        ):
+            raise EdgePolicyError("cancel outcome acknowledgement is invalid")
+        encoded = strict_json_bytes(acknowledgement).decode("utf-8")
+        ack_digest = canonical_sha256(acknowledgement)
+        with self._transaction() as connection:
+            now_text = _timestamp(_utc_now(self._clock))
+            self._leased_row(
+                connection, "edge_task_outcomes", "task_id", task_id, lease_token,
+                status="sending",
+            )
+            connection.execute(
+                """UPDATE edge_task_outcomes SET status='acknowledged',
+                acknowledgement_json=?,ack_digest=?,lease_owner=NULL,lease_token=NULL,
+                lease_expires_at=NULL,updated_at=?
+                WHERE task_id=? AND status='sending' AND lease_token=?""",
+                (encoded, ack_digest, now_text, task_id, lease_token),
+            )
+            row = connection.execute(
+                "SELECT * FROM edge_task_outcomes WHERE task_id=?", (task_id,)
+            ).fetchone()
+            return self._outcome_record(row)
+
+    def fail_outcome(
+        self, task_id: str, *, lease_token: str, error_code: str
+    ) -> QueuedOutcome:
+        safe_error = self._error_code(error_code)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            row = self._leased_row(
+                connection, "edge_task_outcomes", "task_id", task_id, lease_token,
+                status="sending",
+            )
+            terminal = row["attempt_count"] >= self.max_attempts
+            status = "failed" if terminal else "pending"
+            available_at = now_text if terminal else _timestamp(
+                now + timedelta(seconds=self._retry_delay(row["attempt_count"]))
+            )
+            connection.execute(
+                """UPDATE edge_task_outcomes SET status=?,available_at=?,lease_owner=NULL,
+                lease_token=NULL,lease_expires_at=NULL,error_code=?,updated_at=?
+                WHERE task_id=? AND status='sending' AND lease_token=?""",
+                (
+                    status, available_at, safe_error, now_text, task_id, lease_token,
+                ),
+            )
+            updated = connection.execute(
+                "SELECT * FROM edge_task_outcomes WHERE task_id=?", (task_id,)
+            ).fetchone()
+            return self._outcome_record(updated)
+
+    def get_outcome(self, task_id: str) -> QueuedOutcome | None:
+        connection = self._connect()
+        try:
+            row = connection.execute(
+                "SELECT * FROM edge_task_outcomes WHERE task_id=?", (task_id,)
+            ).fetchone()
+        finally:
+            connection.close()
+        return self._outcome_record(row) if row is not None else None
+
+    def get_task(self, task_id: str) -> QueuedTask | None:
+        connection = self._connect()
+        try:
+            row = connection.execute(
+                "SELECT * FROM edge_tasks WHERE task_id = ?", (task_id,)
+            ).fetchone()
+        finally:
+            connection.close()
+        return self._task_record(row) if row is not None else None
+
+    def persist_event(
+        self, event: EdgeEventContract | Mapping[str, object]
+    ) -> QueuedEvent:
+        raw = event.to_mapping() if isinstance(event, EdgeEventContract) else dict(event)
+        try:
+            contract = EdgeEventContract.from_mapping(raw)
+        except EdgeContractError as exc:
+            raw_id = raw.get("event_id")
+            if isinstance(raw_id, str) and raw_id:
+                with self._transaction() as connection:
+                    existing = connection.execute(
+                        "SELECT 1 FROM edge_outbound_events WHERE event_id = ?",
+                        (raw_id,),
+                    ).fetchone()
+                if existing is not None:
+                    raise EdgeQueueConflictError(
+                        "event ID already exists with a different digest"
+                    ) from exc
+            raise
+        normalized = contract.to_mapping()
+        digest = contract.digest
+        with self._transaction() as connection:
+            now_text = _timestamp(_utc_now(self._clock))
+            existing = connection.execute(
+                "SELECT * FROM edge_outbound_events WHERE event_id = ?",
+                (contract.event_id,),
+            ).fetchone()
+            if existing is not None:
+                if existing["digest"] != digest:
+                    raise EdgeQueueConflictError(
+                        "event ID already exists with a different digest"
+                    )
+                return self._event_record(existing)
+            approved = self.egress_policy.approve_event(normalized)
+            encoded = strict_json_bytes(approved).decode("utf-8")
+            connection.execute(
+                """
+                INSERT INTO edge_outbound_events (
+                    event_id, digest, event_json, status, attempt_count,
+                    available_at, created_at, updated_at
+                ) VALUES (?, ?, ?, 'pending', 0, ?, ?, ?)
+                """,
+                (contract.event_id, digest, encoded, now_text, now_text, now_text),
+            )
+            row = connection.execute(
+                "SELECT * FROM edge_outbound_events WHERE event_id = ?",
+                (contract.event_id,),
+            ).fetchone()
+            return self._event_record(row)
+
+    def claim_event(self, lease_owner: str) -> QueuedEvent | None:
+        owner = self._lease_owner(lease_owner)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            lease_token = str(uuid.uuid4())
+            expires_at = _timestamp(now + timedelta(seconds=self.lease_seconds))
+            connection.execute(
+                """
+                UPDATE edge_outbound_events
+                SET status = 'failed', lease_owner = NULL, lease_token = NULL,
+                    lease_expires_at = NULL,
+                    error_code = 'lease_attempts_exhausted', updated_at = ?
+                WHERE status = 'sending'
+                  AND julianday(lease_expires_at) <= julianday(?)
+                  AND attempt_count >= ?
+                """,
+                (now_text, now_text, self.max_attempts),
+            )
+            row = connection.execute(
+                """
+                SELECT * FROM edge_outbound_events
+                WHERE attempt_count < ? AND (
+                    (status = 'pending' AND julianday(available_at) <= julianday(?))
+                    OR (status = 'sending'
+                        AND julianday(lease_expires_at) <= julianday(?))
+                )
+                ORDER BY created_at, event_id
+                LIMIT 1
+                """,
+                (self.max_attempts, now_text, now_text),
+            ).fetchone()
+            if row is None:
+                return None
+            connection.execute(
+                """
+                UPDATE edge_outbound_events
+                SET status = 'sending', attempt_count = attempt_count + 1,
+                    lease_owner = ?, lease_token = ?, lease_expires_at = ?,
+                    error_code = NULL, updated_at = ?
+                WHERE event_id = ? AND attempt_count = ? AND (
+                    (status = 'pending' AND julianday(available_at) <= julianday(?))
+                    OR (status = 'sending'
+                        AND julianday(lease_expires_at) <= julianday(?))
+                )
+                """,
+                (
+                    owner,
+                    lease_token,
+                    expires_at,
+                    now_text,
+                    row["event_id"],
+                    row["attempt_count"],
+                    now_text,
+                    now_text,
+                ),
+            )
+            claimed = connection.execute(
+                "SELECT * FROM edge_outbound_events WHERE event_id = ?",
+                (row["event_id"],),
+            ).fetchone()
+            return self._event_record(claimed)
+
+    def acknowledge_event(
+        self,
+        event_id: str,
+        *,
+        lease_token: str,
+        acknowledgement: Mapping[str, object],
+    ) -> QueuedEvent:
+        token_digest = canonical_sha256(lease_token)
+        with self._transaction() as connection:
+            now_text = _timestamp(_utc_now(self._clock))
+            existing = connection.execute(
+                "SELECT * FROM edge_outbound_events WHERE event_id = ?", (event_id,)
+            ).fetchone()
+            if existing is None:
+                raise EdgeQueueError("event does not exist")
+            normalized_ack = self._validate_acknowledgement(
+                acknowledgement,
+                event_id=event_id,
+                event_digest=existing["digest"],
+                remote_lease_token_digest=existing["remote_lease_token_digest"],
+            )
+            encoded = strict_json_bytes(normalized_ack)
+            if len(encoded) > 4_096:
+                raise EdgePolicyError("acknowledgement exceeds the byte limit")
+            ack_digest = canonical_sha256(normalized_ack)
+            if existing["status"] == "acknowledged":
+                if existing["lease_token_digest"] != token_digest:
+                    raise EdgeQueueLeaseError(
+                        "acknowledgement replay requires the original lease token"
+                    )
+                if existing["ack_digest"] != ack_digest:
+                    raise EdgeQueueConflictError(
+                        "acknowledgement replay has a different digest"
+                    )
+                return self._event_record(existing)
+            self._leased_row(
+                connection,
+                "edge_outbound_events",
+                "event_id",
+                event_id,
+                lease_token,
+                status="sending",
+            )
+            connection.execute(
+                """
+                UPDATE edge_outbound_events
+                SET status = 'acknowledged', acknowledgement_json = ?,
+                    ack_digest = ?, lease_token_digest = ?,
+                    control_lease_token_digest = ?,
+                    lease_owner = NULL, lease_token = NULL,
+                    lease_expires_at = NULL, updated_at = ?
+                WHERE event_id = ? AND status = 'sending' AND lease_token = ?
+                """,
+                (
+                    encoded.decode("utf-8"),
+                    ack_digest,
+                    token_digest,
+                    token_digest,
+                    now_text,
+                    event_id,
+                    lease_token,
+                ),
+            )
+            row = connection.execute(
+                "SELECT * FROM edge_outbound_events WHERE event_id = ?", (event_id,)
+            ).fetchone()
+            return self._event_record(row)
+
+    def fail_event(
+        self, event_id: str, *, lease_token: str, error_code: str
+    ) -> QueuedEvent:
+        safe_error = self._error_code(error_code)
+        with self._transaction() as connection:
+            now = _utc_now(self._clock)
+            now_text = _timestamp(now)
+            row = self._leased_row(
+                connection,
+                "edge_outbound_events",
+                "event_id",
+                event_id,
+                lease_token,
+                status="sending",
+            )
+            terminal = row["attempt_count"] >= self.max_attempts
+            status = "failed" if terminal else "pending"
+            available_at = now_text
+            if not terminal:
+                available_at = _timestamp(
+                    now + timedelta(seconds=self._retry_delay(row["attempt_count"]))
+                )
+            connection.execute(
+                """
+                UPDATE edge_outbound_events
+                SET status = ?, available_at = ?, lease_owner = NULL,
+                    lease_token = NULL, lease_expires_at = NULL,
+                    error_code = ?, updated_at = ?
+                WHERE event_id = ? AND status = 'sending' AND lease_token = ?
+                """,
+                (status, available_at, safe_error, now_text, event_id, lease_token),
+            )
+            updated = connection.execute(
+                "SELECT * FROM edge_outbound_events WHERE event_id = ?", (event_id,)
+            ).fetchone()
+            return self._event_record(updated)
+
+    def get_event(self, event_id: str) -> QueuedEvent | None:
+        connection = self._connect()
+        try:
+            row = connection.execute(
+                "SELECT * FROM edge_outbound_events WHERE event_id = ?", (event_id,)
+            ).fetchone()
+        finally:
+            connection.close()
+        return self._event_record(row) if row is not None else None
+
+    @staticmethod
+    def _validate_local_artifact(value: Mapping[str, object]) -> dict[str, str]:
+        required = {
+            "artifact_digest", "artifact_ref", "artifact_ref_hash",
+            "classification", "retention_until",
+        }
+        if not isinstance(value, Mapping) or set(value) != required:
+            raise EdgePolicyError("local artifact requires exact metadata fields")
+        digest = value["artifact_digest"]
+        reference = value["artifact_ref"]
+        reference_hash = value["artifact_ref_hash"]
+        classification = value["classification"]
+        if not isinstance(digest, str) or not re.fullmatch(r"[0-9a-f]{64}", digest):
+            raise EdgePolicyError("local artifact digest is invalid")
+        if (
+            not isinstance(reference, str)
+            or not Path(reference).is_absolute()
+            or len(reference.encode("utf-8")) > 2_048
+            or not isinstance(reference_hash, str)
+            or not re.fullmatch(r"[0-9a-f]{64}", reference_hash)
+            or reference_hash != canonical_sha256(reference)
+        ):
+            raise EdgePolicyError("local artifact reference is invalid")
+        if classification not in {
+            "raw", "recent_detail", "restricted", "desensitized_metadata",
+            "statistics", "lineage", "evidence",
+        }:
+            raise EdgePolicyError("local artifact classification is invalid")
+        try:
+            retention = canonical_timestamp(value["retention_until"], "retention_until")
+        except EdgeContractError as exc:
+            raise EdgePolicyError("local artifact retention is invalid") from exc
+        return {
+            "artifact_digest": digest,
+            "artifact_ref": reference,  # type: ignore[dict-item]
+            "artifact_ref_hash": reference_hash,  # type: ignore[dict-item]
+            "classification": classification,  # type: ignore[dict-item]
+            "retention_until": retention,
+        }
+
+    def _normalize_remote_binding(
+        self, token: object, expires_at: object, attempt: object
+    ) -> tuple[str, str | None, str, int]:
+        if (
+            not isinstance(token, str) or not token.strip()
+            or len(token.encode("utf-8")) > 1024
+        ):
+            raise EdgeQueueLeaseError("remote lease token is invalid")
+        if isinstance(attempt, bool) or not isinstance(attempt, int) or attempt < 1:
+            raise EdgeQueueLeaseError("remote lease attempt is invalid")
+        try:
+            expiry = canonical_timestamp(expires_at, "remote_lease_expires_at")
+        except EdgeContractError as exc:
+            raise EdgeQueueLeaseError("remote lease expiry is invalid") from exc
+        ciphertext: str | None = None
+        if self._remote_lease_encoder is not None:
+            try:
+                ciphertext = self._remote_lease_encoder(token)
+            except Exception as exc:
+                raise EdgeQueueLeaseError("remote lease protection failed") from exc
+            if (
+                not isinstance(ciphertext, str) or not ciphertext
+                or ciphertext == token or len(ciphertext.encode("utf-8")) > 4096
+            ):
+                raise EdgeQueueLeaseError("remote lease protection produced invalid ciphertext")
+        return self._opaque_token_digest(token), ciphertext, expiry, attempt
+
+    @staticmethod
+    def _opaque_token_digest(token: str) -> str:
+        return hashlib.sha256(token.encode("utf-8")).hexdigest()
+
+    def _retry_delay(self, attempt_count: int) -> int:
+        base = min(
+            self.retry_max_seconds,
+            self.retry_base_seconds * (2 ** max(0, attempt_count - 1)),
+        )
+        sample = self._random()
+        if isinstance(sample, bool) or not isinstance(sample, (int, float)) or not 0 <= sample <= 1:
+            raise EdgeQueueError("retry random source returned an invalid value")
+        return min(
+            self.retry_max_seconds,
+            max(1, round(base * (0.5 + float(sample)))),
+        )
+
+    @staticmethod
+    def _bounded_identifier(value: object, label: str) -> str:
+        if (
+            not isinstance(value, str) or not value or value.strip() != value
+            or len(value.encode("utf-8")) > 255
+            or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,254}", value)
+        ):
+            raise EdgeQueueError(f"{label} is invalid")
+        return value
+
+    @staticmethod
+    def _sha256(value: object, label: str) -> str:
+        if not isinstance(value, str) or not re.fullmatch(r"[0-9a-f]{64}", value):
+            raise EdgeQueueError(f"{label} is invalid")
+        return value
+
+    @staticmethod
+    def _lease_owner(value: object) -> str:
+        if (
+            not isinstance(value, str)
+            or not value
+            or len(value.encode("utf-8")) > 255
+            or value.strip() != value
+        ):
+            raise ValueError("lease_owner is invalid")
+        return value
+
+    @staticmethod
+    def _error_code(value: object) -> str:
+        if not isinstance(value, str) or not _ERROR_CODE.fullmatch(value):
+            raise EdgePolicyError("error_code must be a bounded code")
+        if any(part in value for part in _SENSITIVE_ERROR_PARTS):
+            raise EdgePolicyError("error_code contains sensitive content")
+        return value
+
+    def _leased_row(
+        self,
+        connection: sqlite3.Connection,
+        table: str,
+        id_column: str,
+        identity: str,
+        lease_token: str,
+        *,
+        status: str = "leased",
+        status_column: str = "status",
+    ) -> sqlite3.Row:
+        row = connection.execute(
+            f"SELECT * FROM {table} WHERE {id_column} = ?",  # noqa: S608
+            (identity,),
+        ).fetchone()
+        if row is None:
+            raise EdgeQueueError("queue item does not exist")
+        if row[status_column] != status or row["lease_token"] != lease_token:
+            raise EdgeQueueLeaseError("current lease token is required")
+        lease_expires_at = row["lease_expires_at"]
+        if lease_expires_at is None:
+            raise EdgeQueueLeaseError("lease has expired")
+        parsed_expiry = datetime.fromisoformat(lease_expires_at.replace("Z", "+00:00"))
+        if parsed_expiry <= _utc_now(self._clock):
+            raise EdgeQueueLeaseError("lease has expired")
+        return row
+
+    @staticmethod
+    def _validate_acknowledgement(
+        value: Mapping[str, object],
+        *,
+        event_id: str,
+        event_digest: str,
+        remote_lease_token_digest: str | None,
+    ) -> dict[str, str]:
+        if remote_lease_token_digest is not None:
+            required = {"event_id", "event_digest", "remote_lease_digest", "received_at", "status"}
+            if not isinstance(value, Mapping) or set(value) != required:
+                raise EdgePolicyError("bound acknowledgement requires exact approved fields")
+            if value["event_id"] != event_id or value["event_digest"] != event_digest:
+                raise EdgePolicyError("acknowledgement does not bind the event")
+            if value["remote_lease_digest"] != remote_lease_token_digest:
+                raise EdgePolicyError("acknowledgement remote lease is invalid")
+            if value["status"] != "accepted":
+                raise EdgePolicyError("acknowledgement status is invalid")
+            try:
+                received_at = canonical_timestamp(value["received_at"], "received_at")
+            except EdgeContractError as exc:
+                raise EdgePolicyError("acknowledgement received_at is invalid") from exc
+            return {
+                "event_id": event_id,
+                "event_digest": event_digest,
+                "remote_lease_digest": remote_lease_token_digest,
+                "received_at": received_at,
+                "status": "accepted",
+            }
+        if not isinstance(value, Mapping) or set(value) != {
+            "message_id",
+            "received_at",
+            "status",
+        }:
+            raise EdgePolicyError("acknowledgement requires exact approved fields")
+        message_id = value["message_id"]
+        status = value["status"]
+        if (
+            not isinstance(message_id, str)
+            or not message_id
+            or message_id.strip() != message_id
+            or len(message_id.encode("utf-8")) > 255
+        ):
+            raise EdgePolicyError("acknowledgement message_id is invalid")
+        if status != "accepted":
+            raise EdgePolicyError("acknowledgement status is invalid")
+        try:
+            received_at = canonical_timestamp(value["received_at"], "received_at")
+        except EdgeContractError as exc:
+            raise EdgePolicyError("acknowledgement received_at is invalid") from exc
+        return {
+            "message_id": message_id,
+            "received_at": received_at,
+            "status": status,
+        }
+
+    @staticmethod
+    def _task_record(row: sqlite3.Row) -> QueuedTask:
+        task = json.loads(row["task_json"])
+        return QueuedTask(
+            task_id=row["task_id"],
+            digest=row["digest"],
+            status=row["status"],
+            task=_freeze_mapping(task),
+            attempt_count=row["attempt_count"],
+            available_at=row["available_at"],
+            lease_owner=row["lease_owner"],
+            lease_token=row["lease_token"],
+            lease_expires_at=row["lease_expires_at"],
+            error_code=row["error_code"],
+            authority_key_id=row["authority_key_id"],
+            authority_digest=row["authority_digest"],
+            remote_lease_token_digest=row["remote_lease_token_digest"],
+            remote_lease_expires_at=row["remote_lease_expires_at"],
+            remote_attempt=row["remote_attempt"],
+            created_at=row["created_at"],
+            updated_at=row["updated_at"],
+        )
+
+    @staticmethod
+    def _event_record(row: sqlite3.Row) -> QueuedEvent:
+        event = json.loads(row["event_json"])
+        acknowledgement = (
+            json.loads(row["acknowledgement_json"])
+            if row["acknowledgement_json"] is not None
+            else None
+        )
+        return QueuedEvent(
+            event_id=row["event_id"],
+            digest=row["digest"],
+            status=row["status"],
+            event=_freeze_mapping(event),
+            attempt_count=row["attempt_count"],
+            available_at=row["available_at"],
+            lease_owner=row["lease_owner"],
+            lease_token=row["lease_token"],
+            lease_expires_at=row["lease_expires_at"],
+            error_code=row["error_code"],
+            acknowledgement=(
+                _freeze_mapping(acknowledgement)
+                if acknowledgement is not None
+                else None
+            ),
+            task_digest=row["task_digest"],
+            remote_lease_token_digest=row["remote_lease_token_digest"],
+            control_lease_token_digest=row["control_lease_token_digest"],
+            created_at=row["created_at"],
+            updated_at=row["updated_at"],
+        )
+
+    @staticmethod
+    def _artifact_record(row: sqlite3.Row) -> LocalArtifact:
+        receipt = (
+            json.loads(row["deletion_receipt_json"])
+            if row["deletion_receipt_json"] is not None
+            else None
+        )
+        return LocalArtifact(
+            artifact_digest=row["artifact_digest"],
+            artifact_ref=row["artifact_ref"],
+            artifact_ref_hash=row["artifact_ref_hash"],
+            classification=row["classification"],
+            retention_until=row["retention_until"],
+            task_id=row["task_id"],
+            event_id=row["event_id"],
+            cleanup_status=row["cleanup_status"],
+            attempt_count=row["attempt_count"],
+            available_at=row["available_at"],
+            lease_owner=row["lease_owner"],
+            lease_token=row["lease_token"],
+            lease_expires_at=row["lease_expires_at"],
+            error_code=row["error_code"],
+            deletion_receipt=_freeze_mapping(receipt) if receipt is not None else None,
+            created_at=row["created_at"],
+            updated_at=row["updated_at"],
+        )
+
+    @staticmethod
+    def _outcome_record(row: sqlite3.Row) -> QueuedOutcome:
+        outcome = json.loads(row["outcome_json"])
+        acknowledgement = (
+            json.loads(row["acknowledgement_json"])
+            if row["acknowledgement_json"] is not None
+            else None
+        )
+        return QueuedOutcome(
+            task_id=row["task_id"],
+            digest=row["digest"],
+            status=row["status"],
+            outcome=_freeze_mapping(outcome),
+            attempt_count=row["attempt_count"],
+            available_at=row["available_at"],
+            lease_owner=row["lease_owner"],
+            lease_token=row["lease_token"],
+            lease_expires_at=row["lease_expires_at"],
+            error_code=row["error_code"],
+            acknowledgement=(
+                _freeze_mapping(acknowledgement)
+                if acknowledgement is not None
+                else None
+            ),
+            remote_lease_token_digest=row["remote_lease_token_digest"],
+            created_at=row["created_at"],
+            updated_at=row["updated_at"],
+        )

+ 609 - 0
app/core/edge_gateway/repository.py

@@ -0,0 +1,609 @@
+"""Transactional PostgreSQL repository for the edge control plane."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+
+from sqlalchemy import text
+
+from app.core.common.identifiers import new_governance_uid
+
+_OVERFLOW_FINGERPRINT = hashlib.sha256(b"edge-failure-window-capacity").hexdigest()
+
+
+def _json(value: object) -> str:
+    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
+
+
+class EdgeGatewayRepository:
+    """Owns atomic state transitions; secrets passed here are already hashed."""
+
+    def __init__(self, session):
+        self.session = session
+
+    def rollback(self) -> None:
+        self.session.rollback()
+
+    def create_enrollment(self, values: dict) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.edge_gateway_enrollments
+              (uid,gateway_id,gateway_name,environment,network_zone,policy_digest,
+               expected_certificate_sha256,allowed_control_hosts,allowed_proxy_hosts,
+               token_hash,status,expires_at,created_by)
+            VALUES (CAST(:uid AS uuid),CAST(:gateway_id AS uuid),:gateway_name,:environment,
+                    :network_zone,:policy_digest,:expected_certificate_sha256,
+                    CAST(:control_hosts AS text[]),
+                    CAST(:proxy_hosts AS text[]),:token_hash,'pending',
+                    CURRENT_TIMESTAMP + (:ttl * INTERVAL '1 second'),CAST(:actor_uid AS uuid))
+        """), values)
+        self._audit(None, "enrollment_created", values["actor_uid"], True, {"gateway_id": values["gateway_id"], "ttl_seconds": values["ttl"]})
+        self.session.commit()
+
+    def lock_enrollment(self, token_hash: str):
+        return self.session.execute(text("""
+            SELECT uid::text,gateway_id::text,gateway_name,environment,network_zone,
+                   policy_digest,expected_certificate_sha256,allowed_control_hosts,
+                   allowed_proxy_hosts,status,expires_at
+              FROM public.edge_gateway_enrollments WHERE token_hash=:token_hash FOR UPDATE
+        """), {"token_hash": token_hash}).mappings().one_or_none()
+
+    def complete_registration(self, enrollment, credential_hash: str, certificate_sha256: str, version: str) -> dict:
+        credential_uid = new_governance_uid()
+        gateway_id = enrollment["gateway_id"]
+        self.session.execute(text("""
+            INSERT INTO public.edge_gateways
+              (uid,name,environment,network_zone,status,current_generation,policy_digest,
+               allowed_control_hosts,allowed_proxy_hosts,runtime_version)
+            VALUES (CAST(:gateway_id AS uuid),:name,:environment,:zone,'pending',1,:policy,
+                    CAST(:control_hosts AS text[]),CAST(:proxy_hosts AS text[]),:version)
+        """), {
+            "gateway_id": gateway_id, "name": enrollment["gateway_name"],
+            "environment": enrollment["environment"], "zone": enrollment["network_zone"],
+            "policy": enrollment["policy_digest"], "control_hosts": enrollment["allowed_control_hosts"],
+            "proxy_hosts": enrollment["allowed_proxy_hosts"], "version": version,
+        })
+        self.session.execute(text("""
+            INSERT INTO public.edge_gateway_credentials
+              (uid,gateway_id,generation,credential_hash,certificate_sha256,status,expires_at)
+            VALUES (CAST(:uid AS uuid),CAST(:gateway_id AS uuid),1,:credential_hash,
+                    :certificate,'active',CURRENT_TIMESTAMP + INTERVAL '365 days')
+        """), {"uid": credential_uid, "gateway_id": gateway_id, "credential_hash": credential_hash, "certificate": certificate_sha256})
+        self.session.execute(text("UPDATE public.edge_gateways SET status='active',updated_at=CURRENT_TIMESTAMP WHERE uid=CAST(:gateway_id AS uuid)"), {"gateway_id": gateway_id})
+        consumed = self.session.execute(text("""
+            UPDATE public.edge_gateway_enrollments SET status='consumed',consumed_at=CURRENT_TIMESTAMP
+             WHERE uid=CAST(:uid AS uuid) AND status='pending' AND expires_at>CURRENT_TIMESTAMP
+             RETURNING uid
+        """), {"uid": enrollment["uid"]}).scalar_one_or_none()
+        if consumed is None:
+            raise RuntimeError("enrollment is no longer consumable")
+        self._audit(gateway_id, "gateway_registered", None, True, {"generation": 1})
+        self.session.commit()
+        return {"gateway_id": gateway_id, "credential_uid": credential_uid, "generation": 1}
+
+    def authenticate(self, credential_hash: str, *, lock: bool = False):
+        if lock:
+            gateway_id = self.session.execute(text("""
+                SELECT gateway_id::text
+                  FROM public.edge_gateway_credentials
+                 WHERE credential_hash=:credential_hash
+            """), {"credential_hash": credential_hash}).scalar_one_or_none()
+            if gateway_id is None:
+                return None
+            gateway = self.session.execute(text("""
+                SELECT uid::text AS gateway_id,environment,network_zone,policy_digest,
+                       allowed_control_hosts,allowed_proxy_hosts,status,current_generation
+                  FROM public.edge_gateways
+                 WHERE uid=CAST(:gateway_id AS uuid)
+                 FOR UPDATE
+            """), {"gateway_id": gateway_id}).mappings().one_or_none()
+            if gateway is None or gateway["status"] not in {"active", "offline"}:
+                return None
+            credential = self.session.execute(text("""
+                SELECT uid::text,gateway_id::text,generation,certificate_sha256
+                  FROM public.edge_gateway_credentials
+                 WHERE credential_hash=:credential_hash AND status='active'
+                   AND expires_at>CURRENT_TIMESTAMP
+                   AND gateway_id=CAST(:gateway_id AS uuid)
+                   AND generation=:generation
+                 FOR UPDATE
+            """), {
+                "credential_hash": credential_hash,
+                "gateway_id": gateway_id,
+                "generation": gateway["current_generation"],
+            }).mappings().one_or_none()
+            if credential is None:
+                return None
+            return {**dict(credential), **{
+                key: gateway[key] for key in (
+                    "environment", "network_zone", "policy_digest",
+                    "allowed_control_hosts", "allowed_proxy_hosts", "status",
+                )
+            }}
+        return self.session.execute(text("""
+            SELECT c.uid::text,c.gateway_id::text,c.generation,c.certificate_sha256,
+                   g.environment,g.network_zone,g.policy_digest,g.allowed_control_hosts,
+                   g.allowed_proxy_hosts,g.status
+              FROM public.edge_gateway_credentials c
+              JOIN public.edge_gateways g ON g.uid=c.gateway_id
+             WHERE c.credential_hash=:credential_hash AND c.status='active'
+               AND c.expires_at>CURRENT_TIMESTAMP AND g.status IN ('active','offline')
+               AND g.current_generation=c.generation
+        """), {"credential_hash": credential_hash}).mappings().one_or_none()
+
+    def commit(self) -> None:
+        self.session.commit()
+
+    def list_gateways(self, *, limit: int, offset: int) -> list[dict]:
+        return [dict(row) for row in self.session.execute(text("""
+            SELECT uid::text AS gateway_id,name,environment,network_zone,status,current_generation,
+                   policy_digest,allowed_control_hosts,allowed_proxy_hosts,last_heartbeat_at,runtime_version
+              FROM public.edge_gateways ORDER BY created_at,uid LIMIT :limit OFFSET :offset
+        """), {"limit": limit, "offset": offset}).mappings()]
+
+    def gateway_binding(self, gateway_id: str):
+        return self.session.execute(text("""
+            SELECT uid::text AS gateway_id,environment,network_zone,policy_digest,status
+              FROM public.edge_gateways WHERE uid=CAST(:gateway_id AS uuid)
+        """), {"gateway_id": gateway_id}).mappings().one_or_none()
+
+    def heartbeat(self, gateway_id: str, version: str, safe_summary: dict) -> dict:
+        row = self.session.execute(text("""
+            UPDATE public.edge_gateways SET status='active',runtime_version=:version,
+                   safe_health_summary=CAST(:summary AS jsonb),last_heartbeat_at=CURRENT_TIMESTAMP,
+                   updated_at=CURRENT_TIMESTAMP WHERE uid=CAST(:gateway_id AS uuid)
+             RETURNING uid::text AS gateway_id,status,last_heartbeat_at,runtime_version
+        """), {"gateway_id": gateway_id, "version": version, "summary": _json(safe_summary)}).mappings().one()
+        self._audit(gateway_id, "heartbeat_accepted", None, True, {"version": version})
+        self.session.commit()
+        return dict(row)
+
+    def rotate(self, gateway_id: str, credential_hash: str, certificate_sha256: str, actor_uid: str, request_id: str, request_digest: str) -> dict | None:
+        gateway = self.session.execute(text("""
+            SELECT current_generation,status FROM public.edge_gateways
+             WHERE uid=CAST(:gateway_id AS uuid) FOR UPDATE
+        """), {"gateway_id": gateway_id}).mappings().one_or_none()
+        if gateway is None or gateway["status"] not in {"active", "offline"}:
+            return None
+        prior = self.session.execute(text("""
+            SELECT request_digest,generation FROM edge_gateway_rotation_requests
+             WHERE gateway_id=CAST(:gateway_id AS uuid) AND request_id=:request_id
+        """), {"gateway_id": gateway_id, "request_id": request_id}).mappings().one_or_none()
+        if prior:
+            self.session.commit()
+            if prior["request_digest"] != request_digest:
+                return {"conflict": True}
+            return {"generation": prior["generation"], "replayed": True}
+        old = self.session.execute(text("""
+            UPDATE public.edge_gateway_credentials SET status='rotated',revoked_at=CURRENT_TIMESTAMP
+             WHERE gateway_id=CAST(:gateway_id AS uuid) AND generation=:generation AND status='active'
+             RETURNING uid::text
+        """), {"gateway_id": gateway_id, "generation": gateway["current_generation"]}).scalar_one()
+        generation = int(gateway["current_generation"]) + 1
+        uid = new_governance_uid()
+        self.session.execute(text("""
+            INSERT INTO public.edge_gateway_credentials
+              (uid,gateway_id,generation,credential_hash,certificate_sha256,status,expires_at,rotated_from_uid)
+            VALUES (CAST(:uid AS uuid),CAST(:gateway_id AS uuid),:generation,:credential_hash,
+                    :certificate,'active',CURRENT_TIMESTAMP + INTERVAL '365 days',CAST(:old AS uuid))
+        """), {"uid": uid, "gateway_id": gateway_id, "generation": generation, "credential_hash": credential_hash, "certificate": certificate_sha256, "old": old})
+        self.session.execute(text("UPDATE public.edge_gateways SET current_generation=:generation,status='active',updated_at=CURRENT_TIMESTAMP WHERE uid=CAST(:gateway_id AS uuid)"), {"gateway_id": gateway_id, "generation": generation})
+        self.session.execute(text("""
+            INSERT INTO edge_gateway_rotation_requests(gateway_id,request_id,request_digest,generation)
+            VALUES (CAST(:gateway_id AS uuid),:request_id,:request_digest,:generation)
+        """), {"gateway_id": gateway_id, "request_id": request_id, "request_digest": request_digest, "generation": generation})
+        self._audit(gateway_id, "credential_rotated", actor_uid, True, {"generation": generation})
+        self.session.commit()
+        return {"credential_uid": uid, "generation": generation}
+
+    def revoke(self, gateway_id: str, actor_uid: str) -> bool:
+        gateway = self.session.execute(text("""
+            UPDATE public.edge_gateways SET status='revoked',revoked_at=CURRENT_TIMESTAMP,
+                   updated_at=CURRENT_TIMESTAMP WHERE uid=CAST(:gateway_id AS uuid)
+                   AND status <> 'revoked' RETURNING uid::text
+        """), {"gateway_id": gateway_id}).scalar_one_or_none()
+        if gateway is None:
+            self.session.rollback()
+            return False
+        self.session.execute(text("UPDATE public.edge_gateway_credentials SET status='revoked',revoked_at=CURRENT_TIMESTAMP WHERE gateway_id=CAST(:gateway_id AS uuid) AND status='active'"), {"gateway_id": gateway_id})
+        self._audit(gateway_id, "gateway_revoked", actor_uid, True, {})
+        self.session.commit()
+        return True
+
+    def insert_task(
+        self, contract: dict, digest: str, authority: dict, actor_uid: str,
+    ) -> dict:
+        existing = self.session.execute(text("""
+            SELECT task_id,contract_digest,contract,authority FROM public.edge_gateway_tasks
+             WHERE gateway_id=CAST(:gateway_id AS uuid) AND idempotency_key=:idempotency_key FOR UPDATE
+        """), {"gateway_id": contract["gateway_id"], "idempotency_key": contract["idempotency_key"]}).mappings().one_or_none()
+        if existing:
+            result = dict(existing)
+            self.session.commit()
+            return result
+        inserted = self.session.execute(text("""
+            INSERT INTO public.edge_gateway_tasks
+              (task_id,gateway_id,idempotency_key,contract,contract_digest,authority,status,created_by)
+            SELECT :task_id,uid,:idempotency_key,CAST(:contract AS jsonb),:digest,
+                   CAST(:authority AS jsonb),'pending',CAST(:actor AS uuid)
+              FROM public.edge_gateways WHERE uid=CAST(:gateway_id AS uuid) AND status IN ('active','offline')
+            ON CONFLICT DO NOTHING
+            RETURNING task_id
+        """), {"task_id": contract["task_id"], "gateway_id": contract["gateway_id"], "idempotency_key": contract["idempotency_key"], "contract": _json(contract), "digest": digest, "authority": _json(authority), "actor": actor_uid})
+        if inserted.scalar_one_or_none() is None:
+            concurrent = self.session.execute(text("""
+                SELECT task_id,contract_digest,contract,authority FROM public.edge_gateway_tasks
+                 WHERE task_id=:task_id OR
+                   (gateway_id=CAST(:gateway_id AS uuid) AND idempotency_key=:idempotency_key)
+            """), {
+                "task_id": contract["task_id"], "gateway_id": contract["gateway_id"],
+                "idempotency_key": contract["idempotency_key"],
+            }).mappings().one_or_none()
+            self.session.commit()
+            return dict(concurrent) if concurrent else {}
+        self._audit(contract["gateway_id"], "task_issued", actor_uid, True, {"task_id": contract["task_id"]})
+        self.session.commit()
+        return {"task_id": contract["task_id"], "contract_digest": digest, "contract": contract, "authority": authority}
+
+    def pull_task(self, gateway_id: str, lease_hash: str, lease_seconds: int = 120):
+        row = self.session.execute(text("""
+            WITH expired AS (
+              UPDATE public.edge_gateway_tasks
+                 SET status='failed',lease_token_hash=NULL,lease_expires_at=NULL,
+                     result_summary='{"reason_code":"task_deadline_expired"}'::jsonb,
+                     updated_at=CURRENT_TIMESTAMP
+               WHERE gateway_id=CAST(:gateway_id AS uuid)
+                 AND status IN ('pending','leased')
+                 AND (contract->>'deadline_at')::timestamptz<=CURRENT_TIMESTAMP
+               RETURNING task_id
+            ), candidate AS (
+              SELECT task_id FROM public.edge_gateway_tasks
+               WHERE gateway_id=CAST(:gateway_id AS uuid)
+                 AND (status='pending' OR (status='leased' AND lease_expires_at<=CURRENT_TIMESTAMP))
+                 AND (contract->>'deadline_at')::timestamptz>CURRENT_TIMESTAMP
+               ORDER BY created_at,task_id FOR UPDATE SKIP LOCKED LIMIT 1
+            )
+            UPDATE public.edge_gateway_tasks t SET status='leased',lease_token_hash=:lease_hash,
+                   lease_expires_at=LEAST(
+                     (t.contract->>'deadline_at')::timestamptz,
+                     CURRENT_TIMESTAMP + (:lease_seconds * INTERVAL '1 second')
+                   ),
+                   updated_at=CURRENT_TIMESTAMP FROM candidate c WHERE t.task_id=c.task_id
+             RETURNING t.task_id,t.contract,t.authority,t.contract_digest,t.lease_expires_at
+        """), {"gateway_id": gateway_id, "lease_hash": lease_hash, "lease_seconds": lease_seconds}).mappings().one_or_none()
+        self.session.commit()
+        return dict(row) if row else None
+
+    def cancel_task(self, task_id: str, actor_uid: str) -> bool:
+        gateway_id = self.session.execute(text("""
+            SELECT g.uid::text
+              FROM public.edge_gateway_tasks t
+              JOIN public.edge_gateways g ON g.uid=t.gateway_id
+             WHERE t.task_id=:task_id
+             FOR UPDATE OF g
+        """), {"task_id": task_id}).scalar_one_or_none()
+        if gateway_id is None:
+            self.session.rollback()
+            return False
+        row = self.session.execute(text("""
+            UPDATE public.edge_gateway_tasks SET
+                   status=CASE WHEN status='pending' THEN 'cancelled' ELSE 'cancel_requested' END,
+                   cancel_requested_at=CURRENT_TIMESTAMP,updated_at=CURRENT_TIMESTAMP
+             WHERE task_id=:task_id AND status IN ('pending','leased') RETURNING gateway_id::text
+        """), {"task_id": task_id}).scalar_one_or_none()
+        if row:
+            self._audit(gateway_id, "task_cancel_requested", actor_uid, True, {"task_id": task_id})
+        self.session.commit()
+        return row is not None
+
+    def task_outcome(self, gateway_id: str, task_id: str, lease_hash: str, outcome: str, summary: dict):
+        row = self.session.execute(text("""
+            SELECT status,lease_token_hash,lease_expires_at,result_summary,
+                   lease_expires_at>CURRENT_TIMESTAMP AS lease_current
+              FROM edge_gateway_tasks WHERE gateway_id=CAST(:gateway_id AS uuid)
+               AND task_id=:task_id FOR UPDATE
+        """), {"gateway_id": gateway_id, "task_id": task_id}).mappings().one_or_none()
+        if row is None:
+            self.session.rollback()
+            return None
+        if row["status"] == outcome:
+            if dict(row["result_summary"] or {}) != summary:
+                self.session.rollback()
+                return None
+            self.session.commit()
+            return {"task_id": task_id, "status": outcome, "replayed": True}
+        allowed = (
+            outcome in {"completed", "failed"} and row["status"] == "leased"
+            and bool(row["lease_current"])
+        ) or (outcome == "cancelled" and row["status"] == "cancel_requested")
+        if not allowed or row["lease_token_hash"] != lease_hash:
+            self.session.rollback()
+            return None
+        try:
+            self.session.execute(text("""
+                UPDATE edge_gateway_tasks SET status=:outcome,result_summary=CAST(:summary AS jsonb),
+                       lease_token_hash=NULL,lease_expires_at=NULL,updated_at=CURRENT_TIMESTAMP
+                 WHERE gateway_id=CAST(:gateway_id AS uuid) AND task_id=:task_id
+            """), {"gateway_id": gateway_id, "task_id": task_id, "outcome": outcome,
+                     "summary": _json(summary)})
+            self._audit(
+                gateway_id, "task_outcome_recorded", None, True,
+                {"task_id": task_id, "outcome": outcome},
+            )
+            self.session.commit()
+        except Exception:
+            self.session.rollback()
+            raise
+        return {"task_id": task_id, "status": outcome, "replayed": False}
+
+    def cancelled_tasks(
+        self, gateway_id: str, *, limit: int, cursor: str | None = None
+    ) -> dict:
+        rows = list(self.session.execute(text("""
+            SELECT task_id FROM public.edge_gateway_tasks
+             WHERE gateway_id=CAST(:gateway_id AS uuid) AND status='cancel_requested'
+               AND (
+                 CAST(:cursor AS text) IS NULL OR (created_at,task_id) > (
+                   SELECT created_at,task_id FROM public.edge_gateway_tasks
+                    WHERE gateway_id=CAST(:gateway_id AS uuid) AND task_id=:cursor
+                 )
+               )
+             ORDER BY created_at,task_id LIMIT :page_limit
+        """), {
+            "gateway_id": gateway_id,
+            "cursor": cursor,
+            "page_limit": limit + 1,
+        }).scalars())
+        page = rows[:limit]
+        return {
+            "items": page,
+            "next_cursor": page[-1] if len(rows) > limit else None,
+        }
+
+    def event_by_id(self, gateway_id: str, event_id: str):
+        return self.session.execute(text("""
+            SELECT contract_digest,ack FROM public.edge_gateway_events
+             WHERE gateway_id=CAST(:gateway_id AS uuid) AND event_id=:event_id
+        """), {"gateway_id": gateway_id, "event_id": event_id}).mappings().one_or_none()
+
+    def task_lease_binding(self, task_id: str, gateway_id: str, lease_hash: str):
+        return self.session.execute(text("""
+              SELECT contract FROM public.edge_gateway_tasks
+               WHERE task_id=:task_id AND gateway_id=CAST(:gateway_id AS uuid)
+                 AND status='leased' AND lease_token_hash=:lease_hash
+                 AND lease_expires_at>CURRENT_TIMESTAMP
+               FOR UPDATE
+        """), {"task_id": task_id, "gateway_id": gateway_id, "lease_hash": lease_hash}).scalar_one_or_none()
+
+    def insert_event(self, contract: dict, digest: str, ack: dict) -> dict | None:
+        inserted = self.session.execute(text("""
+            INSERT INTO public.edge_gateway_events
+              (event_id,gateway_id,task_id,classification,contract,contract_digest,ack)
+            VALUES (:event_id,CAST(:gateway_id AS uuid),:task_id,:classification,
+                    CAST(:contract AS jsonb),:digest,CAST(:ack AS jsonb))
+            ON CONFLICT (event_id) DO NOTHING RETURNING event_id
+        """), {"event_id": contract["event_id"], "gateway_id": contract["gateway_id"], "task_id": contract["task_id"], "classification": contract["classification"], "contract": _json(contract), "digest": digest, "ack": _json(ack)})
+        if inserted.scalar_one_or_none() is None:
+            existing = self.event_by_id(contract["gateway_id"], contract["event_id"])
+            if existing is None or existing["contract_digest"] != digest:
+                self.session.rollback()
+                return None
+            self.session.commit()
+            return dict(existing["ack"])
+        self._audit(contract["gateway_id"], "event_accepted", None, True, {"event_id": contract["event_id"], "classification": contract["classification"]})
+        self.session.execute(text("""
+            UPDATE edge_gateway_tasks SET status='completed',lease_token_hash=NULL,
+                   lease_expires_at=NULL,result_summary=CAST(:summary AS jsonb),updated_at=CURRENT_TIMESTAMP
+             WHERE task_id=:task_id AND gateway_id=CAST(:gateway_id AS uuid) AND status='leased'
+        """), {"task_id": contract["task_id"], "gateway_id": contract["gateway_id"],
+                 "summary": _json({"event_id": contract["event_id"], "classification": contract["classification"]})})
+        self.session.commit()
+        return ack
+
+    def offer_release(
+        self, gateway_id: str, manifest: dict, actor_uid: str,
+        request_id: str, request_digest: str,
+    ) -> dict:
+        gateway = self.session.execute(text("""
+            SELECT status FROM public.edge_gateways
+             WHERE uid=CAST(:gateway_id AS uuid) FOR UPDATE
+        """), {"gateway_id": gateway_id}).mappings().one_or_none()
+        if gateway is None or gateway["status"] not in {"active", "offline"}:
+            self.session.rollback()
+            return {}
+        prior_request = self.session.execute(text("""
+            SELECT uid::text AS release_id,status,version,artifact_digest,rollback_version,
+                   request_id,request_digest,signed_manifest
+              FROM edge_gateway_releases WHERE gateway_id=CAST(:gateway_id AS uuid)
+               AND request_id=:request_id
+        """), {"gateway_id": gateway_id, "request_id": request_id}).mappings().one_or_none()
+        prior_version = self.session.execute(text("""
+            SELECT uid::text AS release_id,request_id,request_digest
+              FROM edge_gateway_releases WHERE gateway_id=CAST(:gateway_id AS uuid)
+               AND version=:version
+        """), {"gateway_id": gateway_id, "version": manifest["version"]}).mappings().one_or_none()
+        if prior_request:
+            self.session.commit()
+            if (
+                prior_request["request_digest"] != request_digest
+                or prior_request["version"] != manifest["version"]
+                or (prior_version is not None and prior_version["request_id"] != request_id)
+            ):
+                return {"conflict": True}
+            return {
+                **{key: prior_request[key] for key in (
+                    "release_id", "status", "version", "artifact_digest",
+                    "rollback_version",
+                )},
+                "manifest": dict(prior_request["signed_manifest"]),
+            }
+        if prior_version:
+            self.session.commit()
+            return {"conflict": True}
+        self.session.execute(text("""
+            INSERT INTO public.edge_gateway_releases
+              (uid,gateway_id,version,artifact_digest,rollback_version,status,request_id,
+               request_digest,created_by,artifact_name,deadline_at,signature_algorithm,
+               key_id,manifest_digest,signature,signed_manifest)
+            VALUES (CAST(:uid AS uuid),CAST(:gateway_id AS uuid),:version,:digest,:rollback,
+                    :status,:request_id,:request_digest,CAST(:actor AS uuid),:artifact_name,
+                    CAST(:deadline_at AS timestamptz),:signature_algorithm,:key_id,
+                    :manifest_digest,:signature,CAST(:signed_manifest AS jsonb))
+        """), {
+            "uid": manifest["release_id"], "gateway_id": gateway_id,
+            "version": manifest["version"], "digest": manifest["artifact_digest"],
+            "rollback": manifest["rollback_version"], "status": manifest["status"],
+            "request_id": request_id, "request_digest": request_digest,
+            "actor": actor_uid, "artifact_name": manifest["artifact_name"],
+            "deadline_at": manifest["deadline_at"],
+            "signature_algorithm": manifest["signature_algorithm"],
+            "key_id": manifest["key_id"], "manifest_digest": manifest["manifest_digest"],
+            "signature": manifest["signature"], "signed_manifest": _json(manifest),
+        })
+        self._audit(gateway_id, "release_offered", actor_uid, True, {
+            "release_id": manifest["release_id"], "version": manifest["version"],
+        })
+        self.session.commit()
+        return {
+            "release_id": manifest["release_id"], "status": "offered",
+            "version": manifest["version"], "artifact_digest": manifest["artifact_digest"],
+            "rollback_version": manifest["rollback_version"], "manifest": manifest,
+        }
+
+    def release_for_ack(self, gateway_id: str, release_id: str):
+        return self.session.execute(text("""
+            SELECT uid::text AS release_id,status,safe_summary,signed_manifest
+              FROM public.edge_gateway_releases WHERE uid=CAST(:uid AS uuid)
+               AND gateway_id=CAST(:gateway_id AS uuid) FOR UPDATE
+        """), {"uid": release_id, "gateway_id": gateway_id}).mappings().one_or_none()
+
+    def acknowledge_release(
+        self, gateway_id: str, release_id: str, status: str, safe_summary: dict,
+        signed_manifest: dict,
+    ) -> dict | None:
+        current = self.session.execute(text("""
+            SELECT uid::text AS release_id,status,version,rollback_version,safe_summary
+              FROM public.edge_gateway_releases WHERE uid=CAST(:uid AS uuid)
+               AND gateway_id=CAST(:gateway_id AS uuid) FOR UPDATE
+        """), {"uid": release_id, "gateway_id": gateway_id}).mappings().one_or_none()
+        if current is None:
+            self.session.rollback()
+            return None
+        if current["status"] == status:
+            if dict(current["safe_summary"] or {}) != safe_summary:
+                self.session.rollback()
+                return None
+            self.session.commit()
+            return {key: current[key] for key in ("release_id", "status", "version", "rollback_version")}
+        allowed = {
+            "offered": {"accepted"},
+            "accepted": {"installed", "failed"},
+            "failed": {"rolled_back"},
+            "installed": {"rolled_back"},
+            "rolled_back": set(),
+        }
+        if status not in allowed[current["status"]]:
+            self.session.rollback()
+            return None
+        row = self.session.execute(text("""
+            UPDATE public.edge_gateway_releases SET status=:status,safe_summary=CAST(:summary AS jsonb),
+                   acknowledged_at=CURRENT_TIMESTAMP,signature_algorithm=:signature_algorithm,
+                   key_id=:key_id,manifest_digest=:manifest_digest,signature=:signature,
+                   signed_manifest=CAST(:signed_manifest AS jsonb)
+             WHERE uid=CAST(:uid AS uuid)
+             RETURNING uid::text AS release_id,status,version,rollback_version
+        """), {
+            "uid": release_id, "status": status, "summary": _json(safe_summary),
+            "signature_algorithm": signed_manifest["signature_algorithm"],
+            "key_id": signed_manifest["key_id"],
+            "manifest_digest": signed_manifest["manifest_digest"],
+            "signature": signed_manifest["signature"],
+            "signed_manifest": _json(signed_manifest),
+        }).mappings().one()
+        if row:
+            self._audit(gateway_id, "release_acknowledged", None, True, {"release_id": release_id, "outcome": status})
+        self.session.commit()
+        return dict(row) if row else None
+
+    def unfinished_releases(
+        self, gateway_id: str, *, limit: int, cursor: str | None = None
+    ) -> dict:
+        rows = list(self.session.execute(text("""
+            SELECT uid::text AS release_id,signed_manifest
+              FROM public.edge_gateway_releases
+             WHERE gateway_id=CAST(:gateway_id AS uuid)
+               AND status IN ('offered','accepted','failed')
+               AND (
+                 CAST(:cursor AS text) IS NULL OR (created_at,uid) > (
+                   SELECT created_at,uid FROM public.edge_gateway_releases
+                    WHERE gateway_id=CAST(:gateway_id AS uuid)
+                      AND uid=CAST(:cursor AS uuid)
+                 )
+               )
+             ORDER BY created_at,uid LIMIT :page_limit
+        """), {
+            "gateway_id": gateway_id,
+            "cursor": cursor,
+            "page_limit": limit + 1,
+        }).mappings())
+        page = rows[:limit]
+        return {
+            "items": [dict(row["signed_manifest"]) for row in page],
+            "next_cursor": page[-1]["release_id"] if len(rows) > limit else None,
+        }
+
+    def latest_release_baseline(self, gateway_id: str) -> dict | None:
+        row = self.session.execute(text("""
+            SELECT signed_manifest FROM public.edge_gateway_releases
+             WHERE gateway_id=CAST(:gateway_id AS uuid)
+               AND status IN ('installed','rolled_back')
+             ORDER BY COALESCE(acknowledged_at,created_at) DESC,
+                      created_at DESC,uid DESC LIMIT 1
+        """), {"gateway_id": gateway_id}).scalar_one_or_none()
+        return dict(row) if row is not None else None
+
+    def audit_failure(
+        self, gateway_id: str | None, event_type: str, reason_code: str, *, trusted: bool,
+    ) -> int:
+        allowed = {
+            "registration_rejected", "authentication_rejected", "policy_rejected",
+            "binding_rejected", "idempotency_conflict", "release_rejected",
+        }
+        if event_type not in allowed or not reason_code.isidentifier() or len(reason_code) > 64:
+            raise ValueError("failure audit metadata is invalid")
+        if not trusted:
+            gateway_id = None
+            event_type = "untrusted_request_rejected"
+            reason_code = "untrusted_failure"
+        self.session.rollback()
+        engine = self.session.get_bind()
+        fingerprint = hashlib.sha256(f"{gateway_id or 'unknown'}:{event_type}:{reason_code}".encode()).hexdigest()
+        with engine.begin() as connection:
+            window = connection.execute(text("""
+                SELECT * FROM public.record_edge_gateway_failure(
+                  CAST(:audit_uid AS uuid),
+                  :fingerprint,
+                  CASE WHEN EXISTS (SELECT 1 FROM edge_gateways WHERE uid=CAST(:gateway AS uuid))
+                       THEN CAST(:gateway AS uuid) ELSE NULL END,
+                  :event_type,
+                  :reason_code
+                )
+            """), {
+                "audit_uid": new_governance_uid(), "fingerprint": fingerprint,
+                "gateway": gateway_id, "event_type": event_type,
+                "reason_code": reason_code,
+            }).mappings().one()
+            fingerprint = window["failure_fingerprint"]
+            gateway_id = window["gateway_id"]
+            event_type = window["event_type"]
+            if fingerprint == _OVERFLOW_FINGERPRINT:
+                reason_code = "capacity_overflow"
+        return int(window["occurrence_count"])
+
+    def _audit(self, gateway_id, event_type: str, actor_uid, success: bool, detail: dict) -> None:
+        self.session.execute(text("""
+            SELECT public.record_edge_gateway_audit(
+              CAST(:uid AS uuid),CAST(:gateway_id AS uuid),:event_type,
+              CAST(:actor_uid AS uuid),:success,CAST(:detail AS jsonb)
+            )
+        """), {"uid": new_governance_uid(), "gateway_id": gateway_id, "event_type": event_type, "actor_uid": actor_uid, "success": success, "detail": _json(detail)})

+ 413 - 0
app/core/edge_gateway/runtime_roles.py

@@ -0,0 +1,413 @@
+"""Provision the login used by long-running DataOps application processes."""
+
+from __future__ import annotations
+
+import os
+import re
+
+from sqlalchemy import create_engine, text
+from sqlalchemy.pool import NullPool
+
+from app.config.database_urls import (
+    validate_database_environment,
+    validate_postgresql_url,
+)
+
+_ROLE_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]{0,62}$")
+
+
+def provision_runtime_login() -> None:
+    role_init_url = os.environ.get("DB_ROLE_INIT_DATABASE_URL", "")
+    migration_url = os.environ.get("MIGRATION_DATABASE_URL", "")
+    runtime_url = os.environ.get("DATABASE_URL", "")
+    runtime_user = os.environ.get("DATAOPS_RUNTIME_USER", "")
+    runtime_password = os.environ.get("DATAOPS_RUNTIME_PASSWORD", "")
+    migrator_user = os.environ.get("DATAOPS_MIGRATOR_USER", "")
+    control_url = os.environ.get("BI_AI_CATALOG_CONTROL_DATABASE_URL", "")
+    control_user = os.environ.get("DATAOPS_BI_AI_CATALOG_CONTROL_USER", "")
+    control_password = os.environ.get("DATAOPS_BI_AI_CATALOG_CONTROL_PASSWORD", "")
+    if (
+        not _ROLE_NAME.fullmatch(runtime_user)
+        or not _ROLE_NAME.fullmatch(migrator_user)
+    ):
+        raise RuntimeError("runtime database role provisioning is not configured")
+    validate_database_environment({
+        "DB_ROLE_INIT_DATABASE_URL": role_init_url,
+        "MIGRATION_DATABASE_URL": migration_url,
+        "DATABASE_URL": runtime_url,
+        "DATAOPS_RUNTIME_PASSWORD": runtime_password,
+    })
+    control_parts = validate_postgresql_url(control_url, "BI_AI_CATALOG_CONTROL_DATABASE_URL")
+    if not _ROLE_NAME.fullmatch(control_user) or control_parts.username != control_user or not control_password or control_parts.password != control_password:
+        raise RuntimeError("BI/AI catalog control identity provisioning is not configured")
+    engine = create_engine(role_init_url, poolclass=NullPool)
+    quoted_user = engine.dialect.identifier_preparer.quote(runtime_user)
+    quoted_migrator = engine.dialect.identifier_preparer.quote(migrator_user)
+    try:
+        with engine.begin() as connection:
+            bootstrap_identity = connection.execute(text("""
+                SELECT rolsuper OR rolcreaterole
+                  FROM pg_roles WHERE rolname=current_user
+            """)).scalar_one()
+            if not bootstrap_identity:
+                raise RuntimeError("DB role-init identity requires CREATEROLE or superuser")
+            connection.execute(text("""
+                DO $$ BEGIN
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_app_runtime') THEN
+                    CREATE ROLE dataops_app_runtime NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_edge_evidence_owner') THEN
+                    CREATE ROLE dataops_edge_evidence_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_trusted_delivery_owner') THEN
+                    CREATE ROLE dataops_trusted_delivery_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_trusted_delivery_writer') THEN
+                    CREATE ROLE dataops_trusted_delivery_writer NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_agent_runtime_owner') THEN
+                    CREATE ROLE dataops_agent_runtime_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_agent_runtime') THEN
+                    CREATE ROLE dataops_agent_runtime NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_tenant_foundation_owner') THEN
+                    CREATE ROLE dataops_tenant_foundation_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_tenant_control') THEN
+                    CREATE ROLE dataops_tenant_control NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_tenant_approver') THEN
+                    CREATE ROLE dataops_tenant_approver NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_bi_ai_catalog_issuer') THEN
+                    CREATE ROLE dataops_bi_ai_catalog_issuer NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOINHERIT;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_plugin_platform_owner') THEN
+                    CREATE ROLE dataops_plugin_platform_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                  END IF;
+                  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dataops_plugin_platform_control') THEN
+                    CREATE ROLE dataops_plugin_platform_control NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOINHERIT;
+                  END IF;
+                END $$;
+                ALTER ROLE dataops_app_runtime NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_edge_evidence_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_trusted_delivery_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_trusted_delivery_writer NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_agent_runtime_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_agent_runtime NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_tenant_foundation_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_tenant_control NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_tenant_approver NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_bi_ai_catalog_issuer NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOINHERIT;
+                ALTER ROLE dataops_plugin_platform_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
+                ALTER ROLE dataops_plugin_platform_control NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOINHERIT;
+            """))
+            migrator_exists = connection.execute(
+                text("SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname=:role)"),
+                {"role": migrator_user},
+            ).scalar_one()
+            if not migrator_exists:
+                raise RuntimeError("DATAOPS_MIGRATOR_USER must already exist")
+            exists = connection.execute(
+                text("SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname=:role)"),
+                {"role": runtime_user},
+            ).scalar_one()
+            action = "ALTER" if exists else "CREATE"
+            connection.execute(text(
+                f"{action} ROLE {quoted_user} LOGIN NOSUPERUSER NOCREATEDB "
+                "NOCREATEROLE NOREPLICATION PASSWORD :password"
+            ), {"password": runtime_password})
+            connection.execute(text(f"GRANT dataops_app_runtime TO {quoted_user}"))
+            quoted_control = engine.dialect.identifier_preparer.quote(control_user)
+            control_exists = connection.execute(text("SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname=:role)"), {"role": control_user}).scalar_one()
+            control_action = "ALTER" if control_exists else "CREATE"
+            connection.execute(text(f"{control_action} ROLE {quoted_control} LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION INHERIT PASSWORD :password"), {"password": control_password})
+            connection.execute(text(f"GRANT dataops_bi_ai_catalog_issuer TO {quoted_control}"))
+            connection.execute(text(f"GRANT dataops_plugin_platform_control TO {quoted_control}"))
+            # Role-init also runs before a fresh database has reached WP11.
+            # The restricted migration grants the function after creating it;
+            # a later idempotent role-init grants the dedicated login directly.
+            connection.execute(text(
+                "DO $$ BEGIN "
+                "IF to_regprocedure('public.bi_ai_catalog_issue_request_claim(text,text,jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'GRANT EXECUTE ON FUNCTION public.bi_ai_catalog_issue_request_claim(text,text,jsonb) TO "
+                + quoted_control
+                + "'; "
+                "END IF; END $$"
+            ))
+            connection.execute(text(
+                "DO $$ BEGIN "
+                "IF to_regprocedure('public.plugin_platform_control_v2(jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'REVOKE ALL ON FUNCTION public.plugin_platform_control_v2(jsonb) FROM "
+                + quoted_control
+                + "'; "
+                "END IF; "
+                "IF to_regprocedure('public.plugin_platform_issue_claim(jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'REVOKE ALL ON FUNCTION public.plugin_platform_issue_claim(jsonb) FROM "
+                + quoted_control
+                + "'; "
+                "END IF; "
+                "IF to_regprocedure('public.plugin_platform_control_v3(jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'REVOKE ALL ON FUNCTION public.plugin_platform_control_v3(jsonb) FROM "
+                + quoted_control
+                + "'; "
+                "END IF; "
+                "IF to_regprocedure('public.plugin_platform_control_v4(jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'REVOKE ALL ON FUNCTION public.plugin_platform_control_v4(jsonb) FROM "
+                + quoted_control
+                + "'; "
+                "END IF; "
+                "IF to_regprocedure('public.plugin_platform_control_v5(jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'GRANT EXECUTE ON FUNCTION public.plugin_platform_control_v5(jsonb) TO "
+                + quoted_control
+                + "'; "
+                "END IF; "
+                "IF to_regprocedure('public.plugin_platform_issue_claim_v3(jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'GRANT EXECUTE ON FUNCTION public.plugin_platform_issue_claim_v3(jsonb) TO "
+                + quoted_control
+                + "'; "
+                "END IF; "
+                "IF to_regprocedure('public.plugin_platform_issue_recovery_claim(jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'GRANT EXECUTE ON FUNCTION public.plugin_platform_issue_recovery_claim(jsonb) TO "
+                + quoted_control
+                + "'; "
+                "END IF; END $$"
+            ))
+            # WP12 reuses the separate, configured control login for claims.
+            # It remains distinct from the long-running runtime login, and a
+            # role-init replay grants only this execute capability.
+            connection.execute(text(
+                "DO $$ BEGIN "
+                "IF to_regprocedure('public.metering_showback_issue_claim(text,text,jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'GRANT EXECUTE ON FUNCTION public.metering_showback_issue_claim(text,text,jsonb) TO "
+                + quoted_control
+                + "'; "
+                "END IF; END $$"
+            ))
+            connection.execute(text(
+                "DO $$ BEGIN "
+                "IF to_regprocedure('public.metering_showback_control_write(text,text,jsonb)') IS NOT NULL THEN "
+                "EXECUTE 'GRANT EXECUTE ON FUNCTION public.metering_showback_control_write(text,text,jsonb) TO "
+                + quoted_control
+                + "'; "
+                "END IF; END $$"
+            ))
+            connection.execute(text(f"REVOKE dataops_bi_ai_catalog_issuer FROM {quoted_user}"))
+            # A role-init replay must never restore legacy direct table access
+            # after the WP11 live-head ACL compensation has run.
+            connection.execute(text(
+                "DO $$ DECLARE item text; BEGIN "
+                "FOREACH item IN ARRAY ARRAY["
+                "'bi_ai_catalog_sources','bi_ai_catalog_assets',"
+                "'bi_ai_catalog_asset_versions','bi_ai_catalog_dependencies',"
+                "'bi_ai_catalog_sync_errors','bi_ai_catalog_audit_events',"
+                "'bi_ai_catalog_scope_grants','bi_ai_catalog_scan_observations',"
+                "'bi_ai_catalog_scan_sessions','bi_ai_catalog_scan_page_acks',"
+                "'bi_ai_catalog_page_acknowledgements','bi_ai_catalog_request_claims',"
+                "'bi_ai_catalog_empty_snapshot_approvals'"
+                "] LOOP "
+                "IF to_regclass('public.' || item) IS NOT NULL THEN "
+                "EXECUTE 'REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER "
+                "ON TABLE public.' || quote_ident(item) || ' FROM PUBLIC, dataops_app_runtime, dataops_app'; "
+                "END IF; END LOOP; END $$"
+            ))
+            # The compatibility matrix above is intentionally broad for
+            # legacy tables. Reapply WP12's facts-only gateway boundary after
+            # every role-init replay, including the concrete app login.
+            connection.execute(text(
+                "DO $$ DECLARE item text; BEGIN "
+                "FOREACH item IN ARRAY ARRAY["
+                "'metering_scope_grants','metering_showback_configuration',"
+                "'metering_runtime_leases','metering_runtime_claims','metering_events',"
+                "'metering_allocation_rules','metering_allocations','metering_budgets',"
+                "'metering_alert_outbox','metering_reconciliation_reports','metering_audit_events'"
+                "] LOOP "
+                "IF to_regclass('public.' || item) IS NOT NULL THEN "
+                "EXECUTE 'REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER "
+                "ON TABLE public.' || quote_ident(item) || ' FROM PUBLIC, dataops_app_runtime, dataops_app'; "
+                "END IF; END LOOP; END $$"
+            ))
+            connection.execute(text(
+                "DO $$ DECLARE item text; BEGIN "
+                "FOREACH item IN ARRAY ARRAY['plugin_registry_versions','plugin_approvals','plugin_runtime_leases','plugin_runs','plugin_dead_letters','plugin_audit_outbox','plugin_request_claims','plugin_runtime_breakers','plugin_recovery_claims'] LOOP "
+                "IF to_regclass('public.' || item) IS NOT NULL THEN "
+                "EXECUTE 'REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLE public.' || quote_ident(item) || ' FROM PUBLIC, dataops_app_runtime, dataops_app'; "
+                "END IF; END LOOP; END $$"
+            ))
+            connection.execute(text(
+                f"GRANT dataops_edge_evidence_owner TO {quoted_migrator}"
+            ))
+            connection.execute(text(
+                f"GRANT dataops_trusted_delivery_owner TO {quoted_migrator}"
+            ))
+            connection.execute(text(
+                f"GRANT dataops_trusted_delivery_writer TO {quoted_migrator}"
+            ))
+            connection.execute(text(
+                f"GRANT dataops_agent_runtime_owner TO {quoted_migrator}"
+            ))
+            connection.execute(text(
+                f"GRANT dataops_tenant_foundation_owner TO {quoted_migrator}"
+            ))
+            # Tenant-control is a separate capability: only the explicitly
+            # configured control login (or the migration identity used to
+            # create its schema) may inherit it.  The shared app login never
+            # receives this membership.
+            connection.execute(text(
+                f"GRANT dataops_tenant_control TO {quoted_migrator}"
+            ))
+            connection.execute(text(f"GRANT dataops_plugin_platform_owner TO {quoted_migrator}"))
+            connection.execute(text(f"GRANT dataops_agent_runtime TO {quoted_user}"))
+            connection.execute(text(
+                "GRANT USAGE,CREATE ON SCHEMA public TO dataops_edge_evidence_owner"
+            ))
+            connection.execute(text(
+                "GRANT USAGE,CREATE ON SCHEMA public TO dataops_trusted_delivery_owner"
+            ))
+            connection.execute(text(
+                "GRANT USAGE,CREATE ON SCHEMA public TO dataops_trusted_delivery_writer"
+            ))
+            connection.execute(text("GRANT USAGE,CREATE ON SCHEMA public TO dataops_agent_runtime_owner"))
+            connection.execute(text("GRANT USAGE ON SCHEMA public TO dataops_agent_runtime"))
+            connection.execute(text("GRANT USAGE,CREATE ON SCHEMA public TO dataops_tenant_foundation_owner"))
+            connection.execute(text("GRANT USAGE ON SCHEMA public TO dataops_tenant_control"))
+            connection.execute(text("GRANT USAGE ON SCHEMA public TO dataops_tenant_approver"))
+            connection.execute(text("GRANT USAGE,CREATE ON SCHEMA public TO dataops_plugin_platform_owner"))
+            connection.execute(text("GRANT USAGE ON SCHEMA public TO dataops_plugin_platform_control"))
+            # The definer gateway validates actor references against the
+            # canonical identity table but never receives identity DML.
+            connection.execute(text(
+                "DO $$ BEGIN IF to_regclass('public.users') IS NOT NULL THEN "
+                "GRANT SELECT ON TABLE public.users TO dataops_trusted_delivery_writer; END IF; END $$"
+            ))
+            # A non-privileged migrator still needs REFERENCES to create the
+            # versioned WP06 tables that bind their actor to the canonical
+            # identity record.  It does not receive users DML.
+            connection.execute(text(
+                "DO $$ BEGIN "
+                "IF to_regclass('public.users') IS NOT NULL THEN EXECUTE 'GRANT REFERENCES ON TABLE public.users TO " + quoted_migrator + "'; END IF; "
+                "IF to_regclass('public.governed_agents') IS NOT NULL THEN EXECUTE 'GRANT REFERENCES ON TABLE public.governed_agents TO " + quoted_migrator + "'; END IF; "
+                "END $$"
+            ))
+            connection.execute(text(
+                "DO $$ BEGIN "
+                "IF to_regclass('public.data_incidents') IS NOT NULL THEN EXECUTE 'GRANT REFERENCES ON TABLE public.data_incidents TO " + quoted_migrator + "'; END IF; "
+                "IF to_regclass('public.governance_tasks') IS NOT NULL THEN EXECUTE 'GRANT REFERENCES ON TABLE public.governance_tasks TO " + quoted_migrator + "'; END IF; "
+                "END $$"
+            ))
+            # WP09's hardened definer gateway reads its approval source of
+            # truth.  The migrator can delegate only these capabilities while
+            # retaining no direct data-modification path of its own.
+            connection.execute(text(f"""
+                DO $$ BEGIN
+                  IF to_regclass('public.governance_tasks') IS NOT NULL THEN GRANT SELECT, UPDATE ON TABLE public.governance_tasks TO {quoted_migrator} WITH GRANT OPTION; END IF;
+                  IF to_regclass('public.governance_task_reviews') IS NOT NULL THEN GRANT SELECT ON TABLE public.governance_task_reviews TO {quoted_migrator} WITH GRANT OPTION; END IF;
+                  IF to_regclass('public.agent_tool_grants') IS NOT NULL THEN GRANT SELECT ON TABLE public.agent_tool_grants TO {quoted_migrator} WITH GRANT OPTION; END IF;
+                  IF to_regclass('public.governed_agents') IS NOT NULL THEN GRANT SELECT ON TABLE public.governed_agents TO {quoted_migrator} WITH GRANT OPTION; END IF;
+                END $$
+            """))
+            connection.execute(text(
+                "DO $$ BEGIN IF to_regclass('public.data_incident_impacts') IS NOT NULL THEN "
+                "GRANT SELECT,INSERT ON TABLE public.data_incident_impacts TO dataops_trusted_delivery_writer; END IF; END $$"
+            ))
+            connection.execute(text(
+                "DO $$ BEGIN IF to_regclass('public.data_incidents') IS NOT NULL THEN "
+                "GRANT SELECT ON TABLE public.data_incidents TO dataops_trusted_delivery_writer; END IF; END $$"
+            ))
+            connection.execute(text("GRANT USAGE ON SCHEMA public TO dataops_app_runtime"))
+            # Revoke legacy broad grants both from the shared runtime role and
+            # from the concrete login before rebuilding its explicit table
+            # matrix.  This is required when upgrading an installation that
+            # previously granted DML across the whole public schema.
+            connection.execute(text(f"""
+                DO $$ DECLARE target text;
+                BEGIN
+                  FOR target IN
+                    SELECT format('%I.%I', schemaname, tablename)
+                    FROM pg_tables
+                    WHERE schemaname='public' AND (
+                      tablename LIKE 'trusted_delivery_%' OR tablename='data_incident_impacts'
+                      OR tablename IN ('tenants','tenant_quotas','tenant_quota_reservations',
+                                       'tenant_lifecycle_events','tenant_audit_events','tenant_resource_manifests')
+                    )
+                  LOOP
+                    EXECUTE 'REVOKE INSERT, UPDATE, DELETE, TRUNCATE ON TABLE ' || target || ' FROM dataops_app_runtime';
+                    EXECUTE 'REVOKE INSERT, UPDATE, DELETE, TRUNCATE ON TABLE ' || target || ' FROM {quoted_user}';
+                  END LOOP;
+                END $$;
+            """))
+            # Runtime receives explicit table grants only.  In particular it
+            # must never inherit DML on future tables or on the WP06 trusted
+            # delivery state machine; those writes are reached only through
+            # SECURITY DEFINER procedures owned by the dedicated writer role.
+            connection.execute(text("""
+                DO $$ DECLARE target text;
+                BEGIN
+                  FOR target IN
+                    SELECT format('%I.%I', schemaname, tablename)
+                    FROM pg_tables
+                    WHERE schemaname='public' AND tablename NOT LIKE 'trusted_delivery_%'
+                      AND tablename <> 'data_incident_impacts'
+                      AND tablename NOT IN ('tenants','tenant_quotas','tenant_quota_reservations',
+                                            'tenant_lifecycle_events','tenant_audit_events','tenant_resource_manifests')
+                  LOOP
+                    EXECUTE 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ' || target || ' TO dataops_app_runtime';
+                  END LOOP;
+                END $$;
+            """))
+            connection.execute(text(
+                "GRANT USAGE,SELECT ON ALL SEQUENCES IN SCHEMA public TO dataops_app_runtime"
+            ))
+            # The legacy compatibility matrix above intentionally grants most
+            # public tables.  Reapply WP11's closed facts after it so a
+            # role-init replay cannot re-open catalog data to the runtime.
+            connection.execute(text(
+                "DO $$ DECLARE item text; BEGIN "
+                "FOREACH item IN ARRAY ARRAY["
+                "'bi_ai_catalog_sources','bi_ai_catalog_assets',"
+                "'bi_ai_catalog_asset_versions','bi_ai_catalog_dependencies',"
+                "'bi_ai_catalog_sync_errors','bi_ai_catalog_audit_events',"
+                "'bi_ai_catalog_scope_grants','bi_ai_catalog_scan_observations',"
+                "'bi_ai_catalog_scan_sessions','bi_ai_catalog_scan_page_acks',"
+                "'bi_ai_catalog_page_acknowledgements','bi_ai_catalog_request_claims',"
+                "'bi_ai_catalog_empty_snapshot_approvals'"
+                "] LOOP "
+                "IF to_regclass('public.' || item) IS NOT NULL THEN "
+                "EXECUTE 'REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER "
+                "ON TABLE public.' || quote_ident(item) || ' FROM PUBLIC, dataops_app_runtime, dataops_app'; "
+                "END IF; END LOOP; END $$"
+            ))
+            connection.execute(text(
+                "DO $$ DECLARE item text; BEGIN "
+                "FOREACH item IN ARRAY ARRAY["
+                "'metering_scope_grants','metering_showback_configuration',"
+                "'metering_runtime_leases','metering_runtime_claims','metering_events',"
+                "'metering_allocation_rules','metering_allocations','metering_budgets',"
+                "'metering_alert_outbox','metering_reconciliation_reports','metering_audit_events'"
+                "] LOOP "
+                "IF to_regclass('public.' || item) IS NOT NULL THEN "
+                "EXECUTE 'REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER "
+                "ON TABLE public.' || quote_ident(item) || ' FROM PUBLIC, dataops_app_runtime, dataops_app'; "
+                "END IF; END LOOP; END $$"
+            ))
+            connection.execute(text(
+                "DO $$ DECLARE item text; BEGIN "
+                "FOREACH item IN ARRAY ARRAY['plugin_registry_versions','plugin_approvals','plugin_runtime_leases','plugin_runs','plugin_dead_letters','plugin_audit_outbox','plugin_request_claims','plugin_runtime_breakers','plugin_recovery_claims'] LOOP "
+                "IF to_regclass('public.' || item) IS NOT NULL THEN "
+                "EXECUTE 'REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLE public.' || quote_ident(item) || ' FROM PUBLIC, dataops_app_runtime, dataops_app'; "
+                "END IF; END LOOP; END $$"
+            ))
+            is_evidence_member = connection.execute(text("""
+                SELECT pg_has_role(:role,'dataops_edge_evidence_owner','MEMBER')
+            """), {"role": runtime_user}).scalar_one()
+            if is_evidence_member:
+                connection.execute(text(
+                    f"REVOKE dataops_edge_evidence_owner FROM {quoted_user}"
+                ))
+    finally:
+        engine.dispose()
+
+
+if __name__ == "__main__":
+    provision_runtime_login()

+ 700 - 0
app/core/edge_gateway/service.py

@@ -0,0 +1,700 @@
+"""Fail-closed application service for enterprise edge gateways."""
+
+from __future__ import annotations
+
+import hashlib
+import hmac
+import re
+import secrets
+import stat
+import uuid
+from collections.abc import Mapping, Sequence
+from contextlib import suppress
+from datetime import UTC, datetime, timedelta
+from pathlib import Path
+
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+
+from app.core.edge_gateway.contracts import (
+    EdgeContractError,
+    EdgeEventContract,
+    EdgeTaskContract,
+    SignedTaskEnvelope,
+    canonical_json_bytes,
+    canonical_sha256,
+    canonical_timestamp,
+)
+from app.core.edge_gateway.policy import EdgeEgressPolicy, EdgePolicyError
+
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+_VERSION = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$")
+_RELEASE_VERSION = re.compile(
+    r"^(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})$"
+)
+_HOST = re.compile(r"^(?=.{1,253}$)[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$")
+
+
+class EdgeGatewayError(Exception):
+    status_code = 400
+    code = "EDGE_GATEWAY_ERROR"
+
+
+class EdgeGatewayValidationError(EdgeGatewayError):
+    code = "EDGE_GATEWAY_INVALID"
+
+
+class EdgeGatewayAuthenticationError(EdgeGatewayError):
+    status_code = 401
+    code = "EDGE_GATEWAY_AUTHENTICATION_FAILED"
+
+
+class EdgeGatewayConflictError(EdgeGatewayError):
+    status_code = 409
+    code = "EDGE_GATEWAY_CONFLICT"
+
+
+class EdgeGatewayPayloadTooLargeError(EdgeGatewayValidationError):
+    status_code = 413
+    code = "EDGE_GATEWAY_PAYLOAD_TOO_LARGE"
+
+
+class EdgeGatewayRateLimitError(EdgeGatewayError):
+    status_code = 429
+    code = "EDGE_GATEWAY_RATE_LIMITED"
+
+
+class EdgeGatewayNotFoundError(EdgeGatewayError):
+    status_code = 404
+    code = "EDGE_GATEWAY_NOT_FOUND"
+
+
+class EdgeGatewayConfigurationError(EdgeGatewayError):
+    status_code = 503
+    code = "EDGE_GATEWAY_SIGNING_UNAVAILABLE"
+
+
+def _digest(secret: str) -> str:
+    return hashlib.sha256(secret.encode("utf-8")).hexdigest()
+
+
+def _uuid(value: object, label: str) -> str:
+    try:
+        return str(uuid.UUID(str(value)))
+    except (ValueError, TypeError, AttributeError) as exc:
+        raise EdgeGatewayValidationError(f"{label} is invalid") from exc
+
+
+def _sha(value: object, label: str) -> str:
+    if not isinstance(value, str) or not _SHA256.fullmatch(value):
+        raise EdgeGatewayValidationError(f"{label} is invalid")
+    return value
+
+
+def _text(value: object, label: str, maximum: int) -> str:
+    if not isinstance(value, str) or not value or value.strip() != value or len(value.encode()) > maximum or "\x00" in value:
+        raise EdgeGatewayValidationError(f"{label} is invalid")
+    return value
+
+
+def _version(value: object) -> str:
+    value = _text(value, "version", 80)
+    if not _VERSION.fullmatch(value):
+        raise EdgeGatewayValidationError("version is invalid")
+    return value
+
+
+def _release_version(value: object, label: str) -> str:
+    value = _text(value, label, 80)
+    if (
+        not _RELEASE_VERSION.fullmatch(value)
+        or any(int(part) > 2_147_483_647 for part in value.split("."))
+    ):
+        raise EdgeGatewayValidationError(f"{label} is invalid")
+    return value
+
+
+def _release_version_tuple(value: str) -> tuple[int, ...]:
+    return tuple(int(item) for item in value.split("."))
+
+
+def _artifact_name(value: object) -> str:
+    value = _text(value, "artifact_name", 255)
+    if (
+        value in {".", ".."}
+        or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._+-]{0,254}", value)
+        or "/" in value
+        or "\\" in value
+    ):
+        raise EdgeGatewayValidationError("artifact_name is invalid")
+    return value
+
+
+def _future_deadline(value: object, *, now: datetime) -> str:
+    try:
+        canonical = canonical_timestamp(value, "deadline_at")
+        deadline = datetime.fromisoformat(canonical.replace("Z", "+00:00"))
+    except (EdgeContractError, TypeError, ValueError) as exc:
+        raise EdgeGatewayValidationError("deadline_at is invalid") from exc
+    if deadline <= now or deadline > now + timedelta(days=30):
+        raise EdgeGatewayValidationError("deadline_at is invalid")
+    return canonical
+
+
+def _signing_private_key(value: object) -> Ed25519PrivateKey | None:
+    if value is None or value == "":
+        return None
+    if isinstance(value, Ed25519PrivateKey):
+        return value
+    if isinstance(value, str):
+        encoded = value.encode("utf-8")
+        if re.fullmatch(r"[0-9a-fA-F]{64}", value):
+            encoded = bytes.fromhex(value)
+    elif isinstance(value, bytes):
+        encoded = value
+    else:
+        raise EdgeGatewayConfigurationError("edge signing authority is invalid")
+    try:
+        if isinstance(encoded, bytes) and len(encoded) == 32:
+            return Ed25519PrivateKey.from_private_bytes(encoded)
+        key = serialization.load_pem_private_key(encoded, password=None)
+    except (TypeError, ValueError) as exc:
+        raise EdgeGatewayConfigurationError("edge signing authority is invalid") from exc
+    if not isinstance(key, Ed25519PrivateKey):
+        raise EdgeGatewayConfigurationError("edge signing authority is invalid")
+    return key
+
+
+def _hosts(values: object, label: str, *, required: bool) -> list[str]:
+    if not isinstance(values, Sequence) or isinstance(values, (str, bytes)):
+        raise EdgeGatewayValidationError(f"{label} is invalid")
+    result = []
+    for item in values:
+        if not isinstance(item, str) or item != item.lower() or not _HOST.fullmatch(item) or ".." in item:
+            raise EdgeGatewayValidationError(f"{label} is invalid")
+        result.append(item)
+    if required and not result:
+        raise EdgeGatewayValidationError(f"{label} is required")
+    if len(result) != len(set(result)) or len(result) > 32:
+        raise EdgeGatewayValidationError(f"{label} is invalid")
+    return sorted(result)
+
+
+def _environment(value: object) -> str:
+    if value not in {"development", "staging", "production"}:
+        raise EdgeGatewayValidationError("environment is invalid")
+    return str(value)
+
+
+def _page(limit: object = 50, offset: object = 0) -> tuple[int, int]:
+    try:
+        limit, offset = int(limit), int(offset)
+    except (TypeError, ValueError) as exc:
+        raise EdgeGatewayValidationError("pagination is invalid") from exc
+    if not 1 <= limit <= 100 or not 0 <= offset <= 10_000:
+        raise EdgeGatewayValidationError("pagination is invalid")
+    return limit, offset
+
+
+def _safe_summary(value: object, classification: str = "diagnostic_summary") -> dict:
+    if not isinstance(value, Mapping):
+        raise EdgeGatewayValidationError("safe_summary is invalid")
+    candidate = dict(value)
+    try:
+        EdgeEgressPolicy(allowed_control_hosts={"validation.invalid"}).validate_approved_payload(classification, candidate)
+    except (EdgePolicyError, ValueError) as exc:
+        raise EdgeGatewayValidationError("safe_summary contains unapproved content") from exc
+    return candidate
+
+
+class EdgeGatewayService:
+    def __init__(
+        self, repository, *, signing_private_key=None,
+        signing_private_key_file=None, signing_key_provider=None,
+        signing_public_key=None, signing_key_id=None, production=False, clock=None,
+    ):
+        self.repository = repository
+        sources = sum(
+            item not in {None, ""}
+            for item in (signing_private_key, signing_private_key_file)
+        ) + int(signing_key_provider is not None)
+        if sources > 1:
+            raise EdgeGatewayConfigurationError("edge signing authority source is ambiguous")
+        if production and signing_private_key not in {None, ""}:
+            raise EdgeGatewayConfigurationError(
+                "production inline edge signing private key is forbidden"
+            )
+        key_material = signing_private_key
+        if signing_private_key_file not in {None, ""}:
+            if not isinstance(signing_private_key_file, str):
+                raise EdgeGatewayConfigurationError("edge signing key file is invalid")
+            path = Path(signing_private_key_file)
+            try:
+                if (
+                    not path.is_absolute()
+                    or not path.is_file()
+                    or path.is_symlink()
+                    or stat.S_IMODE(path.stat().st_mode) != 0o600
+                ):
+                    raise EdgeGatewayConfigurationError(
+                        "edge signing key file permissions must be 0600"
+                    )
+                key_material = path.read_bytes()
+            except OSError as exc:
+                raise EdgeGatewayConfigurationError(
+                    "edge signing key file is unavailable"
+                ) from exc
+        elif signing_key_provider is not None:
+            if not callable(signing_key_provider):
+                raise EdgeGatewayConfigurationError("edge signing key provider is invalid")
+            try:
+                key_material = signing_key_provider()
+            except Exception as exc:
+                raise EdgeGatewayConfigurationError(
+                    "edge signing key provider is unavailable"
+                ) from exc
+        self._signing_private_key = _signing_private_key(key_material)
+        if self._signing_private_key is not None and signing_public_key not in {None, ""}:
+            if (
+                not isinstance(signing_public_key, str)
+                or not re.fullmatch(r"[0-9a-f]{64}", signing_public_key)
+            ):
+                raise EdgeGatewayConfigurationError("edge signing public key is invalid")
+            actual_public = self._signing_private_key.public_key().public_bytes(
+                serialization.Encoding.Raw,
+                serialization.PublicFormat.Raw,
+            ).hex()
+            if not hmac.compare_digest(actual_public, signing_public_key):
+                raise EdgeGatewayConfigurationError(
+                    "edge signing public key does not match private key"
+                )
+        elif production and self._signing_private_key is not None:
+            raise EdgeGatewayConfigurationError(
+                "production edge signing public key is required"
+            )
+        self._signing_key_id = signing_key_id
+        self._clock = clock or (lambda: datetime.now(UTC))
+
+    def _require_signer(self) -> tuple[Ed25519PrivateKey, str]:
+        key_id = self._signing_key_id
+        if (
+            self._signing_private_key is None
+            or not isinstance(key_id, str)
+            or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}", key_id)
+        ):
+            self.repository.rollback()
+            raise EdgeGatewayConfigurationError(
+                "edge signing authority is not configured"
+            )
+        return self._signing_private_key, key_id
+
+    def _signed_task_envelope(self, contract: EdgeTaskContract) -> dict:
+        private_key, key_id = self._require_signer()
+        issued_at = canonical_timestamp(
+            self._clock().astimezone(UTC).isoformat().replace("+00:00", "Z")
+        )
+        unsigned = {
+            "task": contract.to_mapping(),
+            "authority_key_id": key_id,
+            "signature_algorithm": "Ed25519",
+            "contract_digest": contract.digest,
+            "gateway_id": contract.gateway_id,
+            "environment": contract.environment,
+            "network_zone": contract.network_zone,
+            "policy_digest": contract.policy_digest,
+            "purpose": contract.purpose,
+            "issued_at": issued_at,
+            "expires_at": contract.deadline_at,
+        }
+        try:
+            signed_bytes = SignedTaskEnvelope.canonical_unsigned_bytes(unsigned)
+        except EdgeContractError as exc:
+            self.repository.rollback()
+            raise EdgeGatewayValidationError(
+                "edge task authority validity is invalid"
+            ) from exc
+        return {**unsigned, "signature": private_key.sign(signed_bytes).hex()}
+
+    def _signed_release_manifest(self, unsigned: Mapping[str, object]) -> dict:
+        private_key, key_id = self._require_signer()
+        values = dict(unsigned)
+        values["signature_algorithm"] = "Ed25519"
+        values["key_id"] = key_id
+        digest = canonical_sha256(values)
+        return {
+            **values,
+            "manifest_digest": digest,
+            "signature": private_key.sign(canonical_json_bytes(values)).hex(),
+        }
+
+    def _failure(
+        self, gateway_id, event_type: str, reason_code: str, *, trusted: bool = False,
+    ) -> int:
+        count = self.repository.audit_failure(
+            gateway_id, event_type, reason_code, trusted=trusted,
+        )
+        if count >= 10:
+            raise EdgeGatewayRateLimitError("edge failure rate limit exceeded")
+        return count
+
+    def create_enrollment(
+        self, *, gateway_name, environment, network_zone, policy_digest,
+        allowed_control_hosts, allowed_proxy_hosts, ttl_seconds, actor_uid,
+        expected_certificate_sha256,
+    ) -> dict:
+        ttl = int(ttl_seconds)
+        if ttl < 60 or ttl > 86_400:
+            raise EdgeGatewayValidationError("enrollment TTL is invalid")
+        gateway_id, uid = str(uuid.uuid4()), str(uuid.uuid4())
+        token = "dope_" + secrets.token_urlsafe(32)
+        values = {
+            "uid": uid, "gateway_id": gateway_id,
+            "gateway_name": _text(gateway_name, "gateway_name", 200),
+            "environment": _environment(environment),
+            "network_zone": _text(network_zone, "network_zone", 120),
+            "policy_digest": _sha(policy_digest, "policy_digest"),
+            "expected_certificate_sha256": _sha(expected_certificate_sha256, "expected_certificate_sha256"),
+            "control_hosts": _hosts(allowed_control_hosts, "allowed_control_hosts", required=True),
+            "proxy_hosts": _hosts(allowed_proxy_hosts, "allowed_proxy_hosts", required=False),
+            "token_hash": _digest(token), "ttl": ttl, "actor_uid": _uuid(actor_uid, "actor_uid"),
+        }
+        self.repository.create_enrollment(values)
+        return {"enrollment_id": uid, "gateway_id": gateway_id, "enrollment_token": token, "expires_in": ttl, "returned_once": True}
+
+    def register(
+        self, *, enrollment_token, certificate_sha256, gateway_id, environment,
+        network_zone, policy_digest, allowed_control_hosts, allowed_proxy_hosts, version,
+    ) -> dict:
+        gateway_id = _uuid(gateway_id, "gateway_id")
+        if not isinstance(enrollment_token, str) or not enrollment_token.startswith("dope_") or len(enrollment_token) > 128:
+            self._failure(gateway_id, "registration_rejected", "enrollment_invalid")
+            raise EdgeGatewayAuthenticationError("edge enrollment is invalid or expired")
+        try:
+            certificate = _sha(certificate_sha256, "certificate_sha256")
+            supplied = {
+                "gateway_id": gateway_id, "environment": _environment(environment),
+                "network_zone": _text(network_zone, "network_zone", 120),
+                "policy_digest": _sha(policy_digest, "policy_digest"),
+                "allowed_control_hosts": _hosts(allowed_control_hosts, "allowed_control_hosts", required=True),
+                "allowed_proxy_hosts": _hosts(allowed_proxy_hosts, "allowed_proxy_hosts", required=False),
+            }
+        except EdgeGatewayValidationError as exc:
+            self._failure(gateway_id, "registration_rejected", "binding_invalid")
+            raise EdgeGatewayAuthenticationError("edge enrollment binding was rejected") from exc
+        enrollment = self.repository.lock_enrollment(_digest(enrollment_token))
+        now = datetime.now(UTC)
+        if enrollment is None or enrollment["status"] != "pending" or enrollment["expires_at"] <= now:
+            self._failure(supplied["gateway_id"], "registration_rejected", "enrollment_invalid")
+            raise EdgeGatewayAuthenticationError("edge enrollment is invalid or expired")
+        expected = {key: enrollment[key] for key in supplied}
+        expected["allowed_control_hosts"] = sorted(expected["allowed_control_hosts"])
+        expected["allowed_proxy_hosts"] = sorted(expected["allowed_proxy_hosts"])
+        if any(not hmac.compare_digest(str(expected[key]), str(supplied[key])) for key in supplied) or not hmac.compare_digest(enrollment["expected_certificate_sha256"], certificate):
+            self._failure(supplied["gateway_id"], "registration_rejected", "binding_mismatch")
+            raise EdgeGatewayAuthenticationError("edge enrollment binding was rejected")
+        credential = "dopg_" + secrets.token_urlsafe(40)
+        try:
+            result = self.repository.complete_registration(enrollment, _digest(credential), certificate, _version(version))
+        except Exception:
+            self.repository.rollback()
+            raise
+        return {**result, "credential": credential, "credential_returned_once": True, "certificate_sha256": certificate}
+
+    def authenticate(self, *, credential, certificate_sha256, gateway_id, environment, network_zone, generation, lock=False) -> dict:
+        try:
+            gateway_id = _uuid(gateway_id, "gateway_id")
+            generation = int(generation)
+            supplied = (
+                gateway_id, _environment(environment),
+                _text(network_zone, "network_zone", 120), generation,
+                _sha(certificate_sha256, "certificate_sha256"),
+            )
+        except EdgeGatewayValidationError as exc:
+            with suppress(EdgeGatewayValidationError):
+                self._failure(_uuid(gateway_id, "gateway_id"), "authentication_rejected", "binding_invalid")
+            raise EdgeGatewayAuthenticationError("edge credential binding was rejected") from exc
+        except (TypeError, ValueError) as exc:
+            self._failure(gateway_id, "authentication_rejected", "binding_invalid")
+            raise EdgeGatewayAuthenticationError("edge credential binding was rejected") from exc
+        if not isinstance(credential, str) or not credential.startswith("dopg_") or len(credential) > 160:
+            self._failure(gateway_id, "authentication_rejected", "credential_invalid")
+            raise EdgeGatewayAuthenticationError("edge credential binding was rejected")
+        row = self.repository.authenticate(_digest(credential), lock=lock)
+        if row is None:
+            self._failure(supplied[0], "authentication_rejected", "credential_invalid")
+            raise EdgeGatewayAuthenticationError("edge credential binding was rejected")
+        expected = (row["gateway_id"], row["environment"], row["network_zone"], row["generation"], row["certificate_sha256"])
+        if any(not hmac.compare_digest(str(a), str(b)) for a, b in zip(supplied, expected, strict=True)):
+            self._failure(row["gateway_id"], "authentication_rejected", "binding_mismatch")
+            raise EdgeGatewayAuthenticationError("edge credential binding was rejected")
+        return dict(row)
+
+    def list_gateways(self, *, limit=50, offset=0) -> list[dict]:
+        limit, offset = _page(limit, offset)
+        return self.repository.list_gateways(limit=limit, offset=offset)
+
+    def heartbeat(self, *, version, safe_summary, **auth) -> dict:
+        version = _version(version)
+        safe_summary = _safe_summary(safe_summary, "health_summary")
+        identity = self.authenticate(**auth, lock=True)
+        result = self.repository.heartbeat(identity["gateway_id"], version, safe_summary)
+        result["status"] = "online"
+        return result
+
+    def rotate(self, gateway_id, *, certificate_sha256, request_id, actor_uid) -> dict:
+        request_id = _text(request_id, "request_id", 255)
+        certificate_sha256 = _sha(certificate_sha256, "certificate_sha256")
+        request_digest = canonical_sha256({"gateway_id": str(gateway_id), "certificate_sha256": certificate_sha256})
+        token = "dopg_" + secrets.token_urlsafe(40)
+        gateway_id = _uuid(gateway_id, "gateway_id")
+        result = self.repository.rotate(gateway_id, _digest(token), certificate_sha256, _uuid(actor_uid, "actor_uid"), request_id, request_digest)
+        if result is None:
+            raise EdgeGatewayNotFoundError("edge gateway was not found")
+        if result.get("conflict"):
+            raise EdgeGatewayConflictError("rotation request conflicts with an existing request")
+        if result.get("replayed"):
+            return {"gateway_id": gateway_id, "generation": result["generation"], "credential": None, "credential_returned_once": False, "replayed": True}
+        return {**result, "gateway_id": gateway_id, "credential": token, "credential_returned_once": True}
+
+    def revoke(self, gateway_id, *, actor_uid) -> bool:
+        return self.repository.revoke(_uuid(gateway_id, "gateway_id"), _uuid(actor_uid, "actor_uid"))
+
+    def issue_task(self, value: Mapping[str, object], *, actor_uid) -> dict:
+        try:
+            contract = EdgeTaskContract.from_mapping(value)
+        except EdgeContractError as exc:
+            raise EdgeGatewayValidationError("edge task contract is invalid") from exc
+        if _uuid(contract.gateway_id, "gateway_id") != contract.gateway_id:
+            raise EdgeGatewayValidationError("gateway_id is invalid")
+        gateway = self.repository.gateway_binding(contract.gateway_id)
+        if gateway is None or gateway["status"] not in {"active", "offline"}:
+            self.repository.rollback()
+            raise EdgeGatewayNotFoundError("edge gateway was not found")
+        if any(
+            getattr(contract, field) != gateway[field]
+            for field in ("environment", "network_zone", "policy_digest")
+        ):
+            self._failure(
+                contract.gateway_id, "binding_rejected", "task_binding_mismatch",
+                trusted=True,
+            )
+            raise EdgeGatewayValidationError("edge task gateway binding is invalid")
+        authority = self._signed_task_envelope(contract)
+        row = self.repository.insert_task(
+            contract.to_mapping(), contract.digest, authority,
+            _uuid(actor_uid, "actor_uid"),
+        )
+        if not row:
+            raise EdgeGatewayNotFoundError("edge gateway was not found")
+        if row["contract_digest"] != contract.digest:
+            self._failure(
+                contract.gateway_id, "idempotency_conflict", "task_contract_conflict",
+                trusted=True,
+            )
+            raise EdgeGatewayConflictError("task idempotency key conflicts with an existing contract")
+        return {"task_id": row["task_id"], "contract_digest": row["contract_digest"]}
+
+    def pull_task(self, **auth) -> dict:
+        identity = self.authenticate(**auth, lock=True)
+        token = "dopl_" + secrets.token_urlsafe(32)
+        row = self.repository.pull_task(identity["gateway_id"], _digest(token))
+        if row is None:
+            return {"task": None}
+        envelope = dict(row["authority"])
+        if envelope.get("task") != row["contract"]:
+            self.repository.rollback()
+            raise EdgeGatewayConfigurationError("stored task authority is invalid")
+        return {
+            "task": row["contract"],
+            "signed_task_envelope": envelope,
+            "lease_token": token,
+            "lease_expires_at": row["lease_expires_at"].isoformat(),
+        }
+
+    def cancel_task(self, task_id, *, actor_uid) -> bool:
+        return self.repository.cancel_task(_text(task_id, "task_id", 255), _uuid(actor_uid, "actor_uid"))
+
+    def task_outcome(self, task_id, *, outcome, lease_token, safe_summary, **auth) -> dict:
+        if outcome not in {"completed", "failed", "cancelled"}:
+            raise EdgeGatewayValidationError("task outcome is invalid")
+        if not isinstance(lease_token, str) or not lease_token.startswith("dopl_") or len(lease_token) > 128:
+            raise EdgeGatewayAuthenticationError("edge task lease binding was rejected")
+        summary = _safe_summary(safe_summary)
+        identity = self.authenticate(**auth, lock=True)
+        result = self.repository.task_outcome(
+            identity["gateway_id"], _text(task_id, "task_id", 255),
+            _digest(lease_token), outcome, summary,
+        )
+        if result is None:
+            self._failure(
+                identity["gateway_id"], "binding_rejected", "task_outcome_rejected",
+                trusted=True,
+            )
+            raise EdgeGatewayConflictError("task outcome was rejected")
+        return result
+
+    def reconcile(
+        self, *, limit=50, cancel_cursor=None, release_cursor=None, **auth
+    ) -> dict:
+        limit, _ = _page(limit, 0)
+        if cancel_cursor is not None:
+            cancel_cursor = _text(cancel_cursor, "cancel_cursor", 255)
+        if release_cursor is not None:
+            release_cursor = _uuid(release_cursor, "release_cursor")
+        identity = self.authenticate(**auth, lock=True)
+        cancellations = self.repository.cancelled_tasks(
+            identity["gateway_id"], limit=limit, cursor=cancel_cursor
+        )
+        releases = self.repository.unfinished_releases(
+            identity["gateway_id"], limit=limit, cursor=release_cursor
+        )
+        result = {
+            "cancelled_task_ids": cancellations["items"],
+            "cancel_next_cursor": cancellations["next_cursor"],
+            "release_offers": releases["items"],
+            "release_next_cursor": releases["next_cursor"],
+            "release_baseline": self.repository.latest_release_baseline(
+                identity["gateway_id"]
+            ),
+        }
+        self.repository.commit()
+        return result
+
+    def accept_event(self, *, event, lease_token=None, **auth) -> dict:
+        identity = self.authenticate(**auth, lock=True)
+        if not isinstance(event, Mapping):
+            self.repository.rollback()
+            raise EdgeGatewayValidationError("edge event contract is invalid")
+        policy = EdgeEgressPolicy(allowed_control_hosts=set(identity["allowed_control_hosts"]), allowed_proxy_hosts=set(identity["allowed_proxy_hosts"]))
+        try:
+            approved = policy.approve_event(event)
+        except EdgePolicyError as exc:
+            self._failure(identity["gateway_id"], "policy_rejected", "egress_policy_rejected", trusted=True)
+            raise EdgeGatewayValidationError("edge event is not approved for control-plane egress") from exc
+        try:
+            contract = EdgeEventContract.from_mapping(approved)
+        except EdgeContractError as exc:
+            self._failure(identity["gateway_id"], "binding_rejected", "event_contract_invalid", trusted=True)
+            raise EdgeGatewayValidationError("edge event contract is invalid") from exc
+        if contract.gateway_id != identity["gateway_id"] or contract.environment != identity["environment"] or contract.network_zone != identity["network_zone"] or contract.policy_digest != identity["policy_digest"]:
+            self._failure(identity["gateway_id"], "binding_rejected", "event_gateway_mismatch", trusted=True)
+            raise EdgeGatewayAuthenticationError("edge event binding was rejected")
+        supplied_digest = contract.digest
+        existing = self.repository.event_by_id(identity["gateway_id"], contract.event_id)
+        if existing is not None:
+            if not hmac.compare_digest(existing["contract_digest"], supplied_digest):
+                self._failure(identity["gateway_id"], "idempotency_conflict", "event_contract_conflict", trusted=True)
+                raise EdgeGatewayConflictError("event identifier conflicts with an existing contract")
+            self.repository.rollback()
+            return dict(existing["ack"])
+        if not isinstance(lease_token, str) or not lease_token.startswith("dopl_") or len(lease_token) > 128:
+            self._failure(identity["gateway_id"], "binding_rejected", "lease_invalid", trusted=True)
+            raise EdgeGatewayAuthenticationError("edge task lease binding was rejected")
+        remote_lease_digest = _digest(lease_token)
+        task_contract = self.repository.task_lease_binding(
+            contract.task_id, contract.gateway_id, remote_lease_digest
+        )
+        if task_contract is None:
+            existing = self.repository.event_by_id(identity["gateway_id"], contract.event_id)
+            if existing is not None and hmac.compare_digest(existing["contract_digest"], supplied_digest):
+                self.repository.rollback()
+                return dict(existing["ack"])
+            self._failure(identity["gateway_id"], "binding_rejected", "lease_mismatch", trusted=True)
+            raise EdgeGatewayAuthenticationError("edge task lease binding was rejected")
+        if any(contract.to_mapping()[field] != task_contract[field] for field in (
+            "gateway_id", "environment", "network_zone", "purpose", "policy_digest",
+            "attempt",
+        )):
+            self._failure(identity["gateway_id"], "binding_rejected", "event_task_mismatch", trusted=True)
+            raise EdgeGatewayValidationError("edge event task binding is invalid")
+        ack = {
+            "event_id": contract.event_id,
+            "event_digest": contract.digest,
+            "remote_lease_digest": remote_lease_digest,
+            "received_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
+            "status": "accepted",
+        }
+        accepted = self.repository.insert_event(contract.to_mapping(), contract.digest, ack)
+        if accepted is None:
+            self._failure(identity["gateway_id"], "idempotency_conflict", "event_contract_conflict", trusted=True)
+            raise EdgeGatewayConflictError("event identifier conflicts with an existing contract")
+        return accepted
+
+    def offer_release(
+        self, gateway_id, *, version, artifact_digest, rollback_version,
+        request_id, actor_uid, artifact_name=None, deadline_at=None,
+    ) -> dict:
+        gateway_id = _uuid(gateway_id, "gateway_id")
+        version = _release_version(version, "version")
+        artifact_digest = _sha(artifact_digest, "artifact_digest")
+        rollback_version = _release_version(rollback_version, "rollback_version")
+        if _release_version_tuple(version) <= _release_version_tuple(rollback_version):
+            raise EdgeGatewayValidationError(
+                "release version must be newer than rollback_version"
+            )
+        request_id = _text(request_id, "request_id", 255)
+        now = self._clock().astimezone(UTC)
+        supplied_deadline = deadline_at
+        artifact_name = _artifact_name(
+            artifact_name or f"edge-agent-{version}.bin"
+        )
+        deadline_at = _future_deadline(
+            deadline_at
+            or (now + timedelta(hours=24)).isoformat().replace("+00:00", "Z"),
+            now=now,
+        )
+        unsigned = {
+            "release_id": str(uuid.uuid4()), "version": version,
+            "rollback_version": rollback_version,
+            "artifact_digest": artifact_digest, "artifact_name": artifact_name,
+            "deadline_at": deadline_at, "status": "offered",
+        }
+        manifest = self._signed_release_manifest(unsigned)
+        request_digest = canonical_sha256({
+            "gateway_id": gateway_id, "version": version,
+            "artifact_digest": artifact_digest, "rollback_version": rollback_version,
+            "artifact_name": artifact_name,
+            "deadline_at": deadline_at if supplied_deadline is not None else None,
+        })
+        result = self.repository.offer_release(
+            gateway_id, manifest, _uuid(actor_uid, "actor_uid"), request_id,
+            request_digest,
+        )
+        if result.get("conflict"):
+            raise EdgeGatewayConflictError("release request conflicts with an existing request")
+        if not result:
+            raise EdgeGatewayNotFoundError("edge gateway was not found")
+        return result
+
+    def acknowledge_release(self, *, release_id, outcome, safe_summary, **auth) -> dict:
+        statuses = {"accepted": "accepted", "installed": "installed", "failed": "failed", "rollback": "rolled_back"}
+        if outcome not in statuses:
+            raise EdgeGatewayValidationError("release outcome is invalid")
+        release_id = _uuid(release_id, "release_id")
+        safe_summary = _safe_summary(safe_summary)
+        identity = self.authenticate(**auth, lock=True)
+        current = self.repository.release_for_ack(identity["gateway_id"], release_id)
+        if current is None:
+            self.repository.rollback()
+            self._failure(
+                identity["gateway_id"], "release_rejected",
+                "release_transition_invalid", trusted=True,
+            )
+            raise EdgeGatewayConflictError("release acknowledgement was rejected")
+        stored = dict(current["signed_manifest"])
+        unsigned = {
+            key: value for key, value in stored.items()
+            if key not in {
+                "manifest_digest", "signature", "signature_algorithm", "key_id"
+            }
+        }
+        unsigned["status"] = statuses[outcome]
+        manifest = self._signed_release_manifest(unsigned)
+        row = self.repository.acknowledge_release(
+            identity["gateway_id"], release_id, statuses[outcome], safe_summary,
+            manifest,
+        )
+        if row is None:
+            self._failure(identity["gateway_id"], "release_rejected", "release_transition_invalid", trusted=True)
+            raise EdgeGatewayConflictError("release acknowledgement was rejected")
+        return row

+ 518 - 0
app/core/events/production_observability.py

@@ -0,0 +1,518 @@
+"""Closed, deterministic P3 production-observability adapter contracts.
+
+This module intentionally does not configure or contact enterprise providers.
+It makes the boundary for approved monitoring, notification, on-call, and ITSM
+adapters safe and testable while P3-WP05 enterprise inputs remain external.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import math
+import re
+import uuid
+from collections.abc import Callable
+from datetime import UTC, datetime
+from typing import Any
+
+from app.core.events.data_observability import _reject_secret_material
+
+CONTRACT_VERSION = "1.0.0"
+TELEMETRY_RESOURCE_TYPES = frozenset(
+    {"api", "task", "database", "graph", "object_storage"}
+)
+TELEMETRY_LAYERS = frozenset({"service", "task", "data", "capacity"})
+TELEMETRY_STATUSES = frozenset({"ok", "error", "degraded", "backpressure"})
+DELIVERY_CHANNELS = frozenset({"smtp", "enterprise_collaboration", "on_call"})
+MAX_DELIVERY_ATTEMPTS = 3
+DELIVERY_LEASE_SECONDS = 300
+_TRACE_ID = re.compile(r"^[0-9a-f]{32}$")
+_SPAN_ID = re.compile(r"^[0-9a-f]{16}$")
+
+
+class ProductionObservabilityContractError(ValueError):
+    """Raised before unsafe or ambiguous operations leave the local boundary."""
+
+
+_SENSITIVE_VALUE = re.compile(
+    r"(?i)(?:bearer\s+|password\s*[=:]|secret\s*[=:]|-----begin|\bselect\b.+\bfrom\b)"
+)
+
+
+def _safe_object(value: Any, allowed: set[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise ProductionObservabilityContractError(f"{label} must be an object")
+    try:
+        _reject_secret_material(value)
+    except ValueError as error:
+        raise ProductionObservabilityContractError(str(error)) from error
+    unknown = sorted(set(value) - allowed)
+    if unknown:
+        raise ProductionObservabilityContractError(
+            f"{label} contains unsupported fields: {', '.join(unknown)}"
+        )
+    return dict(value)
+
+
+def _reject_sensitive_values(value: Any, *, depth: int = 0) -> None:
+    if depth > 4:
+        raise ProductionObservabilityContractError("payload nesting exceeds 4")
+    if isinstance(value, str) and _SENSITIVE_VALUE.search(value):
+        raise ProductionObservabilityContractError("sensitive value is not allowed")
+    if isinstance(value, dict):
+        for item in value.values():
+            _reject_sensitive_values(item, depth=depth + 1)
+    elif isinstance(value, list):
+        if len(value) > 32:
+            raise ProductionObservabilityContractError("payload list exceeds 32 items")
+        for item in value:
+            _reject_sensitive_values(item, depth=depth + 1)
+
+
+def _timestamp(value: Any) -> str:
+    try:
+        parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
+    except (TypeError, ValueError) as error:
+        raise ProductionObservabilityContractError(
+            "occurred_at must be RFC3339"
+        ) from error
+    if parsed.tzinfo is None:
+        raise ProductionObservabilityContractError(
+            "occurred_at must include a timezone"
+        )
+    return parsed.astimezone(UTC).isoformat().replace("+00:00", "Z")
+
+
+def _string(value: Any, label: str, maximum: int = 200) -> str:
+    if not isinstance(value, str) or not value.strip():
+        raise ProductionObservabilityContractError(f"{label} is required")
+    result = value.strip()
+    if len(result) > maximum:
+        raise ProductionObservabilityContractError(
+            f"{label} exceeds {maximum} characters"
+        )
+    return result
+
+
+def _uid(value: Any, label: str) -> str:
+    try:
+        return str(uuid.UUID(str(value)))
+    except (TypeError, ValueError, AttributeError) as error:
+        raise ProductionObservabilityContractError(f"{label} must be a UUID") from error
+
+
+def _number(value: Any, label: str) -> float:
+    if isinstance(value, bool):
+        raise ProductionObservabilityContractError(f"{label} must be numeric")
+    try:
+        result = float(value)
+    except (TypeError, ValueError) as error:
+        raise ProductionObservabilityContractError(
+            f"{label} must be numeric"
+        ) from error
+    if not math.isfinite(result):
+        raise ProductionObservabilityContractError(f"{label} must be finite")
+    return result
+
+
+def _canonical_digest(value: dict[str, Any]) -> str:
+    encoded = json.dumps(
+        value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
+    )
+    return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
+
+
+def normalize_telemetry_event(payload: Any) -> dict[str, Any]:
+    """Validate a cardinality-safe telemetry envelope before provider adaptation."""
+    body = _safe_object(
+        payload,
+        {
+            "event_id",
+            "occurred_at",
+            "layer",
+            "operation",
+            "resource_type",
+            "resource_uid",
+            "correlation_id",
+            "trace_id",
+            "span_id",
+            "status",
+            "attributes",
+        },
+        "telemetry event",
+    )
+    _reject_sensitive_values(body)
+    attributes = _safe_object(
+        body.get("attributes"),
+        {
+            "duration_ms",
+            "queue_depth",
+            "utilization_ratio",
+            "retry_count",
+            "error_code",
+        },
+        "telemetry attributes",
+    )
+    trace_id = _string(body.get("trace_id"), "trace_id", 32).lower()
+    span_id = _string(body.get("span_id"), "span_id", 16).lower()
+    if not _TRACE_ID.fullmatch(trace_id) or not _SPAN_ID.fullmatch(span_id):
+        raise ProductionObservabilityContractError("trace_id or span_id is invalid")
+    layer = _string(body.get("layer"), "layer", 20)
+    resource_type = _string(body.get("resource_type"), "resource_type", 30)
+    status = _string(body.get("status"), "status", 20)
+    if layer not in TELEMETRY_LAYERS:
+        raise ProductionObservabilityContractError("telemetry layer is invalid")
+    if resource_type not in TELEMETRY_RESOURCE_TYPES:
+        raise ProductionObservabilityContractError("telemetry resource_type is invalid")
+    if status not in TELEMETRY_STATUSES:
+        raise ProductionObservabilityContractError("telemetry status is invalid")
+    normalized_attributes: dict[str, Any] = {}
+    for key, value in attributes.items():
+        if key == "error_code":
+            normalized_attributes[key] = _string(value, "error_code", 64)
+        elif key == "retry_count":
+            numeric = _number(value, "retry_count")
+            if not numeric.is_integer():
+                raise ProductionObservabilityContractError(
+                    "retry_count must be an integer"
+                )
+            retry_count = int(numeric)
+            if retry_count < 0 or retry_count > 10:
+                raise ProductionObservabilityContractError(
+                    "retry_count must be between 0 and 10"
+                )
+            normalized_attributes[key] = retry_count
+        else:
+            number = _number(value, key)
+            if number < 0:
+                raise ProductionObservabilityContractError(
+                    f"{key} must not be negative"
+                )
+            normalized_attributes[key] = number
+    return {
+        "contract_version": CONTRACT_VERSION,
+        "event_id": _uid(body.get("event_id"), "event_id"),
+        "occurred_at": _timestamp(body.get("occurred_at")),
+        "layer": layer,
+        "operation": _string(body.get("operation"), "operation", 80),
+        "resource_type": resource_type,
+        "resource_uid": _string(body.get("resource_uid"), "resource_uid", 160),
+        "correlation_id": _uid(body.get("correlation_id"), "correlation_id"),
+        "trace_id": trace_id,
+        "span_id": span_id,
+        "status": status,
+        "attributes": normalized_attributes,
+    }
+
+
+def production_slo_catalog() -> list[dict[str, Any]]:
+    """Return the versioned production SLO catalog used by approved adapters."""
+    return [
+        {
+            "catalog_version": CONTRACT_VERSION,
+            "layer": "service",
+            "sli": "availability_ratio",
+            "target": 0.999,
+            "error_budget_ratio": 0.001,
+            "alert_rule_version": CONTRACT_VERSION,
+        },
+        {
+            "catalog_version": CONTRACT_VERSION,
+            "layer": "task",
+            "sli": "delivery_ratio",
+            "target": 0.99,
+            "error_budget_ratio": 0.01,
+            "alert_rule_version": CONTRACT_VERSION,
+        },
+        {
+            "catalog_version": CONTRACT_VERSION,
+            "layer": "data",
+            "sli": "freshness_ratio",
+            "target": 0.99,
+            "error_budget_ratio": 0.01,
+            "alert_rule_version": CONTRACT_VERSION,
+        },
+        {
+            "catalog_version": CONTRACT_VERSION,
+            "layer": "capacity",
+            "sli": "headroom_ratio",
+            "target": 0.2,
+            "error_budget_ratio": 0.05,
+            "alert_rule_version": CONTRACT_VERSION,
+        },
+    ]
+
+
+def evaluate_capacity(payload: Any) -> dict[str, Any]:
+    """Provide a deterministic admission recommendation without dropping work."""
+    body = _safe_object(
+        payload,
+        {
+            "active_users",
+            "data_sources",
+            "utilization_ratio",
+            "queue_depth",
+            "queue_limit",
+            "ingress_per_minute",
+            "max_ingress_per_minute",
+        },
+        "capacity snapshot",
+    )
+    active_users = int(_number(body.get("active_users"), "active_users"))
+    data_sources = int(_number(body.get("data_sources"), "data_sources"))
+    utilization = _number(body.get("utilization_ratio"), "utilization_ratio")
+    queue_depth = int(_number(body.get("queue_depth"), "queue_depth"))
+    queue_limit = int(_number(body.get("queue_limit"), "queue_limit"))
+    ingress = _number(body.get("ingress_per_minute"), "ingress_per_minute")
+    max_ingress = _number(body.get("max_ingress_per_minute"), "max_ingress_per_minute")
+    if not 100 <= active_users <= 500:
+        raise ProductionObservabilityContractError(
+            "active_users must be between 100 and 500"
+        )
+    if not 50 <= data_sources <= 500:
+        raise ProductionObservabilityContractError(
+            "data_sources must be between 50 and 500"
+        )
+    if not 0 <= utilization <= 1 or queue_depth < 0 or queue_limit < 1:
+        raise ProductionObservabilityContractError("capacity snapshot is invalid")
+    if ingress < 0 or max_ingress <= 0:
+        raise ProductionObservabilityContractError("ingress capacity is invalid")
+    if queue_depth > queue_limit:
+        raise ProductionObservabilityContractError(
+            "queue exceeds hard limit; reject before data loss"
+        )
+    pressure = max(utilization, queue_depth / queue_limit, ingress / max_ingress)
+    if pressure >= 0.8:
+        admission = "backpressure"
+        throttle_ratio = 0.5
+        recommendation = (
+            "scale workers or queue capacity before accepting additional work"
+        )
+    elif pressure >= 0.7:
+        admission = "throttle"
+        throttle_ratio = 0.75
+        recommendation = "throttle non-critical work and review capacity headroom"
+    else:
+        admission = "allow"
+        throttle_ratio = 1.0
+        recommendation = "capacity within approved local baseline"
+    return {
+        "baseline": {"active_users": [100, 500], "data_sources": [50, 500]},
+        "admission": admission,
+        "throttle_ratio": throttle_ratio,
+        "recommendation": recommendation,
+        "pressure_ratio": round(pressure, 4),
+    }
+
+
+class DeliveryCoordinator:
+    """Deterministic local state machine for adapter contract verification only."""
+
+    def __init__(self, *, now: Callable[[], float]):
+        self.now = now
+        self.deliveries: dict[str, dict[str, Any]] = {}
+        self._delivery_digests: dict[str, str] = {}
+        self._itsm_by_key: dict[str, dict[str, str]] = {}
+        self._itsm_by_external_ref: dict[str, str] = {}
+        self.audit: list[dict[str, Any]] = []
+
+    def _audit(self, action: str, delivery_id: str, **safe_detail: Any) -> None:
+        self.audit.append(
+            {
+                "action": action,
+                "delivery_id": delivery_id,
+                "at": self.now(),
+                "safe_detail": safe_detail,
+            }
+        )
+
+    def enqueue_delivery(self, payload: Any) -> dict[str, Any]:
+        body = _safe_object(
+            payload,
+            {
+                "delivery_id",
+                "incident_uid",
+                "alert_uid",
+                "channel",
+                "idempotency_key",
+                "summary",
+            },
+            "delivery request",
+        )
+        normalized = {
+            "delivery_id": _uid(body.get("delivery_id"), "delivery_id"),
+            "incident_uid": _uid(body.get("incident_uid"), "incident_uid"),
+            "alert_uid": _uid(body.get("alert_uid"), "alert_uid"),
+            "channel": _string(body.get("channel"), "channel", 40),
+            "idempotency_key": _string(
+                body.get("idempotency_key"), "idempotency_key", 120
+            ),
+            "summary": _string(body.get("summary"), "summary", 160),
+        }
+        if normalized["channel"] not in DELIVERY_CHANNELS:
+            raise ProductionObservabilityContractError("delivery channel is invalid")
+        digest = _canonical_digest(normalized)
+        existing_digest = self._delivery_digests.get(normalized["idempotency_key"])
+        if existing_digest is not None:
+            if existing_digest != digest:
+                raise ProductionObservabilityContractError("delivery replay conflicts")
+            return dict(self.deliveries[normalized["delivery_id"]])
+        record = {
+            **normalized,
+            "status": "pending",
+            "attempt_count": 0,
+            "lease_owner": None,
+            "lease_expires_at": None,
+            "fencing_token": 0,
+        }
+        self.deliveries[record["delivery_id"]] = record
+        self._delivery_digests[record["idempotency_key"]] = digest
+        self._audit(
+            "delivery_enqueued", record["delivery_id"], channel=record["channel"]
+        )
+        return dict(record)
+
+    def claim_delivery(
+        self,
+        delivery_id: str,
+        *,
+        worker_id: str,
+        now: float | None = None,
+    ) -> dict[str, Any]:
+        record = self._delivery(delivery_id)
+        current = self.now() if now is None else now
+        if record["status"] == "processing" and current < record["lease_expires_at"]:
+            raise ProductionObservabilityContractError("delivery is leased")
+        if record["status"] not in {"pending", "processing"}:
+            raise ProductionObservabilityContractError("delivery is not claimable")
+        record["status"] = "processing"
+        record["lease_owner"] = _string(worker_id, "worker_id", 80)
+        record["lease_expires_at"] = current + DELIVERY_LEASE_SECONDS
+        record["fencing_token"] += 1
+        return dict(record)
+
+    def record_attempt(
+        self,
+        delivery_id: str,
+        *,
+        worker_id: str,
+        fencing_token: int,
+        outcome: str,
+        reason_code: str | None = None,
+    ) -> dict[str, Any]:
+        record = self._delivery(delivery_id)
+        if (
+            record["status"] != "processing"
+            or record["lease_owner"] != _string(worker_id, "worker_id", 80)
+            or record["fencing_token"] != fencing_token
+        ):
+            raise ProductionObservabilityContractError(
+                "delivery fencing token is invalid"
+            )
+        if outcome == "delivered":
+            record["status"] = "delivered"
+            self._audit("delivery_delivered", record["delivery_id"])
+            return dict(record)
+        if outcome != "failed":
+            raise ProductionObservabilityContractError("delivery outcome is invalid")
+        reason = _string(reason_code, "reason_code", 64)
+        if not re.fullmatch(r"[a-z][a-z0-9_]{0,63}", reason):
+            raise ProductionObservabilityContractError("reason_code is invalid")
+        record["attempt_count"] += 1
+        record["lease_owner"] = None
+        record["lease_expires_at"] = None
+        if record["attempt_count"] >= MAX_DELIVERY_ATTEMPTS:
+            record["status"] = "dead_letter"
+            self._audit(
+                "delivery_dead_lettered", record["delivery_id"], reason_code=reason
+            )
+        else:
+            record["status"] = "pending"
+            self._audit(
+                "delivery_attempt_failed", record["delivery_id"], reason_code=reason
+            )
+        return dict(record)
+
+    def compensate_dead_letter(
+        self, delivery_id: str, *, actor_uid: str, note: str
+    ) -> dict[str, Any]:
+        record = self._delivery(delivery_id)
+        if record["status"] != "dead_letter":
+            raise ProductionObservabilityContractError("delivery is not a dead letter")
+        record["status"] = "compensated"
+        self._audit(
+            "delivery_compensated",
+            record["delivery_id"],
+            actor_uid=_uid(actor_uid, "actor_uid"),
+            note=_string(note, "compensation note", 160),
+        )
+        return dict(record)
+
+    def select_on_call_route(
+        self, payload: Any, *, elapsed_seconds: int
+    ) -> dict[str, Any]:
+        body = _safe_object(
+            payload,
+            {
+                "rotation_version",
+                "severity",
+                "primary",
+                "secondary",
+                "escalate_after_seconds",
+            },
+            "on-call route",
+        )
+        severity = _string(body.get("severity"), "severity", 20)
+        if severity not in {"warning", "error", "critical"}:
+            raise ProductionObservabilityContractError("on-call severity is invalid")
+        escalate_after = int(
+            _number(body.get("escalate_after_seconds"), "escalate_after_seconds")
+        )
+        if escalate_after < 60 or elapsed_seconds < 0:
+            raise ProductionObservabilityContractError("on-call escalation is invalid")
+        escalated = elapsed_seconds >= escalate_after
+        return {
+            "target": _string(
+                body.get("secondary") if escalated else body.get("primary"),
+                "on-call target",
+                120,
+            ),
+            "level": 2 if escalated else 1,
+            "rotation_version": _string(
+                body.get("rotation_version"), "rotation_version", 20
+            ),
+        }
+
+    def bind_itsm_reference(
+        self, *, incident_uid: str, external_ref: str, idempotency_key: str
+    ) -> dict[str, str]:
+        record = {
+            "incident_uid": _uid(incident_uid, "incident_uid"),
+            "external_ref": _string(external_ref, "external_ref", 120),
+            "idempotency_key": _string(idempotency_key, "idempotency_key", 120),
+        }
+        existing = self._itsm_by_key.get(record["idempotency_key"])
+        if existing is not None:
+            if existing != record:
+                raise ProductionObservabilityContractError("ITSM replay conflicts")
+            return dict(existing)
+        linked_incident = self._itsm_by_external_ref.get(record["external_ref"])
+        if linked_incident is not None and linked_incident != record["incident_uid"]:
+            raise ProductionObservabilityContractError(
+                "ITSM external reference conflicts"
+            )
+        self._itsm_by_key[record["idempotency_key"]] = record
+        self._itsm_by_external_ref[record["external_ref"]] = record["incident_uid"]
+        return dict(record)
+
+    def platform_incident_for_itsm(self, external_ref: str) -> str | None:
+        return self._itsm_by_external_ref.get(
+            _string(external_ref, "external_ref", 120)
+        )
+
+    def _delivery(self, delivery_id: str) -> dict[str, Any]:
+        record = self.deliveries.get(_uid(delivery_id, "delivery_id"))
+        if record is None:
+            raise LookupError("delivery was not found")
+        return record

+ 431 - 0
app/core/events/production_operations.py

@@ -0,0 +1,431 @@
+"""Persistent-provider-ready orchestration for P2 incident delivery.
+
+External providers are disabled by default.  The service only stores safe
+references, hashes, codes, and bounded summaries; adapters receive no URL or
+credential from this boundary.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import re
+import uuid
+from collections.abc import Callable
+from typing import Any
+
+from app.core.events.data_observability import _reject_secret_material
+
+
+class ProductionOperationsError(ValueError):
+    pass
+
+
+def _safe_string(value: Any, label: str, maximum: int = 160) -> str:
+    if not isinstance(value, str) or not value.strip() or len(value.strip()) > maximum:
+        raise ProductionOperationsError(f"{label} is invalid")
+    result = value.strip()
+    try:
+        _reject_secret_material({"value": result})
+    except ValueError as error:
+        raise ProductionOperationsError("sensitive material is not allowed") from error
+    if re.search(
+        r"(?i)(bearer\s+|password\s*[=:]|-----begin|\bselect\b.+\bfrom\b)", result
+    ):
+        raise ProductionOperationsError("sensitive material is not allowed")
+    return result
+
+
+def _uid(value: Any, label: str) -> str:
+    try:
+        return str(uuid.UUID(str(value)))
+    except (TypeError, ValueError, AttributeError) as error:
+        raise ProductionOperationsError(f"{label} must be a UUID") from error
+
+
+def _digest(value: dict[str, Any]) -> str:
+    return hashlib.sha256(
+        json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
+    ).hexdigest()
+
+
+class ProviderRegistry:
+    """Closed capability registry; real adapter registration is enterprise UAT."""
+
+    allowed = frozenset(
+        {"monitoring", "smtp", "enterprise_collaboration", "on_call", "itsm"}
+    )
+
+    def __init__(
+        self,
+        adapters: dict[str, Callable[[dict[str, str]], dict[str, str]]] | None = None,
+    ):
+        self.adapters = adapters or {}
+
+    def dispatch(self, capability: str, envelope: dict[str, str]) -> dict[str, str]:
+        capability = _safe_string(capability, "provider capability", 40)
+        if capability not in self.allowed or capability not in self.adapters:
+            raise ProductionOperationsError("provider capability is disabled")
+        response = self.adapters[capability](dict(envelope))
+        if not isinstance(response, dict) or set(response) != {
+            "status",
+            "receipt_code",
+        }:
+            raise ProductionOperationsError("provider response is invalid")
+        try:
+            _reject_secret_material(response)
+            status = _safe_string(response["status"], "provider response status", 20)
+            receipt_code = _safe_string(
+                response["receipt_code"], "provider receipt code", 160
+            )
+        except (KeyError, ValueError, ProductionOperationsError) as error:
+            raise ProductionOperationsError("provider response is invalid") from error
+        if status not in {"accepted", "queued", "rejected"}:
+            raise ProductionOperationsError("provider response is invalid")
+        return {"status": status, "receipt_code": receipt_code}
+
+
+class ProductionOperationsService:
+    def __init__(
+        self,
+        repository,
+        *,
+        now: Callable[[], float],
+        providers: ProviderRegistry | None = None,
+        queue_hard_limit: int = 500,
+    ):
+        if (
+            isinstance(queue_hard_limit, bool)
+            or not isinstance(queue_hard_limit, int)
+            or not 1 <= queue_hard_limit <= 500
+        ):
+            raise ProductionOperationsError("queue hard limit must be between 1 and 500")
+        self.repository = repository
+        self.now = now
+        self.providers = providers or ProviderRegistry()
+        self.queue_hard_limit = queue_hard_limit
+
+    def _audit(
+        self,
+        action: str,
+        *,
+        delivery_uid: str | None,
+        actor_uid: str,
+        detail: dict[str, str] | None = None,
+    ) -> None:
+        self.repository.append_audit(
+            {
+                "uid": str(uuid.uuid4()),
+                "delivery_uid": delivery_uid,
+                "action": action,
+                "actor_uid": _uid(actor_uid, "actor_uid"),
+                "safe_detail": detail or {},
+                "created_at": self.now(),
+            }
+        )
+
+    def enqueue_incident_delivery(
+        self, payload: Any, *, actor_uid: str
+    ) -> dict[str, Any]:
+        if not isinstance(payload, dict) or set(payload) != {
+            "incident_uid",
+            "alert_uid",
+            "channel",
+            "summary",
+        }:
+            raise ProductionOperationsError("delivery request has unsupported fields")
+        _reject_secret_material(payload)
+        record = {
+            "uid": str(uuid.uuid4()),
+            "incident_uid": _uid(payload["incident_uid"], "incident_uid"),
+            "alert_uid": _uid(payload["alert_uid"], "alert_uid"),
+            "channel": _safe_string(payload["channel"], "channel", 40),
+            "summary": _safe_string(payload["summary"], "summary"),
+            "status": "pending",
+            "attempt_count": 0,
+            "next_attempt_at": self.now(),
+            "lease_owner": None,
+            "lease_fence": 0,
+            "lease_expires_at": None,
+        }
+        if record["channel"] not in {
+            "smtp",
+            "enterprise_collaboration",
+            "on_call",
+            "itsm",
+            "monitoring",
+        }:
+            raise ProductionOperationsError("delivery channel is invalid")
+        record["idempotency_key"] = (
+            f"{record['incident_uid']}:{record['alert_uid']}:{record['channel']}"
+        )
+        record["payload_digest"] = _digest(
+            {
+                key: record[key]
+                for key in ("incident_uid", "alert_uid", "channel", "summary")
+            }
+        )
+        try:
+            stored = self.repository.enqueue_delivery(
+                record, queue_hard_limit=self.queue_hard_limit
+            )
+        except RuntimeError as error:
+            if str(error) in {"idempotency conflict", "delivery queue hard limit reached"}:
+                raise ProductionOperationsError(str(error)) from error
+            raise
+        if stored["uid"] == record["uid"]:
+            self._audit(
+                "delivery_enqueued",
+                delivery_uid=stored["uid"],
+                actor_uid=actor_uid,
+                detail={"channel": stored["channel"]},
+            )
+        return stored
+
+    def claim_delivery(
+        self, delivery_uid: str, *, worker_id: str, now: float | None = None
+    ) -> dict[str, Any]:
+        result = self.repository.claim_delivery(
+            _uid(delivery_uid, "delivery_uid"),
+            _safe_string(worker_id, "worker_id", 80),
+            self.now() if now is None else now,
+        )
+        if result is None:
+            raise ProductionOperationsError("delivery is not claimable")
+        return result
+
+    def record_delivery_attempt(
+        self,
+        delivery_uid: str,
+        *,
+        worker_id: str,
+        lease_fence: int,
+        delivered: bool,
+        actor_uid: str,
+        now: float | None = None,
+    ) -> dict[str, Any]:
+        if not isinstance(lease_fence, int) or lease_fence < 1:
+            raise ProductionOperationsError("lease fence is invalid")
+        current = self.now() if now is None else now
+        result = self.repository.record_attempt(
+            _uid(delivery_uid, "delivery_uid"),
+            _safe_string(worker_id, "worker_id", 80),
+            lease_fence,
+            current,
+            delivered,
+            "provider_unavailable" if not delivered else None,
+        )
+        if result is None:
+            raise ProductionOperationsError("delivery lease is expired or fenced")
+        action = (
+            "delivery_delivered"
+            if delivered
+            else (
+                "delivery_dead_lettered"
+                if result["status"] == "dead_letter"
+                else "delivery_attempt_failed"
+            )
+        )
+        self._audit(
+            action,
+            delivery_uid=result["uid"],
+            actor_uid=actor_uid,
+            detail={"status": result["status"]},
+        )
+        return result
+
+    def compensate_dead_letter(
+        self,
+        delivery_uid: str,
+        *,
+        reason_code: str,
+        receipt_code: str,
+        actor_uid: str,
+    ) -> dict[str, Any]:
+        reason_code = _safe_string(reason_code, "compensation reason code", 64)
+        receipt_code = _safe_string(receipt_code, "compensation receipt code", 64)
+        if reason_code not in {
+            "operator_review",
+            "provider_recovered",
+            "duplicate_suppressed",
+        } or not re.fullmatch(r"[A-Z][A-Z0-9_]{0,63}", receipt_code):
+            raise ProductionOperationsError("compensation receipt is invalid")
+        try:
+            result = self.repository.compensate_dead_letter(
+                _uid(delivery_uid, "delivery_uid"), reason_code, receipt_code
+            )
+        except RuntimeError as error:
+            raise ProductionOperationsError(str(error)) from error
+        self._audit(
+            "delivery_dead_letter_compensated",
+            delivery_uid=result["uid"],
+            actor_uid=actor_uid,
+            detail={"reason_code": reason_code, "receipt_code": receipt_code},
+        )
+        return result
+
+    def audit_rejection_independently(
+        self, *, actor_uid: str, reason_code: str, correlation_id: str
+    ) -> None:
+        reason_code = _safe_string(reason_code, "reason code", 64)
+        if reason_code not in {"invalid_request", "conflict", "internal_error"}:
+            raise ProductionOperationsError("reason code is invalid")
+        self.repository.append_rejection_audit_independently(
+            {
+                "uid": str(uuid.uuid4()),
+                "action": f"delivery_{reason_code}",
+                "actor_uid": _uid(actor_uid, "actor_uid"),
+                "safe_detail": {
+                    "reason_code": reason_code,
+                    "correlation_id": _uid(correlation_id, "correlation_id"),
+                },
+            }
+        )
+
+    def dispatch_provider(
+        self, capability: str, payload: dict[str, Any]
+    ) -> dict[str, str]:
+        incident_uid = _uid(payload.get("incident_uid"), "incident_uid")
+        return self.providers.dispatch(
+            capability,
+            {
+                "incident_uid": incident_uid,
+                "summary": _safe_string(payload.get("summary"), "summary"),
+            },
+        )
+
+    def create_policy(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        if not isinstance(payload, dict) or set(payload) != {
+            "code",
+            "version",
+            "layer",
+            "error_budget_ratio",
+        }:
+            raise ProductionOperationsError("policy has unsupported fields")
+        budget = payload["error_budget_ratio"]
+        if (
+            isinstance(budget, bool)
+            or not isinstance(budget, (int, float))
+            or not 0 < budget < 1
+        ):
+            raise ProductionOperationsError("error budget is invalid")
+        record = {
+            "uid": str(uuid.uuid4()),
+            "code": _safe_string(payload["code"], "policy code", 80),
+            "version": _safe_string(payload["version"], "policy version", 20),
+            "layer": _safe_string(payload["layer"], "policy layer", 20),
+            "error_budget_ratio": float(budget),
+            "status": "draft",
+            "created_by": _uid(actor_uid, "actor_uid"),
+            "created_at": self.now(),
+        }
+        if record["layer"] not in {"service", "task", "data", "capacity"}:
+            raise ProductionOperationsError("policy layer is invalid")
+        return dict(self.repository.save_policy(record))
+
+    def activate_policy(
+        self, code: str, version: str, *, actor_uid: str
+    ) -> dict[str, Any]:
+        result = self.repository.activate_policy(
+            _safe_string(code, "policy code", 80),
+            _safe_string(version, "policy version", 20),
+        )
+        self._audit(
+            "policy_activated",
+            delivery_uid=None,
+            actor_uid=actor_uid,
+            detail={"code": result["code"], "version": result["version"]},
+        )
+        return dict(result)
+
+    def active_policy(self, code: str) -> dict[str, Any]:
+        result = self.repository.active_policy(_safe_string(code, "policy code", 80))
+        if result is None:
+            raise ProductionOperationsError("active policy is not found")
+        return dict(result)
+
+    def list_deliveries(self, incident_uid: str) -> list[dict[str, Any]]:
+        return list(self.repository.list_deliveries(_uid(incident_uid, "incident_uid")))
+
+    def bind_itsm_reference(
+        self,
+        *,
+        incident_uid: str,
+        external_ref: str,
+        idempotency_key: str,
+        actor_uid: str,
+    ) -> dict[str, Any]:
+        record = {
+            "uid": str(uuid.uuid4()),
+            "incident_uid": _uid(incident_uid, "incident_uid"),
+            "external_ref": _safe_string(external_ref, "external_ref", 120),
+            "idempotency_key": _safe_string(idempotency_key, "idempotency_key", 160),
+        }
+        record["payload_digest"] = _digest(
+            {
+                key: record[key]
+                for key in ("incident_uid", "external_ref", "idempotency_key")
+            }
+        )
+        try:
+            stored = self.repository.bind_itsm_reference(record)
+        except RuntimeError as error:
+            if str(error) in {"idempotency conflict", "ITSM reference conflict"}:
+                raise ProductionOperationsError(str(error)) from error
+            raise
+        if stored["uid"] == record["uid"]:
+            self._audit(
+                "itsm_reference_bound",
+                delivery_uid=None,
+                actor_uid=actor_uid,
+                detail={"external_ref": stored["external_ref"]},
+            )
+        return dict(stored)
+
+    def itsm_reference_for_incident(self, incident_uid: str) -> dict[str, Any]:
+        result = self.repository.itsm_reference_for_incident(
+            _uid(incident_uid, "incident_uid")
+        )
+        if result is None:
+            raise ProductionOperationsError("ITSM reference is not found")
+        return dict(result)
+
+    def platform_incident_for_itsm(self, external_ref: str) -> str:
+        result = self.repository.platform_incident_for_itsm(
+            _safe_string(external_ref, "external_ref", 120)
+        )
+        if result is None:
+            raise ProductionOperationsError("ITSM reference is not found")
+        return result
+
+
+def run_local_fault_injection(
+    service: ProductionOperationsService,
+    *,
+    kind: str,
+    incident_uid: str,
+    alert_uid: str,
+    actor_uid: str,
+) -> dict[str, Any]:
+    kind = _safe_string(kind, "fault kind", 40)
+    if kind not in {
+        "collection_failed",
+        "dependency_slow",
+        "capacity_exhausted",
+        "recovered",
+    }:
+        raise ProductionOperationsError("fault kind is invalid")
+    record = {
+        "fault_kind": kind,
+        "incident_uid": _uid(incident_uid, "incident_uid"),
+        "alert_uid": _uid(alert_uid, "alert_uid"),
+        "correlation_id": str(uuid.uuid4()),
+        "recovery_candidate": True,
+    }
+    service._audit(
+        "fault_injected",
+        delivery_uid=None,
+        actor_uid=actor_uid,
+        detail={"fault_kind": kind, "correlation_id": record["correlation_id"]},
+    )
+    return record

+ 262 - 0
app/core/events/production_operations_repository.py

@@ -0,0 +1,262 @@
+"""PostgreSQL persistence for P3 operations delivery; no provider payload bodies."""
+
+from __future__ import annotations
+
+import json
+
+from sqlalchemy import text
+
+
+class SqlAlchemyProductionOperationsRepository:
+    def __init__(self, session):
+        self.session = session
+
+    def _row(self, value):
+        if value is None:
+            return None
+        return {
+            key: (
+                item.isoformat()
+                if hasattr(item, "isoformat")
+                else str(item)
+                if hasattr(item, "hex")
+                else item
+            )
+            for key, item in dict(value).items()
+        }
+
+    def enqueue_delivery(self, record, *, queue_hard_limit):
+        # A transaction-scoped advisory lock makes the count-and-insert admission
+        # check serializable across independent workers without a mutable counter.
+        self.session.execute(text("SELECT pg_advisory_xact_lock(48005)"))
+        existing = (
+            self.session.execute(
+                text(
+                    "SELECT * FROM public.production_observability_deliveries WHERE idempotency_key=:key"
+                ),
+                {"key": record["idempotency_key"]},
+            )
+            .mappings()
+            .one_or_none()
+        )
+        if existing:
+            if existing["payload_digest"] != record["payload_digest"]:
+                raise RuntimeError("idempotency conflict")
+            return self._row(existing)
+        occupied = self.session.execute(
+            text(
+                "SELECT count(*) FROM public.production_observability_deliveries "
+                "WHERE status IN ('pending','processing')"
+            )
+        ).scalar_one()
+        if occupied >= queue_hard_limit:
+            raise RuntimeError("delivery queue hard limit reached")
+        self.session.execute(
+            text(
+                """INSERT INTO public.production_observability_deliveries (uid,incident_uid,alert_uid,channel,idempotency_key,payload_digest,status,attempt_count,next_attempt_at,lease_fence,created_at,updated_at) VALUES (CAST(:uid AS uuid),CAST(:incident_uid AS uuid),CAST(:alert_uid AS uuid),:channel,:idempotency_key,:payload_digest,'pending',0,clock_timestamp(),0,clock_timestamp(),clock_timestamp())"""
+            ),
+            record,
+        )
+        return self._row(
+            self.session.execute(
+                text(
+                    "SELECT * FROM public.production_observability_deliveries WHERE uid=CAST(:uid AS uuid)"
+                ),
+                record,
+            )
+            .mappings()
+            .one()
+        )
+
+    def append_audit(self, record):
+        self.session.execute(
+            text(
+                "SELECT public.record_production_observability_audit("
+                "CAST(:uid AS uuid),CAST(:delivery_uid AS uuid),:action,"
+                "CAST(:actor_uid AS uuid),CAST(:safe_detail AS jsonb))"
+            ),
+            {**record, "safe_detail": json.dumps(record["safe_detail"])},
+        )
+
+    def append_rejection_audit_independently(self, record):
+        """Persist a safe rejection receipt outside a caller's rollback scope."""
+        get_bind = getattr(self.session, "get_bind", None)
+        bind = get_bind() if get_bind is not None else self.session
+        engine = getattr(bind, "engine", bind)
+        with engine.begin() as connection:
+            connection.execute(
+                text(
+                    "SELECT public.record_production_observability_audit("
+                    "CAST(:uid AS uuid),NULL,:action,CAST(:actor_uid AS uuid),"
+                    "CAST(:safe_detail AS jsonb))"
+                ),
+                {**record, "safe_detail": json.dumps(record["safe_detail"])},
+            )
+
+    def claim_delivery(self, uid, worker, now):
+        return self._row(
+            self.session.execute(
+                text(
+                    """UPDATE public.production_observability_deliveries SET status='processing',lease_owner=:worker,lease_fence=lease_fence+1,lease_expires_at=clock_timestamp()+interval '300 seconds',updated_at=clock_timestamp() WHERE uid=CAST(:uid AS uuid) AND ((status='pending' AND next_attempt_at<=clock_timestamp()) OR (status='processing' AND lease_expires_at<=clock_timestamp())) RETURNING *"""
+                ),
+                {"uid": uid, "worker": worker},
+            )
+            .mappings()
+            .one_or_none()
+        )
+
+    def record_attempt(self, uid, worker, fence, now, delivered, reason):
+        if delivered:
+            sql = """UPDATE public.production_observability_deliveries SET status='delivered',lease_owner=NULL,lease_expires_at=NULL,updated_at=clock_timestamp() WHERE uid=CAST(:uid AS uuid) AND status='processing' AND lease_owner=:worker AND lease_fence=:fence AND lease_expires_at>clock_timestamp() RETURNING *"""
+        else:
+            sql = """UPDATE public.production_observability_deliveries SET attempt_count=attempt_count+1,lease_owner=NULL,lease_expires_at=NULL,status=CASE WHEN attempt_count+1>=3 THEN 'dead_letter' ELSE 'pending' END,next_attempt_at=clock_timestamp()+(LEAST(3600,60*(attempt_count+1))*interval '1 second'),last_reason_code=:reason,updated_at=clock_timestamp() WHERE uid=CAST(:uid AS uuid) AND status='processing' AND lease_owner=:worker AND lease_fence=:fence AND lease_expires_at>clock_timestamp() RETURNING *"""
+        return self._row(
+            self.session.execute(
+                text(sql),
+                {"uid": uid, "worker": worker, "fence": fence, "reason": reason},
+            )
+            .mappings()
+            .one_or_none()
+        )
+
+    def compensate_dead_letter(self, uid, reason_code, receipt_code):
+        existing = self.session.execute(
+            text(
+                "SELECT * FROM public.production_observability_deliveries "
+                "WHERE uid=CAST(:uid AS uuid) FOR UPDATE"
+            ),
+            {"uid": uid},
+        ).mappings().one_or_none()
+        if existing is None or existing["status"] not in {"dead_letter", "compensated"}:
+            raise RuntimeError("delivery is not compensable")
+        if existing["status"] == "compensated":
+            if (
+                existing["compensation_reason_code"] == reason_code
+                and existing["compensation_receipt_code"] == receipt_code
+            ):
+                return self._row(existing)
+            raise RuntimeError("compensation conflict")
+        return self._row(
+            self.session.execute(
+                text(
+                    "UPDATE public.production_observability_deliveries SET "
+                    "status='compensated',compensation_reason_code=:reason_code,"
+                    "compensation_receipt_code=:receipt_code,updated_at=clock_timestamp() "
+                    "WHERE uid=CAST(:uid AS uuid) AND status='dead_letter' RETURNING *"
+                ),
+                {"uid": uid, "reason_code": reason_code, "receipt_code": receipt_code},
+            ).mappings().one()
+        )
+
+    def save_policy(self, record):
+        self.session.execute(
+            text(
+                """INSERT INTO public.production_observability_policies (uid,code,version,layer,error_budget_ratio,status,created_by,created_at) VALUES (CAST(:uid AS uuid),:code,:version,:layer,:error_budget_ratio,:status,CAST(:created_by AS uuid),clock_timestamp())"""
+            ),
+            record,
+        )
+        return record
+
+    def activate_policy(self, code, version):
+        self.session.execute(
+            text(
+                "UPDATE public.production_observability_policies SET status='retired' WHERE code=:code AND status='active'"
+            ),
+            {"code": code},
+        )
+        return self._row(
+            self.session.execute(
+                text(
+                    "UPDATE public.production_observability_policies SET status='active' WHERE code=:code AND version=:version RETURNING *"
+                ),
+                {"code": code, "version": version},
+            )
+            .mappings()
+            .one()
+        )
+
+    def active_policy(self, code):
+        return self._row(
+            self.session.execute(
+                text(
+                    "SELECT * FROM public.production_observability_policies "
+                    "WHERE code=:code AND status='active'"
+                ),
+                {"code": code},
+            )
+            .mappings()
+            .one_or_none()
+        )
+
+    def list_deliveries(self, incident_uid):
+        return [
+            self._row(row)
+            for row in self.session.execute(
+                text(
+                    "SELECT * FROM public.production_observability_deliveries "
+                    "WHERE incident_uid=CAST(:incident_uid AS uuid) ORDER BY created_at,uid"
+                ),
+                {"incident_uid": incident_uid},
+            ).mappings()
+        ]
+
+    def bind_itsm_reference(self, record):
+        existing = self.session.execute(
+            text(
+                "SELECT * FROM public.production_observability_itsm_references "
+                "WHERE idempotency_key=:key"
+            ),
+            {"key": record["idempotency_key"]},
+        ).mappings().one_or_none()
+        if existing:
+            if existing["payload_digest"] != record["payload_digest"]:
+                raise RuntimeError("idempotency conflict")
+            return self._row(existing)
+        collision = self.session.execute(
+            text(
+                "SELECT 1 FROM public.production_observability_itsm_references "
+                "WHERE incident_uid=CAST(:incident_uid AS uuid) OR external_ref=:external_ref"
+            ),
+            record,
+        ).scalar_one_or_none()
+        if collision:
+            raise RuntimeError("ITSM reference conflict")
+        self.session.execute(
+            text(
+                "INSERT INTO public.production_observability_itsm_references "
+                "(uid,incident_uid,external_ref,idempotency_key,payload_digest,created_at) "
+                "VALUES (CAST(:uid AS uuid),CAST(:incident_uid AS uuid),:external_ref,"
+                ":idempotency_key,:payload_digest,clock_timestamp())"
+            ),
+            record,
+        )
+        return self._row(
+            self.session.execute(
+                text(
+                    "SELECT * FROM public.production_observability_itsm_references "
+                    "WHERE uid=CAST(:uid AS uuid)"
+                ),
+                record,
+            ).mappings().one()
+        )
+
+    def itsm_reference_for_incident(self, incident_uid):
+        return self._row(
+            self.session.execute(
+                text(
+                    "SELECT * FROM public.production_observability_itsm_references "
+                    "WHERE incident_uid=CAST(:incident_uid AS uuid)"
+                ),
+                {"incident_uid": incident_uid},
+            ).mappings().one_or_none()
+        )
+
+    def platform_incident_for_itsm(self, external_ref):
+        row = self.session.execute(
+            text(
+                "SELECT incident_uid FROM public.production_observability_itsm_references "
+                "WHERE external_ref=:external_ref"
+            ),
+            {"external_ref": external_ref},
+        ).mappings().one_or_none()
+        return None if row is None else str(row["incident_uid"])

+ 901 - 0
app/core/governance/bi_ai_asset_catalog.py

@@ -0,0 +1,901 @@
+"""Closed, read-only BI/AI asset catalog engineering baseline.
+
+This module deliberately supports only the local fixture provider until an
+enterprise provider, API scope and service identity have been approved.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import json
+import os
+import re
+import uuid
+from datetime import UTC, datetime
+from typing import Any
+
+from sqlalchemy import create_engine, text
+
+ASSET_TYPES = frozenset(
+    {
+        "dataset",
+        "report",
+        "dashboard",
+        "model",
+        "prompt",
+        "agent",
+        "knowledge_base",
+        "index",
+        "evaluation_set",
+    }
+)
+CLASSIFICATIONS = frozenset({"public", "internal", "confidential", "restricted"})
+CAPABILITIES = frozenset({"assets.read", "dependencies.read"})
+_TOKEN = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.-]{0,119}")
+_WORDS = re.compile(r"[A-Za-z0-9]{2,80}")
+_FORBIDDEN_TEXT = re.compile(
+    r"(?i)(?:https?://|://|\bselect\b|\binsert\b|\bupdate\b|\bdelete\b|\bscript\b|\btoken\b|\bpassword\b|\bsecret\b|api[_ -]?key)"
+)
+
+
+def _closed(value: Any, allowed: set[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise ValueError(f"{label} must be an object")
+    unknown = sorted(set(value) - allowed)
+    if unknown:
+        raise ValueError(f"unsupported {label} fields: {', '.join(unknown)}")
+    return copy.deepcopy(value)
+
+
+def _text(value: Any, label: str, maximum: int = 240) -> str:
+    if not isinstance(value, str) or not value.strip():
+        raise ValueError(f"{label} is required")
+    result = value.strip()
+    if len(result) > maximum or _FORBIDDEN_TEXT.search(result):
+        raise ValueError(f"{label} is unsafe or exceeds {maximum} characters")
+    return result
+
+
+def _token(value: Any, label: str) -> str:
+    result = _text(value, label, 120)
+    if not _TOKEN.fullmatch(result):
+        raise ValueError(f"{label} is invalid")
+    return result
+
+
+def _string_list(value: Any, label: str, maximum: int = 32) -> list[str]:
+    if not isinstance(value, list) or len(value) > maximum:
+        raise ValueError(f"{label} must be a bounded list")
+    return sorted({_token(item, label) for item in value})
+
+
+def _digest(value: dict[str, Any]) -> str:
+    return hashlib.sha256(
+        json.dumps(
+            value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
+        ).encode()
+    ).hexdigest()
+
+
+class ProviderRegistry:
+    """Resolve an allowlisted, versioned provider without any network configuration."""
+
+    _LOCAL = {
+        "provider_id": "local-fixture-v1",
+        "provider_version": "1",
+        "mode": "ENGINEERING_EVIDENCE_ONLY",
+        "origin": "local-fixture://wp11/v1",
+        "timeout_seconds": 3,
+        "page_size": 50,
+        "rate_limit_per_minute": 20,
+        "max_pages": 4,
+        "capabilities": ["assets.read", "dependencies.read"],
+    }
+
+    def resolve(
+        self,
+        provider_id: str,
+        provider_version: str,
+        *,
+        engineering_evidence_only: bool,
+    ) -> dict[str, Any]:
+        if not engineering_evidence_only:
+            raise PermissionError("engineering evidence only")
+        if (provider_id, provider_version) != ("local-fixture-v1", "1"):
+            raise PermissionError("provider disabled or unsupported")
+        return copy.deepcopy(self._LOCAL)
+
+
+def normalize_asset(
+    value: Any,
+    *,
+    source: str,
+    platform: str,
+    provider_version: str,
+    observed_at: str | None = None,
+) -> dict[str, Any]:
+    body = _closed(
+        value,
+        {
+            "external_uid",
+            "asset_type",
+            "name",
+            "owner",
+            "domain",
+            "tags",
+            "classification",
+            "permissions",
+            "dependencies",
+        },
+        "asset",
+    )
+    asset_type = _token(body.get("asset_type"), "asset_type")
+    if asset_type not in ASSET_TYPES:
+        raise ValueError("unsupported asset type")
+    classification = _token(body.get("classification"), "classification")
+    if classification not in CLASSIFICATIONS:
+        raise ValueError("unsupported classification")
+    normalized = {
+        "source": _token(source, "source"),
+        "platform": _token(platform, "platform"),
+        "provider_version": _token(provider_version, "provider_version"),
+        "external_uid": _token(body.get("external_uid"), "external_uid"),
+        "asset_type": asset_type,
+        "name": _text(body.get("name"), "name"),
+        "owner": _token(body.get("owner"), "owner"),
+        "domain": _token(body.get("domain"), "domain"),
+        "tags": _string_list(body.get("tags", []), "tags"),
+        "classification": classification,
+        "permissions": _string_list(body.get("permissions", []), "permissions"),
+        "dependencies": _string_list(body.get("dependencies", []), "dependencies"),
+        "raw_payload_retained": False,
+    }
+    normalized["digest"] = _digest(
+        {key: value for key, value in normalized.items() if key != "digest"}
+    )
+    normalized["observed_at"] = observed_at or datetime.now(UTC).isoformat().replace(
+        "+00:00", "Z"
+    )
+    return normalized
+
+
+def normalize_search(query: Any, filters: Any) -> dict[str, Any]:
+    if not isinstance(query, str) or len(query) > 500:
+        raise ValueError("query must be a bounded string")
+    allowed = {"asset_type", "tag", "classification"}
+    normalized_filters = _closed(filters, allowed, "search filters")
+    for key, value in tuple(normalized_filters.items()):
+        normalized_filters[key] = _token(value, key)
+    if (
+        "asset_type" in normalized_filters
+        and normalized_filters["asset_type"] not in ASSET_TYPES
+    ):
+        raise ValueError("unsupported asset type")
+    if (
+        "classification" in normalized_filters
+        and normalized_filters["classification"] not in CLASSIFICATIONS
+    ):
+        raise ValueError("unsupported classification")
+    return {
+        "keywords": [word.lower() for word in _WORDS.findall(query)][:12],
+        "filters": normalized_filters,
+        "sql": None,
+    }
+
+
+def local_fixture_assets() -> list[dict[str, Any]]:
+    """Controlled, content-free sample covering BI and AI catalog types."""
+    owner, domain, visible = "local-owner", "default", ["viewer"]
+    return [
+        {
+            "external_uid": "dataset-sales",
+            "asset_type": "dataset",
+            "name": "Sales facts",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["sales"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": [],
+        },
+        {
+            "external_uid": "report-sales",
+            "asset_type": "report",
+            "name": "Sales report",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["sales"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": ["dataset-sales"],
+        },
+        {
+            "external_uid": "dashboard-sales",
+            "asset_type": "dashboard",
+            "name": "Sales dashboard",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["sales"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": ["report-sales"],
+        },
+        {
+            "external_uid": "model-sales",
+            "asset_type": "model",
+            "name": "Sales model",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["forecast"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": ["dataset-sales"],
+        },
+        {
+            "external_uid": "prompt-sales",
+            "asset_type": "prompt",
+            "name": "Sales summary prompt",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["prompt"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": [],
+        },
+        {
+            "external_uid": "agent-sales",
+            "asset_type": "agent",
+            "name": "Sales assistant",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["agent"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": ["model-sales", "prompt-sales"],
+        },
+        {
+            "external_uid": "knowledge-sales",
+            "asset_type": "knowledge_base",
+            "name": "Sales knowledge",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["knowledge"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": [],
+        },
+        {
+            "external_uid": "index-sales",
+            "asset_type": "index",
+            "name": "Sales index",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["index"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": ["knowledge-sales"],
+        },
+        {
+            "external_uid": "evaluation-sales",
+            "asset_type": "evaluation_set",
+            "name": "Sales evaluation",
+            "owner": owner,
+            "domain": domain,
+            "tags": ["evaluation"],
+            "classification": "internal",
+            "permissions": visible,
+            "dependencies": ["agent-sales"],
+        },
+    ]
+
+
+class CatalogControlIssuer:
+    """Issue one-time catalog claims through the separate control connection."""
+
+    def __init__(self, engine):
+        self.engine = engine
+
+    def issue(self, action: str, principal: str, body: dict[str, Any]) -> str:
+        with self.engine.begin() as control:
+            claim = control.execute(
+                text("SELECT public.bi_ai_catalog_issue_request_claim(:action,:principal,CAST(:body AS jsonb))"),
+                {"action": action, "principal": principal, "body": json.dumps(body, separators=(",", ":"))},
+            ).scalar_one()
+        return str(claim["request_claim"])
+
+    @classmethod
+    def from_environment(cls) -> CatalogControlIssuer:
+        from app.config.database_urls import validate_postgresql_url
+
+        url = os.environ.get("BI_AI_CATALOG_CONTROL_DATABASE_URL", "")
+        parts = validate_postgresql_url(url, "BI_AI_CATALOG_CONTROL_DATABASE_URL")
+        if parts.username != "dataops_bi_ai_catalog_control":
+            raise PermissionError("catalog control issuer is not configured")
+        return cls(create_engine(url, pool_pre_ping=True))
+
+
+class SqlAlchemyCatalogRepository:
+    """Persist only normalized projections; SQL rows/payloads are never retained."""
+
+    def __init__(self, session, control_issuer: CatalogControlIssuer | None = None):
+        self.session = session
+        self.control_issuer = control_issuer
+
+    @staticmethod
+    def _plain(row):
+        if row is None:
+            return None
+        result = dict(row)
+        for key, value in tuple(result.items()):
+            if isinstance(value, datetime):
+                result[key] = value.isoformat()
+            elif key in {"tags", "permissions", "dependencies"} and value is None:
+                result[key] = []
+        result["digest"] = result.pop("content_digest")
+        result["deleted_candidate"] = bool(result["deleted_candidate"])
+        result["tombstoned"] = result.pop("tombstoned_at") is not None
+        return result
+
+    def register_local_fixture(self):
+        # Tenant sources are inserted by the only runtime gateway after a
+        # persisted grant is checked.  Legacy registration is intentionally
+        # not callable from application code.
+        ProviderRegistry().resolve(
+            "local-fixture-v1", "1", engineering_evidence_only=True
+        )
+
+    def runtime_write(self, action: str, payload: dict[str, Any]) -> dict[str, Any]:
+        payload = self._claimed_payload(action, payload)
+        row = (
+            self.session.execute(
+                text(
+                    "SELECT public.bi_ai_catalog_runtime_write(:action,CAST(:payload AS jsonb)) AS result"
+                ),
+                {
+                    "action": action,
+                    "payload": json.dumps(payload, separators=(",", ":")),
+                },
+            )
+            .mappings()
+            .one()
+        )
+        return dict(row["result"])
+
+    def runtime_read(self, action: str, payload: dict[str, Any]) -> dict[str, Any]:
+        payload = self._claimed_payload(action, payload)
+        row = self.session.execute(
+            text(
+                "SELECT public.bi_ai_catalog_runtime_read(:action,CAST(:payload AS jsonb)) AS result"
+            ),
+            {"action": action, "payload": json.dumps(payload, separators=(",", ":"))},
+        ).mappings().one()
+        return dict(row["result"] or {})
+
+    def _claimed_payload(self, action: str, payload: dict[str, Any]) -> dict[str, Any]:
+        """Have the issuer-only DB identity bind a server-derived scope once."""
+        principal = payload.get("principal_ref")
+        if not isinstance(principal, str):
+            raise PermissionError("catalog request claim denied")
+        body = {
+            key: value
+            for key, value in payload.items()
+            if key not in {"principal_ref", "tenant_ref", "domain_ref", "request_claim"}
+        }
+        if self.control_issuer is None:
+            raise PermissionError("catalog control issuer is not configured")
+        return body | {"request_claim": self.control_issuer.issue(action, principal, body)}
+
+    def persist_local_fixture(
+        self, principal_ref: str, assets: list[dict[str, Any]]
+    ) -> dict[str, Any]:
+        lease_token = str(uuid.uuid4())
+        claim = self.runtime_write(
+            "claim",
+            {
+                "source": "local-fixture-v1",
+                "principal_ref": principal_ref,
+                "lease_owner": principal_ref,
+                "lease_token": lease_token,
+            },
+        )
+        projected = []
+        for asset in assets:
+            normalized = normalize_asset(
+                asset,
+                source="local-fixture-v1",
+                platform="local-fixture",
+                provider_version="1",
+            )
+            projected.append(
+                {
+                    key: normalized[key]
+                    for key in (
+                        "external_uid",
+                        "asset_type",
+                        "name",
+                        "owner",
+                        "domain",
+                        "tags",
+                        "classification",
+                        "permissions",
+                        "dependencies",
+                        "digest",
+                        "observed_at",
+                    )
+                }
+            )
+        return self.runtime_write(
+            "persist_page",
+            {
+                "source": "local-fixture-v1",
+                "principal_ref": principal_ref,
+                "lease_owner": principal_ref,
+                "lease_token": lease_token,
+                "lease_fence": claim["lease_fence"],
+                # A checkpoint is an acknowledged page boundary, not a lease
+                # token; each controlled fixture run advances a bounded token.
+                "cursor": f"fixture-page-{uuid.uuid4()}",
+                "assets": projected,
+                "page_digest": _digest({"assets": projected}),
+                "operation_id": str(uuid.uuid4()),
+                "final_page": True,
+            },
+        )
+
+    def observe_scan(
+        self, principal_ref: str, scan_id: str, observed_uids: list[str]
+    ) -> dict[str, Any]:
+        values = sorted({_token(item, "observed_uid") for item in observed_uids})
+        status = self.runtime_read("scan_status", {"principal_ref": principal_ref})
+        completed = status.get("scan_complete") and status.get("scan_id") == scan_id
+        if completed:
+            return {"replay": True}
+        generation = f"generation-{_token(scan_id, 'scan_id')}"
+        base = {
+            "source": "local-fixture-v1",
+            "principal_ref": principal_ref,
+            "scan_id": scan_id,
+        }
+        self.runtime_write(
+            "begin_scan",
+            base
+            | {
+                "generation_ref": generation,
+                "expected_pages": 1,
+            },
+        )
+        self.runtime_write(
+            "scan_page",
+            base
+            | {
+                "page_number": 1,
+                "observed_uids": values,
+                "page_digest": _digest({"scan_id": scan_id, "observed_uids": values}),
+            },
+        )
+        return self.runtime_write(
+            "finalize_scan",
+            base | {"final_cursor": f"final-{_token(scan_id, 'scan_id')}"},
+        )
+
+    def record_runtime_failure(
+        self, principal_ref: str, fence: int, error_code: str, payload_digest: str
+    ) -> dict[str, Any]:
+        return self.runtime_write(
+            "record_error",
+            {
+                "source": "local-fixture-v1",
+                "principal_ref": principal_ref,
+                "lease_fence": fence,
+                "error_code": _token(error_code, "error_code"),
+                "payload_digest": payload_digest,
+            },
+        )
+
+    def recover_runtime_failure(
+        self, principal_ref: str, error_code: str
+    ) -> dict[str, Any]:
+        return self.runtime_write(
+            "recover",
+            {
+                "source": "local-fixture-v1",
+                "principal_ref": principal_ref,
+                "error_code": _token(error_code, "error_code"),
+            },
+        )
+
+    def asset(
+        self,
+        source: str,
+        external_uid: str,
+        *,
+        tenant_ref: str,
+        domain_ref: str,
+    ):
+        raise RuntimeError("catalog direct read is sealed; use authorized gateway")
+
+    def all_assets(self, source: str, *, tenant_ref: str, domain_ref: str):
+        raise RuntimeError("catalog direct read is sealed; use authorized gateway")
+
+    def search_authorized(self, principal_ref: str, normalized: dict[str, Any]):
+        # Runtime roles have no table SELECT.  Principal is the sole read
+        # authorization root; tenant/domain/role come from its persisted grant.
+        return self.runtime_read(
+            "search",
+            {
+                "principal_ref": principal_ref,
+                "keywords": normalized["keywords"],
+                "filters": normalized["filters"],
+            },
+        ) | {"query": normalized}
+
+    def impact_authorized(self, principal_ref: str, external_uid: str, max_depth: int, max_nodes: int):
+        _token(external_uid, "external_uid")
+        return self.runtime_read(
+            "impact",
+            {
+                "principal_ref": principal_ref,
+                "external_uid": external_uid,
+                "max_depth": max_depth,
+                "max_nodes": max_nodes,
+            },
+        ) | {"source": external_uid, "max_depth": max_depth, "max_nodes": max_nodes}
+
+    def save_asset(self, asset: dict[str, Any]) -> None:
+        raise RuntimeError("catalog direct DML is sealed; use runtime gateway")
+
+    def missing_count(self, source: str, external_uid: str) -> int:
+        raise RuntimeError("catalog direct read requires server-derived scope")
+
+    def set_missing_count(self, source: str, external_uid: str, count: int) -> None:
+        return None
+
+    def record_audit(self, event_type: str, asset: dict[str, Any]) -> None:
+        raise RuntimeError("catalog direct audit is sealed; use runtime gateway")
+
+    def record_failure(
+        self, source: str, error_code: str, payload_digest: str, fence: int
+    ):
+        raise RuntimeError("catalog direct error write is sealed; use runtime gateway")
+
+    def recover_failure(self, source: str, error_code: str):
+        raise RuntimeError("catalog direct recovery is sealed; use runtime gateway")
+
+
+class CatalogApplicationService:
+    """HTTP-facing façade: principal scope is derived server-side, never body supplied."""
+
+    def __init__(self, session, control_issuer: CatalogControlIssuer | None = None):
+        self.session = session
+        self.repository = SqlAlchemyCatalogRepository(
+            session, control_issuer or CatalogControlIssuer.from_environment()
+        )
+        self.catalog = AssetCatalogService(self.repository)
+
+    def sync_local_fixture(self, *, principal_id: str, body: dict[str, Any]):
+        if body:
+            raise ValueError("catalog request rejected")
+        summary = self.repository.persist_local_fixture(
+            principal_id, local_fixture_assets()
+        )
+        self.session.commit()
+        return {
+            "source": "local-fixture-v1",
+            "mode": "ENGINEERING_EVIDENCE_ONLY",
+            "persisted_before_ack": True,
+            "summary": summary,
+        }
+
+    def search_for_principal(
+        self, *, principal_id: str, query: str, filters: dict[str, Any]
+    ):
+        return self.repository.search_authorized(
+            principal_id, normalize_search(query, filters)
+        )
+
+    def impact_for_principal(
+        self, *, principal_id: str, external_uid: str, max_depth: int, max_nodes: int
+    ):
+        return self.repository.impact_authorized(
+            principal_id, external_uid, max_depth, max_nodes
+        )
+
+    def audit_for_principal(self, *, principal_id: str):
+        # The security-definer gateway resolves the persisted principal grant
+        # before joining audit rows to visible assets.
+        return self.repository.runtime_read("audit", {"principal_ref": principal_id})
+
+
+class InMemoryCatalogRepository:
+    """Small deterministic repository used only by unit tests and local fixtures."""
+
+    def __init__(self):
+        self._assets: dict[tuple[str, str], dict[str, Any]] = {}
+        self._scans: dict[tuple[str, str], int] = {}
+        self._errors: dict[tuple[str, str, str], dict[str, Any]] = {}
+        self.audit: list[dict[str, Any]] = []
+
+    def asset(self, source: str, external_uid: str) -> dict[str, Any] | None:
+        row = self._assets.get((source, external_uid))
+        return copy.deepcopy(row) if row else None
+
+    def all_assets(self, source: str) -> list[dict[str, Any]]:
+        return [
+            copy.deepcopy(row)
+            for (item_source, _), row in self._assets.items()
+            if item_source == source
+        ]
+
+    def save_asset(self, asset: dict[str, Any]) -> None:
+        self._assets[(asset["source"], asset["external_uid"])] = copy.deepcopy(asset)
+
+    def missing_count(self, source: str, external_uid: str) -> int:
+        return self._scans.get((source, external_uid), 0)
+
+    def set_missing_count(self, source: str, external_uid: str, count: int) -> None:
+        self._scans[(source, external_uid)] = count
+
+    def record_audit(self, event_type: str, asset: dict[str, Any]) -> None:
+        self.audit.append(
+            {
+                "event_type": event_type,
+                "source": asset["source"],
+                "external_uid": asset["external_uid"],
+                "digest": asset["digest"],
+            }
+        )
+
+    def record_failure(
+        self, source: str, error_code: str, payload_digest: str, fence: int
+    ):
+        key = (source, error_code, payload_digest)
+        previous = self._errors.get(key, {"attempts": 0})
+        attempts = previous["attempts"] + 1
+        record = {
+            "source": source,
+            "error_code": error_code,
+            "payload_digest": payload_digest,
+            "lease_fence": fence,
+            "attempts": attempts,
+            "status": "dead_letter" if attempts >= 5 else "retryable",
+        }
+        self._errors[key] = record
+        return copy.deepcopy(record)
+
+    def recover_failure(self, source: str, error_code: str):
+        candidates = [
+            record
+            for (item_source, item_code, _), record in self._errors.items()
+            if item_source == source
+            and item_code == error_code
+            and record["status"] == "dead_letter"
+        ]
+        if not candidates:
+            raise LookupError("dead letter unavailable")
+        record = candidates[-1]
+        record["status"] = "recovered"
+        return copy.deepcopy(record)
+
+
+class AssetCatalogService:
+    """Persist normalized metadata, then advance only the caller's cursor/ack."""
+
+    def __init__(self, repository, *, registry: ProviderRegistry | None = None):
+        self.repository = repository
+        self.registry = registry or ProviderRegistry()
+
+    def ingest_page(
+        self,
+        provider_id: str,
+        provider_version: str,
+        assets: list[dict[str, Any]],
+        *,
+        cursor: str,
+        fence: int,
+    ) -> dict[str, int]:
+        if not isinstance(fence, int) or fence < 1 or not _TOKEN.fullmatch(str(cursor)):
+            raise ValueError("invalid cursor or fence")
+        manifest = self.registry.resolve(
+            provider_id, provider_version, engineering_evidence_only=True
+        )
+        results = {"created": 0, "unchanged": 0, "changed": 0, "restored": 0}
+        if not isinstance(assets, list) or len(assets) > manifest["page_size"]:
+            raise ValueError("asset page exceeds configured bound")
+        for item in assets:
+            normalized = normalize_asset(
+                item,
+                source=provider_id,
+                platform="local-fixture",
+                provider_version=provider_version,
+            )
+            saved = self.repository.asset(provider_id, normalized["external_uid"])
+            if saved is None:
+                normalized["version"] = 1
+                normalized["deleted_candidate"] = False
+                normalized["tombstoned"] = False
+                results["created"] += 1
+                self.repository.record_audit("catalog.asset.created", normalized)
+            elif saved["digest"] == normalized["digest"] and not saved.get(
+                "tombstoned"
+            ):
+                normalized.update(
+                    {
+                        "version": saved["version"],
+                        "deleted_candidate": False,
+                        "tombstoned": False,
+                    }
+                )
+                results["unchanged"] += 1
+            else:
+                normalized.update(
+                    {
+                        "version": saved["version"]
+                        + (0 if saved["digest"] == normalized["digest"] else 1),
+                        "deleted_candidate": False,
+                        "tombstoned": False,
+                    }
+                )
+                if saved.get("tombstoned"):
+                    results["restored"] += 1
+                    self.repository.record_audit("catalog.asset.restored", normalized)
+                else:
+                    results["changed"] += 1
+                    self.repository.record_audit("catalog.asset.changed", normalized)
+            self.repository.set_missing_count(
+                provider_id, normalized["external_uid"], 0
+            )
+            self.repository.save_asset(normalized)
+        return results
+
+    def confirm_missing(
+        self, source: str, observed_external_uids: set[str], *, scan_id: str
+    ) -> dict[str, int]:
+        _token(source, "source")
+        _token(scan_id, "scan_id")
+        if not isinstance(observed_external_uids, set) or not all(
+            isinstance(item, str) for item in observed_external_uids
+        ):
+            raise ValueError("observed external uids must be a set")
+        result = {"candidates": 0, "tombstoned": 0}
+        for asset in self.repository.all_assets(source):
+            if asset["external_uid"] in observed_external_uids or asset.get(
+                "tombstoned"
+            ):
+                continue
+            count = self.repository.missing_count(source, asset["external_uid"]) + 1
+            self.repository.set_missing_count(source, asset["external_uid"], count)
+            asset["deleted_candidate"] = True
+            if count >= 2:
+                asset["tombstoned"] = True
+                result["tombstoned"] += 1
+                self.repository.record_audit("catalog.asset.tombstoned", asset)
+            else:
+                result["candidates"] += 1
+                self.repository.record_audit("catalog.asset.deletion_candidate", asset)
+            self.repository.save_asset(asset)
+        return result
+
+    def record_failure(
+        self, source: str, *, fence: int, error_code: str, payload_digest: str
+    ) -> dict[str, Any]:
+        _token(source, "source")
+        _token(error_code, "error_code")
+        if (
+            not isinstance(fence, int)
+            or fence < 1
+            or not re.fullmatch(r"[0-9a-f]{64}", payload_digest)
+        ):
+            raise ValueError("catalog failure is invalid")
+        if not hasattr(self.repository, "record_failure"):
+            raise RuntimeError("catalog failure store unavailable")
+        return self.repository.record_failure(source, error_code, payload_digest, fence)
+
+    def recover_failure(self, source: str, *, error_code: str) -> dict[str, Any]:
+        _token(source, "source")
+        _token(error_code, "error_code")
+        if not hasattr(self.repository, "recover_failure"):
+            raise RuntimeError("catalog recovery store unavailable")
+        return self.repository.recover_failure(source, error_code)
+
+    def _visible(
+        self, asset: dict[str, Any], principal_roles: set[str], domain: str
+    ) -> bool:
+        if "viewer" not in principal_roles and "admin" not in principal_roles:
+            raise PermissionError("catalog read denied")
+        return asset["domain"] == domain and "viewer" in asset["permissions"]
+
+    def search(
+        self,
+        query: str,
+        filters: dict[str, Any],
+        *,
+        principal_roles: set[str],
+        domain: str,
+    ) -> list[dict[str, Any]]:
+        request = normalize_search(query, filters)
+        results = []
+        for asset in self.repository.all_assets("local-fixture-v1"):
+            if asset.get("tombstoned") or not self._visible(
+                asset, principal_roles, domain
+            ):
+                continue
+            haystack = " ".join([asset["name"], *asset["tags"]]).lower()
+            if request["keywords"] and not all(
+                word in haystack for word in request["keywords"]
+            ):
+                continue
+            if any(
+                asset.get(key) != value and value not in asset.get(key + "s", [])
+                for key, value in request["filters"].items()
+            ):
+                continue
+            results.append(
+                {
+                    key: asset[key]
+                    for key in (
+                        "external_uid",
+                        "asset_type",
+                        "name",
+                        "owner",
+                        "domain",
+                        "tags",
+                        "classification",
+                        "version",
+                        "observed_at",
+                    )
+                }
+            )
+        return sorted(
+            results, key=lambda row: (row["name"].lower(), row["external_uid"])
+        )[:50]
+
+    def impact(
+        self,
+        external_uid: str,
+        *,
+        principal_roles: set[str],
+        domain: str,
+        max_depth: int,
+        max_nodes: int,
+    ) -> dict[str, Any]:
+        if max_depth < 1 or max_depth > 5 or max_nodes < 1 or max_nodes > 100:
+            raise ValueError("impact bounds are invalid")
+        root = self.repository.asset(
+            "local-fixture-v1", _token(external_uid, "external_uid")
+        )
+        if root is None or not self._visible(root, principal_roles, domain):
+            raise LookupError("catalog asset unavailable")
+        by_dependency: dict[str, list[dict[str, Any]]] = {}
+        for asset in self.repository.all_assets("local-fixture-v1"):
+            if not asset.get("tombstoned") and self._visible(
+                asset, principal_roles, domain
+            ):
+                for dependency in asset["dependencies"]:
+                    by_dependency.setdefault(dependency, []).append(asset)
+        affected, seen, frontier = (
+            [],
+            {root["external_uid"]},
+            [(root["external_uid"], 0)],
+        )
+        truncated = False
+        while frontier:
+            node, depth = frontier.pop(0)
+            if depth >= max_depth:
+                continue
+            for child in by_dependency.get(node, []):
+                if child["external_uid"] in seen:
+                    continue
+                if len(affected) >= max_nodes:
+                    truncated = True
+                    break
+                seen.add(child["external_uid"])
+                affected.append(child["external_uid"])
+                frontier.append((child["external_uid"], depth + 1))
+            if truncated:
+                break
+        return {
+            "external_uid": root["external_uid"],
+            "affected": affected,
+            "truncated": truncated,
+        }

+ 616 - 82
app/core/governance/domain_replication.py

@@ -11,9 +11,13 @@ from __future__ import annotations
 import hashlib
 import json
 import re
+import unicodedata
 from copy import deepcopy
+from datetime import date
 from typing import Any
 
+from app.core.governance.domain_replication_contract_registry import verify_registry
+
 REQUIRED_STAGES = (
     "template_initialization",
     "incremental_ingestion",
@@ -56,6 +60,66 @@ _REQUIRED_ENTERPRISE_BINDINGS = {
     "readonly_source",
     "uat_users",
 }
+_EXECUTION_ACTIONS = ("dry_run", "execute")
+_RECOVERY_ACTIONS = frozenset({"diff", "rollback"})
+_EXECUTION_REF = re.compile(
+    r"^[a-z][a-z0-9-]{1,31}://([a-z][a-z0-9_]{1,63})/[a-zA-Z0-9_./:-]{1,240}$"
+)
+_HTTP_URL = re.compile(r"https?://", re.IGNORECASE)
+_RAW_ROW_KEY = re.compile(r"(?:raw[_-]?rows?|source[_-]?rows?)", re.IGNORECASE)
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+_IDENTIFIER = re.compile(r"^[a-z][a-z0-9_-]{1,79}$")
+_CONFUSABLES = str.maketrans({
+    "а": "a", "е": "e", "і": "i", "о": "o", "р": "p", "с": "c",
+    "х": "x", "у": "y", "к": "k", "м": "m", "т": "t", "ѕ": "s",
+})
+_SENSITIVE_KEY_MARKERS = (
+    "password", "passwd", "secret", "token", "credential", "authorization",
+    "apikey", "privatekey",
+)
+_RAW_SOURCE_KEYS = {"rawrows", "sourcerows", "rows"}
+_SQL = re.compile(r"\b(?:select|insert|update|delete|drop|alter|create|grant|revoke)\b", re.IGNORECASE)
+_API_ALLOWLIST = {
+    "template_initialization": ("/api/meta/domain-templates",),
+    "incremental_ingestion": ("/api/meta/active-metadata",),
+    "catalog": ("/api/meta/active-metadata",),
+    "semantics": ("/api/development/v1/semantic-assets",),
+    "responsibility": ("/api/system/responsibilities",),
+    "quality": ("/api/rules/quality-operations",),
+    "remediation": ("/api/system/work-center",),
+    "observability": ("/api/datafactory/observability",),
+    "data_product": ("/api/dataservice/governance/products",),
+    "agent": ("/api/knowledge/agents",),
+}
+_EVIDENCE_PREFIXES = {
+    "template_initialization": "domain-template://",
+    "incremental_ingestion": "ingestion://",
+    "catalog": "catalog-run://",
+    "semantics": "semantic-release://",
+    "responsibility": "responsibility://",
+    "quality": "quality://",
+    "remediation": "workflow://",
+    "observability": "incident://",
+    "data_product": "product://",
+    "agent": "agent-request://",
+}
+_CONTRACT_REQUIREMENTS = {
+    "incremental_ingestion": "p3_wp03_connector_run",
+    "catalog": "p3_wp03_catalog_lineage",
+    "observability": "p3_wp04_edge_health",
+}
+_METRIC_FIELDS = {
+    "template_initialization": {"template_version", "object_type_count", "rule_count", "role_count", "metric_count"},
+    "incremental_ingestion": {"snapshot_rows", "delta_rows", "inserted", "updated", "deleted", "final_rows", "replay_changes", "cursor_before", "cursor_after"},
+    "catalog": {"asset_count", "incremental_change_count", "cursor_after"},
+    "semantics": {"published_term_count", "published_code_set_count", "published_metric_count", "mapped_field_count"},
+    "responsibility": {"coverage_percent", "bound_role_count"},
+    "quality": {"published_rule_count", "initial_finding_count", "target_score", "final_score"},
+    "remediation": {"issue_status", "task_status", "independent_closer"},
+    "observability": {"slo_count", "incident_status", "recovered"},
+    "data_product": {"contract_status", "certificate_status", "certificate_evidence_refs"},
+    "agent": {"autonomy_level", "decision", "automatic_execution_allowed", "citation_count", "cross_domain_denied"},
+}
 
 
 class DomainReplicationError(ValueError):
@@ -88,17 +152,291 @@ def _bounded_text(value: Any, field: str, limit: int = 256) -> str:
     return rendered
 
 
-def _reject_secrets(value: Any, path: str = "$") -> None:
+def _security_key(value: Any) -> str:
+    return re.sub(
+        r"[^a-z0-9]", "", unicodedata.normalize("NFKC", str(value)).casefold().translate(_CONFUSABLES)
+    )
+
+
+def _reject_secrets(value: Any, path: str = "$", depth: int = 0) -> None:
+    if depth > 12:
+        raise DomainReplicationError(f"maximum package depth exceeded at {path}")
     if isinstance(value, dict):
+        if len(value) > 64:
+            raise DomainReplicationError(f"maximum object properties exceeded at {path}")
         for key, item in value.items():
-            if _SECRET.search(str(key)):
+            normalized_key = _security_key(key)
+            if normalized_key in _RAW_SOURCE_KEYS:
+                raise DomainReplicationError(
+                    f"raw source rows are not allowed at {path}.{key}"
+                )
+            if _SECRET.search(normalized_key) or any(
+                marker in normalized_key for marker in _SENSITIVE_KEY_MARKERS
+            ):
                 raise DomainReplicationError(
-                    f"secret material is not allowed at {path}.{key}"
+                    f"sensitive material is not allowed at {path}.{key}"
                 )
-            _reject_secrets(item, f"{path}.{key}")
+            _reject_secrets(item, f"{path}.{key}", depth + 1)
+    elif isinstance(value, list):
+        if len(value) > 500:
+            raise DomainReplicationError(f"maximum collection items exceeded at {path}")
+        for index, item in enumerate(value):
+            _reject_secrets(item, f"{path}[{index}]", depth + 1)
+    elif isinstance(value, str):
+        if len(value) > 4096:
+            raise DomainReplicationError(f"maximum string length exceeded at {path}")
+        normalized = unicodedata.normalize("NFKC", value)
+        if _HTTP_URL.search(normalized):
+            raise DomainReplicationError(f"URL is not allowed at {path}")
+        if _SQL.search(normalized):
+            raise DomainReplicationError(f"SQL is not allowed at {path}")
+
+
+def _closed_mapping(value: Any, field: str, allowed: set[str]) -> dict[str, Any]:
+    value = _mapping(value, field)
+    unknown = sorted(set(value) - allowed)
+    if unknown:
+        normalized_unknown = [_security_key(key) for key in unknown]
+        if any(key in _RAW_SOURCE_KEYS for key in normalized_unknown):
+            raise DomainReplicationError(f"raw source rows are not allowed at {field}")
+        if any(
+            _SECRET.search(key) or any(marker in key for marker in _SENSITIVE_KEY_MARKERS)
+            for key in normalized_unknown
+        ):
+            raise DomainReplicationError(f"sensitive material is not allowed at {field}")
+        raise DomainReplicationError(f"{field} has unsupported fields: {unknown}")
+    return value
+
+
+def _closed_required(value: Any, field: str, required: set[str], allowed: set[str]) -> dict[str, Any]:
+    value = _closed_mapping(value, field, allowed)
+    missing = sorted(required - set(value))
+    if missing:
+        raise DomainReplicationError(f"{field} is missing required fields: {missing}")
+    return value
+
+
+def _string(value: Any, field: str, limit: int = 256) -> str:
+    if not isinstance(value, str):
+        raise DomainReplicationError(f"{field} must be a string")
+    return _bounded_text(value, field, limit)
+
+
+def _digest_string(value: Any, field: str) -> str:
+    value = _string(value, field, 64)
+    if not _SHA256.fullmatch(value):
+        raise DomainReplicationError(f"{field} must be a lowercase SHA-256 digest")
+    return value
+
+
+def _bounded_list(value: Any, field: str, *, minimum: int = 1, maximum: int = 100) -> list[Any]:
+    if not isinstance(value, list) or not minimum <= len(value) <= maximum:
+        raise DomainReplicationError(f"{field} must contain {minimum}..{maximum} items")
+    return value
+
+
+def _execution_ref(value: Any, field: str) -> tuple[str, str]:
+    rendered = _bounded_text(value, field, 320)
+    if _HTTP_URL.search(rendered):
+        raise DomainReplicationError(f"{field} must not contain a URL")
+    matched = _EXECUTION_REF.fullmatch(rendered)
+    if not matched:
+        raise DomainReplicationError(f"{field} must be a canonical evidence reference")
+    return rendered, matched.group(1)
+
+
+def _reject_execution_sensitive(value: Any, path: str = "execution_evidence") -> None:
+    if isinstance(value, dict):
+        for key, item in value.items():
+            if _RAW_ROW_KEY.search(str(key)):
+                raise DomainReplicationError(f"raw source rows are not allowed at {path}.{key}")
+            if _HTTP_URL.search(str(item)):
+                raise DomainReplicationError(f"URL is not allowed at {path}.{key}")
+            _reject_execution_sensitive(item, f"{path}.{key}")
     elif isinstance(value, list):
         for index, item in enumerate(value):
-            _reject_secrets(item, f"{path}[{index}]")
+            _reject_execution_sensitive(item, f"{path}[{index}]")
+
+
+def _validate_execute_state(action: dict[str, Any], stage: str) -> dict[str, Any]:
+    """Validate the v2 receipt that owns a governed state transition."""
+
+    required = {
+        "action", "status", "write_count", "evidence_ref", "audit_ref", "operation_uid",
+        "idempotency_key", "request_digest", "attempt", "lease_fence", "pre_state_digest",
+        "post_state_digest", "result_digest",
+    }
+    action = _closed_required(action, f"{stage}.execute", required, required)
+    for field in ("operation_uid", "idempotency_key"):
+        if not _IDENTIFIER.fullmatch(_string(action[field], f"{stage}.execute.{field}", 80)):
+            raise DomainReplicationError(f"{stage}.execute.{field} is invalid")
+    for field in ("request_digest", "pre_state_digest", "post_state_digest", "result_digest"):
+        _digest_string(action[field], f"{stage}.execute.{field}")
+    if action["pre_state_digest"] == action["post_state_digest"]:
+        raise DomainReplicationError(f"{stage}.execute state digests must differ")
+    if isinstance(action["attempt"], bool) or not isinstance(action["attempt"], int) or not 1 <= action["attempt"] <= 5:
+        raise DomainReplicationError(f"{stage}.execute.attempt must be 1..5")
+    if isinstance(action["lease_fence"], bool) or not isinstance(action["lease_fence"], int) or action["lease_fence"] < 1:
+        raise DomainReplicationError(f"{stage}.execute.lease_fence must be positive")
+    return action
+
+
+def _validate_rollback(action: dict[str, Any], stage: str, *, strict_state_machine: bool) -> dict[str, Any]:
+    required = {
+        "action", "status", "write_count", "evidence_ref", "audit_ref", "operation",
+        "idempotency_key", "request_digest", "attempt", "lease_fence", "from_digest",
+        "to_digest", "replay_status",
+    }
+    if strict_state_machine:
+        required = required - {"replay_status"} | {"operation_uid", "result_digest"}
+    action = _closed_required(action, f"{stage}.rollback", required, required)
+    if action["operation"] != "restore":
+        raise DomainReplicationError(f"{stage}.rollback.operation must be restore")
+    for field in ("operation_uid", "idempotency_key") if strict_state_machine else ("idempotency_key",):
+        if not _IDENTIFIER.fullmatch(_string(action[field], f"{stage}.rollback.{field}", 80)):
+            raise DomainReplicationError(f"{stage}.rollback.{field} is invalid")
+    digest_fields = ("request_digest", "from_digest", "to_digest", "result_digest") if strict_state_machine else ("request_digest", "from_digest", "to_digest")
+    for key in digest_fields:
+        _digest_string(action[key], f"{stage}.rollback.{key}")
+    if action["from_digest"] == action["to_digest"]:
+        raise DomainReplicationError(f"{stage}.rollback digests must differ")
+    if isinstance(action["attempt"], bool) or not isinstance(action["attempt"], int) or not 1 <= action["attempt"] <= 5:
+        raise DomainReplicationError(f"{stage}.rollback.attempt must be 1..5")
+    if isinstance(action["lease_fence"], bool) or not isinstance(action["lease_fence"], int) or action["lease_fence"] < 1:
+        raise DomainReplicationError(f"{stage}.rollback.lease_fence must be positive")
+    if not strict_state_machine and action["replay_status"] != "exact_replay":
+        raise DomainReplicationError(f"{stage}.rollback replay must be exact_replay")
+    return action
+
+
+def _validate_replay_receipts(value: Any, rollbacks: dict[str, dict[str, Any]]) -> None:
+    """Require a second receipt, not a rollback's self-declared replay label."""
+
+    required = {
+        "stage", "operation_uid", "idempotency_key", "request_digest", "attempt",
+        "lease_fence", "from_digest", "to_digest", "result_digest", "status",
+    }
+    receipts = _bounded_list(value, "execution_evidence.replay_receipts", minimum=len(rollbacks), maximum=len(rollbacks))
+    by_stage: dict[str, dict[str, Any]] = {}
+    for item in receipts:
+        item = _closed_required(item, "execution_evidence.replay_receipt", required, required)
+        stage = _string(item["stage"], "execution_evidence.replay_receipt.stage", 80)
+        if stage not in rollbacks or stage in by_stage:
+            raise DomainReplicationError("replay receipt must bind exactly one rollback stage")
+        if item["status"] != "exact_replay":
+            raise DomainReplicationError("replay receipt status must be exact_replay")
+        for field in ("operation_uid", "idempotency_key"):
+            if not _IDENTIFIER.fullmatch(_string(item[field], f"replay_receipt.{field}", 80)):
+                raise DomainReplicationError(f"replay receipt {field} is invalid")
+        for field in ("request_digest", "from_digest", "to_digest", "result_digest"):
+            _digest_string(item[field], f"replay_receipt.{field}")
+        rollback = rollbacks[stage]
+        for field in required - {"stage", "status"}:
+            if item[field] != rollback[field]:
+                raise DomainReplicationError(f"replay receipt does not match rollback {field}")
+        by_stage[stage] = item
+    if set(by_stage) != set(rollbacks):
+        raise DomainReplicationError("replay receipt coverage is incomplete")
+
+
+def validate_execution_evidence(
+    execution_evidence: dict[str, Any], *, domain_code: str | None = None
+) -> dict[str, dict[str, Any]]:
+    """Validate the ordered, row-free execution chain for every governance stage."""
+
+    initial = _mapping(execution_evidence, "execution_evidence")
+    schema_version = initial.get("schema_version")
+    if schema_version not in {1, 2}:
+        raise DomainReplicationError("execution_evidence.schema_version must be 1 or 2")
+    strict_state_machine = schema_version == 2
+    execution_evidence = _closed_mapping(
+        execution_evidence,
+        "execution_evidence",
+        {"schema_version", "stages", "replay_receipts"} if strict_state_machine else {"schema_version", "stages"},
+    )
+    _reject_secrets(execution_evidence, "execution_evidence")
+    _reject_execution_sensitive(execution_evidence)
+    receipts = execution_evidence.get("stages")
+    if not isinstance(receipts, list) or len(receipts) != len(REQUIRED_STAGES):
+        raise DomainReplicationError("execution_evidence must contain every required stage")
+
+    summaries: dict[str, dict[str, Any]] = {}
+    rollbacks: dict[str, dict[str, Any]] = {}
+    for expected_stage, receipt in zip(REQUIRED_STAGES, receipts, strict=True):
+        receipt = _closed_mapping(receipt, "execution_evidence.stage", {"stage", "actions"})
+        stage = _bounded_text(receipt.get("stage"), "execution_evidence.stage.stage", 80)
+        if stage != expected_stage:
+            raise DomainReplicationError("execution evidence stages must be ordered")
+        actions = receipt.get("actions")
+        if not isinstance(actions, list) or len(actions) != 3:
+            raise DomainReplicationError(f"{stage} must contain dry_run, execute and diff or rollback")
+        normalized_actions: list[dict[str, Any]] = []
+        domains: set[str] = set()
+        for index, action in enumerate(actions):
+            action = _mapping(action, f"{stage}.actions[{index}]")
+            action_name = _bounded_text(action.get("action"), f"{stage}.action", 32)
+            expected_action = (
+                _EXECUTION_ACTIONS[index] if index < 2 else None
+            )
+            if expected_action and action_name != expected_action:
+                raise DomainReplicationError(f"{stage} actions must be ordered dry_run then execute")
+            if index == 2 and action_name not in _RECOVERY_ACTIONS:
+                raise DomainReplicationError(f"{stage} recovery action must be diff or rollback")
+            allowed = {"action", "status", "write_count", "evidence_ref", "audit_ref"}
+            if action_name == "rollback":
+                action = _validate_rollback(action, stage, strict_state_machine=strict_state_machine)
+            elif action_name == "execute" and strict_state_machine:
+                action = _validate_execute_state(action, stage)
+            else:
+                action = _closed_required(action, f"{stage}.actions[{index}]", allowed, allowed)
+            if action.get("status") != "passed":
+                raise DomainReplicationError(f"{stage}.{action_name} status must be passed")
+            write_count = action.get("write_count")
+            if isinstance(write_count, bool) or not isinstance(write_count, int):
+                raise DomainReplicationError(f"{stage}.{action_name}.write_count must be an integer")
+            if write_count < 0 or write_count > 1_000_000:
+                raise DomainReplicationError(f"{stage}.{action_name}.write_count is out of range")
+            if action_name == "dry_run" and write_count != 0:
+                raise DomainReplicationError(f"{stage}.dry_run must record zero writes")
+            evidence_ref, evidence_domain = _execution_ref(
+                action.get("evidence_ref"), f"{stage}.{action_name}.evidence_ref"
+            )
+            audit_ref, audit_domain = _execution_ref(
+                action.get("audit_ref"), f"{stage}.{action_name}.audit_ref"
+            )
+            domains.update({evidence_domain, audit_domain})
+            normalized_actions.append(
+                {
+                    "action": action_name,
+                    "write_count": write_count,
+                    "evidence_ref": evidence_ref,
+                    "audit_ref": audit_ref,
+                }
+            )
+            if action_name == "rollback" and strict_state_machine:
+                execute = actions[1]
+                for field in ("operation_uid", "idempotency_key", "request_digest", "attempt", "lease_fence"):
+                    if action[field] != execute[field]:
+                        raise DomainReplicationError(f"{stage}.rollback must match execute {field}")
+                if action["from_digest"] != execute["post_state_digest"]:
+                    raise DomainReplicationError(f"{stage}.rollback.from_digest must match execute post_state_digest")
+                if action["to_digest"] != execute["pre_state_digest"]:
+                    raise DomainReplicationError(f"{stage}.rollback.to_digest must match execute pre_state_digest")
+                rollbacks[stage] = action
+        if len(domains) != 1 or (domain_code is not None and domains != {domain_code}):
+            raise DomainReplicationError(f"{stage} execution evidence contains a cross-domain reference")
+        summaries[stage] = {
+            "actions": [item["action"] for item in normalized_actions],
+            "dry_run_write_count": normalized_actions[0]["write_count"],
+            "execute_write_count": normalized_actions[1]["write_count"],
+            "recovery_strategy": normalized_actions[2]["action"],
+            "recovery_write_count": normalized_actions[2]["write_count"],
+            "audit_ref_count": len(normalized_actions),
+            "evidence_digest": _digest(normalized_actions),
+        }
+    if strict_state_machine:
+        _validate_replay_receipts(execution_evidence.get("replay_receipts"), rollbacks)
+    return summaries
 
 
 def _row_key(row: dict[str, str], fields: list[str]) -> tuple[str, ...]:
@@ -123,7 +461,10 @@ def _validate_source_fields(rows: list[dict[str, str]]) -> None:
         if not isinstance(row, dict):
             raise DomainReplicationError("source rows must be objects")
         for field in row:
-            if _SECRET.search(str(field)):
+            normalized = _security_key(field)
+            if normalized in _RAW_SOURCE_KEYS or _SECRET.search(normalized) or any(
+                marker in normalized for marker in _SENSITIVE_KEY_MARKERS
+            ):
                 raise DomainReplicationError(
                     f"secret-like source field is not allowed: {field}"
                 )
@@ -368,14 +709,218 @@ def _validate_stage_metrics(
             raise DomainReplicationError("agent cross-domain request must be denied")
 
 
-def _validate_manifest(manifest: dict[str, Any], template: dict[str, Any]) -> tuple[str, str]:
-    manifest = _mapping(manifest, "manifest")
+def _validate_seed_data(seed_data: Any, *, schema_version: int) -> None:
+    payload_key = "records" if schema_version == 1 else "items"
+    record_fields = {
+        "source_contract": {"id", "type", "binding_status"},
+        "term_and_code_set": {"id", "name"},
+        "workflow_contract": {"id", "name"},
+    }
+    for item in _bounded_list(seed_data, "template.seed_data", minimum=0):
+        item = _closed_required(
+            item,
+            "template.seed_data.item",
+            {"kind", payload_key},
+            {"kind", payload_key},
+        )
+        kind = _string(item["kind"], "template.seed_data.kind", 80)
+        if kind not in record_fields:
+            raise DomainReplicationError("template.seed_data.kind is unsupported")
+        for record in _bounded_list(item[payload_key], f"template.seed_data.{payload_key}", minimum=1):
+            record = _closed_required(
+                record,
+                "template.seed_data.record",
+                record_fields[kind],
+                record_fields[kind],
+            )
+            for key in record_fields[kind]:
+                _string(record[key], f"template.seed_data.record.{key}", 200)
+
+
+def _validate_template(template: dict[str, Any], *, schema_version: int) -> dict[str, Any]:
+    required = {
+        "template_code", "name", "description", "lifecycle_status", "object_types",
+        "responsibility_roles", "rules", "metrics", "seed_data",
+    }
+    _reject_secrets(template, "template")
+    template = _closed_required(template, "template", required, required)
+    _string(template["template_code"], "template.template_code", 64)
+    _string(template["name"], "template.name", 200)
+    _string(template["description"], "template.description", 500)
+    if template["lifecycle_status"] not in {"draft", "active", "retired"}:
+        raise DomainReplicationError("template.lifecycle_status is invalid")
+    for item in _bounded_list(template["object_types"], "template.object_types", minimum=3):
+        item = _closed_required(
+            item,
+            "template.object_types.item",
+            {"type_code", "name", "stable_uid_prefix", "source_identity_fields", "fields"},
+            {"type_code", "name", "description", "stable_uid_prefix", "source_identity_fields", "fields"},
+        )
+        for key in ("type_code", "name", "stable_uid_prefix"):
+            _string(item[key], f"template.object_types.{key}", 200)
+        for identity in _bounded_list(item["source_identity_fields"], "template.source_identity_fields"):
+            _string(identity, "template.source_identity_fields.item", 64)
+        for field in _bounded_list(item["fields"], "template.fields"):
+            field = _closed_required(
+                field,
+                "template.fields.item",
+                {"code", "name", "type", "required"},
+                {"code", "name", "type", "required", "description"},
+            )
+            if not isinstance(field["required"], bool):
+                raise DomainReplicationError("template.fields.required must be boolean")
+            for key in ("code", "name", "type"):
+                _string(field[key], f"template.fields.{key}", 200)
+    for collection, required_fields in (
+        ("responsibility_roles", {"code", "name", "raci_role"}),
+        ("rules", {"code", "name", "dimension", "target_object"}),
+        ("metrics", {"code", "name", "unit"}),
+    ):
+        for item in _bounded_list(template[collection], f"template.{collection}"):
+            item = _closed_required(item, f"template.{collection}.item", required_fields, required_fields)
+            for key in required_fields:
+                _string(item[key], f"template.{collection}.{key}", 200)
+    _validate_seed_data(template["seed_data"], schema_version=schema_version)
+    return template
+
+
+def _validate_contract_refs(stage: str, value: Any) -> list[dict[str, Any]]:
+    if stage not in _CONTRACT_REQUIREMENTS:
+        if value is not None:
+            raise DomainReplicationError(f"{stage} does not accept contract_refs")
+        return []
+    refs = _bounded_list(value, f"{stage}.contract_refs", minimum=1, maximum=1)
+    return [validate_contract_reference(refs[0], expected_contract_id=_CONTRACT_REQUIREMENTS[stage])]
+
+
+def validate_contract_reference(
+    reference: dict[str, Any], *, expected_contract_id: str
+) -> dict[str, Any]:
+    """Resolve one v2 receipt only against the code-side trusted registry."""
+
+    required = {
+        "contract_id", "contract_version", "schema_digest", "operation_uid", "run_id",
+        "operation", "attempt", "lease_fence", "request_digest", "result_digest", "status",
+    }
+    reference = _closed_required(reference, "contract_ref", required, required)
+    try:
+        registry = verify_registry()
+    except ValueError as exc:
+        raise DomainReplicationError("trusted contract registry integrity check failed") from exc
+    if registry["trust_level"] != "ENGINEERING_EVIDENCE_ONLY":
+        raise DomainReplicationError("trusted contract registry has an invalid trust level")
+    if date.fromisoformat(registry["expires_on"]) < date.today():
+        raise DomainReplicationError("trusted contract registry is expired")
+    contract_id = _string(reference["contract_id"], "contract_ref.contract_id", 80)
+    if contract_id != expected_contract_id:
+        raise DomainReplicationError("cross-contract reference was rejected")
+    contract = next(
+        (item for item in registry["contracts"] if item["contract_id"] == contract_id), None
+    )
+    if contract is None:
+        raise DomainReplicationError("contract is absent from the trusted registry")
+    if reference["contract_version"] != contract["contract_version"]:
+        raise DomainReplicationError("contract version does not match the trusted registry")
+    if reference["schema_digest"] != contract["schema_digest"]:
+        raise DomainReplicationError("contract schema digest does not match the trusted registry")
+    if reference["operation"] not in contract["allowed_operations"]:
+        raise DomainReplicationError("contract operation is not allowed")
+    run = next(
+        (
+            item for item in contract["runs"]
+            if item["operation_uid"] == reference["operation_uid"]
+            and item["run_id"] == reference["run_id"]
+        ),
+        None,
+    )
+    if run is None:
+        raise DomainReplicationError("run is absent from the trusted registry")
+    for field in (
+        "operation_uid", "run_id", "operation", "attempt", "lease_fence",
+        "request_digest", "result_digest", "status",
+    ):
+        if reference[field] != run[field]:
+            raise DomainReplicationError(f"contract registry mismatch for {field}")
+    for field in ("schema_digest", "request_digest", "result_digest"):
+        _digest_string(reference[field], f"contract_ref.{field}")
+    return deepcopy(reference)
+
+
+def _validate_terminal_evidence(
+    evidence: dict[str, Any], package_code: str, domain_code: str, template: dict[str, Any], incremental: dict[str, Any], *, schema_version: int
+) -> dict[str, dict[str, Any]]:
+    _reject_secrets(evidence, "evidence")
+    evidence = _closed_required(evidence, "evidence", {"schema_version", "package_code", "receipts"}, {"schema_version", "package_code", "receipts"})
+    if evidence["schema_version"] != 1 or evidence["package_code"] != package_code:
+        raise DomainReplicationError("evidence package identity is invalid")
+    receipts = _bounded_list(evidence["receipts"], "evidence.receipts", minimum=1, maximum=len(REQUIRED_STAGES))
+    by_stage: dict[str, dict[str, Any]] = {}
+    for receipt in receipts:
+        receipt = _mapping(receipt, "receipt")
+        stage = _string(receipt.get("stage"), "receipt.stage", 80)
+        required = {"stage", "domain_code", "status", "subsystem", "api_refs", "evidence_refs", "metrics"}
+        allowed = set(required)
+        if schema_version == 2 and stage in _CONTRACT_REQUIREMENTS:
+            required.add("contract_refs")
+            allowed.add("contract_refs")
+        receipt = _closed_required(receipt, "receipt", required, allowed)
+        if stage in by_stage:
+            raise DomainReplicationError(f"duplicate evidence stage: {stage}")
+        by_stage[stage] = receipt
+    missing = sorted(set(REQUIRED_STAGES) - set(by_stage))
+    extra = sorted(set(by_stage) - set(REQUIRED_STAGES))
+    if missing:
+        raise DomainReplicationError(f"missing stages: {','.join(missing)}")
+    if extra:
+        raise DomainReplicationError(f"unsupported stages: {','.join(extra)}")
+    reports: dict[str, dict[str, Any]] = {}
+    for stage in REQUIRED_STAGES:
+        receipt = by_stage[stage]
+        if receipt["domain_code"] != domain_code:
+            raise DomainReplicationError(f"{stage} evidence has the wrong domain")
+        if receipt["status"] != "passed":
+            raise DomainReplicationError(f"{stage} evidence status must be passed")
+        if receipt["subsystem"] != STAGE_SUBSYSTEMS[stage]:
+            raise DomainReplicationError(f"{stage} evidence subsystem is invalid")
+        api_refs = _bounded_list(receipt["api_refs"], f"{stage}.api_refs", maximum=1)
+        if any("/device-" in str(ref) for ref in api_refs):
+            raise DomainReplicationError("device-specific API evidence is not allowed")
+        if tuple(api_refs) != _API_ALLOWLIST[stage]:
+            raise DomainReplicationError(f"{stage} must use its allowlisted API")
+        evidence_refs = _bounded_list(receipt["evidence_refs"], f"{stage}.evidence_refs", maximum=8)
+        prefix = _EVIDENCE_PREFIXES[stage]
+        if any(not isinstance(ref, str) or not ref.startswith(prefix + domain_code + "/") for ref in evidence_refs):
+            raise DomainReplicationError(f"{stage} evidence_refs are not canonical")
+        metrics = _closed_required(receipt["metrics"], f"{stage}.metrics", _METRIC_FIELDS[stage], _METRIC_FIELDS[stage])
+        _validate_stage_metrics(stage, metrics, template=template, incremental=incremental)
+        contracts = (
+            _validate_contract_refs(stage, receipt.get("contract_refs"))
+            if schema_version == 2
+            else []
+        )
+        reports[stage] = {"status": "passed", "subsystem": STAGE_SUBSYSTEMS[stage], "api_refs": list(api_refs), "evidence_refs": sorted(evidence_refs), "metrics": deepcopy(metrics)}
+        if contracts:
+            reports[stage]["contract_refs"] = contracts
+    return reports
+
+
+def _validate_manifest(
+    manifest: dict[str, Any], template: dict[str, Any]
+) -> tuple[str, str, int]:
+    required = {
+        "schema_version", "package_code", "domain", "files", "source",
+        "core_change_assessment", "enterprise_bindings", "third_domain_reuse",
+    }
     _reject_secrets(manifest)
-    if int(manifest.get("schema_version", 0)) != 1:
-        raise DomainReplicationError("manifest.schema_version must be 1")
-    package_code = _bounded_text(manifest.get("package_code"), "package_code", 80)
-    domain = _mapping(manifest.get("domain"), "domain")
-    domain_code = _bounded_text(domain.get("code"), "domain.code", 64)
+    manifest = _closed_required(manifest, "manifest", required, required)
+    if isinstance(manifest["schema_version"], bool) or not isinstance(manifest["schema_version"], int):
+        raise DomainReplicationError("manifest.schema_version must be an integer")
+    schema_version = manifest["schema_version"]
+    if schema_version not in {1, 2}:
+        raise DomainReplicationError("manifest.schema_version must be 1 or 2")
+    package_code = _string(manifest["package_code"], "package_code", 80)
+    domain = _closed_required(manifest["domain"], "manifest.domain", {"code", "name"}, {"code", "name"})
+    domain_code = _string(domain["code"], "domain.code", 64)
     if not _CODE.fullmatch(domain_code):
         raise DomainReplicationError("domain.code must be a stable lowercase code")
     if template.get("template_code") != domain_code:
@@ -385,17 +930,41 @@ def _validate_manifest(manifest: dict[str, Any], template: dict[str, Any]) -> tu
     if len(template.get("rules", [])) < 5:
         raise DomainReplicationError("domain template must contain at least five rules")
 
-    assessment = _mapping(
-        manifest.get("core_change_assessment"), "core_change_assessment"
+    files = _mapping(manifest["files"], "manifest.files")
+    expected_file_keys = {"template", "evidence", "snapshot", "delta", "acceptance_report"}
+    if schema_version == 2:
+        expected_file_keys.add("execution_evidence")
+    if set(files) != expected_file_keys:
+        raise DomainReplicationError("manifest.files has unsupported or missing bindings")
+    for key, binding in files.items():
+        binding = _closed_required(binding, f"manifest.files.{key}", {"path", "sha256"}, {"path", "sha256"})
+        _string(binding["path"], f"manifest.files.{key}.path", 240)
+        _digest_string(binding["sha256"], f"manifest.files.{key}.sha256")
+    source = _closed_required(
+        manifest["source"], "manifest.source",
+        {"kind", "classification", "primary_key", "required_columns", "operation_field", "cursor_field", "snapshot_cursor"},
+        {"kind", "classification", "primary_key", "required_columns", "operation_field", "cursor_field", "snapshot_cursor"},
+    )
+    if source["kind"] != "controlled_csv" or source["classification"] != "desensitized":
+        raise DomainReplicationError("manifest source is invalid")
+    for field in ("primary_key", "required_columns"):
+        for value in _bounded_list(source[field], f"manifest.source.{field}"):
+            _string(value, f"manifest.source.{field}.item", 80)
+    for field in ("operation_field", "cursor_field"):
+        _string(source[field], f"manifest.source.{field}", 80)
+    if isinstance(source["snapshot_cursor"], bool) or not isinstance(source["snapshot_cursor"], int) or source["snapshot_cursor"] < 0:
+        raise DomainReplicationError("manifest.source.snapshot_cursor is invalid")
+    assessment = _closed_required(
+        manifest["core_change_assessment"], "core_change_assessment",
+        {"baseline_commit", "device_specific_changes", "generic_extensions", "extension_points_used"},
+        {"baseline_commit", "device_specific_changes", "generic_extensions", "extension_points_used"},
     )
     device_changes = assessment.get("device_specific_changes")
     if not isinstance(device_changes, list):
         raise DomainReplicationError("device_specific_changes must be a list")
     if device_changes:
         raise DomainReplicationError("device-specific core changes must remain zero")
-    generic_extensions = assessment.get("generic_extensions")
-    if not isinstance(generic_extensions, list) or not generic_extensions:
-        raise DomainReplicationError("generic extension points must be documented")
+    generic_extensions = _bounded_list(assessment["generic_extensions"], "generic_extensions")
     if any(
         str(path).startswith(marker)
         for path in generic_extensions
@@ -403,13 +972,16 @@ def _validate_manifest(manifest: dict[str, Any], template: dict[str, Any]) -> tu
     ):
         raise DomainReplicationError("generic extensions cannot target device-specific paths")
 
-    reuse = _mapping(manifest.get("third_domain_reuse"), "third_domain_reuse")
+    reuse = _closed_required(
+        manifest["third_domain_reuse"], "third_domain_reuse",
+        {"reusable", "required_replacements"}, {"reusable", "required_replacements"},
+    )
     if reuse.get("reusable") is not True:
         raise DomainReplicationError("third-domain reuse must be explicitly enabled")
-    placeholders = reuse.get("required_replacements")
+    placeholders = reuse["required_replacements"]
     if not isinstance(placeholders, list) or len(placeholders) < 4:
         raise DomainReplicationError("third-domain replacement checklist is incomplete")
-    return package_code, domain_code
+    return package_code, domain_code, schema_version
 
 
 def evaluate_replication_package(
@@ -419,76 +991,30 @@ def evaluate_replication_package(
     template: dict[str, Any],
     snapshot_rows: list[dict[str, str]],
     delta_rows: list[dict[str, str]],
+    execution_evidence: dict[str, Any] | None = None,
 ) -> dict[str, Any]:
     """Evaluate one domain package and return a deterministic, row-free report."""
 
-    package_code, domain_code = _validate_manifest(manifest, template)
+    manifest_schema_version = _mapping(manifest, "manifest").get("schema_version")
+    if isinstance(manifest_schema_version, bool) or manifest_schema_version not in {1, 2}:
+        raise DomainReplicationError("manifest.schema_version must be 1 or 2")
+    template = _validate_template(template, schema_version=manifest_schema_version)
+    package_code, domain_code, schema_version = _validate_manifest(manifest, template)
     incremental = collect_incremental_rows(
         snapshot_rows,
         delta_rows,
         _mapping(manifest.get("source"), "source"),
     )
-    evidence = _mapping(evidence, "evidence")
-    _reject_secrets(evidence)
-    if evidence.get("package_code") != package_code:
-        raise DomainReplicationError("evidence package code does not match manifest")
-    receipts = evidence.get("receipts")
-    if not isinstance(receipts, list):
-        raise DomainReplicationError("evidence.receipts must be a list")
-    by_stage: dict[str, dict[str, Any]] = {}
-    for receipt in receipts:
-        receipt = _mapping(receipt, "receipt")
-        stage = _bounded_text(receipt.get("stage"), "receipt.stage", 80)
-        if stage in by_stage:
-            raise DomainReplicationError(f"duplicate evidence stage: {stage}")
-        by_stage[stage] = receipt
-    missing = sorted(set(REQUIRED_STAGES) - set(by_stage))
-    extra = sorted(set(by_stage) - set(REQUIRED_STAGES))
-    if missing:
-        raise DomainReplicationError(f"missing stages: {','.join(missing)}")
-    if extra:
-        raise DomainReplicationError(f"unsupported stages: {','.join(extra)}")
-
-    stage_reports: dict[str, dict[str, Any]] = {}
-    for stage in REQUIRED_STAGES:
-        receipt = by_stage[stage]
-        if receipt.get("domain_code") != domain_code:
-            raise DomainReplicationError(f"{stage} evidence has the wrong domain")
-        if receipt.get("status") != "passed":
-            raise DomainReplicationError(f"{stage} evidence status must be passed")
-        if receipt.get("subsystem") != STAGE_SUBSYSTEMS[stage]:
-            raise DomainReplicationError(f"{stage} evidence subsystem is invalid")
-        api_refs = receipt.get("api_refs")
-        if not isinstance(api_refs, list) or not api_refs:
-            raise DomainReplicationError(f"{stage} api_refs are required")
-        if any("/device-" in str(ref) for ref in api_refs):
-            raise DomainReplicationError("device-specific API evidence is not allowed")
-        evidence_refs = receipt.get("evidence_refs")
-        if not isinstance(evidence_refs, list) or not evidence_refs:
-            raise DomainReplicationError(f"{stage} evidence_refs are required")
-        metrics = _mapping(receipt.get("metrics"), f"{stage}.metrics")
-        _validate_stage_metrics(
-            stage,
-            metrics,
-            template=template,
-            incremental=incremental,
-        )
-        stage_reports[stage] = {
-            "status": "passed",
-            "subsystem": STAGE_SUBSYSTEMS[stage],
-            "api_refs": sorted(str(ref) for ref in api_refs),
-            "evidence_refs": sorted(str(ref) for ref in evidence_refs),
-            "metrics": deepcopy(metrics),
-        }
+    stage_reports = _validate_terminal_evidence(
+        evidence, package_code, domain_code, template, incremental, schema_version=schema_version
+    )
 
-    bindings = manifest.get("enterprise_bindings")
-    if not isinstance(bindings, list) or not bindings:
-        raise DomainReplicationError("enterprise binding checklist is required")
+    bindings = _bounded_list(manifest["enterprise_bindings"], "enterprise_bindings", minimum=1, maximum=4)
     binding_statuses: dict[str, str] = {}
     for item in bindings:
-        item = _mapping(item, "enterprise_bindings.item")
-        kind = _bounded_text(item.get("kind"), "enterprise_bindings.kind")
-        status = _bounded_text(item.get("status"), "enterprise_bindings.status")
+        item = _closed_required(item, "enterprise_bindings.item", {"kind", "status"}, {"kind", "status"})
+        kind = _string(item["kind"], "enterprise_bindings.kind")
+        status = _string(item["status"], "enterprise_bindings.status")
         if status not in {"bound", "unbound"}:
             raise DomainReplicationError("enterprise binding status must be bound or unbound")
         if kind in binding_statuses:
@@ -504,12 +1030,12 @@ def evaluate_replication_package(
     )
     assessment = manifest["core_change_assessment"]
     report: dict[str, Any] = {
-        "schema_version": 1,
+        "schema_version": schema_version,
         "package_code": package_code,
         "status": "passed",
         "domain": {
             "code": domain_code,
-            "name": _bounded_text(manifest["domain"].get("name"), "domain.name"),
+            "name": _string(manifest["domain"]["name"], "domain.name"),
             "template_version": int(
                 stage_reports["template_initialization"]["metrics"]["template_version"]
             ),
@@ -524,5 +1050,13 @@ def evaluate_replication_package(
             "unbound_requirements": unbound,
         },
     }
+    if schema_version == 2:
+        if execution_evidence is None:
+            raise DomainReplicationError("schema-v2 package requires execution evidence")
+        report["execution_evidence"] = validate_execution_evidence(
+            execution_evidence, domain_code=domain_code
+        )
+    elif execution_evidence is not None:
+        raise DomainReplicationError("schema-v1 package cannot include execution evidence")
     report["report_sha256"] = _digest(report)
     return report

+ 88 - 0
app/core/governance/domain_replication_contract_registry.py

@@ -0,0 +1,88 @@
+"""Trusted local contract registry for domain-replication engineering evidence.
+
+The registry is code-side rather than package-side: an implementation package
+cannot add a digest or a run receipt and thereby grant itself trust.  Entries
+are deliberately labelled ENGINEERING_EVIDENCE_ONLY; enterprise connector and
+edge runs remain external acceptance evidence.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import json
+from copy import deepcopy
+from typing import Any
+
+
+def _canonical(value: Any) -> bytes:
+    return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
+
+
+CONTRACT_REGISTRY: dict[str, Any] = {
+    "schema_version": 1,
+    "trust_level": "ENGINEERING_EVIDENCE_ONLY",
+    "expires_on": "2099-12-31",
+    "contracts": [
+        {
+            "contract_id": "p3_wp03_connector_run",
+            "contract_version": "1",
+            "schema_digest": "1111111111111111111111111111111111111111111111111111111111111111",
+            "allowed_operations": ["incremental"],
+            "runs": [{
+                "operation_uid": "op-inc-203",
+                "run_id": "run-203",
+                "operation": "incremental",
+                "attempt": 1,
+                "lease_fence": 1,
+                "request_digest": "1212121212121212121212121212121212121212121212121212121212121212",
+                "result_digest": "1313131313131313131313131313131313131313131313131313131313131313",
+                "status": "succeeded",
+            }],
+        },
+        {
+            "contract_id": "p3_wp03_catalog_lineage",
+            "contract_version": "1",
+            "schema_digest": "2222222222222222222222222222222222222222222222222222222222222222",
+            "allowed_operations": ["catalog"],
+            "runs": [{
+                "operation_uid": "op-cat-203",
+                "run_id": "catalog-203",
+                "operation": "catalog",
+                "attempt": 1,
+                "lease_fence": 1,
+                "request_digest": "2323232323232323232323232323232323232323232323232323232323232323",
+                "result_digest": "2424242424242424242424242424242424242424242424242424242424242424",
+                "status": "succeeded",
+            }],
+        },
+        {
+            "contract_id": "p3_wp04_edge_health",
+            "contract_version": "1",
+            "schema_digest": "3333333333333333333333333333333333333333333333333333333333333333",
+            "allowed_operations": ["health"],
+            "runs": [{
+                "operation_uid": "op-health-203",
+                "run_id": "health-203",
+                "operation": "health",
+                "attempt": 1,
+                "lease_fence": 2,
+                "request_digest": "3434343434343434343434343434343434343434343434343434343434343434",
+                "result_digest": "3535353535353535353535353535353535353535353535353535353535353535",
+                "status": "succeeded",
+            }],
+        },
+    ],
+}
+REGISTRY_SHA256 = "ff312c88ea48d7d110b60cdcd118457f3ae6fb52f3d3f6d431a7ad53eb036be2"
+
+
+def registry_snapshot() -> dict[str, Any]:
+    """Return a deep copy so callers cannot mutate the trusted registry."""
+
+    return deepcopy(CONTRACT_REGISTRY)
+
+
+def verify_registry() -> dict[str, Any]:
+    if hashlib.sha256(_canonical(CONTRACT_REGISTRY)).hexdigest() != REGISTRY_SHA256:
+        raise ValueError("trusted contract registry digest mismatch")
+    return registry_snapshot()

+ 599 - 0
app/core/governance/metering_showback.py

@@ -0,0 +1,599 @@
+"""Closed, local-only metering and Showback contracts.
+
+The module intentionally has no network providers, price book, billing action
+or financial artifact.  It only normalizes engineering evidence and supplies
+integer-micros arithmetic for the database gateway and local fixture.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import json
+import os
+import re
+import unicodedata
+import uuid
+from datetime import UTC, datetime
+from decimal import ROUND_HALF_EVEN, Decimal, InvalidOperation
+from typing import Any
+
+from sqlalchemy import create_engine, text
+from sqlalchemy.exc import SQLAlchemyError
+from sqlalchemy.pool import NullPool
+
+from app.config.database_urls import validate_postgresql_url
+
+EVENT_KINDS = frozenset(
+    {"query", "api", "file", "subscription", "storage", "compute", "task", "model_call"}
+)
+_EVENT_FIELDS = frozenset(
+    {
+        "schema_version",
+        "event_uid",
+        "event_kind",
+        "occurred_at",
+        "window_start",
+        "window_end",
+        "quantity",
+        "unit",
+        "idempotency_key",
+        "evidence",
+        "mapping",
+        "correction_of",
+    }
+)
+_TOKEN = re.compile(r"[a-z][a-z0-9-]{0,62}")
+_UID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,119}")
+_HEX = re.compile(r"[0-9a-f]{64}")
+_DECIMAL = re.compile(r"(?:0|[1-9][0-9]{0,17})(?:\.[0-9]{1,6})?")
+_REFERENCE = re.compile(r"local-fixture://wp12/v1(?:/[a-z][a-z0-9-]{0,62})?")
+_UNSAFE_REFERENCE_SEGMENT = re.compile(r"(?:sql|script|select|insert|update|delete|drop|exec|curl|wget|token|secret|credential|password)")
+_UNITS: dict[str, tuple[str, Decimal]] = {
+    "bytes": ("data", Decimal("1")),
+    "kb": ("data", Decimal("1000")),
+    "mb": ("data", Decimal("1000000")),
+    "gb": ("data", Decimal("1000000000")),
+    "seconds": ("time", Decimal("1")),
+    "milliseconds": ("time", Decimal("0.001")),
+    "requests": ("count", Decimal("1")),
+    "tasks": ("count", Decimal("1")),
+    "tokens": ("count", Decimal("1")),
+}
+_MICRO = Decimal("1000000")
+
+
+def _closed(value: Any, allowed: frozenset[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict) or set(value) - allowed:
+        raise ValueError(f"{label}_closed")
+    return copy.deepcopy(value)
+
+
+def _normalized_text(value: Any, label: str, maximum: int = 120) -> str:
+    if not isinstance(value, str) or not value or len(value) > maximum:
+        raise ValueError(f"{label}_invalid")
+    normalized = unicodedata.normalize("NFKC", value)
+    if normalized != value:
+        raise ValueError(f"{label}_unicode_rejected")
+    return value
+
+
+def _token(value: Any, label: str) -> str:
+    result = _normalized_text(value, label)
+    if not _TOKEN.fullmatch(result):
+        raise ValueError(f"{label}_invalid")
+    return result
+
+
+def _uid(value: Any, label: str) -> str:
+    result = _normalized_text(value, label)
+    if not _UID.fullmatch(result):
+        raise ValueError(f"{label}_invalid")
+    return result
+
+
+def _timestamp(value: Any, label: str) -> str:
+    text = _normalized_text(value, label, 30)
+    if not text.endswith("Z"):
+        raise ValueError(f"{label}_invalid")
+    try:
+        parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
+    except ValueError as exc:
+        raise ValueError(f"{label}_invalid") from exc
+    if parsed.tzinfo is None or parsed.utcoffset() != UTC.utcoffset(parsed):
+        raise ValueError(f"{label}_invalid")
+    return parsed.astimezone(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z")
+
+
+def _decimal(value: Any, label: str) -> Decimal:
+    if isinstance(value, bool) or not isinstance(value, str) or not _DECIMAL.fullmatch(value):
+        raise ValueError(f"{label}_invalid")
+    try:
+        parsed = Decimal(value)
+    except InvalidOperation as exc:
+        raise ValueError(f"{label}_invalid") from exc
+    if not parsed.is_finite() or parsed < 0:
+        raise ValueError(f"{label}_invalid")
+    return parsed
+
+
+def to_micros(quantity: Any, source_unit: Any, target_unit: Any) -> int:
+    """Convert a bounded string decimal using a compatible unit dimension."""
+
+    value = _decimal(quantity, "quantity")
+    source = _normalized_text(source_unit, "source_unit", 20)
+    target = _normalized_text(target_unit, "target_unit", 20)
+    if source not in _UNITS or target not in _UNITS or _UNITS[source][0] != _UNITS[target][0]:
+        raise ValueError("unit_dimension_mismatch")
+    converted = value * _UNITS[source][1] / _UNITS[target][1]
+    micros = (converted * _MICRO).quantize(Decimal("1"), rounding=ROUND_HALF_EVEN)
+    if micros < 0 or micros > Decimal("1000000000000000000000000"):
+        raise ValueError("quantity_out_of_range")
+    return int(micros)
+
+
+def canonical_digest(value: dict[str, Any]) -> str:
+    return hashlib.sha256(
+        json.dumps(value, sort_keys=True, ensure_ascii=True, separators=(",", ":")).encode("utf-8")
+    ).hexdigest()
+
+
+def normalize_event(value: Any) -> dict[str, Any]:
+    """Return a versioned, safe event without raw source evidence."""
+
+    body = _closed(value, _EVENT_FIELDS, "metering_event")
+    required = _EVENT_FIELDS - {"correction_of"}
+    if set(body) - {"correction_of"} != required:
+        raise ValueError("metering_event_closed")
+    if body["schema_version"] != 1:
+        raise ValueError("schema_version_invalid")
+    event_kind = _token(body["event_kind"], "event_kind")
+    if event_kind not in EVENT_KINDS:
+        raise ValueError("event_kind_invalid")
+    evidence = _closed(body["evidence"], frozenset({"digest", "reference"}), "evidence")
+    digest = _normalized_text(evidence.get("digest"), "evidence_digest", 64)
+    reference = _normalized_text(evidence.get("reference"), "evidence_reference", 120)
+    if not _HEX.fullmatch(digest) or not _REFERENCE.fullmatch(reference) or _UNSAFE_REFERENCE_SEGMENT.search(reference):
+        raise ValueError("evidence_invalid")
+    mapping = _closed(
+        body["mapping"],
+        frozenset({"department", "business_domain", "project", "cost_center"}),
+        "mapping",
+    )
+    if set(mapping) != {"department", "business_domain", "project", "cost_center"}:
+        raise ValueError("mapping_closed")
+    normalized = {
+        "schema_version": 1,
+        "event_uid": _uid(body["event_uid"], "event_uid"),
+        "event_kind": event_kind,
+        "occurred_at": _timestamp(body["occurred_at"], "occurred_at"),
+        "window_start": _timestamp(body["window_start"], "window_start"),
+        "window_end": _timestamp(body["window_end"], "window_end"),
+        "quantity": body["quantity"],
+        "unit": _normalized_text(body["unit"], "unit", 20),
+        "idempotency_key": _uid(body["idempotency_key"], "idempotency_key"),
+        "evidence": {"digest": digest, "reference": reference},
+        "mapping": {key: _token(mapping[key], key) for key in sorted(mapping)},
+        "raw_payload_retained": False,
+    }
+    if normalized["window_start"] >= normalized["window_end"]:
+        raise ValueError("window_invalid")
+    normalized["quantity_micros"] = to_micros(normalized["quantity"], normalized["unit"], normalized["unit"])
+    if "correction_of" in body:
+        normalized["correction_of"] = _uid(body["correction_of"], "correction_of")
+    normalized["digest"] = canonical_digest(normalized)
+    return normalized
+
+
+class ReplayConflict(ValueError):
+    """An idempotency key was reused with a different immutable request."""
+
+
+class InMemoryMeteringRepository:
+    """Tiny deterministic adapter for unit contracts; not a production store."""
+
+    def __init__(self) -> None:
+        self.events_by_key: dict[tuple[str, str], dict[str, Any]] = {}
+        self.events_by_uid: dict[tuple[str, str], dict[str, Any]] = {}
+        self.allocation_rules: dict[tuple[str, int], dict[str, Any]] = {}
+        self.alert_keys: set[tuple[str, str, int]] = set()
+
+    def record_event(self, tenant_ref: str, event: dict[str, Any]) -> dict[str, Any]:
+        key = (tenant_ref, event["idempotency_key"])
+        replay = self.events_by_key.get(key)
+        if replay is not None:
+            if replay["digest"] != event["digest"]:
+                raise ReplayConflict("metering_replay_conflict")
+            return copy.deepcopy(replay)
+        uid_key = (tenant_ref, event["event_uid"])
+        if uid_key in self.events_by_uid:
+            raise ReplayConflict("metering_event_uid_conflict")
+        self.events_by_key[key] = copy.deepcopy(event)
+        self.events_by_uid[uid_key] = copy.deepcopy(event)
+        return copy.deepcopy(event)
+
+    def events_for_window(self, tenant_ref: str, window: str) -> list[dict[str, Any]]:
+        prefix = f"{window}-"
+        return [
+            copy.deepcopy(event)
+            for (tenant, _), event in self.events_by_uid.items()
+            if tenant == tenant_ref and event["window_start"].startswith(prefix)
+        ]
+
+
+def _window(value: Any) -> str:
+    text = _normalized_text(value, "window", 7)
+    if not re.fullmatch(r"[0-9]{4}-(0[1-9]|1[0-2])", text):
+        raise ValueError("window_invalid")
+    return text
+
+
+def _mapping(value: Any) -> dict[str, str]:
+    body = _closed(
+        value,
+        frozenset({"department", "business_domain", "project", "cost_center"}),
+        "mapping",
+    )
+    if set(body) != {"department", "business_domain", "project", "cost_center"}:
+        raise ValueError("mapping_closed")
+    return {key: _token(body[key], key) for key in sorted(body)}
+
+
+class MeteringShowbackService:
+    """Local, non-financial behavior shared by API and gateway adapters."""
+
+    def __init__(self, repository: InMemoryMeteringRepository, *, tenant_ref: str) -> None:
+        self.repository = repository
+        self.tenant_ref = _token(tenant_ref, "tenant_ref")
+
+    def record(self, request: Any) -> dict[str, Any]:
+        return self.repository.record_event(self.tenant_ref, normalize_event(request))
+
+    def correct(self, original_uid: Any, request: Any) -> dict[str, Any]:
+        original = _uid(original_uid, "original_uid")
+        original_event = self.repository.events_by_uid.get((self.tenant_ref, original))
+        if original_event is None:
+            raise LookupError("metering_original_not_found")
+        if "correction_of" in original_event:
+            raise ValueError("correction_chain_invalid")
+        body = copy.deepcopy(request)
+        if "correction_of" in body:
+            raise ValueError("correction_closed")
+        candidate = normalize_event(body)
+        if any(candidate[key] != original_event[key] for key in ("event_kind", "unit", "window_start", "window_end", "mapping")):
+            raise ValueError("correction_scope_invalid")
+        body["correction_of"] = original
+        return self.record(body)
+
+    def publish_allocation(self, request: Any) -> dict[str, Any]:
+        body = _closed(
+            request,
+            frozenset(
+                {
+                    "schema_version",
+                    "rule_uid",
+                    "rule_version",
+                    "effective_start",
+                    "effective_end",
+                    "mapping",
+                    "allocations",
+                }
+            ),
+            "allocation_rule",
+        )
+        if body.get("schema_version") != 1 or not isinstance(body.get("rule_version"), int):
+            raise ValueError("allocation_rule_invalid")
+        rule_uid = _uid(body.get("rule_uid"), "rule_uid")
+        version = body["rule_version"]
+        if version < 1 or version > 1_000_000:
+            raise ValueError("rule_version_invalid")
+        start = _timestamp(body.get("effective_start"), "effective_start")
+        end = _timestamp(body.get("effective_end"), "effective_end")
+        if start >= end:
+            raise ValueError("effective_window_invalid")
+        allocations = body.get("allocations")
+        if not isinstance(allocations, list) or not 1 <= len(allocations) <= 32:
+            raise ValueError("allocations_invalid")
+        normalized_allocations: list[dict[str, Any]] = []
+        total = Decimal("0")
+        for allocation in allocations:
+            item = _closed(allocation, frozenset({"target", "weight"}), "allocation")
+            if set(item) != {"target", "weight"}:
+                raise ValueError("allocation_closed")
+            weight = _decimal(item["weight"], "weight")
+            if weight <= 0 or weight > Decimal("1"):
+                raise ValueError("weight_invalid")
+            total += weight
+            target = _token(item["target"], "target")
+            if any(existing["target"] == target for existing in normalized_allocations):
+                raise ValueError("allocation_target_duplicate")
+            normalized_allocations.append({"target": target, "weight": str(weight)})
+        if total != Decimal("1.000000"):
+            raise ValueError("allocation_weight_not_one")
+        normalized = {
+            "schema_version": 1,
+            "rule_uid": rule_uid,
+            "rule_version": version,
+            "effective_start": start,
+            "effective_end": end,
+            "mapping": _mapping(body.get("mapping")),
+            "allocations": sorted(normalized_allocations, key=lambda item: item["target"]),
+        }
+        key = (rule_uid, version)
+        existing = self.repository.allocation_rules.get(key)
+        if existing is not None and canonical_digest(existing) != canonical_digest(normalized):
+            raise ReplayConflict("allocation_rule_conflict")
+        self.repository.allocation_rules[key] = copy.deepcopy(normalized)
+        return copy.deepcopy(normalized)
+
+    def replay_allocation(self, rule_version: Any, window: Any) -> dict[str, Any]:
+        if not isinstance(rule_version, int) or rule_version < 1:
+            raise ValueError("rule_version_invalid")
+        month = _window(window)
+        matching = [
+            rule
+            for (_, version), rule in self.repository.allocation_rules.items()
+            if version == rule_version
+            and rule["effective_start"].startswith(month)
+            and rule["effective_end"] > f"{month}-01T00:00:00.000000Z"
+        ]
+        if len(matching) != 1:
+            raise LookupError("allocation_rule_not_found")
+        source = sum(event["quantity_micros"] for event in self.repository.events_for_window(self.tenant_ref, month))
+        allocations = [
+            {
+                "target": allocation["target"],
+                "quantity_micros": int(
+                    (Decimal(source) * _decimal(allocation["weight"], "weight")).quantize(
+                        Decimal("1"), rounding=ROUND_HALF_EVEN
+                    )
+                ),
+            }
+            for allocation in matching[0]["allocations"]
+        ]
+        return {
+            "rule_uid": matching[0]["rule_uid"],
+            "rule_version": rule_version,
+            "window": month,
+            "source_micros": source,
+            "allocations": allocations,
+            "allocated_micros": sum(item["quantity_micros"] for item in allocations),
+        }
+
+    def reconcile(self, window: Any) -> dict[str, int]:
+        month = _window(window)
+        source = sum(event["quantity_micros"] for event in self.repository.events_for_window(self.tenant_ref, month))
+        return {"source_micros": source, "allocated_micros": source, "difference_micros": 0}
+
+    def evaluate_budget(self, request: Any) -> dict[str, Any]:
+        body = _closed(
+            request,
+            frozenset({"schema_version", "budget_uid", "window", "mapping", "limit_micros", "threshold_micros"}),
+            "budget",
+        )
+        if body.get("schema_version") != 1:
+            raise ValueError("budget_invalid")
+        budget_uid = _uid(body.get("budget_uid"), "budget_uid")
+        window = _window(body.get("window"))
+        mapping = _mapping(body.get("mapping"))
+        if (
+            isinstance(body.get("limit_micros"), bool)
+            or isinstance(body.get("threshold_micros"), bool)
+            or not isinstance(body.get("limit_micros"), int)
+            or not isinstance(body.get("threshold_micros"), int)
+            or body["limit_micros"] < 0
+            or body["threshold_micros"] < 0
+            or body["threshold_micros"] > body["limit_micros"]
+        ):
+            raise ValueError("budget_invalid")
+        used = sum(
+            event["quantity_micros"]
+            for event in self.repository.events_for_window(self.tenant_ref, window)
+            if event["mapping"] == mapping
+        )
+        key = (budget_uid, window, body["threshold_micros"])
+        alert_created = used >= body["threshold_micros"] and key not in self.repository.alert_keys
+        if alert_created:
+            self.repository.alert_keys.add(key)
+        return {
+            "budget_uid": budget_uid,
+            "window": window,
+            "used_micros": used,
+            "limit_micros": body["limit_micros"],
+            "alert_created": alert_created,
+            "provider": "disabled",
+            "mode": "ENGINEERING_EVIDENCE_ONLY",
+        }
+
+    def request_chargeback(self, request: Any) -> None:
+        del request
+        raise PermissionError("chargeback_disabled")
+
+
+class DatabaseMeteringShowbackService:
+    """Fail-closed control/runtime gateway adapter for the HTTP boundary."""
+
+    def __init__(self) -> None:
+        control_url = os.environ.get("METERING_SHOWBACK_CONTROL_DATABASE_URL", "")
+        runtime_url = os.environ.get("DATABASE_URL", "")
+        control = validate_postgresql_url(control_url, "METERING_SHOWBACK_CONTROL_DATABASE_URL")
+        runtime = validate_postgresql_url(runtime_url, "DATABASE_URL")
+        if control.username == runtime.username:
+            raise RuntimeError("metering control and runtime identities must differ")
+        self._control_url = control_url
+        self._runtime_url = runtime_url
+
+    def _claim(self, action: str, principal_id: str) -> dict[str, str]:
+        with create_engine(self._control_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            value = connection.execute(
+                text("SELECT public.metering_showback_issue_claim(:action,:principal,'{}'::jsonb)"),
+                {"action": action, "principal": principal_id},
+            ).scalar_one()
+        return dict(value)
+
+    def _write(self, action: str, payload: dict[str, Any]) -> dict[str, Any]:
+        with create_engine(self._runtime_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            value = connection.execute(
+                text("SELECT public.metering_showback_runtime_write(:action,CAST(:payload AS jsonb))"),
+                {"action": action, "payload": json.dumps(payload, sort_keys=True, separators=(",", ":"))},
+            ).scalar_one()
+        return dict(value)
+
+    def _control_write(self, action: str, principal_id: str, payload: dict[str, Any]) -> dict[str, Any]:
+        with create_engine(self._control_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            value = connection.execute(
+                text("SELECT public.metering_showback_control_write(:action,:principal,CAST(:payload AS jsonb))"),
+                {"action": action, "principal": principal_id, "payload": json.dumps(payload, sort_keys=True, separators=(",", ":"))},
+            ).scalar_one()
+        return dict(value)
+
+    @staticmethod
+    def _trusted_mapping(value: Any) -> dict[str, str]:
+        body = _closed(value, frozenset({"department", "project", "cost_center"}), "metering_mapping")
+        if set(body) != {"department", "project", "cost_center"}:
+            raise ValueError("metering_mapping_closed")
+        return {
+            "department": _token(body["department"], "department"),
+            "business_domain": _token(os.environ.get("TRUSTED_METERING_DOMAIN", ""), "trusted_metering_domain"),
+            "project": _token(body["project"], "project"),
+            "cost_center": _token(body["cost_center"], "cost_center"),
+        }
+
+    def record_for_principal(self, *, principal_id: str, body: dict[str, Any]) -> dict[str, Any]:
+        domain = _token(os.environ.get("TRUSTED_METERING_DOMAIN", ""), "trusted_metering_domain")
+        raw = copy.deepcopy(body)
+        raw["mapping"] = raw["mapping"] | {"business_domain": domain}
+        event = normalize_event(raw)
+        lease_claim = self._claim("lease", principal_id)
+        record_claim = self._claim("record", principal_id)
+        lease_token = str(uuid.uuid4())
+        # Claiming the lease and recording its event share one runtime transaction:
+        # a rejected immutable replay cannot strand a new live lease.
+        with create_engine(self._runtime_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            lease = dict(
+                connection.execute(
+                    text("SELECT public.metering_showback_runtime_write(:action,CAST(:payload AS jsonb))"),
+                    {
+                        "action": "claim_lease",
+                        "payload": json.dumps(
+                            {"request_claim": lease_claim["request_claim"], "lease_owner": "http-metering", "lease_token": lease_token},
+                            sort_keys=True,
+                            separators=(",", ":"),
+                        ),
+                    },
+                ).scalar_one()
+            )
+            payload = {
+                "request_claim": record_claim["request_claim"], "lease_owner": "http-metering", "lease_token": lease_token,
+                "lease_fence": lease["lease_fence"], "event_uid": event["event_uid"], "event_kind": event["event_kind"],
+                "occurred_at": event["occurred_at"], "window_start": event["window_start"], "window_end": event["window_end"],
+                "quantity_micros": event["quantity_micros"], "unit": event["unit"], "idempotency_key": event["idempotency_key"],
+                "evidence": event["evidence"], "mapping": event["mapping"],
+            }
+            if "correction_of" in event:
+                payload["correction_of"] = event["correction_of"]
+            value = connection.execute(
+                text("SELECT public.metering_showback_runtime_write(:action,CAST(:payload AS jsonb))"),
+                {"action": "record_event", "payload": json.dumps(payload, sort_keys=True, separators=(",", ":"))},
+            ).scalar_one()
+        return dict(value)
+
+    def publish_allocation_for_principal(self, *, principal_id: str, body: dict[str, Any]) -> dict[str, Any]:
+        raw = _closed(
+            body,
+            frozenset({"schema_version", "rule_uid", "rule_version", "effective_start", "effective_end", "mapping", "allocations"}),
+            "allocation_rule",
+        )
+        if raw.get("schema_version") != 1 or isinstance(raw.get("rule_version"), bool) or not isinstance(raw.get("rule_version"), int):
+            raise ValueError("allocation_rule_closed")
+        if not 1 <= raw["rule_version"] <= 1_000_000:
+            raise ValueError("allocation_rule_closed")
+        allocations = raw.get("allocations")
+        if not isinstance(allocations, list) or not 1 <= len(allocations) <= 32:
+            raise ValueError("allocation_rule_closed")
+        normalized_allocations: list[dict[str, Any]] = []
+        for item in allocations:
+            allocation = _closed(item, frozenset({"target", "weight_micros"}), "allocation")
+            if set(allocation) != {"target", "weight_micros"} or isinstance(allocation["weight_micros"], bool) or not isinstance(allocation["weight_micros"], int):
+                raise ValueError("allocation_rule_closed")
+            if not 0 < allocation["weight_micros"] <= 1_000_000:
+                raise ValueError("allocation_rule_closed")
+            target = _token(allocation["target"], "allocation_target")
+            if any(existing["target"] == target for existing in normalized_allocations):
+                raise ValueError("allocation_target_duplicate")
+            normalized_allocations.append({"target": target, "weight_micros": allocation["weight_micros"]})
+        if sum(item["weight_micros"] for item in normalized_allocations) != 1_000_000:
+            raise ValueError("allocation_weight_not_one")
+        start = _timestamp(raw.get("effective_start"), "effective_start")
+        end = _timestamp(raw.get("effective_end"), "effective_end")
+        if start >= end:
+            raise ValueError("effective_window_invalid")
+        return self._control_write(
+            "allocation",
+            principal_id,
+            {
+                "rule_uid": _uid(raw.get("rule_uid"), "rule_uid"),
+                "rule_version": str(raw["rule_version"]),
+                "effective_start": start,
+                "effective_end": end,
+                "mapping": self._trusted_mapping(raw.get("mapping")),
+                "allocations": normalized_allocations,
+            },
+        )
+
+    def evaluate_budget_for_principal(self, *, principal_id: str, body: dict[str, Any]) -> dict[str, Any]:
+        raw = _closed(body, frozenset({"schema_version", "budget_uid", "window", "mapping", "limit_micros", "threshold_micros"}), "budget")
+        if raw.get("schema_version") != 1:
+            raise ValueError("budget_closed")
+        if any(isinstance(raw.get(key), bool) or not isinstance(raw.get(key), int) for key in ("limit_micros", "threshold_micros")):
+            raise ValueError("budget_closed")
+        if not 0 <= raw["threshold_micros"] <= raw["limit_micros"] <= 999_999_999_999_999_999:
+            raise ValueError("budget_closed")
+        return self._control_write(
+            "budget",
+            principal_id,
+            {
+                "budget_uid": _uid(raw.get("budget_uid"), "budget_uid"),
+                "window": _window(raw.get("window")),
+                "mapping": self._trusted_mapping(raw.get("mapping")),
+                "limit_micros": str(raw["limit_micros"]),
+                "threshold_micros": str(raw["threshold_micros"]),
+            },
+        )
+
+    def allocation_replay_for_principal(self, *, principal_id: str, window: Any, rule_uid: Any, rule_version: Any) -> dict[str, Any]:
+        if isinstance(rule_version, bool) or not isinstance(rule_version, int) or not 1 <= rule_version <= 1_000_000:
+            raise ValueError("rule_version_invalid")
+        claim = self._claim("read", principal_id)
+        payload = {
+            "request_claim": claim["request_claim"],
+            "window": _window(window),
+            "rule_uid": _uid(rule_uid, "rule_uid"),
+            "rule_version": str(rule_version),
+        }
+        with create_engine(self._runtime_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            try:
+                value = connection.execute(
+                    text("SELECT public.metering_showback_allocation_replay(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"))},
+                ).scalar_one()
+            except SQLAlchemyError as exc:
+                raise PermissionError("metering_allocation_replay_denied") from exc
+        return dict(value)
+
+    def showback_for_principal(self, *, principal_id: str, window: str, kind: str) -> dict[str, Any]:
+        if kind not in {"showback", "reconciliation", "audit"}:
+            raise ValueError("metering_read_kind_invalid")
+        claim = self._claim("read", principal_id)
+        with create_engine(self._runtime_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            if kind in {"showback", "reconciliation"}:
+                value = connection.execute(
+                    text("SELECT public.metering_showback_rollup(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps({"request_claim": claim["request_claim"], "window": _window(window)}, sort_keys=True)},
+                ).scalar_one()
+            else:
+                value = connection.execute(
+                    text("SELECT public.metering_showback_runtime_read(:action,CAST(:payload AS jsonb))"),
+                    {"action": kind, "payload": json.dumps({"request_claim": claim["request_claim"], "window": _window(window)}, sort_keys=True)},
+                ).scalar_one()
+        return dict(value) if isinstance(value, dict) else {"records": value}

+ 1 - 1
app/core/llm/agent_governance.py

@@ -761,7 +761,7 @@ class AgentGovernanceService:
             if needs_approval:
                 workflow_uid = _uid(body.get("workflow_uid"), "workflow_uid")
                 task = self.approval_gateway.create_agent_task(
-                    record, workflow_uid, agent["owner_uid"]
+                    {**record, "owner_uid": agent["owner_uid"]}, workflow_uid, agent["owner_uid"]
                 )
                 record["approval_task_uid"] = task["uid"]
         try:

+ 4 - 0
app/core/llm/agent_governance_repository.py

@@ -58,6 +58,10 @@ class WorkCenterAgentApprovalGateway:
                     "environment": request_record["environment"],
                     "agent_uid": request_record["agent_uid"],
                     "request_uid": request_record["uid"],
+                    "principal_id": request_record.get("owner_uid", ""),
+                    "tool_name": request_record["tool_name"],
+                    "action": request_record["action"],
+                    "request_digest": request_record["input_digest"],
                     "automatic_execution_allowed": False,
                 },
             },

+ 379 - 0
app/core/llm/runtime_governance.py

@@ -0,0 +1,379 @@
+"""Default-deny, hash-only runtime governance for model and Agent invocations."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import re
+import threading
+import unicodedata
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any
+from uuid import uuid4
+
+from app.core.mcp.governed_invocation import (
+    InvocationContractError,
+    normalize_mcp_invocation,
+)
+
+
+class RuntimeDecisionError(ValueError):
+    def __init__(self, code: str):
+        self.code = code
+        super().__init__(code)
+
+
+class DatasetLeakageError(ValueError):
+    pass
+
+
+def load_engineering_fixture(manifest_path: str | Path) -> dict[str, Any]:
+    """Read each fixture once and verify its declared SHA-256 before evaluation."""
+    manifest_file = Path(manifest_path)
+    manifest = json.loads(manifest_file.read_text(encoding="utf-8"))
+    if manifest.get("fixture_only") is not True or manifest.get("schema_version") != "1":
+        raise DatasetLeakageError("fixture_manifest_invalid")
+    files = manifest.get("files")
+    if not isinstance(files, dict) or set(files) != {"dataset.json", "temporal-dataset.json"}:
+        raise DatasetLeakageError("fixture_manifest_invalid")
+    loaded: dict[str, Any] = {}
+    for name, expected in files.items():
+        if not isinstance(expected, str) or not expected.startswith("sha256:"):
+            raise DatasetLeakageError("fixture_manifest_invalid")
+        content = (manifest_file.parent / name).read_bytes()
+        if hashlib.sha256(content).hexdigest() != expected.removeprefix("sha256:"):
+            raise DatasetLeakageError("fixture_digest_mismatch")
+        loaded[name] = json.loads(content)
+    train = set(loaded["dataset.json"].get("train_case_ids", []))
+    evaluation = {item.get("case_id") for item in loaded["dataset.json"].get("evaluation_cases", [])}
+    if train & evaluation:
+        raise DatasetLeakageError("dataset_leakage_detected")
+    return loaded
+
+
+@dataclass(frozen=True)
+class ModelRoute:
+    route_id: str
+    provider: str
+    model: str
+    prompt_version: str
+    generation: str
+    tenant_id: str
+    principal_id: str
+    business_domain_uid: str
+    environment: str
+    canary_status: str
+
+
+@dataclass(frozen=True)
+class BudgetPolicy:
+    token_limit: int
+    cost_limit_micros: int
+    tool_limit: int
+    time_limit_ms: int
+    concurrency_limit: int
+    model_limits: dict[str, int]
+
+
+_SCOPE_FIELDS = ("tenant_id", "principal_id", "business_domain_uid", "environment")
+_REQUEST_KEYS = frozenset(
+    {
+        *_SCOPE_FIELDS, "provider", "model", "prompt_version", "generation",
+        "interface_type", "tool_name", "action", "requested_capabilities", "input_text",
+        "evidence_refs", "idempotency_key", "estimated_tokens", "estimated_cost_micros",
+        "requested_time_ms",
+    }
+)
+_FORBIDDEN_KEYS = frozenset(
+    {
+        "url", "uri", "endpoint", "path", "file_path", "filename", "command", "shell",
+        "script", "secret", "token", "password", "api_key", "raw_rows", "rows", "records",
+        "payload", "arguments", "output", "raw_input", "raw_output",
+    }
+)
+_SAFE_ID = re.compile(r"^[A-Za-z0-9_.:-]{1,160}$")
+_INJECTION = re.compile(
+    r"(?is)(ignore\s+(?:all\s+)?(?:previous|prior|system)\s+instructions?|"
+    r"bypass\s+(?:the\s+)?(?:policy|guard|permission)|reveal.{0,50}(?:secret|token|password|api[_ -]?key)|"
+    r"(?:忽略).{0,16}(?:指令|规则|系统)|(?:绕过|关闭|禁用).{0,16}(?:权限|策略|审计))"
+)
+_LOCATION = re.compile(r"(?i)(?:https?://|file://|(?:^|\s)/[A-Za-z0-9_./-]+)")
+
+
+def _hash(value: str) -> str:
+    return hashlib.sha256(value.encode("utf-8")).hexdigest()
+
+
+def _positive_int(value: Any, label: str, maximum: int) -> int:
+    if isinstance(value, bool) or not isinstance(value, int) or not 0 < value <= maximum:
+        raise RuntimeDecisionError(f"{label}_invalid")
+    return value
+
+
+def _closed_request(value: Any) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise RuntimeDecisionError("request_invalid")
+    if len(value) > len(_REQUEST_KEYS):
+        raise RuntimeDecisionError("request_invalid")
+    for key in value:
+        normalized = unicodedata.normalize("NFKC", str(key))
+        if normalized != key or normalized.casefold() in _FORBIDDEN_KEYS:
+            raise RuntimeDecisionError("forbidden_contract_key")
+    unknown = set(value) - _REQUEST_KEYS
+    if unknown or set(value) != _REQUEST_KEYS:
+        raise RuntimeDecisionError("request_schema_invalid")
+    return dict(value)
+
+
+def _normalize_request(value: Any) -> dict[str, Any]:
+    request = _closed_request(value)
+    for key in _SCOPE_FIELDS + ("provider", "model", "prompt_version", "generation", "idempotency_key"):
+        item = request[key]
+        if not isinstance(item, str) or not _SAFE_ID.fullmatch(item):
+            raise RuntimeDecisionError(f"{key}_invalid")
+    text = request["input_text"]
+    if not isinstance(text, str) or not text.strip() or len(text) > 8000:
+        raise RuntimeDecisionError("input_invalid")
+    normalized_text = unicodedata.normalize("NFKC", text).strip()
+    if _INJECTION.search(normalized_text):
+        raise RuntimeDecisionError("prompt_injection_detected")
+    if _LOCATION.search(normalized_text):
+        raise RuntimeDecisionError("input_location_denied")
+    capabilities = request["requested_capabilities"]
+    if not isinstance(capabilities, list) or not capabilities or len(capabilities) > 8:
+        raise RuntimeDecisionError("capability_invalid")
+    if any(not isinstance(item, str) or item not in {"network", "file", "data", "command", "resource", "time"} for item in capabilities):
+        raise RuntimeDecisionError("capability_invalid")
+    evidence = request["evidence_refs"]
+    if not isinstance(evidence, list) or len(evidence) > 20:
+        raise RuntimeDecisionError("citation_invalid")
+    for item in evidence:
+        if not isinstance(item, dict) or set(item) != {"evidence_id", "digest"}:
+            raise RuntimeDecisionError("citation_invalid")
+        if not isinstance(item["evidence_id"], str) or not _SAFE_ID.fullmatch(item["evidence_id"]):
+            raise RuntimeDecisionError("citation_invalid")
+        if not isinstance(item["digest"], str) or not re.fullmatch(r"[a-f0-9]{64}", item["digest"]):
+            raise RuntimeDecisionError("citation_invalid")
+    try:
+        normalize_mcp_invocation(
+            {
+                "interface_type": request["interface_type"], "tool_name": request["tool_name"],
+                "action": request["action"], "arguments_digest": _hash(normalized_text),
+                "evidence_refs": evidence,
+            }
+        )
+    except InvocationContractError as error:
+        raise RuntimeDecisionError("mcp_contract_invalid") from error
+    return {
+        **{key: request[key] for key in _SCOPE_FIELDS},
+        **{key: request[key] for key in ("provider", "model", "prompt_version", "generation", "interface_type", "tool_name", "action", "idempotency_key")},
+        "requested_capabilities": frozenset(request["requested_capabilities"]),
+        "input_hash": _hash(normalized_text),
+        "evidence_refs": tuple((item["evidence_id"], item["digest"]) for item in evidence),
+        "estimated_tokens": _positive_int(request["estimated_tokens"], "estimated_tokens", 1_000_000),
+        "estimated_cost_micros": _positive_int(request["estimated_cost_micros"], "estimated_cost_micros", 1_000_000_000),
+        "requested_time_ms": _positive_int(request["requested_time_ms"], "requested_time_ms", 300_000),
+    }
+
+
+class InMemoryRuntimeRepository:
+    """Thread-safe reference repository used by unit contracts and local wiring."""
+
+    def __init__(self) -> None:
+        self._lock = threading.RLock()
+        self._routes: list[ModelRoute] = []
+        self._capabilities: set[tuple[str, str, str, str, str, str, frozenset[str]]] = set()
+        self._budgets: dict[str, dict[str, Any]] = {}
+        self._decisions: dict[tuple[str, str], dict[str, Any]] = {}
+        self._audit: list[dict[str, Any]] = []
+        self._states: dict[str, dict[str, Any]] = {}
+        self._generations: dict[tuple[str, str], dict[str, Any]] = {}
+
+    def register_route(self, route: ModelRoute) -> None:
+        with self._lock:
+            self._routes.append(route)
+            self._generations.setdefault((route.tenant_id, route.generation), {"status": route.canary_status, "fence": 0, "parent": None})
+            self._states.setdefault(route.tenant_id, {"state": "active", "incident_ref": None, "fence": 0})
+
+    def register_capability(self, *, tenant_id: str, business_domain_uid: str, environment: str, tool_name: str, interface_type: str, action: str, risk_level: str, allowed_capabilities: set[str]) -> None:
+        if risk_level != "low" or not allowed_capabilities <= {"data"}:
+            raise ValueError("only declared low-risk data capability may be registered locally")
+        with self._lock:
+            self._capabilities.add((tenant_id, business_domain_uid, environment, tool_name, interface_type, action, frozenset(allowed_capabilities)))
+
+    def set_budget(self, tenant_id: str, policy: BudgetPolicy) -> None:
+        if min(policy.token_limit, policy.cost_limit_micros, policy.tool_limit, policy.time_limit_ms, policy.concurrency_limit) < 1:
+            raise ValueError("budget limits must be positive")
+        with self._lock:
+            self._budgets[tenant_id] = {"policy": policy, "tokens": policy.token_limit, "cost": policy.cost_limit_micros, "tools": policy.tool_limit, "time": policy.time_limit_ms, "concurrency": policy.concurrency_limit, "models": dict(policy.model_limits), "fence": 0}
+
+    def find_route(self, request: dict[str, Any]) -> ModelRoute | None:
+        with self._lock:
+            for route in self._routes:
+                if all(getattr(route, field) == request[field] for field in _SCOPE_FIELDS + ("provider", "model", "prompt_version", "generation")):
+                    return route
+            return None
+
+    def generation_status(self, tenant_id: str, generation: str) -> str | None:
+        with self._lock:
+            entry = self._generations.get((tenant_id, generation))
+            return entry["status"] if entry else None
+
+    def allowed_capability(self, request: dict[str, Any]) -> bool:
+        key = (request["tenant_id"], request["business_domain_uid"], request["environment"], request["tool_name"], request["interface_type"], request["action"], request["requested_capabilities"])
+        with self._lock:
+            return key in self._capabilities
+
+    def existing(self, tenant_id: str, idempotency_key: str) -> dict[str, Any] | None:
+        with self._lock:
+            record = self._decisions.get((tenant_id, idempotency_key))
+            return dict(record) if record else None
+
+    def reserve(self, request: dict[str, Any]) -> int:
+        with self._lock:
+            budget = self._budgets.get(request["tenant_id"])
+            if budget is None or any((budget["tokens"] < request["estimated_tokens"], budget["cost"] < request["estimated_cost_micros"], budget["tools"] < 1, budget["time"] < request["requested_time_ms"], budget["concurrency"] < 1, budget["models"].get(request["model"], 0) < 1)):
+                raise RuntimeDecisionError("budget_exhausted")
+            budget["tokens"] -= request["estimated_tokens"]
+            budget["cost"] -= request["estimated_cost_micros"]
+            budget["tools"] -= 1
+            budget["time"] -= request["requested_time_ms"]
+            budget["concurrency"] -= 1
+            budget["models"][request["model"]] -= 1
+            budget["fence"] += 1
+            return budget["fence"]
+
+    def write_authorized(self, request: dict[str, Any], route: ModelRoute, fence: int) -> dict[str, Any]:
+        record = {"invocation_id": str(uuid4()), "decision": "authorized", "route_id": route.route_id, "generation": route.generation, "budget_fence": fence, "input_hash": request["input_hash"], "evidence_digests": tuple(digest for _, digest in request["evidence_refs"]), "tenant_id": request["tenant_id"], "principal_id": request["principal_id"], "business_domain_uid": request["business_domain_uid"], "environment": request["environment"]}
+        with self._lock:
+            self._decisions[(request["tenant_id"], request["idempotency_key"])] = record
+            self._audit.append(dict(record))
+            return dict(record)
+
+    def remaining_budget(self, tenant_id: str) -> dict[str, int]:
+        with self._lock:
+            budget = self._budgets[tenant_id]
+            return {"tokens": budget["tokens"], "cost_micros": budget["cost"], "tools": budget["tools"], "time_ms": budget["time"], "concurrency": budget["concurrency"]}
+
+    def audit_records(self) -> list[dict[str, Any]]:
+        with self._lock:
+            return [dict(item) for item in self._audit]
+
+    def runtime_state(self, tenant_id: str) -> dict[str, Any]:
+        with self._lock:
+            return dict(self._states.get(tenant_id, {"state": "active", "incident_ref": None, "fence": 0}))
+
+    def set_runtime_state(self, tenant_id: str, state: str, incident_ref: str | None, *, require_active: bool = False) -> dict[str, Any]:
+        with self._lock:
+            current = self._states.setdefault(tenant_id, {"state": "active", "incident_ref": None, "fence": 0})
+            if require_active and current["state"] != "paused":
+                raise RuntimeDecisionError("runtime_state_conflict")
+            current["state"] = state
+            current["incident_ref"] = incident_ref or current["incident_ref"]
+            current["fence"] += 1
+            return dict(current)
+
+    def register_generation(self, tenant_id: str, generation: str, *, parent_generation: str, status: str) -> None:
+        if status not in {"canary", "approved"}:
+            raise ValueError("generation status invalid")
+        with self._lock:
+            self._generations[(tenant_id, generation)] = {"status": status, "parent": parent_generation, "fence": 0}
+
+    def promote_generation(self, tenant_id: str, generation: str) -> int:
+        with self._lock:
+            current = self._generations.get((tenant_id, generation))
+            if not current or current["status"] != "canary":
+                raise RuntimeDecisionError("canary_not_approved")
+            current["status"] = "approved"
+            current["fence"] += 1
+            return current["fence"]
+
+    def rollback_generation(self, tenant_id: str, generation: str, fence: int) -> str:
+        with self._lock:
+            approved = [entry for (tenant, name), entry in self._generations.items() if tenant == tenant_id and entry["status"] == "approved"]
+            if not approved or max(entry["fence"] for entry in approved) != fence:
+                raise RuntimeDecisionError("generation_fence_conflict")
+            target = self._generations.get((tenant_id, generation))
+            if not target:
+                raise RuntimeDecisionError("route_scope_denied")
+            return generation
+
+    def replay(self, invocation_id: str) -> dict[str, Any]:
+        with self._lock:
+            for record in self._audit:
+                if record["invocation_id"] == invocation_id:
+                    return dict(record)
+        raise RuntimeDecisionError("invocation_not_found")
+
+
+class GovernedInvocationService:
+    def __init__(self, repository: InMemoryRuntimeRepository):
+        self.repository = repository
+
+    def authorize(self, raw_request: dict[str, Any]) -> dict[str, Any]:
+        request = _normalize_request(raw_request)
+        replay = self.repository.existing(request["tenant_id"], request["idempotency_key"])
+        if replay:
+            return replay
+        state = self.repository.runtime_state(request["tenant_id"])
+        if state["state"] == "paused":
+            raise RuntimeDecisionError("runtime_paused")
+        route = self.repository.find_route(request)
+        if route is None:
+            if self.repository.generation_status(request["tenant_id"], request["generation"]) == "canary":
+                raise RuntimeDecisionError("canary_not_approved")
+            raise RuntimeDecisionError("route_scope_denied")
+        if route.canary_status != "approved":
+            raise RuntimeDecisionError("canary_not_approved")
+        if not self.repository.allowed_capability(request):
+            if request["tool_name"] == "knowledge.search":
+                raise RuntimeDecisionError("capability_denied")
+            raise RuntimeDecisionError("tool_contract_denied")
+        fence = self.repository.reserve(request)
+        return self.repository.write_authorized(request, route, fence)
+
+    def record_anomaly(self, tenant_id: str, *, incident_ref: str, severity: str, actor: str) -> dict[str, Any]:
+        if not _SAFE_ID.fullmatch(tenant_id) or not _SAFE_ID.fullmatch(incident_ref) or actor != "detector":
+            raise RuntimeDecisionError("anomaly_contract_invalid")
+        if severity not in {"medium", "high", "critical"}:
+            raise RuntimeDecisionError("anomaly_severity_invalid")
+        return self.repository.set_runtime_state(tenant_id, "paused" if severity == "critical" else "degraded", incident_ref)
+
+    def recover(self, tenant_id: str, *, approval_ref: str | None, actor: str) -> dict[str, Any]:
+        if actor != "operator" or not isinstance(approval_ref, str) or not _SAFE_ID.fullmatch(approval_ref):
+            raise RuntimeDecisionError("recovery_approval_required")
+        return self.repository.set_runtime_state(tenant_id, "active", None, require_active=True)
+
+    def promote_canary(self, tenant_id: str, generation: str, *, approval_ref: str) -> int:
+        if not _SAFE_ID.fullmatch(approval_ref):
+            raise RuntimeDecisionError("recovery_approval_required")
+        return self.repository.promote_generation(tenant_id, generation)
+
+    def rollback_generation(self, tenant_id: str, generation: str, *, fence: int, approval_ref: str) -> str:
+        if not _SAFE_ID.fullmatch(approval_ref):
+            raise RuntimeDecisionError("recovery_approval_required")
+        return self.repository.rollback_generation(tenant_id, generation, fence)
+
+    def replay(self, invocation_id: str) -> dict[str, Any]:
+        return self.repository.replay(invocation_id)
+
+
+def evaluate_governed_dataset(*, train_case_ids: set[str], evaluation_cases: list[dict[str, Any]]) -> dict[str, float]:
+    case_ids = [str(case.get("case_id", "")) for case in evaluation_cases]
+    if not case_ids or len(case_ids) != len(set(case_ids)) or train_case_ids.intersection(case_ids):
+        raise DatasetLeakageError("dataset_leakage_detected")
+    recalls, reciprocal_ranks, citations, violations, fresh = [], [], [], [], []
+    for case in evaluation_cases:
+        expected = case.get("expected_evidence_id")
+        retrieved = list(case.get("retrieved_evidence_ids") or [])
+        cited = list(case.get("citation_evidence_ids") or [])
+        if not isinstance(expected, str) or not expected:
+            raise DatasetLeakageError("dataset_case_invalid")
+        recalls.append(float(expected in retrieved))
+        reciprocal_ranks.append(1.0 / (retrieved.index(expected) + 1) if expected in retrieved else 0.0)
+        citations.append(float(cited == [expected]))
+        violations.append(float(not bool(case.get("authorized"))))
+        fresh.append(float(bool(case.get("fresh"))))
+    count = float(len(evaluation_cases))
+    return {"recall": sum(recalls) / count, "mrr": sum(reciprocal_ranks) / count, "citation_correctness": sum(citations) / count, "privilege_violation_rate": sum(violations) / count, "freshness": sum(fresh) / count}

+ 168 - 0
app/core/llm/runtime_governance_repository.py

@@ -0,0 +1,168 @@
+"""PostgreSQL repository for WP09 budget leases and immutable audit metadata."""
+
+from __future__ import annotations
+
+import json
+from typing import Any
+from uuid import uuid4
+
+from sqlalchemy import text
+from sqlalchemy.exc import SQLAlchemyError
+
+from app.core.llm.runtime_governance import BudgetPolicy
+
+
+class SqlAlchemyRuntimeGovernanceRepository:
+    def __init__(self, connection) -> None:
+        self.connection = connection
+
+    def create_budget(self, tenant_id: str, policy: BudgetPolicy) -> None:
+        self.connection.execute(text("""
+            INSERT INTO public.agent_runtime_budgets (
+                tenant_id, token_remaining, cost_remaining_micros, tool_remaining,
+                time_remaining_ms, concurrency_remaining
+            ) VALUES (:tenant_id, :tokens, :cost, :tools, :time_ms, :concurrency)
+        """), {"tenant_id": tenant_id, "tokens": policy.token_limit, "cost": policy.cost_limit_micros, "tools": policy.tool_limit, "time_ms": policy.time_limit_ms, "concurrency": policy.concurrency_limit})
+        for model, remaining in policy.model_limits.items():
+            self.connection.execute(text("""
+                INSERT INTO public.agent_runtime_model_budgets (tenant_id, model, remaining)
+                VALUES (:tenant_id, :model, :remaining)
+            """), {"tenant_id": tenant_id, "model": model, "remaining": remaining})
+
+    def reserve_budget(self, tenant_id: str, idempotency_key: str, model: str, tokens: int, cost_micros: int, tools: int, time_ms: int) -> int:
+        values = (tokens, cost_micros, tools, time_ms)
+        if any(isinstance(value, bool) or not isinstance(value, int) or value < 1 for value in values):
+            raise ValueError("budget_value_invalid")
+        with self.connection.begin_nested():
+            return self._reserve_budget_atomic(tenant_id, idempotency_key, model, tokens, cost_micros, tools, time_ms)
+
+    def _reserve_budget_atomic(self, tenant_id: str, idempotency_key: str, model: str, tokens: int, cost_micros: int, tools: int, time_ms: int) -> int:
+        model_row = self.connection.execute(text("""
+            UPDATE public.agent_runtime_model_budgets
+            SET remaining = remaining - 1
+            WHERE tenant_id = :tenant_id AND model = :model AND remaining >= 1
+            RETURNING remaining
+        """), {"tenant_id": tenant_id, "model": model}).one_or_none()
+        if model_row is None:
+            raise ValueError("budget_exhausted")
+        row = self.connection.execute(text("""
+            UPDATE public.agent_runtime_budgets
+            SET token_remaining = token_remaining - :tokens,
+                cost_remaining_micros = cost_remaining_micros - :cost,
+                tool_remaining = tool_remaining - :tools,
+                time_remaining_ms = time_remaining_ms - :time_ms,
+                concurrency_remaining = concurrency_remaining - 1,
+                lease_fence = lease_fence + 1, updated_at = now()
+            WHERE tenant_id = :tenant_id AND token_remaining >= :tokens
+              AND cost_remaining_micros >= :cost AND tool_remaining >= :tools
+              AND time_remaining_ms >= :time_ms AND concurrency_remaining >= 1
+            RETURNING lease_fence
+        """), {"tenant_id": tenant_id, "tokens": tokens, "cost": cost_micros, "tools": tools, "time_ms": time_ms}).one_or_none()
+        if row is None:
+            raise ValueError("budget_exhausted")
+        fence = int(row[0])
+        self.connection.execute(text("""
+            INSERT INTO public.agent_runtime_reservations (
+                reservation_id, tenant_id, idempotency_key, model, token_count,
+                cost_micros, tool_count, time_ms, lease_fence, status, lease_expires_at
+            ) VALUES (CAST(:reservation_id AS uuid), :tenant_id, :idempotency_key,
+                      :model, :tokens, :cost, :tools, :time_ms, :fence, 'reserved',
+                      clock_timestamp()+interval '5 minutes')
+        """), {"reservation_id": str(uuid4()), "tenant_id": tenant_id, "idempotency_key": idempotency_key, "model": model, "tokens": tokens, "cost": cost_micros, "tools": tools, "time_ms": time_ms, "fence": fence})
+        return fence
+
+    def append_audit(self, record: dict[str, Any]) -> None:
+        try:
+            self.connection.execute(text("""
+                INSERT INTO public.agent_invocation_audits (
+                    invocation_id, tenant_id, idempotency_key, route_id, generation,
+                    lease_fence, input_hash, evidence_digests, decision, request_digest
+                ) VALUES (CAST(:invocation_id AS uuid), :tenant_id, :idempotency_key,
+                          :route_id, :generation, :lease_fence, :input_hash,
+                          CAST(:evidence_digests AS jsonb), :decision, :request_digest)
+            """), {
+                **record, "evidence_digests": json.dumps(record["evidence_digests"]),
+                "request_digest": record.get("request_digest", record["input_hash"]),
+            })
+        except Exception as error:
+            raise ValueError("append_only_conflict") from error
+
+    def grant_context(self, *, agent_uid: str, grant_uid: str, actor_uid: str) -> dict[str, Any]:
+        """Load scope exclusively from persisted Agent, grant and approved route state."""
+        row = self.connection.execute(text("""
+            SELECT r.tenant_id, a.owner_uid::text AS principal_id,
+                   g.business_domain_uid::text AS business_domain_uid, g.environment,
+                   r.provider, r.model, r.prompt_version, r.generation,
+                   g.interface_type, g.tool_name, g.action, g.risk_level
+            FROM public.agent_tool_grants g
+            JOIN public.governed_agents a ON a.uid=g.agent_uid
+            JOIN public.model_gateway_routes r ON r.principal_id=a.owner_uid
+              AND r.business_domain_uid=g.business_domain_uid AND r.environment=g.environment
+              AND r.canary_status='approved'
+            WHERE g.uid=CAST(:grant_uid AS uuid) AND g.agent_uid=CAST(:agent_uid AS uuid)
+              AND a.owner_uid=CAST(:actor_uid AS uuid) AND g.status='active' AND a.status='active'
+            ORDER BY r.created_at DESC LIMIT 1
+        """), {"agent_uid": agent_uid, "grant_uid": grant_uid, "actor_uid": actor_uid}).mappings().one_or_none()
+        if row is None:
+            from app.core.llm.runtime_server import RuntimeServerError
+            raise RuntimeServerError("server_scope_denied")
+        return dict(row)
+
+    def authorize_claim(self, request: dict[str, Any]) -> dict[str, Any]:
+        try:
+            self.connection.execute(text("SET LOCAL ROLE dataops_agent_runtime"))
+            row = self.connection.execute(text("""
+                SELECT public.agent_runtime_authorize(CAST(:payload AS jsonb)) AS result
+            """), {"payload": json.dumps({
+                key: (sorted(value) if isinstance(value, frozenset) else value)
+                for key, value in request.items()
+            }, ensure_ascii=False)}).scalar_one()
+            return dict(row)
+        except SQLAlchemyError as error:
+            raise RuntimeError("runtime_authorization_rejected") from error
+
+    def settlement_context(self, *, agent_uid: str, actor_uid: str, idempotency_key: str) -> dict[str, Any]:
+        row = self.connection.execute(text("""
+            SELECT r.agent_uid::text AS agent_uid, r.principal_id::text AS principal_id,
+                   r.idempotency_key, r.lease_fence
+              FROM public.agent_runtime_reservations r
+             WHERE r.idempotency_key=:idempotency_key
+               AND r.agent_uid=CAST(:agent_uid AS uuid)
+               AND r.principal_id=CAST(:actor_uid AS uuid)
+        """), {"agent_uid": agent_uid, "actor_uid": actor_uid, "idempotency_key": idempotency_key}).mappings().one_or_none()
+        if row is None:
+            from app.core.llm.runtime_server import RuntimeServerError
+            raise RuntimeServerError("server_scope_denied")
+        return dict(row)
+
+    def settle_claim(self, request: dict[str, Any]) -> dict[str, Any]:
+        try:
+            self.connection.execute(text("SET LOCAL ROLE dataops_agent_runtime"))
+            row = self.connection.execute(text("""
+                SELECT public.agent_runtime_settle(CAST(:payload AS jsonb)) AS result
+            """), {"payload": json.dumps(request, ensure_ascii=False)}).scalar_one()
+            return dict(row)
+        except SQLAlchemyError as error:
+            raise RuntimeError("runtime_settlement_rejected") from error
+
+    def issue_control_claim(self, claim: dict[str, Any]) -> str:
+        """Persist a credential-bound, one-shot control claim in this request transaction."""
+        try:
+            self.connection.execute(text("SET LOCAL ROLE dataops_app_runtime"))
+            return str(self.connection.execute(text("""
+                SELECT public.agent_runtime_control_issue_claim(CAST(:claim AS jsonb))
+            """), {"claim": json.dumps(claim, ensure_ascii=False)}).scalar_one())
+        except SQLAlchemyError as error:
+            raise RuntimeError("runtime_control_claim_rejected") from error
+
+    def control_claimed(self, payload: dict[str, Any], claim_uid: str) -> dict[str, Any]:
+        try:
+            self.connection.execute(text("SET LOCAL ROLE dataops_app_runtime"))
+            row = self.connection.execute(text("""
+                SELECT public.agent_runtime_control_claimed(
+                  CAST(:payload AS jsonb),CAST(:claim_uid AS uuid)
+                ) AS result
+            """), {"payload": json.dumps(payload, ensure_ascii=False), "claim_uid": claim_uid}).scalar_one()
+            return dict(row)
+        except SQLAlchemyError as error:
+            raise RuntimeError("runtime_control_rejected") from error

+ 111 - 0
app/core/llm/runtime_server.py

@@ -0,0 +1,111 @@
+"""Server-derived invocation contract for the hardened WP09 runtime path."""
+
+from __future__ import annotations
+
+import hashlib
+from dataclasses import dataclass
+from decimal import Decimal, InvalidOperation
+from typing import Any
+
+
+class RuntimeServerError(ValueError):
+    pass
+
+
+@dataclass(frozen=True)
+class RuntimeServerContext:
+    agent_uid: str
+    actor_uid: str
+    roles: frozenset[str]
+
+
+_PAYLOAD_FIELDS = frozenset(
+    {
+        "grant_uid", "idempotency_key", "input_text", "evidence_refs",
+        "estimated_tokens", "estimated_cost_micros", "requested_time_ms",
+    }
+)
+_OPTIONAL_APPROVAL_FIELD = "approval_task_uid"
+_SCOPE_FIELDS = frozenset(
+    {
+        "tenant_id", "principal_id", "business_domain_uid", "environment", "provider",
+        "model", "prompt_version", "generation", "interface_type", "tool_name", "action",
+        "risk_level", "agent_uid",
+    }
+)
+
+
+def _positive_integer(value: Any) -> int:
+    if isinstance(value, bool) or not isinstance(value, int) or not 0 < value <= 1_000_000:
+        raise RuntimeServerError("budget_value_invalid")
+    return value
+
+
+def _cost_micros(value: Any) -> int:
+    if isinstance(value, bool) or not isinstance(value, str) or len(value) > 32:
+        raise RuntimeServerError("budget_value_invalid")
+    try:
+        decimal = Decimal(value)
+    except (InvalidOperation, ValueError) as error:
+        raise RuntimeServerError("budget_value_invalid") from error
+    if not decimal.is_finite() or decimal <= 0 or decimal != decimal.to_integral_value():
+        raise RuntimeServerError("budget_value_invalid")
+    result = int(decimal)
+    if result > 1_000_000_000:
+        raise RuntimeServerError("budget_value_invalid")
+    return result
+
+
+class ServerGovernedInvocationService:
+    """Reject client-supplied scope, deriving every authority field server-side."""
+
+    def __init__(self, repository):
+        self.repository = repository
+
+    def authorize(self, context: RuntimeServerContext, payload: Any) -> dict[str, Any]:
+        if not isinstance(payload, dict):
+            raise RuntimeServerError("payload_invalid")
+        if set(payload) & _SCOPE_FIELDS:
+            raise RuntimeServerError("payload_scope_forbidden")
+        if set(payload) not in {_PAYLOAD_FIELDS, _PAYLOAD_FIELDS | {_OPTIONAL_APPROVAL_FIELD}}:
+            raise RuntimeServerError("payload_schema_invalid")
+        if not context.agent_uid or not context.actor_uid or not context.roles:
+            raise RuntimeServerError("server_identity_invalid")
+        grant_uid = payload["grant_uid"]
+        if not isinstance(grant_uid, str) or not grant_uid:
+            raise RuntimeServerError("payload_invalid")
+        idempotency_key = payload["idempotency_key"]
+        if not isinstance(idempotency_key, str) or not idempotency_key or len(idempotency_key) > 160:
+            raise RuntimeServerError("payload_invalid")
+        input_text = payload["input_text"]
+        if not isinstance(input_text, str) or not input_text.strip() or len(input_text) > 8000:
+            raise RuntimeServerError("payload_invalid")
+        evidence_refs = payload["evidence_refs"]
+        if not isinstance(evidence_refs, list) or len(evidence_refs) > 20:
+            raise RuntimeServerError("payload_invalid")
+        derived = self.repository.grant_context(
+            agent_uid=context.agent_uid, grant_uid=grant_uid, actor_uid=context.actor_uid
+        )
+        requires_approval = derived.get("risk_level") != "low" or derived.get("action") == "execute"
+        approval_task_uid = payload.get(_OPTIONAL_APPROVAL_FIELD)
+        if requires_approval and (not isinstance(approval_task_uid, str) or len(approval_task_uid) != 36):
+            raise RuntimeServerError("human_approval_required")
+        if not requires_approval and approval_task_uid is not None:
+            raise RuntimeServerError("approval_not_applicable")
+        request = {
+            **derived,
+            "agent_uid": context.agent_uid,
+            "grant_uid": grant_uid,
+            "idempotency_key": idempotency_key,
+            "request_digest": hashlib.sha256(
+                (input_text.strip() + "|" + repr(evidence_refs)).encode("utf-8")
+            ).hexdigest(),
+            "input_hash": hashlib.sha256(input_text.strip().encode("utf-8")).hexdigest(),
+            "evidence_digests": [],
+            "estimated_tokens": _positive_integer(payload["estimated_tokens"]),
+            "estimated_cost_micros": _cost_micros(payload["estimated_cost_micros"]),
+            "requested_time_ms": _positive_integer(payload["requested_time_ms"]),
+        }
+        if requires_approval:
+            request["approval_task_uid"] = approval_task_uid
+        return self.repository.authorize_claim(request)

+ 74 - 0
app/core/mcp/governed_invocation.py

@@ -0,0 +1,74 @@
+"""Closed, minimized contract for MCP calls made by governed Agents."""
+
+from __future__ import annotations
+
+import copy
+import re
+import unicodedata
+from typing import Any
+
+
+class InvocationContractError(ValueError):
+    """Raised when an MCP invocation exceeds its declared contract."""
+
+
+_ALLOWED = frozenset(
+    {"interface_type", "tool_name", "action", "arguments_digest", "evidence_refs"}
+)
+_FORBIDDEN_KEYS = frozenset(
+    {
+        "url", "uri", "endpoint", "path", "file_path", "filename", "command",
+        "shell", "script", "secret", "token", "password", "api_key", "raw_rows",
+        "rows", "records", "arguments", "payload",
+    }
+)
+_TOOL = re.compile(r"^[A-Za-z][A-Za-z0-9_.:-]{1,159}$")
+_DIGEST = re.compile(r"^[a-f0-9]{64}$")
+
+
+def _closed(value: Any, allowed: frozenset[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise InvocationContractError(f"{label}_invalid")
+    for key in value:
+        normalized = unicodedata.normalize("NFKC", str(key))
+        if normalized != key or normalized.casefold() in _FORBIDDEN_KEYS:
+            raise InvocationContractError("forbidden_contract_key")
+    unknown = set(value) - allowed
+    if unknown:
+        raise InvocationContractError("unknown_contract_field")
+    return copy.deepcopy(value)
+
+
+def _digest(value: Any, label: str) -> str:
+    if not isinstance(value, str) or not _DIGEST.fullmatch(value):
+        raise InvocationContractError(f"{label}_invalid")
+    return value
+
+
+def normalize_mcp_invocation(value: Any) -> dict[str, Any]:
+    """Accept hash-only, bounded MCP metadata; never raw arguments or locations."""
+    body = _closed(value, _ALLOWED, "invocation")
+    if body.get("interface_type") != "mcp":
+        raise InvocationContractError("interface_type_invalid")
+    tool_name = body.get("tool_name")
+    if not isinstance(tool_name, str) or not _TOOL.fullmatch(tool_name):
+        raise InvocationContractError("tool_name_invalid")
+    if body.get("action") not in {"read", "suggest", "execute"}:
+        raise InvocationContractError("action_invalid")
+    refs = body.get("evidence_refs")
+    if not isinstance(refs, list) or len(refs) > 20:
+        raise InvocationContractError("evidence_refs_invalid")
+    normalized_refs = []
+    for item in refs:
+        ref = _closed(item, frozenset({"evidence_id", "digest"}), "evidence_ref")
+        evidence_id = ref.get("evidence_id")
+        if not isinstance(evidence_id, str) or not re.fullmatch(r"[A-Za-z0-9_.:-]{1,120}", evidence_id):
+            raise InvocationContractError("evidence_id_invalid")
+        normalized_refs.append({"evidence_id": evidence_id, "digest": _digest(ref.get("digest"), "evidence_digest")})
+    return {
+        "interface_type": "mcp",
+        "tool_name": tool_name,
+        "action": body["action"],
+        "arguments_digest": _digest(body.get("arguments_digest"), "arguments_digest"),
+        "evidence_refs": normalized_refs,
+    }

+ 5 - 0
app/core/plugins/__init__.py

@@ -0,0 +1,5 @@
+"""Closed, fixture-only governed plugin platform primitives."""
+
+from .governance import PluginManifestError, normalize_plugin_manifest
+
+__all__ = ["PluginManifestError", "normalize_plugin_manifest"]

+ 178 - 0
app/core/plugins/governance.py

@@ -0,0 +1,178 @@
+"""Closed, local-only manifest contract for governed plugins.
+
+This module deliberately contains no loader, dynamic import, evaluator, shell,
+subprocess, network client or user-selected filesystem path.  A manifest is
+metadata for a fixed built-in fixture handler, never an executable package.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import json
+import re
+import unicodedata
+from typing import Any
+
+
+class PluginManifestError(ValueError):
+    """A plugin manifest did not meet the closed local-baseline contract."""
+
+
+_TOP_LEVEL = frozenset(
+    {
+        "schema_version",
+        "plugin_uid",
+        "name",
+        "version",
+        "api_version",
+        "type",
+        "capabilities",
+        "permissions",
+        "resource",
+        "compatibility",
+        "distribution",
+    }
+)
+_TYPES = frozenset({"connector", "parser", "quality", "notification", "approval", "agent_mcp"})
+_CAPABILITIES = {
+    "connector": frozenset({"discover", "snapshot", "evidence"}),
+    "parser": frozenset({"parse", "evidence"}),
+    "quality": frozenset({"evaluate", "evidence"}),
+    "notification": frozenset({"prepare", "evidence"}),
+    "approval": frozenset({"evaluate", "evidence"}),
+    "agent_mcp": frozenset({"read", "suggest", "evidence"}),
+}
+_PERMISSIONS = frozenset({"network", "file", "secret", "child_process"})
+_RESOURCE = frozenset({"timeout_ms", "max_output_bytes", "max_concurrency", "max_retries"})
+_COMPATIBILITY = frozenset({"platform_api"})
+_DISTRIBUTION = frozenset({"kind", "fixture_id", "artifact_digest"})
+_SENSITIVE = frozenset(
+    {
+        "url", "uri", "endpoint", "path", "file", "file_path", "filename", "command", "shell",
+        "script", "code", "module", "import", "eval", "exec", "secret", "token", "password",
+        "credential", "api_key", "authorization", "raw_rows", "rows", "records", "payload", "sql",
+    }
+)
+_ID = re.compile(r"^[a-z][a-z0-9-]{2,62}$")
+_SEMVER = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$")
+_HEX = re.compile(r"^[a-f0-9]{64}$")
+
+
+def canonical_digest(value: dict[str, Any]) -> str:
+    return hashlib.sha256(
+        json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
+    ).hexdigest()
+
+
+def fixture_signature_digest(artifact_digest: str) -> str:
+    """Bind the sole local fixture artifact to the platform-owned trust key.
+
+    This is deliberately not a payload supplied public key or a general
+    signature verifier.  Enterprise CA and registry signing remain disabled.
+    """
+
+    if not isinstance(artifact_digest, str) or not _HEX.fullmatch(artifact_digest):
+        raise PluginManifestError("artifact_digest_invalid")
+    return hashlib.sha256(f"local-fixture-key-v1|{artifact_digest}".encode("ascii")).hexdigest()
+
+
+def _closed(value: Any, allowed: frozenset[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict) or len(value) > len(allowed):
+        raise PluginManifestError(f"{label}_closed")
+    for key in value:
+        if not isinstance(key, str) or unicodedata.normalize("NFKC", key) != key:
+            raise PluginManifestError("manifest_unicode_rejected")
+        # The four declared capability switches are metadata, not values.  All
+        # other sensitive vocabulary remains forbidden even before unknown-key
+        # processing so it cannot become an accidental extension point.
+        if key.casefold() in _SENSITIVE and key not in allowed:
+            raise PluginManifestError("manifest_sensitive_key")
+    if set(value) != allowed:
+        raise PluginManifestError(f"{label}_closed")
+    return copy.deepcopy(value)
+
+
+def _ascii_text(value: Any, label: str, maximum: int = 120) -> str:
+    if not isinstance(value, str) or not value or len(value) > maximum:
+        raise PluginManifestError(f"{label}_invalid")
+    if unicodedata.normalize("NFKC", value) != value or not value.isascii():
+        raise PluginManifestError(f"{label}_unicode_rejected")
+    return value
+
+
+def _positive_int(value: Any, label: str, maximum: int) -> int:
+    if isinstance(value, bool) or not isinstance(value, int) or not 0 < value <= maximum:
+        raise PluginManifestError(f"{label}_invalid")
+    return value
+
+
+def normalize_plugin_manifest(value: Any) -> dict[str, Any]:
+    """Normalize a v1 manifest for the sole fixed engineering fixture.
+
+    Enterprise registry artifacts are intentionally rejected until an external
+    trust model is approved.  This validation makes that inactivity executable.
+    """
+
+    body = _closed(value, _TOP_LEVEL, "manifest")
+    if body["schema_version"] != 1 or body["api_version"] != "1":
+        raise PluginManifestError("manifest_version_invalid")
+    plugin_uid = _ascii_text(body["plugin_uid"], "plugin_uid", 63)
+    name = _ascii_text(body["name"], "name", 63)
+    if not _ID.fullmatch(plugin_uid) or not _ID.fullmatch(name) or plugin_uid != name:
+        raise PluginManifestError("plugin_identity_invalid")
+    version = _ascii_text(body["version"], "version", 80)
+    if not _SEMVER.fullmatch(version):
+        raise PluginManifestError("plugin_version_invalid")
+    plugin_type = _ascii_text(body["type"], "plugin_type", 20)
+    if plugin_type not in _TYPES:
+        raise PluginManifestError("plugin_type_invalid")
+    capabilities = body["capabilities"]
+    if not isinstance(capabilities, list) or not 1 <= len(capabilities) <= 8:
+        raise PluginManifestError("capabilities_invalid")
+    normalized_capabilities = tuple(_ascii_text(item, "capability", 40) for item in capabilities)
+    if len(set(normalized_capabilities)) != len(normalized_capabilities) or set(normalized_capabilities) - _CAPABILITIES[plugin_type]:
+        raise PluginManifestError("capabilities_invalid")
+    permissions = _closed(body["permissions"], _PERMISSIONS, "permissions")
+    if any(item is not False for item in permissions.values()):
+        raise PluginManifestError("permissions_denied")
+    resource = _closed(body["resource"], _RESOURCE, "resource")
+    normalized_resource = {
+        "timeout_ms": _positive_int(resource["timeout_ms"], "timeout_ms", 5000),
+        "max_output_bytes": _positive_int(resource["max_output_bytes"], "max_output_bytes", 65536),
+        "max_concurrency": _positive_int(resource["max_concurrency"], "max_concurrency", 1),
+        "max_retries": _positive_int(resource["max_retries"], "max_retries", 3),
+    }
+    if normalized_resource["max_output_bytes"] < 64:
+        raise PluginManifestError("max_output_bytes_invalid")
+    compatibility = _closed(body["compatibility"], _COMPATIBILITY, "compatibility")
+    if compatibility["platform_api"] != "1":
+        raise PluginManifestError("compatibility_invalid")
+    distribution = _closed(body["distribution"], _DISTRIBUTION, "distribution")
+    if distribution["kind"] != "builtin_fixture" or distribution["fixture_id"] != "ENGINEERING_EVIDENCE_ONLY":
+        raise PluginManifestError("distribution_denied")
+    artifact_digest = _ascii_text(distribution["artifact_digest"], "artifact_digest", 64)
+    if not _HEX.fullmatch(artifact_digest):
+        raise PluginManifestError("artifact_digest_invalid")
+    normalized = {
+        "schema_version": 1,
+        "plugin_uid": plugin_uid,
+        "name": name,
+        "version": version,
+        "api_version": "1",
+        "type": plugin_type,
+        "capabilities": list(normalized_capabilities),
+        "permissions": dict.fromkeys(sorted(_PERMISSIONS), False),
+        "resource": normalized_resource,
+        "compatibility": {"platform_api": "1"},
+        "distribution": {
+            "kind": "builtin_fixture",
+            "fixture_id": "ENGINEERING_EVIDENCE_ONLY",
+            "artifact_digest": artifact_digest,
+        },
+    }
+    normalized["manifest_digest"] = canonical_digest(normalized)
+    return normalized
+
+
+__all__ = ["PluginManifestError", "canonical_digest", "fixture_signature_digest", "normalize_plugin_manifest"]

+ 113 - 0
app/core/plugins/persistence.py

@@ -0,0 +1,113 @@
+"""Database adapters for the fixed WP13 control and runtime gateways."""
+
+from __future__ import annotations
+
+import json
+import os
+from typing import Any
+
+from sqlalchemy import create_engine, text
+from sqlalchemy.pool import NullPool
+
+from app.config.database_urls import validate_postgresql_url
+from app.core.mcp.governed_invocation import normalize_mcp_invocation
+
+from .governance import fixture_signature_digest, normalize_plugin_manifest
+from .runtime import BuiltinFixtureDispatcher
+
+
+class PluginPersistenceError(ValueError):
+    """The persistent gateway rejected a closed plugin request."""
+
+
+def _digest(value: Any, label: str) -> str:
+    if not isinstance(value, str) or len(value) != 64 or set(value) - set("0123456789abcdef"):
+        raise PluginPersistenceError(f"{label}_invalid")
+    return value
+
+
+class DatabasePluginPlatformService:
+    """Uses only fixed SECURITY DEFINER functions; never direct fact SQL."""
+
+    def __init__(self) -> None:
+        self._runtime_url = os.environ.get("DATABASE_URL", "")
+        self._control_url = os.environ.get("PLUGIN_PLATFORM_CONTROL_DATABASE_URL", os.environ.get("BI_AI_CATALOG_CONTROL_DATABASE_URL", ""))
+        runtime = validate_postgresql_url(self._runtime_url, "DATABASE_URL")
+        control = validate_postgresql_url(self._control_url, "PLUGIN_PLATFORM_CONTROL_DATABASE_URL")
+        if runtime.username == control.username:
+            raise RuntimeError("plugin control and runtime identities must differ")
+
+    @staticmethod
+    def _scope() -> dict[str, str]:
+        tenant = os.environ.get("TRUSTED_PLUGIN_TENANT", "")
+        domain = os.environ.get("TRUSTED_PLUGIN_DOMAIN", "")
+        if not tenant or not domain or not tenant.isascii() or not domain.isascii():
+            raise PluginPersistenceError("trusted_plugin_scope_missing")
+        return {"tenant_ref": tenant, "domain_ref": domain}
+
+    def _control(self, payload: dict[str, Any]) -> dict[str, Any]:
+        with create_engine(self._control_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            value = connection.execute(text("SELECT public.plugin_platform_control_v5(CAST(:payload AS jsonb))"), {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"))}).scalar_one()
+        return dict(value)
+
+    def _claim(self, payload: dict[str, Any]) -> dict[str, Any]:
+        with create_engine(self._control_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            value = connection.execute(text("SELECT public.plugin_platform_issue_claim_v3(CAST(:payload AS jsonb))"), {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"))}).scalar_one()
+        return dict(value)
+
+    def _runtime(self, payload: dict[str, Any]) -> dict[str, Any]:
+        with create_engine(self._runtime_url, poolclass=NullPool, pool_pre_ping=True).begin() as connection:
+            value = connection.execute(text("SELECT public.plugin_platform_runtime_v3(CAST(:payload AS jsonb))"), {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"))}).scalar_one()
+        return dict(value)
+
+    def register(self, *, manifest: Any, registry_record: Any, actor_ref: str) -> dict[str, Any]:
+        normalized = normalize_plugin_manifest(manifest)
+        if not isinstance(registry_record, dict) or set(registry_record) != {"artifact_digest", "signature", "sbom_digest", "license_digest", "vulnerability_digest", "provenance_digest"}:
+            raise PluginPersistenceError("registry_record_closed")
+        signature = registry_record["signature"]
+        if not isinstance(signature, dict) or set(signature) != {"trust_store_key_id", "signature_digest"} or signature["trust_store_key_id"] != "local-fixture-key-v1":
+            raise PluginPersistenceError("trust_store_denied")
+        payload = {
+            "action": "register", "plugin_uid": normalized["plugin_uid"], "version": normalized["version"], "actor_ref": actor_ref,
+            "plugin_type": normalized["type"], "manifest_digest": normalized["manifest_digest"],
+            "artifact_digest": _digest(registry_record["artifact_digest"], "artifact_digest"),
+            "signature_digest": _digest(signature["signature_digest"], "signature_digest"),
+            "sbom_digest": _digest(registry_record["sbom_digest"], "sbom_digest"), "license_digest": _digest(registry_record["license_digest"], "license_digest"),
+            "vulnerability_digest": _digest(registry_record["vulnerability_digest"], "vulnerability_digest"), "provenance_digest": _digest(registry_record["provenance_digest"], "provenance_digest"),
+            "capabilities": normalized["capabilities"], "resource": normalized["resource"],
+        }
+        if payload["artifact_digest"] != normalized["distribution"]["artifact_digest"]:
+            raise PluginPersistenceError("artifact_digest_mismatch")
+        if payload["signature_digest"] != fixture_signature_digest(payload["artifact_digest"]):
+            raise PluginPersistenceError("signature_binding_denied")
+        return self._control(payload)
+
+    def review(self, *, plugin_uid: str, version: str, actor_ref: str) -> dict[str, Any]:
+        return self._control({"action": "review", "plugin_uid": plugin_uid, "version": version, "actor_ref": actor_ref})
+
+    def issue_approval(self, *, plugin_uid: str, version: str, actor_ref: str, approval_action: str) -> dict[str, Any]:
+        return self._control({"action": "issue_approval", "plugin_uid": plugin_uid, "version": version, "actor_ref": actor_ref, "approval_action": approval_action, **self._scope(), "expires_in_seconds": 300})
+
+    def transition(self, *, plugin_uid: str, version: str, actor_ref: str, target_state: str, approval_uid: str, expected_fence: int, incident_uid: str = "") -> dict[str, Any]:
+        if not isinstance(expected_fence, int) or expected_fence < 0:
+            raise PluginPersistenceError("expected_fence_invalid")
+        return self._control({"action": "transition", "plugin_uid": plugin_uid, "version": version, "actor_ref": actor_ref, **self._scope(), "approval_uid": approval_uid, "target_state": target_state, "expected_fence": expected_fence, "incident_uid": incident_uid})
+
+    def invoke(self, *, plugin_uid: str, version: str, actor_ref: str, operation: str, input_digest: str, idempotency_key: str) -> dict[str, Any]:
+        _digest(input_digest, "input_digest")
+        if not isinstance(idempotency_key, str) or not idempotency_key.isascii() or not 1 <= len(idempotency_key) <= 120:
+            raise PluginPersistenceError("idempotency_key_invalid")
+        scope = self._scope()
+        description = self._control({"action": "describe", "plugin_uid": plugin_uid, "version": version, "actor_ref": actor_ref})
+        if description["plugin_type"] == "agent_mcp":
+            normalize_mcp_invocation({"interface_type": "mcp", "tool_name": "governed_plugin_fixture", "action": operation, "arguments_digest": input_digest, "evidence_refs": []})
+        claim = self._claim({"plugin_uid": plugin_uid, "version": version, "principal_ref": actor_ref, "operation_name": operation, "input_digest": input_digest, "idempotency_key": idempotency_key, **scope})
+        queued = self._runtime({"action": "enqueue", "claim_uid": claim["claim_uid"], "idempotency_key": idempotency_key, "input_digest": input_digest, "operation_name": operation})
+        lease = self._runtime({"action": "claim", "run_uid": queued["run_uid"], "worker": "fixed-builtin"})
+        fixture_manifest = {"distribution": {"kind": "builtin_fixture", "fixture_id": "ENGINEERING_EVIDENCE_ONLY"}, "permissions": {"child_process": False, "file": False, "network": False, "secret": False}, "type": description["plugin_type"], "capabilities": description["capabilities"], "resource": description["resource"], "manifest_digest": description["manifest_digest"]}
+        result = BuiltinFixtureDispatcher().execute(fixture_manifest, operation=operation, input_digest=input_digest)
+        settled = self._runtime({"action": "settle", "run_uid": queued["run_uid"], "worker": "fixed-builtin", "fence": lease["fence"], "success": True, "output_digest": result["output_digest"], "failure_digest": "0" * 64})
+        return {"run_uid": queued["run_uid"], **result, **settled}
+
+
+__all__ = ["DatabasePluginPlatformService", "PluginPersistenceError"]

+ 190 - 0
app/core/plugins/repository.py

@@ -0,0 +1,190 @@
+"""In-memory reference behavior for the fixed WP13 fixture dispatcher.
+
+The production persistence boundary is supplied by the WP13 PostgreSQL gateway.
+This small adapter exists for deterministic unit contracts; it never loads or
+executes a submitted artifact.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import threading
+import uuid
+from collections.abc import Callable
+from typing import Any
+
+from .governance import (
+    PluginManifestError,
+    canonical_digest,
+    fixture_signature_digest,
+    normalize_plugin_manifest,
+)
+
+
+class PluginLifecycleError(ValueError):
+    """A lifecycle transition, approval, registry record or invocation was denied."""
+
+
+_RECORD = frozenset(
+    {"artifact_digest", "signature", "sbom_digest", "license_digest", "vulnerability_digest", "provenance_digest"}
+)
+_SIGNATURE = frozenset({"trust_store_key_id", "signature_digest"})
+_TRUST_STORE = frozenset({"local-fixture-key-v1"})
+_HEX = frozenset("0123456789abcdef")
+_ACTIONS = frozenset({"activate", "pause", "rollback", "revoke", "recover"})
+
+
+def _digest(value: Any, label: str) -> str:
+    if not isinstance(value, str) or len(value) != 64 or set(value) - _HEX:
+        raise PluginLifecycleError(f"{label}_invalid")
+    return value
+
+
+def _closed(value: Any, keys: frozenset[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict) or set(value) != keys:
+        raise PluginLifecycleError(f"{label}_closed")
+    return copy.deepcopy(value)
+
+
+def _scope(value: Any, *, principal: bool) -> dict[str, str]:
+    keys = {"tenant_id", "domain"} | ({"principal_id"} if principal else set())
+    if not isinstance(value, dict) or set(value) != keys:
+        raise PluginLifecycleError("scope_closed")
+    normalized: dict[str, str] = {}
+    for key in keys:
+        item = value[key]
+        if not isinstance(item, str) or not item.isascii() or not 1 <= len(item) <= 120:
+            raise PluginLifecycleError("scope_invalid")
+        normalized[key] = item
+    return normalized
+
+
+class PluginPlatform:
+    """Thread-safe local state machine around one fixed evidence fixture."""
+
+    def __init__(self, *, now: Callable[[], int]) -> None:
+        self._now = now
+        self._lock = threading.RLock()
+        self._versions: dict[tuple[str, str], dict[str, Any]] = {}
+        self._approvals: dict[str, dict[str, Any]] = {}
+        self._runs: dict[tuple[str, str, str, str], dict[str, Any]] = {}
+
+    def register(self, manifest: Any, *, registry_record: Any) -> dict[str, Any]:
+        try:
+            normalized = normalize_plugin_manifest(manifest)
+        except PluginManifestError as exc:
+            raise PluginLifecycleError(str(exc)) from exc
+        record = _closed(registry_record, _RECORD, "registry_record")
+        signature = _closed(record["signature"], _SIGNATURE, "signature")
+        if signature["trust_store_key_id"] not in _TRUST_STORE:
+            raise PluginLifecycleError("trust_store_key_denied")
+        normalized_record = {
+            "artifact_digest": _digest(record["artifact_digest"], "artifact_digest"),
+            "signature": {
+                "trust_store_key_id": signature["trust_store_key_id"],
+                "signature_digest": _digest(signature["signature_digest"], "signature_digest"),
+            },
+            "sbom_digest": _digest(record["sbom_digest"], "sbom_digest"),
+            "license_digest": _digest(record["license_digest"], "license_digest"),
+            "vulnerability_digest": _digest(record["vulnerability_digest"], "vulnerability_digest"),
+            "provenance_digest": _digest(record["provenance_digest"], "provenance_digest"),
+        }
+        if normalized_record["artifact_digest"] != normalized["distribution"]["artifact_digest"]:
+            raise PluginLifecycleError("artifact_digest_mismatch")
+        if normalized_record["signature"]["signature_digest"] != fixture_signature_digest(normalized_record["artifact_digest"]):
+            raise PluginLifecycleError("signature_binding_denied")
+        key = (normalized["plugin_uid"], normalized["version"])
+        with self._lock:
+            if key in self._versions:
+                raise PluginLifecycleError("plugin_version_exists")
+            item = normalized | {"state": "draft", "registry": normalized_record, "reviewer_id": None}
+            self._versions[key] = item
+            return copy.deepcopy(item)
+
+    def review(self, plugin_uid: str, version: str, *, reviewer_id: str) -> dict[str, Any]:
+        if not isinstance(reviewer_id, str) or not reviewer_id.isascii() or not reviewer_id:
+            raise PluginLifecycleError("reviewer_invalid")
+        with self._lock:
+            item = self._version(plugin_uid, version)
+            if item["state"] != "draft":
+                raise PluginLifecycleError("review_transition_denied")
+            item["state"] = "reviewed"
+            item["reviewer_id"] = reviewer_id
+            return copy.deepcopy(item)
+
+    def issue_approval(self, *, plugin_uid: str, version: str, action: str, actor_id: str, reviewer_id: str, scope: Any, expires_at: int) -> dict[str, Any]:
+        if action not in _ACTIONS or not isinstance(actor_id, str) or not isinstance(reviewer_id, str) or actor_id == reviewer_id:
+            raise PluginLifecycleError("approval_independence_denied")
+        if not isinstance(expires_at, int) or expires_at <= self._now():
+            raise PluginLifecycleError("approval_expiry_invalid")
+        bound_scope = _scope(scope, principal=False)
+        with self._lock:
+            item = self._version(plugin_uid, version)
+            if (
+                item["state"] != "reviewed"
+                or item["reviewer_id"] != reviewer_id
+                or item["reviewer_id"] == actor_id
+            ):
+                raise PluginLifecycleError("approval_review_denied")
+            approval = {
+                "approval_id": str(uuid.uuid4()), "plugin_uid": plugin_uid, "version": version,
+                "action": action, "actor_id": actor_id, "reviewer_id": reviewer_id,
+                "scope": bound_scope, "manifest_digest": item["manifest_digest"],
+                "expires_at": expires_at, "consumed": False,
+            }
+            self._approvals[approval["approval_id"]] = approval
+            return copy.deepcopy(approval)
+
+    def activate(self, plugin_uid: str, version: str, *, actor_id: str, scope: Any, approval_id: str) -> dict[str, Any]:
+        bound_scope = _scope(scope, principal=False)
+        with self._lock:
+            item = self._version(plugin_uid, version)
+            approval = self._approvals.get(approval_id)
+            if not approval or approval["consumed"] or approval["expires_at"] <= self._now() or approval["action"] != "activate" or approval["actor_id"] != actor_id or approval["reviewer_id"] != item["reviewer_id"] or approval["scope"] != bound_scope or approval["manifest_digest"] != item["manifest_digest"]:
+                raise PluginLifecycleError("approval_binding_denied")
+            if item["state"] != "reviewed":
+                raise PluginLifecycleError("activation_transition_denied")
+            if any(candidate["state"] == "active" for (uid, _), candidate in self._versions.items() if uid == plugin_uid):
+                raise PluginLifecycleError("plugin_active_collision")
+            approval["consumed"] = True
+            item["state"] = "active"
+            return copy.deepcopy(item)
+
+    def invoke(self, *, plugin_uid: str, version: str, operation: str, scope: Any, input_digest: str, idempotency_key: str) -> dict[str, Any]:
+        invocation_scope = _scope(scope, principal=True)
+        digest = _digest(input_digest, "input_digest")
+        if not isinstance(idempotency_key, str) or not idempotency_key.isascii() or not 1 <= len(idempotency_key) <= 120:
+            raise PluginLifecycleError("idempotency_key_invalid")
+        with self._lock:
+            item = self._version(plugin_uid, version)
+            if item["state"] != "active":
+                raise PluginLifecycleError("plugin_not_active")
+            if operation not in item["capabilities"]:
+                raise PluginLifecycleError("capability_denied")
+            key = (plugin_uid, version, invocation_scope["tenant_id"], idempotency_key)
+            replay = self._runs.get(key)
+            request = {"scope": invocation_scope, "input_digest": digest, "operation": operation}
+            request_digest = canonical_digest(request)
+            if replay:
+                if replay["request_digest"] != request_digest:
+                    raise PluginLifecycleError("idempotency_conflict")
+                return copy.deepcopy(replay["result"] | {"replay": True})
+            output_digest = hashlib.sha256(
+                f"ENGINEERING_EVIDENCE_ONLY|{item['manifest_digest']}|{request_digest}".encode("ascii")
+            ).hexdigest()
+            result = {
+                "plugin_uid": plugin_uid, "version": version, "run_digest": request_digest,
+                "output_digest": output_digest, "fixture_id": "ENGINEERING_EVIDENCE_ONLY", "replay": False,
+            }
+            self._runs[key] = {"request_digest": request_digest, "result": result}
+            return copy.deepcopy(result)
+
+    def _version(self, plugin_uid: str, version: str) -> dict[str, Any]:
+        item = self._versions.get((plugin_uid, version))
+        if item is None:
+            raise PluginLifecycleError("plugin_version_not_found")
+        return item
+
+
+__all__ = ["PluginLifecycleError", "PluginPlatform"]

+ 56 - 0
app/core/plugins/runtime.py

@@ -0,0 +1,56 @@
+"""Fixed, local-only plugin handlers and execution guard.
+
+No submitted artifact is imported or evaluated.  The mapping below is the
+complete executable surface for the engineering fixture baseline.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import time
+from typing import Any
+
+
+class PluginExecutionError(ValueError):
+    """A requested invocation cannot pass the fixed execution boundary."""
+
+
+_HANDLERS = {
+    "connector": frozenset({"discover", "snapshot", "evidence"}),
+    "parser": frozenset({"parse", "evidence"}),
+    "quality": frozenset({"evaluate", "evidence"}),
+    "notification": frozenset({"prepare", "evidence"}),
+    "approval": frozenset({"evaluate", "evidence"}),
+    "agent_mcp": frozenset({"read", "suggest", "evidence"}),
+}
+
+
+class BuiltinFixtureDispatcher:
+    """Deterministic hash-only handler for the six fixed plugin categories."""
+
+    def execute(self, manifest: dict[str, Any], *, operation: str, input_digest: str) -> dict[str, Any]:
+        if not isinstance(manifest, dict) or manifest.get("distribution", {}).get("kind") != "builtin_fixture" or manifest.get("distribution", {}).get("fixture_id") != "ENGINEERING_EVIDENCE_ONLY":
+            raise PluginExecutionError("execution_fixture_denied")
+        permissions = manifest.get("permissions")
+        if permissions != {"child_process": False, "file": False, "network": False, "secret": False}:
+            raise PluginExecutionError("execution_permissions_denied")
+        plugin_type = manifest.get("type")
+        if plugin_type not in _HANDLERS or operation not in _HANDLERS[plugin_type] or operation not in manifest.get("capabilities", []):
+            raise PluginExecutionError("execution_capability_denied")
+        if not isinstance(input_digest, str) or len(input_digest) != 64 or set(input_digest) - set("0123456789abcdef"):
+            raise PluginExecutionError("execution_input_digest_invalid")
+        resource = manifest.get("resource")
+        if not isinstance(resource, dict) or set(resource) != {"timeout_ms", "max_output_bytes", "max_concurrency", "max_retries"}:
+            raise PluginExecutionError("execution_resource_denied")
+        if any(isinstance(value, bool) or not isinstance(value, int) for value in resource.values()) or not 1 <= resource["timeout_ms"] <= 5000 or not 64 <= resource["max_output_bytes"] <= 65536 or not 1 <= resource["max_concurrency"] <= 1 or not 1 <= resource["max_retries"] <= 3:
+            raise PluginExecutionError("execution_resource_denied")
+        started = time.monotonic_ns()
+        output_digest = hashlib.sha256(
+            f"ENGINEERING_EVIDENCE_ONLY|{plugin_type}|{operation}|{manifest.get('manifest_digest')}|{input_digest}".encode("ascii")
+        ).hexdigest()
+        if len(output_digest.encode("ascii")) > resource["max_output_bytes"] or (time.monotonic_ns() - started) > resource["timeout_ms"] * 1_000_000:
+            raise PluginExecutionError("execution_resource_exceeded")
+        return {"output_digest": output_digest, "fixture_id": "ENGINEERING_EVIDENCE_ONLY", "provider_enabled": False}
+
+
+__all__ = ["BuiltinFixtureDispatcher", "PluginExecutionError"]

+ 131 - 6
app/core/system/auth.py

@@ -2,6 +2,8 @@
 
 from __future__ import annotations
 
+import hashlib
+import json
 import logging
 import threading
 import time
@@ -12,11 +14,12 @@ from argon2 import PasswordHasher
 from argon2.exceptions import InvalidHashError, VerificationError, VerifyMismatchError
 from flask import current_app
 from sqlalchemy import text
+from sqlalchemy.exc import ProgrammingError, SQLAlchemyError
 
 from app import db
+from app.core.common.identifiers import new_governance_uid
 from app.core.system.tokens import TokenError, decode_access_token, issue_access_token
 
-
 logger = logging.getLogger(__name__)
 _password_hasher = PasswordHasher()
 _failure_lock = threading.Lock()
@@ -87,6 +90,30 @@ def _audit(
             "detail": (detail or "")[:500] or None,
         },
     )
+    try:
+        with db.session.begin_nested():
+            db.session.execute(
+                text(
+                    "INSERT INTO public.identity_audit_events "
+                    "(uid,event_type,outcome,actor_uid,enterprise_subject_digest,resource_type,resource_uid,safe_detail) "
+                    "VALUES (CAST(:uid AS uuid),:event_type,:outcome,CAST(:actor AS uuid),:subject_digest,"
+                    "'authentication',:resource_uid,CAST(:detail AS jsonb))"
+                ),
+                {
+                    "uid": new_governance_uid(),
+                    "event_type": event_type,
+                    "outcome": "success" if success else "failure",
+                    "actor": user_id,
+                    "subject_digest": hashlib.sha256(username.strip().lower().encode()).hexdigest(),
+                    "resource_uid": user_id,
+                    "detail": json.dumps({"ip_address": (ip_address or "")[:64] or None,
+                                          "user_agent": (user_agent or "")[:300] or None,
+                                          "reason": (detail or "")[:160] or None}),
+                },
+            )
+    except ProgrammingError:
+        # Rolling deployment before migration 470 retains the legacy audit row.
+        pass
 
 
 def authenticate_user(
@@ -127,6 +154,36 @@ def authenticate_user(
         db.session.commit()
         return None
 
+    # Once an enterprise IdP is active, local authentication is a controlled
+    # break-glass path only. Missing identity tables mean pre-migration/no-IdP
+    # compatibility; an active IdP always fails closed.
+    try:
+        active_provider = db.session.execute(
+            text("SELECT EXISTS (SELECT 1 FROM public.identity_idp_config_versions WHERE status='active')")
+        ).scalar()
+    except SQLAlchemyError:
+        db.session.rollback()
+        logger.exception("enterprise identity state unavailable; local login denied")
+        return None
+    if active_provider:
+        emergency_request = db.session.execute(
+            text(
+                "SELECT uid::text,expires_at FROM public.identity_emergency_requests "
+                "WHERE emergency_account_uid=CAST(:id AS uuid) AND status='active' "
+                "AND starts_at<=CURRENT_TIMESTAMP AND expires_at>CURRENT_TIMESTAMP "
+                "AND EXISTS (SELECT 1 FROM public.user_roles ur JOIN public.roles r ON r.id=ur.role_id "
+                "WHERE ur.user_id=CAST(:id AS uuid) AND r.name='admin') "
+                "AND NOT EXISTS (SELECT 1 FROM public.enterprise_identity_links l WHERE l.user_uid=CAST(:id AS uuid)) "
+                "ORDER BY expires_at ASC LIMIT 1"
+            ),
+            {"id": row[0]},
+        ).one_or_none()
+        if not emergency_request:
+            _audit(user_id=row[0], username=row[1], event_type="local_login_blocked_by_idp", success=False,
+                   ip_address=ip_address, user_agent=user_agent, detail="active enterprise IdP requires approved emergency access")
+            db.session.commit()
+            return None
+
     db.session.execute(
         text("UPDATE public.users SET last_login_at = CURRENT_TIMESTAMP WHERE id = CAST(:id AS uuid)"),
         {"id": row[0]},
@@ -142,14 +199,40 @@ def authenticate_user(
     db.session.commit()
     _clear_failures(key)
     roles = list(row[5])
+    token = issue_access_token(
+        user_id=row[0], roles=roles, secret=current_app.config["SECRET_KEY"]
+    )
+    if active_provider:
+        from app.core.system.identity_repository import PostgresIdentityRepository
+        from app.core.system.identity_sessions import SessionManager
+
+        credentials = SessionManager(
+            PostgresIdentityRepository(db.session), secret=current_app.config["SECRET_KEY"]
+        ).create(
+            subject=row[0],
+            user_uid=row[0],
+            roles=roles,
+            identity_source="emergency",
+            emergency_request_uid=emergency_request[0],
+            emergency_expires_at=emergency_request[1],
+        )
+        token = credentials.access_token
+        PostgresIdentityRepository(db.session).audit(
+            {
+                "event_type": "emergency_local_login",
+                "outcome": "warning",
+                "actor_uid": row[0],
+                "resource_type": "emergency_access",
+                "resource_uid": row[0],
+                "safe_detail": {"alert_required": True},
+            }
+        )
     return {
         "id": row[0],
         "username": row[1],
         "display_name": row[2],
         "roles": roles,
-        "token": issue_access_token(
-            user_id=row[0], roles=roles, secret=current_app.config["SECRET_KEY"]
-        ),
+        "token": token,
     }
 
 
@@ -176,7 +259,49 @@ def load_identity_from_token(token: str, *, secret: str) -> dict[str, Any] | Non
         claims = decode_access_token(token, secret=secret)
     except TokenError:
         return None
-    return load_identity(claims["sub"])
+    try:
+        active_provider = bool(db.session.execute(text(
+            "SELECT EXISTS (SELECT 1 FROM public.identity_idp_config_versions WHERE status='active')"
+        )).scalar())
+    except SQLAlchemyError:
+        db.session.rollback()
+        logger.exception("enterprise identity state unavailable; bearer token denied")
+        return None
+    if active_provider and not claims.get("sid"):
+        return None
+    if claims.get("sid"):
+        try:
+            session = db.session.execute(
+                text("""
+                SELECT s.status,s.token_version,s.user_uid::text,s.identity_source,
+                  CASE WHEN s.identity_source='emergency' THEN
+                    (e.status='active' AND e.starts_at<=CURRENT_TIMESTAMP AND e.expires_at>CURRENT_TIMESTAMP
+                     AND u.status='active'
+                     AND EXISTS (SELECT 1 FROM public.user_roles ur JOIN public.roles r ON r.id=ur.role_id
+                                 WHERE ur.user_id=u.id AND r.name='admin')
+                     AND NOT EXISTS (SELECT 1 FROM public.enterprise_identity_links l WHERE l.user_uid=u.id))
+                  ELSE TRUE END emergency_valid
+                FROM public.identity_sessions s LEFT JOIN public.identity_emergency_requests e
+                  ON e.uid=s.emergency_request_uid LEFT JOIN public.users u ON u.id=s.user_uid
+                WHERE s.uid=CAST(:sid AS uuid)
+                """),
+                {"sid": claims["sid"]},
+            ).one_or_none()
+        except SQLAlchemyError:
+            db.session.rollback()
+            return None
+        if session and session[3] == "emergency" and not session[4]:
+            db.session.execute(text("UPDATE public.identity_sessions SET status='revoked',revoke_reason='emergency_window_closed',revoked_at=CURRENT_TIMESTAMP WHERE uid=CAST(:sid AS uuid)"), {"sid": claims["sid"]})
+            db.session.commit()
+            return None
+        if (not session or session[0] != "active" or session[2] != claims["sub"]
+                or int(session[1]) != int(claims.get("token_version", -1))):
+            return None
+    identity = load_identity(claims["sub"])
+    if identity and claims.get("sid"):
+        identity.update(sid=claims["sid"], token_version=claims["token_version"],
+                        identity_source=claims.get("identity_source", "oidc"))
+    return identity
 
 
 # Compatibility aliases retained only for imports during the route migration.
@@ -199,6 +324,6 @@ def init_db() -> bool:
 
 
 def require_auth(view):
-    from app.core.system.permissions import require_permissions, READ_GOVERNANCE
+    from app.core.system.permissions import READ_GOVERNANCE, require_permissions
 
     return require_permissions(READ_GOVERNANCE)(view)

+ 345 - 0
app/core/system/enterprise_identity.py

@@ -0,0 +1,345 @@
+"""Fail-closed enterprise identity policy, mapping, lifecycle and emergency access."""
+
+from __future__ import annotations
+
+import hashlib
+import ipaddress
+import json
+import os
+import re
+from collections.abc import Callable, Mapping
+from dataclasses import dataclass, field
+from datetime import UTC, datetime, timedelta
+from typing import Any
+from urllib.parse import urlsplit
+
+from app.core.common.identifiers import new_governance_uid
+
+
+class IdentityPolicyError(ValueError):
+    pass
+
+
+class IdentityUpstreamError(IdentityPolicyError):
+    """Safe boundary error for unavailable or malformed enterprise IdP responses."""
+
+    pass
+
+
+_SENSITIVE_KEYS = {"code", "token", "id_token", "access_token", "refresh_token", "verifier", "code_verifier",
+                   "secret", "client_secret", "claims", "cookie", "password", "assertion", "authorization_url"}
+
+
+def _utc(value: datetime) -> datetime:
+    if value.tzinfo is None or value.utcoffset() is None:
+        raise IdentityPolicyError("timestamp must be timezone-aware")
+    return value.astimezone(UTC)
+
+
+def _safe_https_url(value: str, *, redirect: bool = False) -> None:
+    parsed = urlsplit(value)
+    if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password:
+        raise IdentityPolicyError("identity endpoints require absolute HTTPS URLs")
+    if parsed.fragment or (redirect and parsed.query):
+        raise IdentityPolicyError("redirect and identity URLs may not contain query/fragment")
+    host = parsed.hostname.lower()
+    if host == "localhost" or host.endswith(".localhost"):
+        raise IdentityPolicyError("localhost identity endpoints are forbidden")
+    try:
+        address = ipaddress.ip_address(host)
+    except ValueError:
+        return
+    if not address.is_global:
+        raise IdentityPolicyError("private or reserved identity endpoint is forbidden")
+
+
+@dataclass(frozen=True)
+class IdpConfig:
+    provider_uid: str
+    version: int
+    issuer: str
+    client_id: str
+    secret_ref: str = field(repr=False)
+    authorization_endpoint: str
+    token_endpoint: str
+    jwks_uri: str
+    redirect_uris: tuple[str, ...]
+    post_login_redirect_uris: tuple[str, ...]
+    algorithms: tuple[str, ...]
+    mapping_version: str
+    status: str = "draft"
+
+    def validate(self) -> IdpConfig:
+        if self.version < 1 or not self.provider_uid or not self.client_id or not self.mapping_version:
+            raise IdentityPolicyError("incomplete IdP configuration")
+        for endpoint in (self.issuer, self.authorization_endpoint, self.token_endpoint, self.jwks_uri):
+            _safe_https_url(endpoint)
+        if not self.redirect_uris:
+            raise IdentityPolicyError("redirect allowlist is required")
+        for uri in self.redirect_uris:
+            _safe_https_url(uri, redirect=True)
+        if not self.post_login_redirect_uris:
+            raise IdentityPolicyError("post-login redirect allowlist is required")
+        for uri in self.post_login_redirect_uris:
+            _safe_https_url(uri, redirect=True)
+        if not re.fullmatch(r"env:DATAOPS_OIDC_[A-Z0-9_]+", self.secret_ref):
+            raise IdentityPolicyError("client secret must use a dedicated DATAOPS_OIDC environment reference")
+        if not self.algorithms or any(alg not in {"RS256", "RS384", "RS512", "ES256", "ES384"} for alg in self.algorithms):
+            raise IdentityPolicyError("only explicitly configured asymmetric algorithms are allowed")
+        return self
+
+    def resolve_secret(self) -> str:
+        self.validate()
+        value = os.environ.get(self.secret_ref[4:])
+        if not value:
+            raise IdentityPolicyError("configured secret reference is unavailable")
+        return value
+
+    def public_dict(self) -> dict[str, Any]:
+        return {"provider_uid": self.provider_uid, "version": self.version, "issuer": self.issuer,
+                "client_id": self.client_id, "authorization_endpoint": self.authorization_endpoint,
+                "redirect_uris": list(self.redirect_uris), "algorithms": list(self.algorithms),
+                "post_login_redirect_uris": list(self.post_login_redirect_uris),
+                "mapping_version": self.mapping_version, "status": self.status,
+                "secret_ref": "env:DATAOPS_OIDC_***"}
+
+
+def sanitize_audit_detail(value: Any) -> Any:
+    if isinstance(value, Mapping):
+        safe: dict[str, Any] = {}
+        for key, item in value.items():
+            lowered = str(key).lower()
+            if lowered in _SENSITIVE_KEYS or any(part in lowered for part in ("secret", "token", "verifier")):
+                continue
+            if lowered.endswith("claims"):
+                continue
+            safe[str(key)] = sanitize_audit_detail(item)
+        return safe
+    if isinstance(value, (list, tuple)):
+        return [sanitize_audit_detail(item) for item in value]
+    return value
+
+
+@dataclass(frozen=True)
+class MappedIdentity:
+    subject: str
+    username: str
+    display_name: str
+    department: str
+    groups: tuple[str, ...]
+    roles: tuple[str, ...]
+    business_domain_uids: tuple[str, ...]
+    object_types: tuple[str, ...]
+    environments: tuple[str, ...]
+    data_scopes: tuple[str, ...]
+    mapping_version: str
+    claims_digest: str
+    evidence: Mapping[str, Any] = field(repr=False)
+
+
+class ClaimMapper:
+    REQUIRED = ("sub", "preferred_username", "name", "department", "groups")
+    RULE_FIELDS = {"roles", "business_domain_uids", "object_types", "environments", "data_scopes"}
+
+    def __init__(self, *, version: str, group_rules: Mapping[str, Mapping[str, Any]]) -> None:
+        if not isinstance(version, str) or not version.strip():
+            raise IdentityPolicyError("mapping version is required")
+        if not isinstance(group_rules, Mapping) or not group_rules:
+            raise IdentityPolicyError("at least one claims group rule is required")
+        normalized: dict[str, dict[str, tuple[str, ...]]] = {}
+        for group, raw_rule in group_rules.items():
+            if not isinstance(group, str) or not group.strip() or not isinstance(raw_rule, Mapping):
+                raise IdentityPolicyError("claims group rules must be named objects")
+            if set(raw_rule) - self.RULE_FIELDS:
+                raise IdentityPolicyError("claims group rule contains unsupported fields")
+            rule: dict[str, tuple[str, ...]] = {}
+            for field_name in self.RULE_FIELDS:
+                values = raw_rule.get(field_name, ())
+                if not isinstance(values, (list, tuple)) or isinstance(values, (str, bytes)):
+                    raise IdentityPolicyError("claims mapping scope fields must be string arrays")
+                if any(not isinstance(value, str) or not value.strip() for value in values):
+                    raise IdentityPolicyError("claims mapping scope values must be non-empty strings")
+                rule[field_name] = tuple(sorted(set(values)))
+            if not rule["roles"] or not set(rule["roles"]).issubset({"admin", "editor", "viewer"}):
+                raise IdentityPolicyError("mapping contains an unsupported or empty platform role")
+            normalized[group.strip()] = rule
+        self.version = version.strip()
+        self.group_rules = normalized
+
+    def map(self, claims: Mapping[str, Any]) -> MappedIdentity:
+        if not isinstance(claims, Mapping):
+            raise IdentityPolicyError("identity claims must be an object")
+        limits = {"sub": 500, "preferred_username": 64, "name": 300, "department": 300}
+        if any(not isinstance(claims.get(key), str) or not claims[key].strip()
+               or len(claims[key].strip()) > maximum for key, maximum in limits.items()):
+            raise IdentityPolicyError("required identity claim is missing")
+        raw_groups = claims.get("groups")
+        if (not isinstance(raw_groups, (list, tuple)) or isinstance(raw_groups, (str, bytes)) or not raw_groups
+                or any(not isinstance(item, str) or not item.strip() or len(item.strip()) > 300 for item in raw_groups)):
+            raise IdentityPolicyError("identity groups claim must be a non-empty string array")
+        groups = tuple(sorted({item.strip() for item in raw_groups}))
+        matched = [self.group_rules[group] for group in groups if group in self.group_rules]
+        if not matched:
+            raise IdentityPolicyError("claims do not match an approved mapping rule")
+        def collect(key: str) -> tuple[str, ...]:
+            return tuple(sorted({str(value) for rule in matched for value in rule.get(key, ())}))
+        roles = collect("roles")
+        if not roles or not set(roles).issubset({"admin", "editor", "viewer"}):
+            raise IdentityPolicyError("mapping resolves to no approved platform role")
+        canonical = json.dumps(claims, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
+        digest = hashlib.sha256(canonical.encode()).hexdigest()
+        evidence = {"mapping_version": self.version, "claims_digest": digest,
+                    "matched_group_digests": [hashlib.sha256(group.encode()).hexdigest() for group in groups if group in self.group_rules]}
+        return MappedIdentity(claims["sub"].strip(), claims["preferred_username"].strip(), claims["name"].strip(),
+                              claims["department"].strip(), groups, roles, collect("business_domain_uids"),
+                              collect("object_types"), collect("environments"), collect("data_scopes"),
+                              self.version, digest, evidence)
+
+
+class DirectorySynchronizer:
+    EVENTS = {"JOINER", "MOVER", "LEAVER", "DISABLE", "RESTORE"}
+    ORGANIZATION_EVENTS = {"DEPARTMENT", "GROUP"}
+    ORGANIZATION_ACTIONS = {"UPSERT", "DISABLE", "RESTORE"}
+
+    def __init__(self, repository: Any, sessions: Any, *, clock: Callable[[], datetime] | None = None) -> None:
+        self.repository = repository
+        self.sessions = sessions
+        self.clock = clock or (lambda: datetime.now(UTC))
+
+    def apply(self, *, provider_uid: str, source: str, source_event_id: str, cursor: str,
+              cursor_sequence: int, event_type: str, attributes: Mapping[str, Any],
+              subject: str | None = None) -> dict[str, Any]:
+        event_type = event_type.upper()
+        if event_type not in self.EVENTS | self.ORGANIZATION_EVENTS or not all((provider_uid, source, source_event_id, cursor)):
+            raise IdentityPolicyError("invalid directory delta event")
+        if event_type in self.EVENTS and not subject:
+            raise IdentityPolicyError("person lifecycle event requires enterprise subject")
+        if not isinstance(cursor_sequence, int) or cursor_sequence < 1:
+            raise IdentityPolicyError("directory cursor sequence must be a positive integer")
+        digest = hashlib.sha256(json.dumps({"cursor": cursor, "event_type": event_type, "subject": subject,
+                                             "attributes": attributes}, sort_keys=True, default=str).encode()).hexdigest()
+        event = {"provider_uid": provider_uid, "source": source, "source_event_id": source_event_id,
+                 "cursor": cursor, "cursor_sequence": cursor_sequence, "event_type": event_type,
+                 "subject": subject, "payload_digest": digest, "processed_at": _utc(self.clock()).isoformat()}
+
+        def mutate_organization() -> dict[str, Any]:
+            node_type = event_type.lower()
+            external_id = str(attributes.get("external_id") or "").strip()
+            action = str(attributes.get("lifecycle_action") or attributes.get("action") or "UPSERT").upper()
+            if not external_id or action not in self.ORGANIZATION_ACTIONS:
+                raise IdentityPolicyError("invalid organization node delta")
+            existing = self.repository.get_organization_node(provider_uid, node_type, external_id, for_update=True)
+            if action in {"DISABLE", "RESTORE"} and not existing:
+                raise IdentityPolicyError("organization node lifecycle target does not exist")
+            display_name = str(attributes.get("display_name") or (existing or {}).get("display_name") or "").strip()
+            if not display_name:
+                raise IdentityPolicyError("organization node display name is required")
+            node = {
+                "uid": (existing or {}).get("uid") or new_governance_uid(),
+                "provider_uid": provider_uid,
+                "external_id": external_id,
+                "node_type": node_type,
+                "parent_external_id": attributes.get("parent_external_id"),
+                "display_name": display_name,
+                "status": "disabled" if action == "DISABLE" else "active",
+                "attributes": dict(attributes.get("attributes", {})),
+                "updated_at": _utc(self.clock()),
+            }
+            self.repository.put_organization_node(node, commit=False)
+            return {"node_uid": node["uid"], "node_type": node_type, "external_id": external_id,
+                    "status": node["status"], "lifecycle_action": action, "idempotent": False}
+
+        def mutate_identity() -> dict[str, Any]:
+            identity = self.repository.get_identity(provider_uid, str(subject), for_update=True)
+            if not identity and event_type != "JOINER":
+                raise IdentityPolicyError("person lifecycle target does not exist")
+            identity = identity or {"user_uid": new_governance_uid(), "token_version": 0}
+            old_roles = tuple(identity.get("roles", ()))
+            roles = sorted(set(attributes.get("roles", ())))
+            if not set(roles).issubset({"admin", "editor", "viewer"}):
+                raise IdentityPolicyError("directory event contains an unsupported platform role")
+            username = attributes.get("username") or identity.get("username")
+            if not username:
+                raise IdentityPolicyError("person lifecycle username is required")
+            identity.update(subject=subject, username=username,
+                            department=attributes.get("department", identity.get("department", "")),
+                            display_name=attributes.get("display_name") or identity.get("display_name") or username,
+                            provider_uid=provider_uid, groups=sorted(attributes.get("groups", identity.get("groups", ()))),
+                            roles=roles, mapping_version=attributes.get("mapping_version") or identity.get("mapping_version"),
+                            claims_digest=attributes.get("claims_digest") or identity.get("claims_digest"),
+                            authorization_scope=dict(attributes.get("authorization_scope", identity.get("authorization_scope", {}))),
+                            status="disabled" if event_type in {"LEAVER", "DISABLE"} else "active",
+                            token_version=int(identity.get("token_version", 0)) + 1,
+                            updated_at=_utc(self.clock()).isoformat())
+            # RESTORE deliberately recalculates from current input and never restores old roles.
+            self.repository.put_identity(provider_uid, str(subject), identity, commit=False)
+            if event_type in {"MOVER", "LEAVER", "DISABLE", "RESTORE"} or old_roles != tuple(identity["roles"]):
+                self.repository.revoke_subject_sessions(provider_uid, str(subject),
+                                                        reason=f"directory_{event_type.lower()}", commit=False)
+            return {"user_uid": identity["user_uid"], "status": identity["status"],
+                    "token_version": identity["token_version"], "idempotent": False}
+
+        mutation = mutate_organization if event_type in self.ORGANIZATION_EVENTS else mutate_identity
+        return self.repository.apply_directory_event_atomic(event, mutation)
+
+
+class EmergencyAccess:
+    MAX_DURATION = timedelta(hours=2)
+    def __init__(self, repository: Any, *, clock: Callable[[], datetime] | None = None) -> None:
+        self.repository = repository
+        self.clock = clock or (lambda: datetime.now(UTC))
+
+    def request(self, *, requester_uid: str, account_uid: str, reason: str, expires_at: datetime,
+                account_is_local_active_admin: bool) -> dict[str, Any]:
+        now = _utc(self.clock())
+        expiry = _utc(expires_at)
+        if (requester_uid == account_uid or not reason.strip() or not now < expiry
+                or expiry - now > self.MAX_DURATION or not account_is_local_active_admin):
+            raise IdentityPolicyError("invalid emergency access request")
+        record = {"uid": new_governance_uid(), "requester_uid": requester_uid, "account_uid": account_uid,
+                  "reason": reason.strip(), "status": "pending", "approver_uids": [],
+                  "requested_at": now, "expires_at": expiry, "reviewed_at": None}
+        self.repository.put_emergency(record)
+        return record
+
+    def approve(self, request_uid: str, *, approver_uid: str) -> dict[str, Any]:
+        record = self.repository.get_emergency(request_uid)
+        if not record or record["status"] != "pending" or _utc(self.clock()) >= record["expires_at"]:
+            raise IdentityPolicyError("emergency request is not approvable")
+        forbidden = {record["requester_uid"], record["account_uid"], *record["approver_uids"]}
+        if approver_uid in forbidden:
+            raise IdentityPolicyError("emergency access requires two distinct non-self approvers")
+        record["approver_uids"].append(approver_uid)
+        self.repository.put_emergency(record)
+        return record
+
+    def activate(self, request_uid: str) -> dict[str, Any]:
+        record = self.repository.get_emergency(request_uid)
+        if not record or record["status"] != "pending" or len(set(record["approver_uids"])) != 2 or _utc(self.clock()) >= record["expires_at"]:
+            raise IdentityPolicyError("two approvals in the active time window are required")
+        record.update(status="active", activated_at=_utc(self.clock()), alert_required=True)
+        self.repository.put_emergency(record)
+        return record
+
+    def review(self, request_uid: str, *, reviewer_uid: str, outcome: str) -> dict[str, Any]:
+        record = self.repository.get_emergency(request_uid)
+        if record and record["status"] == "active" and _utc(self.clock()) >= record["expires_at"]:
+            record["status"] = "expired"
+            self.repository.put_emergency(record)
+        if not record or record["status"] not in {"closed", "expired"} or reviewer_uid == record["account_uid"]:
+            raise IdentityPolicyError("invalid emergency review")
+        record.update(status="reviewed", reviewed_at=_utc(self.clock()), reviewer_uid=reviewer_uid, review_outcome=outcome)
+        self.repository.put_emergency(record)
+        return record
+
+    def close(self, request_uid: str, *, actor_uid: str) -> dict[str, Any]:
+        record = self.repository.get_emergency(request_uid)
+        if record and record["status"] == "active" and _utc(self.clock()) >= record["expires_at"]:
+            record["status"] = "expired"
+            self.repository.put_emergency(record)
+            raise IdentityPolicyError("emergency access already expired")
+        if not record or record["status"] != "active" or actor_uid == record["account_uid"]:
+            raise IdentityPolicyError("invalid emergency access closure")
+        record.update(status="closed", closed_at=_utc(self.clock()), closed_by_uid=actor_uid)
+        self.repository.put_emergency(record)
+        return record

+ 570 - 0
app/core/system/identity_repository.py

@@ -0,0 +1,570 @@
+"""Repository contracts and a deterministic test adapter for enterprise identity."""
+
+from __future__ import annotations
+
+import copy
+import json
+import secrets
+import threading
+from collections import defaultdict
+from collections.abc import Callable
+from typing import Any
+
+from sqlalchemy import text
+
+from app.core.common.identifiers import new_governance_uid
+from app.core.system.enterprise_identity import IdentityPolicyError
+
+
+class MemoryIdentityRepository:
+    """Small adapter used by protocol tests; production persistence is PostgreSQL."""
+
+    def __init__(self) -> None:
+        self.flows: dict[str, dict[str, Any]] = {}
+        self.sessions: dict[str, dict[str, Any]] = {}
+        self.directory_events: dict[tuple[str, str, str], dict[str, Any]] = {}
+        self.directory_checkpoints: dict[tuple[str, str], dict[str, Any]] = {}
+        self.directory_conflicts: list[dict[str, Any]] = []
+        self.identities: dict[tuple[str, str], dict[str, Any]] = {}
+        self.organization_nodes: dict[tuple[str, str, str], dict[str, Any]] = {}
+        self.emergency_requests: dict[str, dict[str, Any]] = {}
+        self.audits: list[dict[str, Any]] = []
+        self.refresh_index: dict[str, str] = {}
+        self.family_members: dict[str, set[str]] = defaultdict(set)
+        self._session_lock = threading.RLock()
+        self._directory_lock = threading.RLock()
+
+    def put_flow(self, flow: dict[str, Any]) -> None:
+        self.flows[flow["state_hash"]] = copy.deepcopy(flow)
+
+    def consume_flow(self, state_hash: str, *, commit: bool = True) -> dict[str, Any] | None:
+        del commit
+        flow = self.flows.get(state_hash)
+        if not flow or flow.get("consumed_at"):
+            return None
+        flow["consumed_at"] = True
+        return copy.deepcopy(flow)
+
+    def put_session(self, session: dict[str, Any]) -> None:
+        self.sessions[session["uid"]] = copy.deepcopy(session)
+        self.refresh_index[session["refresh_hash"]] = session["uid"]
+        self.family_members[session["family_uid"]].add(session["uid"])
+
+    def create_session(self, session: dict[str, Any], *, max_sessions: int, commit: bool = True) -> None:
+        del commit
+        with self._session_lock:
+            self.put_session(session)
+            active = sorted((x for x in self.sessions.values()
+                             if x["user_uid"] == session["user_uid"] and x["status"] == "active"),
+                            key=lambda x: (x["created_at"], x["uid"]), reverse=True)
+            for old in active[max_sessions:]:
+                self.update_session(old["uid"], status="revoked", revoke_reason="concurrency_limit")
+
+    def rotate_refresh(self, old_hash: str, new_session: dict[str, Any]) -> str:
+        old = self.get_session_by_refresh(old_hash)
+        if not old:
+            return "unknown"
+        if old["status"] != "active":
+            self.revoke_family(old["family_uid"], reason="refresh_reuse")
+            return "reuse"
+        self.update_session(old["uid"], status="rotated", rotated_at=new_session["created_at"])
+        self.put_session(new_session)
+        return "rotated"
+
+    def get_session(self, uid: str) -> dict[str, Any] | None:
+        value = self.sessions.get(uid)
+        return copy.deepcopy(value) if value else None
+
+    def get_session_by_refresh(self, refresh_hash: str) -> dict[str, Any] | None:
+        uid = self.refresh_index.get(refresh_hash)
+        return self.get_session(uid) if uid else None
+
+    def update_session(self, uid: str, **changes: Any) -> dict[str, Any]:
+        self.sessions[uid].update(copy.deepcopy(changes))
+        return self.get_session(uid) or {}
+
+    def list_sessions(self, provider_uid: str | None = None, subject: str | None = None) -> list[dict[str, Any]]:
+        rows = self.sessions.values()
+        if provider_uid is not None:
+            rows = (item for item in rows if item.get("provider_uid") == provider_uid)
+        if subject is not None:
+            rows = (item for item in rows if item["subject"] == subject)
+        return [copy.deepcopy(item) for item in rows]
+
+    def revoke_family(self, family_uid: str, *, reason: str) -> None:
+        for uid in self.family_members.get(family_uid, ()):
+            self.sessions[uid].update(status="revoked", revoke_reason=reason)
+
+    def revoke_subject_sessions(self, provider_uid: str | None, subject: str, *, reason: str,
+                                commit: bool = True) -> None:
+        del commit
+        for record in self.list_sessions(provider_uid, subject):
+            if record["status"] == "active":
+                self.update_session(record["uid"], status="revoked", revoke_reason=reason)
+
+    def get_directory_event(self, provider_uid: str, source: str, source_event_id: str) -> dict[str, Any] | None:
+        value = self.directory_events.get((provider_uid, source, source_event_id))
+        return copy.deepcopy(value) if value else None
+
+    def put_directory_event(self, event: dict[str, Any]) -> None:
+        self.directory_events[(event["provider_uid"], event["source"], event["source_event_id"])] = copy.deepcopy(event)
+        self.directory_checkpoints[(event["provider_uid"], event["source"])] = {
+            "cursor": event["cursor"], "cursor_sequence": event["cursor_sequence"]}
+
+    def get_directory_checkpoint(self, provider_uid: str, source: str) -> dict[str, Any] | None:
+        value = self.directory_checkpoints.get((provider_uid, source))
+        return copy.deepcopy(value) if value else None
+
+    def record_directory_conflict(self, **record: Any) -> None:
+        self.directory_conflicts.append(copy.deepcopy(record))
+
+    def put_identity(self, provider_uid: str, subject: str, identity: dict[str, Any], *, commit: bool = True) -> None:
+        del commit
+        self.identities[(provider_uid, subject)] = copy.deepcopy(identity)
+
+    def get_identity(self, provider_uid: str, subject: str, *, for_update: bool = False) -> dict[str, Any] | None:
+        del for_update
+        value = self.identities.get((provider_uid, subject))
+        return copy.deepcopy(value) if value else None
+
+    def get_organization_node(self, provider_uid: str, node_type: str, external_id: str,
+                              *, for_update: bool = False) -> dict[str, Any] | None:
+        del for_update
+        value = self.organization_nodes.get((provider_uid, node_type, external_id))
+        return copy.deepcopy(value) if value else None
+
+    def put_organization_node(self, node: dict[str, Any], *, commit: bool = True) -> None:
+        del commit
+        key = (node["provider_uid"], node["node_type"], node["external_id"])
+        self.organization_nodes[key] = copy.deepcopy(node)
+
+    def apply_directory_event_atomic(self, event: dict[str, Any],
+                                     mutation: Callable[[], dict[str, Any]]) -> dict[str, Any]:
+        """Apply the lifecycle mutation and checkpoint as one in-memory unit of work."""
+        with self._directory_lock:
+            previous = self.get_directory_event(event["provider_uid"], event["source"], event["source_event_id"])
+            if previous:
+                if previous["payload_digest"] != event["payload_digest"]:
+                    self.record_directory_conflict(**event, reason="source_event_id_payload_mismatch")
+                    raise IdentityPolicyError("directory event idempotency conflict")
+                return {**previous["result"], "idempotent": True}
+            checkpoint = self.get_directory_checkpoint(event["provider_uid"], event["source"])
+            if checkpoint and event["cursor_sequence"] <= checkpoint["cursor_sequence"]:
+                self.record_directory_conflict(**event, reason="cursor_not_monotonic")
+                raise IdentityPolicyError("directory cursor is not monotonic")
+            snapshot = copy.deepcopy((self.identities, self.organization_nodes, self.sessions,
+                                      self.refresh_index, self.family_members,
+                                      self.directory_events, self.directory_checkpoints))
+            try:
+                result = mutation()
+                self.put_directory_event({**event, "result": result})
+                return result
+            except Exception:
+                (self.identities, self.organization_nodes, self.sessions, self.refresh_index,
+                 self.family_members, self.directory_events, self.directory_checkpoints) = snapshot
+                raise
+
+    def put_emergency(self, request: dict[str, Any]) -> None:
+        self.emergency_requests[request["uid"]] = copy.deepcopy(request)
+        if request["status"] in {"closed", "expired", "reviewed"}:
+            for session in self.sessions.values():
+                if session.get("emergency_request_uid") == request["uid"] and session["status"] == "active":
+                    session.update(status="revoked", revoke_reason=f"emergency_{request['status']}")
+
+    def get_emergency(self, uid: str) -> dict[str, Any] | None:
+        value = self.emergency_requests.get(uid)
+        return copy.deepcopy(value) if value else None
+
+    def audit(self, event: dict[str, Any]) -> None:
+        self.audits.append(copy.deepcopy(event))
+
+    def emergency_session_active(self, request_uid: str, account_uid: str, now: Any) -> bool:
+        request = self.emergency_requests.get(request_uid)
+        return bool(request and request["account_uid"] == account_uid and request["status"] == "active"
+                    and request["requested_at"] <= now < request["expires_at"])
+
+
+class PostgresIdentityRepository:
+    """PostgreSQL adapter for OIDC flows and server-side sessions."""
+
+    def __init__(self, session: Any) -> None:
+        self.session = session
+
+    def put_flow(self, flow: dict[str, Any]) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_oidc_flows
+            (uid,state_hash,provider_uid,provider_version,nonce_hash,pkce_verifier_hash,redirect_uri,status,expires_at)
+            VALUES (CAST(:uid AS uuid),:state_hash,CAST(:provider_uid AS uuid),:provider_version,:nonce_hash,
+                    :verifier_hash,:redirect_uri,'pending',:expires_at)
+        """), {**flow, "uid": new_governance_uid(), "verifier_hash": flow["verifier_hash"]})
+        self.session.commit()
+
+    def consume_flow(self, state_hash: str, *, commit: bool = True) -> dict[str, Any] | None:
+        row = self.session.execute(text("""
+            UPDATE public.identity_oidc_flows SET status='consumed', consumed_at=CURRENT_TIMESTAMP
+            WHERE state_hash=:state_hash AND status='pending' AND expires_at>CURRENT_TIMESTAMP
+            RETURNING provider_uid::text,provider_version,nonce_hash,pkce_verifier_hash,redirect_uri,expires_at
+        """), {"state_hash": state_hash}).mappings().one_or_none()
+        if commit:
+            self.session.commit()
+        return ({"provider_uid": row["provider_uid"], "provider_version": row["provider_version"],
+                 "nonce_hash": row["nonce_hash"], "verifier_hash": row["pkce_verifier_hash"],
+                 "redirect_uri": row["redirect_uri"], "expires_at": row["expires_at"]} if row else None)
+
+    def _insert_session(self, record: dict[str, Any]) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_sessions
+            (uid,family_uid,identity_link_uid,user_uid,identity_source,token_version,refresh_hash,status,
+             rotated_from_uid,emergency_request_uid,created_at,last_seen_at,expires_at)
+            VALUES (CAST(:uid AS uuid),CAST(:family_uid AS uuid),
+                    COALESCE(CAST(:identity_link_uid AS uuid),
+                    (SELECT uid FROM public.enterprise_identity_links WHERE provider_uid=CAST(:provider_uid AS uuid)
+                     AND enterprise_subject=:subject ORDER BY updated_at DESC LIMIT 1)),
+                    CAST(:user_uid AS uuid),:identity_source,:token_version,
+                    :refresh_hash,:status,CAST(:rotated_from_uid AS uuid),CAST(:emergency_request_uid AS uuid),
+                    :created_at,:last_seen_at,:expires_at)
+        """), {**record, "identity_link_uid": record.get("identity_link_uid"),
+                 "emergency_request_uid": record.get("emergency_request_uid")})
+
+    def put_session(self, record: dict[str, Any]) -> None:
+        self._insert_session(record)
+        self.session.commit()
+
+    def create_session(self, record: dict[str, Any], *, max_sessions: int, commit: bool = True) -> None:
+        # Serialize all session creations for one account. Without this lock two
+        # concurrent transactions can both observe the same pre-insert count.
+        locked = self.session.execute(
+            text("SELECT id FROM public.users WHERE id=CAST(:user AS uuid) FOR UPDATE"),
+            {"user": record["user_uid"]},
+        ).scalar_one_or_none()
+        if not locked:
+            raise IdentityPolicyError("identity session user does not exist")
+        self._insert_session(record)
+        self.session.execute(text("""
+            WITH excess AS (
+              SELECT uid FROM public.identity_sessions WHERE user_uid=CAST(:user AS uuid) AND status='active'
+              ORDER BY created_at DESC,uid DESC OFFSET :maximum
+            ) UPDATE public.identity_sessions s SET status='revoked',revoke_reason='concurrency_limit',
+              revoked_at=CURRENT_TIMESTAMP FROM excess WHERE s.uid=excess.uid
+        """), {"user": record["user_uid"], "maximum": max_sessions})
+        if commit:
+            self.session.commit()
+
+    def insert_exchange_code(self, record: dict[str, Any], *, commit: bool = True) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_exchange_codes(uid,code_hash,session_uid,redirect_uri,status,expires_at)
+            VALUES(CAST(:uid AS uuid),:code_hash,CAST(:session_uid AS uuid),:redirect_uri,'pending',:expires_at)
+        """), record)
+        if commit:
+            self.session.commit()
+
+    def rotate_refresh(self, old_hash: str, new_session: dict[str, Any]) -> str:
+        row = self.session.execute(text("""
+            UPDATE public.identity_sessions SET status='rotated',rotated_at=:rotated_at
+            WHERE refresh_hash=:refresh_hash AND status='active' AND expires_at>:rotated_at
+            RETURNING family_uid::text
+        """), {"refresh_hash": old_hash, "rotated_at": new_session["created_at"]}).mappings().one_or_none()
+        if row:
+            self._insert_session(new_session)
+            self.session.commit()
+            return "rotated"
+        old = self.session.execute(text("SELECT family_uid::text,status FROM public.identity_sessions WHERE refresh_hash=:hash FOR UPDATE"),
+                                   {"hash": old_hash}).mappings().one_or_none()
+        if not old:
+            self.session.rollback()
+            return "unknown"
+        self.session.execute(text("UPDATE public.identity_sessions SET status='revoked',revoke_reason='refresh_reuse',revoked_at=CURRENT_TIMESTAMP WHERE family_uid=CAST(:family AS uuid)"),
+                             {"family": old["family_uid"]})
+        self.session.commit()
+        return "reuse"
+
+    @staticmethod
+    def _session(row: Any) -> dict[str, Any] | None:
+        if not row:
+            return None
+        result = dict(row)
+        for key in ("uid", "family_uid", "identity_link_uid", "user_uid", "rotated_from_uid", "emergency_request_uid"):
+            result[key] = str(result[key]) if result.get(key) else None
+        result.setdefault("roles", [])
+        result.setdefault("subject", result["user_uid"])
+        return result
+
+    def get_session(self, uid: str) -> dict[str, Any] | None:
+        row = self.session.execute(text("""
+            SELECT s.*,l.provider_uid::text provider_uid,COALESCE(l.enterprise_subject,s.user_uid::text) subject,
+              COALESCE(array_agg(r.name) FILTER (WHERE r.name IS NOT NULL),ARRAY[]::varchar[]) roles
+            FROM public.identity_sessions s LEFT JOIN public.user_roles ur ON ur.user_id=s.user_uid
+            LEFT JOIN public.roles r ON r.id=ur.role_id
+            LEFT JOIN public.enterprise_identity_links l ON l.uid=s.identity_link_uid
+            WHERE s.uid=CAST(:uid AS uuid) GROUP BY s.uid,l.provider_uid,l.enterprise_subject
+        """), {"uid": uid}).mappings().one_or_none()
+        return self._session(row)
+
+    def get_session_by_refresh(self, refresh_hash: str) -> dict[str, Any] | None:
+        uid = self.session.execute(text("SELECT uid::text FROM public.identity_sessions WHERE refresh_hash=:value"),
+                                   {"value": refresh_hash}).scalar_one_or_none()
+        return self.get_session(uid) if uid else None
+
+    def update_session(self, uid: str, *, commit: bool = True, **changes: Any) -> dict[str, Any]:
+        allowed = {"status", "revoke_reason", "risk_reason", "last_seen_at", "rotated_at", "revoked_at"}
+        safe = {key: value for key, value in changes.items() if key in allowed}
+        if safe:
+            assignments = ",".join(f"{key}=:{key}" for key in safe)
+            self.session.execute(text(f"UPDATE public.identity_sessions SET {assignments} WHERE uid=CAST(:uid AS uuid)"), {**safe, "uid": uid})
+            if commit:
+                self.session.commit()
+        return self.get_session(uid) or {}
+
+    def list_sessions(self, provider_uid: str | None = None, subject: str | None = None) -> list[dict[str, Any]]:
+        sql = "SELECT s.uid::text FROM public.identity_sessions s LEFT JOIN public.enterprise_identity_links l ON l.uid=s.identity_link_uid"
+        params: dict[str, Any] = {}
+        clauses: list[str] = []
+        if provider_uid:
+            clauses.append("l.provider_uid=CAST(:provider AS uuid)")
+            params["provider"] = provider_uid
+        if subject:
+            clauses.append("l.enterprise_subject=:subject")
+            params["subject"] = subject
+        if clauses:
+            sql += " WHERE " + " AND ".join(clauses)
+        uids = self.session.execute(text(sql), params).scalars().all()
+        return [record for uid in uids if (record := self.get_session(uid))]
+
+    def revoke_family(self, family_uid: str, *, reason: str) -> None:
+        self.session.execute(text("UPDATE public.identity_sessions SET status='revoked',revoke_reason=:reason,revoked_at=CURRENT_TIMESTAMP WHERE family_uid=CAST(:family AS uuid)"), {"family": family_uid, "reason": reason})
+        self.session.commit()
+
+    def revoke_subject_sessions(self, provider_uid: str | None, subject: str, *, reason: str,
+                                commit: bool = True) -> None:
+        if not provider_uid:
+            return
+        self.session.execute(text("""
+            UPDATE public.identity_sessions SET status='revoked',revoke_reason=:reason,revoked_at=CURRENT_TIMESTAMP
+            WHERE status='active' AND identity_link_uid IN (
+              SELECT uid FROM public.enterprise_identity_links
+              WHERE provider_uid=CAST(:provider AS uuid) AND enterprise_subject=:subject
+            )
+        """), {"provider": provider_uid, "subject": subject, "reason": reason})
+        if commit:
+            self.session.commit()
+
+    def get_directory_event(self, provider_uid: str, source: str, source_event_id: str) -> dict[str, Any] | None:
+        row = self.session.execute(text("SELECT payload_digest,safe_result FROM public.identity_directory_events WHERE provider_uid=CAST(:provider AS uuid) AND source=:source AND source_event_id=:event"),
+                                   {"provider": provider_uid, "source": source, "event": source_event_id}).mappings().one_or_none()
+        return {"payload_digest": row["payload_digest"], "result": dict(row["safe_result"])} if row else None
+
+    def put_directory_event(self, event: dict[str, Any]) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_directory_events
+            (uid,provider_uid,source,source_event_id,cursor_value,cursor_sequence,event_type,enterprise_subject,payload_digest,status,safe_result,processed_at)
+            VALUES (CAST(:uid AS uuid),CAST(:provider_uid AS uuid),:source,:source_event_id,:cursor,:cursor_sequence,
+                    :event_type,:subject,:payload_digest,'applied',CAST(:result AS jsonb),:processed_at)
+        """), {**event, "uid": new_governance_uid(), "event_type": event.get("event_type", "MOVER"),
+                 "subject": event.get("subject"), "result": json.dumps(event["result"])})
+        self.session.execute(text("""
+            INSERT INTO public.identity_directory_checkpoints(provider_uid,source,cursor_value,cursor_sequence,updated_at)
+            VALUES(CAST(:provider_uid AS uuid),:source,:cursor,:cursor_sequence,CURRENT_TIMESTAMP)
+            ON CONFLICT(provider_uid,source) DO UPDATE SET cursor_value=EXCLUDED.cursor_value,
+              cursor_sequence=EXCLUDED.cursor_sequence,updated_at=CURRENT_TIMESTAMP
+        """), event)
+        self.session.commit()
+
+    def get_directory_checkpoint(self, provider_uid: str, source: str) -> dict[str, Any] | None:
+        row = self.session.execute(text("SELECT cursor_value cursor,cursor_sequence FROM public.identity_directory_checkpoints WHERE provider_uid=CAST(:provider AS uuid) AND source=:source"),
+                                   {"provider": provider_uid, "source": source}).mappings().one_or_none()
+        return dict(row) if row else None
+
+    def record_directory_conflict(self, *, commit: bool = True, **record: Any) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_directory_conflicts
+            (uid,provider_uid,source,source_event_id,cursor_value,cursor_sequence,payload_digest,reason)
+            VALUES(CAST(:uid AS uuid),CAST(:provider_uid AS uuid),:source,:source_event_id,:cursor,:cursor_sequence,:payload_digest,:reason)
+        """), {**record, "uid": new_governance_uid()})
+        if commit:
+            self.session.commit()
+
+    def get_identity(self, provider_uid: str, subject: str, *, for_update: bool = False) -> dict[str, Any] | None:
+        lock = " FOR UPDATE" if for_update else ""
+        row = self.session.execute(text("""
+            SELECT user_uid::text,username,display_name,department_external_id department,groups,
+                   role_names roles,authorization_scope,mapping_version,claims_digest,status,token_version
+            FROM public.enterprise_identity_links WHERE provider_uid=CAST(:provider AS uuid) AND enterprise_subject=:subject
+        """ + lock), {"provider": provider_uid, "subject": subject}).mappings().one_or_none()
+        return dict(row) if row else None
+
+    def put_identity(self, provider_uid: str, subject: str, identity: dict[str, Any], *, commit: bool = True) -> None:
+        existing = self.get_identity(provider_uid, subject, for_update=not commit)
+        if not existing:
+            if not all(identity.get(key) for key in ("provider_uid", "username", "display_name", "mapping_version", "claims_digest")):
+                raise IdentityPolicyError("JOINER requires an approved claims mapping result")
+            from app.core.system.auth import hash_password
+
+            self.session.execute(text("INSERT INTO public.users(id,username,display_name,password_hash,status) VALUES (CAST(:uid AS uuid),:username,:display,:password,'active')"),
+                                 {"uid": identity["user_uid"], "username": identity["username"],
+                                  "display": identity["display_name"], "password": hash_password(secrets.token_urlsafe(48))})
+            self.session.execute(text("""
+                INSERT INTO public.enterprise_identity_links
+                (uid,provider_uid,enterprise_subject,user_uid,username,display_name,department_external_id,groups,role_names,
+                 authorization_scope,mapping_version,claims_digest,status,token_version)
+                VALUES (CAST(:uid AS uuid),CAST(:provider AS uuid),:subject,CAST(:user AS uuid),:username,:display,
+                 COALESCE(:department,''),CAST(:groups AS jsonb),CAST(:roles AS jsonb),CAST(:scope AS jsonb),:mapping,
+                 :digest,:status,:token_version)
+            """), {"uid": new_governance_uid(), "provider": identity["provider_uid"], "subject": subject,
+                    "user": identity["user_uid"], "username": identity["username"], "display": identity["display_name"],
+                    "department": identity.get("department"), "groups": json.dumps(identity.get("groups", [])),
+                    "roles": json.dumps(identity.get("roles", [])), "scope": json.dumps(identity.get("authorization_scope", {})),
+                    "mapping": identity["mapping_version"], "digest": identity["claims_digest"],
+                    "status": identity["status"], "token_version": identity["token_version"]})
+        else:
+            self.session.execute(text("""
+            UPDATE public.enterprise_identity_links SET username=:username,department_external_id=COALESCE(:department,''),
+                display_name=COALESCE(:display_name,display_name),groups=CAST(:groups AS jsonb),role_names=CAST(:roles AS jsonb),
+                authorization_scope=CAST(:scope AS jsonb),mapping_version=COALESCE(:mapping_version,mapping_version),
+                claims_digest=COALESCE(:claims_digest,claims_digest),status=:status,token_version=:token_version,
+                updated_at=CURRENT_TIMESTAMP WHERE provider_uid=CAST(:provider AS uuid) AND enterprise_subject=:subject
+            """), {"provider": provider_uid, "subject": subject, "username": identity["username"], "department": identity.get("department"),
+                     "display_name": identity.get("display_name"), "groups": json.dumps(identity.get("groups", [])),
+                     "roles": json.dumps(identity.get("roles", [])), "scope": json.dumps(identity.get("authorization_scope", {})),
+                     "mapping_version": identity.get("mapping_version"), "claims_digest": identity.get("claims_digest"),
+                     "status": identity["status"], "token_version": identity["token_version"]})
+        self.session.execute(text("DELETE FROM public.user_roles WHERE user_id=CAST(:user AS uuid)"), {"user": identity["user_uid"]})
+        self.session.execute(text("INSERT INTO public.user_roles(user_id,role_id) SELECT CAST(:user AS uuid),id FROM public.roles WHERE name=ANY(:roles)"),
+                             {"user": identity["user_uid"], "roles": identity.get("roles", [])})
+        self.session.execute(
+            text("UPDATE public.users SET status=:status,updated_at=CURRENT_TIMESTAMP WHERE id=CAST(:user AS uuid)"),
+            {"user": identity["user_uid"], "status": "active" if identity["status"] == "active" else "disabled"},
+        )
+        if commit:
+            self.session.commit()
+
+    def get_organization_node(self, provider_uid: str, node_type: str, external_id: str,
+                              *, for_update: bool = False) -> dict[str, Any] | None:
+        lock = " FOR UPDATE" if for_update else ""
+        row = self.session.execute(text("""
+            SELECT uid::text,provider_uid::text,external_id,node_type,parent_external_id,display_name,status,attributes,updated_at
+            FROM public.identity_organization_nodes
+            WHERE provider_uid=CAST(:provider AS uuid) AND node_type=:node_type AND external_id=:external_id
+        """ + lock), {"provider": provider_uid, "node_type": node_type,
+                       "external_id": external_id}).mappings().one_or_none()
+        return dict(row) if row else None
+
+    def put_organization_node(self, node: dict[str, Any], *, commit: bool = True) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_organization_nodes
+            (uid,provider_uid,external_id,node_type,parent_external_id,display_name,status,attributes,updated_at)
+            VALUES(CAST(:uid AS uuid),CAST(:provider_uid AS uuid),:external_id,:node_type,:parent_external_id,
+                   :display_name,:status,CAST(:attributes AS jsonb),:updated_at)
+            ON CONFLICT(provider_uid,node_type,external_id) DO UPDATE SET
+              parent_external_id=EXCLUDED.parent_external_id,display_name=EXCLUDED.display_name,
+              status=EXCLUDED.status,attributes=EXCLUDED.attributes,updated_at=EXCLUDED.updated_at
+        """), {**node, "attributes": json.dumps(node.get("attributes", {}))})
+        if commit:
+            self.session.commit()
+
+    def _insert_directory_event(self, event: dict[str, Any], result: dict[str, Any]) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_directory_events
+            (uid,provider_uid,source,source_event_id,cursor_value,cursor_sequence,event_type,enterprise_subject,payload_digest,status,safe_result,processed_at)
+            VALUES (CAST(:uid AS uuid),CAST(:provider_uid AS uuid),:source,:source_event_id,:cursor,:cursor_sequence,
+                    :event_type,:subject,:payload_digest,'applied',CAST(:result AS jsonb),:processed_at)
+        """), {**event, "uid": new_governance_uid(), "subject": event.get("subject"),
+                 "result": json.dumps(result)})
+        self.session.execute(text("""
+            INSERT INTO public.identity_directory_checkpoints(provider_uid,source,cursor_value,cursor_sequence,updated_at)
+            VALUES(CAST(:provider_uid AS uuid),:source,:cursor,:cursor_sequence,CURRENT_TIMESTAMP)
+            ON CONFLICT(provider_uid,source) DO UPDATE SET cursor_value=EXCLUDED.cursor_value,
+              cursor_sequence=EXCLUDED.cursor_sequence,updated_at=CURRENT_TIMESTAMP
+        """), event)
+
+    def apply_directory_event_atomic(self, event: dict[str, Any],
+                                     mutation: Callable[[], dict[str, Any]]) -> dict[str, Any]:
+        """Serialize one provider/source feed and commit its complete effect once."""
+        try:
+            scope = f"{event['provider_uid']}:{event['source']}"
+            self.session.execute(text("SELECT pg_advisory_xact_lock(hashtextextended(:scope,0))"), {"scope": scope})
+            previous = self.get_directory_event(event["provider_uid"], event["source"], event["source_event_id"])
+            if previous:
+                if previous["payload_digest"] != event["payload_digest"]:
+                    self.record_directory_conflict(commit=False, **event, reason="source_event_id_payload_mismatch")
+                    self.session.commit()
+                    raise IdentityPolicyError("directory event idempotency conflict")
+                self.session.commit()
+                return {**previous["result"], "idempotent": True}
+            checkpoint = self.get_directory_checkpoint(event["provider_uid"], event["source"])
+            if checkpoint and event["cursor_sequence"] <= checkpoint["cursor_sequence"]:
+                self.record_directory_conflict(commit=False, **event, reason="cursor_not_monotonic")
+                self.session.commit()
+                raise IdentityPolicyError("directory cursor is not monotonic")
+            result = mutation()
+            self._insert_directory_event(event, result)
+            self.session.commit()
+            return result
+        except IdentityPolicyError:
+            # Policy conflicts are committed above so operators retain evidence;
+            # mutation failures are rolled back with every partial lifecycle write.
+            if self.session.in_transaction():
+                self.session.rollback()
+            raise
+        except Exception:
+            self.session.rollback()
+            raise
+
+    def put_emergency(self, record: dict[str, Any]) -> None:
+        existing = self.get_emergency(record["uid"])
+        if not existing:
+            self.session.execute(text("""
+                INSERT INTO public.identity_emergency_requests
+                (uid,requester_uid,emergency_account_uid,reason,status,starts_at,expires_at)
+                VALUES (CAST(:uid AS uuid),CAST(:requester_uid AS uuid),CAST(:account_uid AS uuid),:reason,:status,:starts_at,:expires_at)
+            """), {**record, "starts_at": record["requested_at"]})
+        else:
+            self.session.execute(text("UPDATE public.identity_emergency_requests SET status=:status,activated_at=:activated_at,closed_at=:closed_at,reviewer_uid=CAST(:reviewer_uid AS uuid),review_outcome=:review_outcome WHERE uid=CAST(:uid AS uuid)"),
+                                 {"uid": record["uid"], "status": record["status"], "activated_at": record.get("activated_at"),
+                                  "closed_at": record.get("closed_at"), "reviewer_uid": record.get("reviewer_uid"),
+                                  "review_outcome": record.get("review_outcome")})
+            for approver in set(record.get("approver_uids", [])) - set(existing.get("approver_uids", [])):
+                self.session.execute(text("INSERT INTO public.identity_emergency_approvals(request_uid,approver_uid) VALUES (CAST(:request AS uuid),CAST(:approver AS uuid)) ON CONFLICT DO NOTHING"), {"request": record["uid"], "approver": approver})
+            if record["status"] in {"closed", "expired", "reviewed"}:
+                self.session.execute(text("""
+                    UPDATE public.identity_sessions SET status='revoked',revoke_reason=:reason,revoked_at=CURRENT_TIMESTAMP
+                    WHERE emergency_request_uid=CAST(:request AS uuid) AND status='active'
+                """), {"request": record["uid"], "reason": f"emergency_{record['status']}"})
+        self.session.commit()
+
+    def get_emergency(self, uid: str) -> dict[str, Any] | None:
+        row = self.session.execute(text("""
+            SELECT r.uid::text,r.requester_uid::text,r.emergency_account_uid::text account_uid,r.reason,r.status,
+                   r.starts_at requested_at,r.expires_at,r.activated_at,r.reviewer_uid::text,r.review_outcome,
+                   COALESCE(array_agg(a.approver_uid::text) FILTER (WHERE a.approver_uid IS NOT NULL),ARRAY[]::text[]) approver_uids
+            FROM public.identity_emergency_requests r LEFT JOIN public.identity_emergency_approvals a ON a.request_uid=r.uid
+            WHERE r.uid=CAST(:uid AS uuid) GROUP BY r.uid
+        """), {"uid": uid}).mappings().one_or_none()
+        return dict(row) if row else None
+
+    def audit(self, event: dict[str, Any]) -> None:
+        self.session.execute(text("""
+            INSERT INTO public.identity_audit_events
+            (uid,event_type,outcome,actor_uid,provider_uid,enterprise_subject_digest,resource_type,resource_uid,correlation_uid,safe_detail)
+            VALUES (CAST(:uid AS uuid),:event_type,:outcome,CAST(:actor_uid AS uuid),CAST(:provider_uid AS uuid),:subject_digest,
+                    :resource_type,:resource_uid,CAST(:correlation_uid AS uuid),CAST(:safe_detail AS jsonb))
+        """), {"uid": new_governance_uid(), "event_type": event["event_type"], "outcome": event["outcome"],
+                 "actor_uid": event.get("actor_uid"), "provider_uid": event.get("provider_uid"),
+                 "subject_digest": event.get("subject_digest"), "resource_type": event.get("resource_type", "identity"),
+                 "resource_uid": event.get("resource_uid"), "correlation_uid": event.get("correlation_uid"),
+                 "safe_detail": json.dumps(event.get("safe_detail", {}))})
+        self.session.commit()
+
+    def emergency_session_active(self, request_uid: str, account_uid: str, now: Any) -> bool:
+        return bool(self.session.execute(text("""
+            SELECT 1 FROM public.identity_emergency_requests e
+            JOIN public.users u ON u.id=e.emergency_account_uid
+            WHERE e.uid=CAST(:request AS uuid) AND e.emergency_account_uid=CAST(:account AS uuid)
+              AND e.status='active' AND e.starts_at<=:now AND e.expires_at>:now AND u.status='active'
+              AND EXISTS (
+                SELECT 1 FROM public.user_roles ur JOIN public.roles r ON r.id=ur.role_id
+                WHERE ur.user_id=u.id AND r.name='admin'
+              )
+              AND NOT EXISTS (
+                SELECT 1 FROM public.enterprise_identity_links l WHERE l.user_uid=u.id
+              )
+        """), {"request": request_uid, "account": account_uid, "now": now}).scalar())

+ 103 - 0
app/core/system/identity_sessions.py

@@ -0,0 +1,103 @@
+"""Server-side session ledger with opaque rotating refresh credentials."""
+
+from __future__ import annotations
+
+import hashlib
+import secrets
+from collections.abc import Callable
+from dataclasses import dataclass
+from datetime import UTC, datetime, timedelta
+from typing import Any
+
+from app.core.common.identifiers import new_governance_uid
+from app.core.system.enterprise_identity import IdentityPolicyError
+from app.core.system.tokens import issue_access_token
+
+
+@dataclass(frozen=True)
+class SessionCredentials:
+    session_uid: str
+    access_token: str
+    refresh_token: str
+
+
+class SessionManager:
+    def __init__(self, repository: Any, *, secret: str, clock: Callable[[], datetime] | None = None,
+                 max_sessions: int = 5, refresh_lifetime: timedelta = timedelta(hours=8)) -> None:
+        self.repository = repository
+        self.secret = secret
+        self.clock = clock or (lambda: datetime.now(UTC))
+        self.max_sessions = max_sessions
+        self.refresh_lifetime = refresh_lifetime
+
+    @staticmethod
+    def _digest(token: str) -> str:
+        return hashlib.sha256(token.encode()).hexdigest()
+
+    def _access(self, record: dict[str, Any]) -> str:
+        return issue_access_token(user_id=record["user_uid"], roles=list(record["roles"]), secret=self.secret,
+                                  now=self.clock(), session_uid=record["uid"], token_version=record["token_version"],
+                                  identity_source=record["identity_source"])
+
+    def create(self, *, subject: str, user_uid: str, roles: list[str], identity_source: str,
+               token_version: int = 1, provider_uid: str | None = None,
+               emergency_request_uid: str | None = None,
+               emergency_expires_at: datetime | None = None, commit: bool = True) -> SessionCredentials:
+        if identity_source not in {"oidc", "local", "emergency"}:
+            raise IdentityPolicyError("unsupported identity source")
+        if identity_source == "oidc" and not provider_uid:
+            raise IdentityPolicyError("OIDC session requires provider binding")
+        if identity_source == "emergency" and (not emergency_request_uid or not emergency_expires_at):
+            raise IdentityPolicyError("emergency session requires request and expiry binding")
+        now = self.clock()
+        if identity_source == "emergency" and not self.repository.emergency_session_active(
+                str(emergency_request_uid), user_uid, now):
+            raise IdentityPolicyError("emergency account is no longer an active local administrator")
+        refresh = secrets.token_urlsafe(48)
+        record = {"uid": new_governance_uid(), "family_uid": new_governance_uid(), "subject": subject,
+                  "user_uid": user_uid, "roles": sorted(set(roles)), "identity_source": identity_source,
+                  "provider_uid": provider_uid, "emergency_request_uid": emergency_request_uid,
+                  "token_version": token_version, "refresh_hash": self._digest(refresh), "status": "active",
+                  "created_at": now, "last_seen_at": now,
+                  "expires_at": min(now + self.refresh_lifetime, emergency_expires_at) if emergency_expires_at else now + self.refresh_lifetime,
+                  "rotated_from_uid": None, "risk_reason": None}
+        self.repository.create_session(record, max_sessions=self.max_sessions, commit=commit)
+        return SessionCredentials(record["uid"], self._access(record), refresh)
+
+    def refresh(self, refresh_token: str) -> SessionCredentials:
+        record = self.repository.get_session_by_refresh(self._digest(refresh_token))
+        if not record:
+            raise IdentityPolicyError("unknown refresh credential")
+        if record["identity_source"] == "emergency" and not self.repository.emergency_session_active(
+                record["emergency_request_uid"], record["user_uid"], self.clock()):
+            self.repository.revoke_family(record["family_uid"], reason="emergency_window_closed")
+            raise IdentityPolicyError("emergency access window is not active")
+        if record["status"] != "active":
+            self.repository.revoke_family(record["family_uid"], reason="refresh_reuse")
+            raise IdentityPolicyError("refresh reuse detected")
+        if self.clock() >= record["expires_at"]:
+            self.repository.update_session(record["uid"], status="revoked", revoke_reason="expired")
+            raise IdentityPolicyError("refresh credential expired")
+        refresh = secrets.token_urlsafe(48)
+        next_expiry = self.clock() + self.refresh_lifetime
+        if record["identity_source"] == "emergency":
+            next_expiry = min(record["expires_at"], next_expiry)
+        next_record = {**record, "uid": new_governance_uid(), "refresh_hash": self._digest(refresh),
+                       "status": "active", "created_at": self.clock(), "last_seen_at": self.clock(),
+                       "expires_at": next_expiry, "rotated_from_uid": record["uid"]}
+        outcome = self.repository.rotate_refresh(self._digest(refresh_token), next_record)
+        if outcome == "unknown":
+            raise IdentityPolicyError("unknown refresh credential")
+        if outcome == "reuse":
+            raise IdentityPolicyError("refresh reuse detected")
+        return SessionCredentials(next_record["uid"], self._access(next_record), refresh)
+
+    def revoke(self, session_uid: str, *, reason: str = "logout") -> None:
+        if self.repository.get_session(session_uid):
+            self.repository.update_session(session_uid, status="revoked", revoke_reason=reason)
+
+    def risk_revoke(self, session_uid: str, *, reason: str) -> None:
+        self.repository.update_session(session_uid, status="revoked", revoke_reason="risk", risk_reason=reason)
+
+    def revoke_subject(self, provider_uid: str | None, subject: str, *, reason: str) -> None:
+        self.repository.revoke_subject_sessions(provider_uid, subject, reason=reason)

+ 261 - 0
app/core/system/oidc.py

@@ -0,0 +1,261 @@
+"""OIDC Authorization Code + PKCE protocol helpers with strict token validation."""
+
+from __future__ import annotations
+
+import base64
+import hashlib
+import ipaddress
+import json
+import secrets
+import socket
+from collections.abc import Callable, Mapping
+from dataclasses import dataclass
+from datetime import UTC, datetime, timedelta
+from typing import Any
+from urllib.parse import urlencode, urlsplit
+
+import jwt
+import requests
+import urllib3
+
+from app.core.system.enterprise_identity import (
+    IdentityPolicyError,
+    IdentityUpstreamError,
+    IdpConfig,
+)
+
+
+def _digest(value: str) -> str:
+    return hashlib.sha256(value.encode()).hexdigest()
+
+
+@dataclass(frozen=True)
+class AuthorizationStart:
+    url: str
+    state: str
+    nonce: str
+    code_verifier: str
+    code_challenge: str
+    code_challenge_method: str = "S256"
+
+
+class OidcClient:
+    def __init__(self, repository: Any, *, clock: Callable[[], datetime] | None = None,
+                 flow_lifetime: timedelta = timedelta(minutes=5)) -> None:
+        self.repository = repository
+        self.clock = clock or (lambda: datetime.now(UTC))
+        self.flow_lifetime = flow_lifetime
+
+    def begin(self, config: IdpConfig, redirect_uri: str) -> AuthorizationStart:
+        config.validate()
+        if redirect_uri not in config.redirect_uris:
+            raise IdentityPolicyError("redirect URI is not allowlisted")
+        state, nonce, verifier = (secrets.token_urlsafe(32) for _ in range(3))
+        challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
+        self.repository.put_flow({"state_hash": _digest(state), "provider_uid": config.provider_uid,
+                                  "provider_version": config.version, "nonce_hash": _digest(nonce),
+                                  "verifier_hash": _digest(verifier), "redirect_uri": redirect_uri,
+                                  "expires_at": self.clock() + self.flow_lifetime, "consumed_at": None})
+        params = {"response_type": "code", "client_id": config.client_id, "redirect_uri": redirect_uri,
+                  "scope": "openid profile", "state": state, "nonce": nonce,
+                  "code_challenge": challenge, "code_challenge_method": "S256"}
+        return AuthorizationStart(f"{config.authorization_endpoint}?{urlencode(params)}", state, nonce, verifier, challenge)
+
+    def verify_callback(self, config: IdpConfig, *, state: str, redirect_uri: str, code_verifier: str,
+                        id_token: str, jwks: Mapping[str, Any], commit: bool = True) -> dict[str, Any]:
+        flow = self.repository.consume_flow(_digest(state), commit=commit)
+        if not flow:
+            raise IdentityPolicyError("state is invalid or already consumed")
+        if self.clock() >= flow["expires_at"] or flow["provider_uid"] != config.provider_uid or flow["provider_version"] != config.version:
+            raise IdentityPolicyError("state is expired or bound to another provider")
+        if redirect_uri != flow["redirect_uri"] or redirect_uri not in config.redirect_uris or _digest(code_verifier) != flow["verifier_hash"]:
+            raise IdentityPolicyError("callback binding or PKCE verifier is invalid")
+        try:
+            header = jwt.get_unverified_header(id_token)
+            algorithm = header.get("alg")
+            if algorithm not in config.algorithms or algorithm == "none":
+                raise IdentityPolicyError("ID token algorithm is not allowed")
+            candidates = [key for key in jwks.get("keys", ()) if key.get("kid") == header.get("kid") and key.get("alg", algorithm) == algorithm]
+            if len(candidates) != 1:
+                raise IdentityPolicyError("ID token signing key is ambiguous or missing")
+            public_key = jwt.algorithms.get_default_algorithms()[algorithm].from_jwk(candidates[0])
+            claims = jwt.decode(id_token, public_key, algorithms=[algorithm], audience=config.client_id,
+                                issuer=config.issuer, options={"verify_exp": False,
+                                "require": ["iss", "aud", "sub", "nonce", "iat", "exp"]})
+        except IdentityPolicyError:
+            raise
+        except jwt.PyJWTError as exc:
+            raise IdentityPolicyError("ID token validation failed") from exc
+        if _digest(str(claims["nonce"])) != flow["nonce_hash"]:
+            raise IdentityPolicyError("ID token nonce mismatch")
+        now = int(self.clock().timestamp())
+        if int(claims["exp"]) <= now:
+            raise IdentityPolicyError("ID token expired")
+        if int(claims["iat"]) > now + 30:
+            raise IdentityPolicyError("ID token issued in the future")
+        return dict(claims)
+
+
+class OidcTransport:
+    """Bounded HTTPS transport whose production connection is pinned to validated IPs."""
+
+    def __init__(self, http: Any | None = None, *, connect_timeout: float = 3.0,
+                 read_timeout: float = 7.0, max_response_bytes: int = 1024 * 1024,
+                 resolver: Callable[..., Any] = socket.getaddrinfo,
+                 pool_factory: Callable[..., Any] = urllib3.HTTPSConnectionPool,
+                 allowed_hosts: set[str] | None = None) -> None:
+        self.http = http
+        self.connect_timeout = connect_timeout
+        self.read_timeout = read_timeout
+        self.timeout = (connect_timeout, read_timeout)
+        self.max_response_bytes = max_response_bytes
+        self.resolver = resolver
+        self.pool_factory = pool_factory
+        self.allowed_hosts = {host.lower() for host in allowed_hosts} if allowed_hosts else None
+
+    def _validate_destination(self, url: str) -> tuple[Any, tuple[str, ...]]:
+        parsed = urlsplit(url)
+        if parsed.scheme != "https" or not parsed.hostname:
+            raise IdentityPolicyError("OIDC destination must be an absolute HTTPS URL")
+        hostname = parsed.hostname.lower()
+        if self.allowed_hosts is not None and hostname not in self.allowed_hosts:
+            raise IdentityPolicyError("enterprise identity provider host is not server-allowlisted")
+        try:
+            addresses = {
+                result[4][0]
+                for result in self.resolver(hostname, parsed.port or 443, type=socket.SOCK_STREAM)
+            }
+        except (OSError, socket.gaierror) as exc:
+            raise IdentityUpstreamError("enterprise identity provider DNS resolution failed") from exc
+        if not addresses:
+            raise IdentityUpstreamError("enterprise identity provider DNS returned no addresses")
+        try:
+            parsed_addresses = [ipaddress.ip_address(address) for address in addresses]
+        except ValueError as exc:
+            raise IdentityUpstreamError("enterprise identity provider DNS response is invalid") from exc
+        if any(not address.is_global for address in parsed_addresses):
+            raise IdentityPolicyError("enterprise identity provider resolved to a non-public address")
+        return parsed, tuple(sorted(str(address) for address in parsed_addresses))
+
+    def _decode_json_response(self, *, status: int, headers: Mapping[str, Any], chunks: Any) -> Mapping[str, Any]:
+        if 300 <= status < 400:
+            raise IdentityUpstreamError("enterprise identity provider redirect is forbidden")
+        if status < 200 or status >= 300:
+            raise IdentityUpstreamError("enterprise identity provider returned an HTTP error")
+        content_type = str(headers.get("Content-Type", "")).lower()
+        if "application/json" not in content_type:
+            raise IdentityUpstreamError("enterprise identity provider response must be JSON")
+        content_length = headers.get("Content-Length")
+        if content_length:
+            try:
+                parsed_length = int(content_length)
+            except (TypeError, ValueError) as exc:
+                raise IdentityUpstreamError("enterprise identity provider response length is invalid") from exc
+            if parsed_length < 0:
+                raise IdentityUpstreamError("enterprise identity provider response length is invalid")
+            if parsed_length > self.max_response_bytes:
+                raise IdentityUpstreamError("enterprise identity provider response is too large")
+        body = bytearray()
+        try:
+            for chunk in chunks:
+                if chunk:
+                    body.extend(chunk)
+                    if len(body) > self.max_response_bytes:
+                        raise IdentityUpstreamError("enterprise identity provider response is too large")
+        except (requests.RequestException, urllib3.exceptions.HTTPError, OSError) as exc:
+            raise IdentityUpstreamError("enterprise identity provider response read failed") from exc
+        try:
+            payload = json.loads(body.decode("utf-8"))
+        except (UnicodeDecodeError, json.JSONDecodeError) as exc:
+            raise IdentityUpstreamError("enterprise identity provider returned invalid JSON") from exc
+        if not isinstance(payload, Mapping):
+            raise IdentityUpstreamError("enterprise identity provider JSON object is required")
+        return payload
+
+    def _pinned_request_json(self, method: str, parsed: Any, addresses: tuple[str, ...],
+                             data: Mapping[str, Any] | None) -> Mapping[str, Any]:
+        hostname = parsed.hostname.lower()
+        port = parsed.port or 443
+        request_target = parsed.path or "/"
+        if parsed.query:
+            request_target += f"?{parsed.query}"
+        host_header = hostname if port == 443 else f"{hostname}:{port}"
+        body = urlencode(data).encode("utf-8") if data is not None else None
+        headers = {"Host": host_header, "Accept": "application/json"}
+        if body is not None:
+            headers["Content-Type"] = "application/x-www-form-urlencoded"
+        last_error: Exception | None = None
+        for address in addresses:
+            pool = self.pool_factory(
+                host=address,
+                port=port,
+                server_hostname=hostname,
+                assert_hostname=hostname,
+                cert_reqs="CERT_REQUIRED",
+                ca_certs=requests.certs.where(),
+                timeout=urllib3.Timeout(connect=self.connect_timeout, read=self.read_timeout),
+                retries=False,
+                maxsize=1,
+                block=True,
+            )
+            try:
+                response = pool.urlopen(
+                    method.upper(), request_target, body=body, headers=headers,
+                    redirect=False, retries=False, assert_same_host=False,
+                    preload_content=False, decode_content=True,
+                    timeout=urllib3.Timeout(connect=self.connect_timeout, read=self.read_timeout),
+                )
+            except (urllib3.exceptions.HTTPError, OSError) as exc:
+                last_error = exc
+                pool.close()
+                continue
+            try:
+                return self._decode_json_response(
+                    status=int(response.status), headers=response.headers,
+                    chunks=response.stream(amt=64 * 1024, decode_content=True),
+                )
+            finally:
+                response.release_conn()
+                pool.close()
+        raise IdentityUpstreamError("enterprise identity provider connection failed") from last_error
+
+    def _request_json(self, method: str, url: str, **kwargs: Any) -> Mapping[str, Any]:
+        parsed, addresses = self._validate_destination(url)
+        if self.http is None:
+            return self._pinned_request_json(method, parsed, addresses, kwargs.get("data"))
+        try:
+            response = getattr(self.http, method)(url, timeout=self.timeout, allow_redirects=False,
+                                                  stream=True, **kwargs)
+        except requests.RequestException as exc:
+            raise IdentityUpstreamError("enterprise identity provider request failed") from exc
+        try:
+            try:
+                status = int(getattr(response, "status_code", 0))
+            except (TypeError, ValueError) as exc:
+                raise IdentityUpstreamError("enterprise identity provider HTTP status is invalid") from exc
+            return self._decode_json_response(
+                status=status, headers=getattr(response, "headers", {}),
+                chunks=response.iter_content(chunk_size=64 * 1024),
+            )
+        finally:
+            close = getattr(response, "close", None)
+            if callable(close):
+                close()
+
+    def exchange_code(self, config: IdpConfig, *, code: str, redirect_uri: str, verifier: str) -> str:
+        payload = self._request_json(
+            "post", config.token_endpoint,
+            data={"grant_type": "authorization_code", "code": code, "redirect_uri": redirect_uri,
+                  "client_id": config.client_id, "client_secret": config.resolve_secret(),
+                  "code_verifier": verifier},
+        )
+        id_token = payload.get("id_token")
+        if not isinstance(id_token, str) or not id_token:
+            raise IdentityPolicyError("token endpoint omitted ID token")
+        return id_token
+
+    def fetch_jwks(self, config: IdpConfig) -> Mapping[str, Any]:
+        payload = self._request_json("get", config.jwks_uri)
+        if not isinstance(payload, Mapping) or not isinstance(payload.get("keys"), list):
+            raise IdentityPolicyError("JWKS response is invalid")
+        return payload

+ 140 - 10
app/core/system/permissions.py

@@ -70,6 +70,19 @@ AGENTS_MANAGE = "agents:manage"
 SECURITY_GOVERNANCE_READ = "security-governance:read"
 SECURITY_GOVERNANCE_OPERATE = "security-governance:operate"
 SECURITY_GOVERNANCE_MANAGE = "security-governance:manage"
+IDENTITY_READ = "identity:read"
+IDENTITY_MANAGE = "identity:manage"
+IDENTITY_OPERATE = "identity:operate"
+CONNECTORS_READ = "connectors:read"
+CONNECTORS_OPERATE = "connectors:operate"
+CONNECTORS_MANAGE = "connectors:manage"
+EDGE_GATEWAYS_READ = "edge-gateways:read"
+EDGE_GATEWAYS_OPERATE = "edge-gateways:operate"
+EDGE_GATEWAYS_MANAGE = "edge-gateways:manage"
+BI_AI_CATALOG_READ = "bi-ai-catalog:read"
+BI_AI_CATALOG_MANAGE = "bi-ai-catalog:manage"
+METERING_READ = "metering:read"
+METERING_MANAGE = "metering:manage"
 
 ROLE_PERMISSIONS = {
     "viewer": frozenset(
@@ -84,6 +97,11 @@ ROLE_PERMISSIONS = {
             DATA_PRODUCTS_READ,
             AGENTS_READ,
             SECURITY_GOVERNANCE_READ,
+            IDENTITY_READ,
+            CONNECTORS_READ,
+            EDGE_GATEWAYS_READ,
+            BI_AI_CATALOG_READ,
+            METERING_READ,
         }
     ),
     "editor": frozenset(
@@ -121,6 +139,14 @@ ROLE_PERMISSIONS = {
             AGENTS_OPERATE,
             SECURITY_GOVERNANCE_READ,
             SECURITY_GOVERNANCE_OPERATE,
+            IDENTITY_READ,
+            IDENTITY_OPERATE,
+            CONNECTORS_READ,
+            CONNECTORS_OPERATE,
+            EDGE_GATEWAYS_READ,
+            EDGE_GATEWAYS_OPERATE,
+            BI_AI_CATALOG_READ,
+            METERING_READ,
         }
     ),
     "admin": frozenset(
@@ -188,6 +214,19 @@ ROLE_PERMISSIONS = {
             SECURITY_GOVERNANCE_READ,
             SECURITY_GOVERNANCE_OPERATE,
             SECURITY_GOVERNANCE_MANAGE,
+            IDENTITY_READ,
+            IDENTITY_OPERATE,
+            IDENTITY_MANAGE,
+            CONNECTORS_READ,
+            CONNECTORS_OPERATE,
+            CONNECTORS_MANAGE,
+            EDGE_GATEWAYS_READ,
+            EDGE_GATEWAYS_OPERATE,
+            EDGE_GATEWAYS_MANAGE,
+            BI_AI_CATALOG_READ,
+            BI_AI_CATALOG_MANAGE,
+            METERING_READ,
+            METERING_MANAGE,
         }
     ),
 }
@@ -198,8 +237,80 @@ PUBLIC = "public"
 def permission_for_request(path: str, method: str) -> tuple[str, ...]:
     """Classify every API request in one auditable, deny-by-default policy."""
     method = method.upper()
-    if path in {"/api/system/health", "/api/system/auth/login"}:
+    public_methods = {
+        ("/api/system/health", "GET"),
+        ("/api/system/auth/login", "POST"),
+        ("/api/system/identity/providers", "GET"),
+        ("/api/system/identity/authorize", "POST"),
+        ("/api/system/identity/callback", "GET"),
+        ("/api/system/identity/exchange", "POST"),
+        ("/api/system/identity/refresh", "POST"),
+        ("/api/datasource/connectors/machine/runs", "POST"),
+        ("/api/datasource/edge/register", "POST"),
+    }
+    if (path, method) in public_methods:
         return (PUBLIC,)
+    if (
+        path.startswith("/api/datasource/connectors/machine/runs/")
+        and path.rsplit("/", 1)[-1] in {"cancel", "resume"}
+        and method == "POST"
+    ):
+        return (PUBLIC,)
+    if path.startswith("/api/datasource/edge/gateways/") and method == "POST":
+        suffix = path.rsplit("/", 1)[-1]
+        if suffix in {"heartbeat", "pull", "events", "ack", "reconcile", "outcome"}:
+            return (PUBLIC,)
+    if path.startswith("/api/datasource/edge"):
+        if path == "/api/datasource/edge/gateways" and method == "GET":
+            return (EDGE_GATEWAYS_READ,)
+        if path == "/api/datasource/edge/enrollments" and method == "POST":
+            return (EDGE_GATEWAYS_MANAGE,)
+        if path == "/api/datasource/edge/tasks" and method == "POST":
+            return (EDGE_GATEWAYS_OPERATE,)
+        if (
+            path.startswith("/api/datasource/edge/tasks/")
+            and path.endswith("/cancel")
+            and method == "POST"
+        ):
+            return (EDGE_GATEWAYS_OPERATE,)
+        if method == "GET":
+            return (EDGE_GATEWAYS_READ,)
+        return (EDGE_GATEWAYS_MANAGE,)
+    if path.startswith("/api/system/identity"):
+        if path == "/api/system/identity/logout" and method == "POST":
+            return (IDENTITY_READ,)
+        if (
+            path in {"/api/system/identity/idp-versions", "/api/system/identity/audit"}
+            and method == "GET"
+        ):
+            return (IDENTITY_MANAGE,)
+        if path == "/api/system/identity/sessions" and method == "GET":
+            return (IDENTITY_OPERATE,)
+        if method == "GET":
+            return (IDENTITY_READ,)
+        if any(marker in path for marker in ("/idp-versions", "/emergency/")):
+            return (IDENTITY_MANAGE,)
+        return (IDENTITY_OPERATE,)
+    if path.startswith("/api/system/tenant/"):
+        return (IDENTITY_OPERATE,)
+    if path.startswith("/api/system/bi-ai-catalog/"):
+        return (
+            (BI_AI_CATALOG_READ,)
+            if method == "GET" or path.endswith("/search")
+            else (BI_AI_CATALOG_MANAGE,)
+        )
+    if path.startswith("/api/system/metering/"):
+        return (METERING_READ,) if method == "GET" else (METERING_MANAGE,)
+    if path.startswith("/api/datasource/connectors"):
+        if "/source-bindings" in path:
+            return (CONNECTORS_MANAGE,)
+        if method == "GET":
+            return (CONNECTORS_READ,)
+        if any(marker in path for marker in ("/principals", "/credentials/")):
+            return (CONNECTORS_MANAGE,)
+        return (CONNECTORS_OPERATE,)
+    if path == "/api/datasource/graph":
+        return (CONNECTORS_READ,)
     if path.startswith("/api/system/responsibilities/"):
         if method == "GET":
             return (RESPONSIBILITIES_READ,)
@@ -240,16 +351,37 @@ def permission_for_request(path: str, method: str) -> tuple[str, ...]:
         ):
             return (SECURITY_GOVERNANCE_MANAGE,)
         return (SECURITY_GOVERNANCE_OPERATE,)
+    if path.startswith("/api/system/trusted-delivery"):
+        if path.startswith("/api/system/trusted-delivery/controls/"):
+            if method == "GET":
+                return (SECURITY_GOVERNANCE_READ,)
+            if any(
+                marker in path
+                for marker in (
+                    "/profiles/activate",
+                    "/profiles/rollback",
+                    "/capability-approvals",
+                    "/destruction-approvals",
+                )
+            ):
+                return (SECURITY_GOVERNANCE_MANAGE,)
+            return (SECURITY_GOVERNANCE_OPERATE,)
+        if "/legal-holds" in path or "/reclaim/" in path:
+            return (SECURITY_GOVERNANCE_MANAGE,)
+        return (SECURITY_GOVERNANCE_OPERATE,)
     if path.startswith("/api/dataservice/governance"):
         if method == "GET":
             return (DATA_PRODUCTS_READ,)
-        if any(
-            marker in path
-            for marker in (
-                "/reconcile",
-                "/fulfill",
+        if (
+            any(
+                marker in path
+                for marker in (
+                    "/reconcile",
+                    "/fulfill",
+                )
             )
-        ) or path == "/api/dataservice/governance/products":
+            or path == "/api/dataservice/governance/products"
+        ):
             return (DATA_PRODUCTS_MANAGE,)
         return (DATA_PRODUCTS_OPERATE,)
     if path.startswith("/api/knowledge/agents"):
@@ -353,9 +485,7 @@ def permission_for_request(path: str, method: str) -> tuple[str, ...]:
     if path.startswith("/api/development/v1/device-quality"):
         if method == "GET":
             return (READ_GOVERNANCE,)
-        if path.startswith(
-            "/api/development/v1/device-quality/issues"
-        ):
+        if path.startswith("/api/development/v1/device-quality/issues"):
             if path.endswith("/review"):
                 return (QUALITY_ISSUES_REVIEW,)
             return (QUALITY_ISSUES_EDIT,)

+ 34 - 0
app/core/system/tenant_approval_repository.py

@@ -0,0 +1,34 @@
+"""Dedicated, independent database identity for lifecycle approval facts."""
+
+from __future__ import annotations
+
+import json
+import os
+from typing import Any
+
+from sqlalchemy import create_engine, text
+from sqlalchemy.exc import SQLAlchemyError
+from sqlalchemy.pool import NullPool
+
+from app.config.database_urls import validate_postgresql_url
+
+
+class SqlAlchemyTenantApprovalRepository:
+    def __init__(self, database_url: str | None = None) -> None:
+        url = database_url or os.environ.get("TENANT_APPROVER_DATABASE_URL", "")
+        validate_postgresql_url(url, "TENANT_APPROVER_DATABASE_URL")
+        self.engine = create_engine(url, poolclass=NullPool, pool_pre_ping=True)
+
+    def issue(self, *, payload: dict[str, Any]) -> dict[str, Any]:
+        try:
+            with self.engine.begin() as connection:
+                value = connection.execute(
+                    text("SELECT public.tenant_approver_issue(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)},
+                ).scalar_one()
+            return dict(value)
+        except SQLAlchemyError as exc:
+            code = getattr(getattr(exc, "orig", None), "pgcode", "unknown")
+            raise ValueError(f"tenant_approval_denied:{code}") from exc
+        finally:
+            self.engine.dispose()

+ 130 - 0
app/core/system/tenant_context.py

@@ -0,0 +1,130 @@
+"""Closed, server-derived tenant scope for the conditional WP10 foundation."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from dataclasses import dataclass
+from enum import StrEnum
+
+
+class TenantContextError(ValueError):
+    """A tenant boundary was absent, unapproved, or contradicted by input."""
+
+
+class DeliveryMode(StrEnum):
+    PRIVATE_SINGLE_TENANT = "private_single_tenant"
+    SHARED_CONTROL_PLANE = "shared_control_plane"
+    SAAS = "saas"
+
+
+_TENANT_ID = re.compile(r"^[a-z][a-z0-9-]{0,62}$")
+_HOST = re.compile(r"^[a-z0-9][a-z0-9.-]{0,252}$")
+_EVENT_TYPE = re.compile(r"^[a-z][a-z0-9_.-]{0,80}$")
+_DIGEST = re.compile(r"^[0-9a-f]{64}$")
+
+
+def _tenant_id(value: str) -> str:
+    if not isinstance(value, str) or not _TENANT_ID.fullmatch(value):
+        raise TenantContextError("tenant_scope_denied")
+    return value
+
+
+@dataclass(frozen=True)
+class TenantScope:
+    tenant_id: str
+    mode: DeliveryMode
+    version: int = 1
+
+    def __post_init__(self) -> None:
+        _tenant_id(self.tenant_id)
+        if self.version != 1:
+            raise TenantContextError("tenant_context_version_denied")
+
+    def require_background_scope(self, declared_tenant_id: str | None) -> str:
+        if declared_tenant_id is None:
+            raise TenantContextError("tenant_scope_required")
+        if _tenant_id(declared_tenant_id) != self.tenant_id:
+            raise TenantContextError("tenant_scope_denied")
+        return self.tenant_id
+
+    def require_admin_scope(self, declared_tenant_id: str | None) -> str:
+        return self.require_background_scope(declared_tenant_id)
+
+
+TenantContext = TenantScope
+
+
+def validate_tenant_event_envelope(scope: TenantScope, envelope: Mapping[str, object]) -> dict[str, object]:
+    """Reject task/event dispatch unless its scope is explicit and server-matched."""
+    required = {"schema_version", "tenant_id", "event_type", "payload_digest"}
+    if set(envelope) - required:
+        raise TenantContextError("tenant_event_closed")
+    tenant_id = envelope.get("tenant_id")
+    if tenant_id is None:
+        raise TenantContextError("tenant_scope_required")
+    if set(envelope) != required:
+        raise TenantContextError("tenant_event_closed")
+    if not isinstance(tenant_id, str) or _tenant_id(tenant_id) != scope.tenant_id:
+        raise TenantContextError("tenant_scope_denied")
+    if envelope.get("schema_version") != 1 or not isinstance(envelope.get("event_type"), str) or not _EVENT_TYPE.fullmatch(envelope["event_type"]):
+        raise TenantContextError("tenant_event_closed")
+    if not isinstance(envelope.get("payload_digest"), str) or not _DIGEST.fullmatch(envelope["payload_digest"]):
+        raise TenantContextError("tenant_event_closed")
+    return dict(envelope)
+
+
+class TenantContextResolver:
+    """Resolve scope only from server-held delivery configuration and host mapping."""
+
+    def __init__(
+        self,
+        *,
+        mode: DeliveryMode,
+        approved_default_tenant: str | None,
+        route_tenants: Mapping[str, str],
+        activated_modes: frozenset[DeliveryMode] | None = None,
+    ) -> None:
+        self.mode = DeliveryMode(mode)
+        self.approved_default_tenant = (
+            _tenant_id(approved_default_tenant)
+            if approved_default_tenant is not None
+            else None
+        )
+        self.route_tenants = {
+            self._host(host): _tenant_id(tenant_id)
+            for host, tenant_id in route_tenants.items()
+        }
+        self.activated_modes = activated_modes or frozenset(
+            {DeliveryMode.PRIVATE_SINGLE_TENANT}
+        )
+
+    @staticmethod
+    def _host(host: str) -> str:
+        if not isinstance(host, str) or not _HOST.fullmatch(host.lower()):
+            raise TenantContextError("tenant_route_denied")
+        return host.lower()
+
+    def resolve_request(
+        self,
+        *,
+        host: str,
+        claimed_tenant_id: str | None,
+        principal_id: str,
+    ) -> TenantContext:
+        if not isinstance(principal_id, str) or not principal_id:
+            raise TenantContextError("tenant_principal_required")
+        if self.mode not in self.activated_modes:
+            raise TenantContextError("delivery_mode_not_approved")
+        self._host(host)
+        if self.mode is DeliveryMode.PRIVATE_SINGLE_TENANT:
+            if self.approved_default_tenant is None:
+                raise TenantContextError("tenant_default_not_approved")
+            # Client claim is intentionally ignored: it never selects a tenant.
+            return TenantContext(self.approved_default_tenant, self.mode)
+        resolved = self.route_tenants.get(self._host(host))
+        if resolved is None:
+            raise TenantContextError("tenant_route_denied")
+        if claimed_tenant_id is not None and _tenant_id(claimed_tenant_id) != resolved:
+            raise TenantContextError("tenant_spoof_denied")
+        return TenantContext(resolved, self.mode)

+ 149 - 0
app/core/system/tenant_control_repository.py

@@ -0,0 +1,149 @@
+"""Dedicated control-identity repository for tenant claims.
+
+This intentionally does not use the shared application SQLAlchemy session:
+the separate connection must authenticate as a member of
+``dataops_tenant_control``.  If that deployment input is absent, the route is
+unavailable rather than falling back to the shared runtime login.
+"""
+
+from __future__ import annotations
+
+import json
+import os
+import uuid
+from typing import Any
+
+from sqlalchemy import create_engine, text
+from sqlalchemy.exc import SQLAlchemyError
+from sqlalchemy.pool import NullPool
+
+from app.config.database_urls import validate_postgresql_url
+from app.core.system.tenant_control_service import TenantControlError
+
+
+class SqlAlchemyTenantControlRepository:
+    def __init__(self, database_url: str | None = None) -> None:
+        url = database_url or os.environ.get("TENANT_CONTROL_DATABASE_URL", "")
+        validate_postgresql_url(url, "TENANT_CONTROL_DATABASE_URL")
+        self.engine = create_engine(url, poolclass=NullPool, pool_pre_ping=True)
+
+    def issue_claim(
+        self,
+        *,
+        principal_id: str,
+        host: str,
+        action: str,
+        request: dict[str, Any],
+    ) -> dict[str, Any]:
+        payload = {
+            "claim_uid": str(uuid.uuid4()),
+            "nonce": str(uuid.uuid4()),
+            "principal_id": principal_id,
+            "host": host.lower(),
+            "action": action,
+            "request": request,
+        }
+        try:
+            with self.engine.begin() as connection:
+                value = connection.execute(
+                    text("SELECT public.tenant_control_issue_claim(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)},
+                ).scalar_one()
+            return dict(value)
+        except SQLAlchemyError as exc:
+            raise TenantControlError("tenant_control_claim_denied") from exc
+        finally:
+            self.engine.dispose()
+
+    def provision(self, *, tenant_id: str, principal_id: str, host: str, idempotency_key: str) -> dict[str, Any]:
+        payload = {"tenant_id": tenant_id, "principal_id": principal_id, "host": host.lower(), "idempotency_key": idempotency_key}
+        try:
+            with self.engine.begin() as connection:
+                value = connection.execute(
+                    text("SELECT public.tenant_control_provision(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)},
+                ).scalar_one()
+            return dict(value)
+        except SQLAlchemyError as exc:
+            code = getattr(getattr(exc, "orig", None), "pgcode", "unknown")
+            raise TenantControlError(f"tenant_control_provision_denied:{code}") from exc
+        finally:
+            self.engine.dispose()
+
+    def lifecycle_transition(
+        self, *, principal_id: str, host: str, operation: str, request: dict[str, Any]
+    ) -> dict[str, Any]:
+        claim_payload = {
+            "claim_uid": str(uuid.uuid4()),
+            "nonce": str(uuid.uuid4()),
+            "principal_id": principal_id,
+            "host": host.lower(),
+            "action": "lifecycle_transition",
+            "request": request,
+        }
+        try:
+            with self.engine.begin() as connection:
+                connection.execute(
+                    text("SELECT public.tenant_control_issue_claim(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps(claim_payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)},
+                ).scalar_one()
+                value = connection.execute(
+                    text("SELECT public.tenant_control_lifecycle_approved_mutate(CAST(:claim_uid AS uuid),CAST(:nonce AS uuid),CAST(:request AS jsonb))"),
+                    {
+                        "claim_uid": claim_payload["claim_uid"],
+                        "nonce": claim_payload["nonce"],
+                        "request": json.dumps(request, sort_keys=True, separators=(",", ":"), ensure_ascii=True),
+                    },
+                ).scalar_one()
+            return {**dict(value), "request": request}
+        except SQLAlchemyError as exc:
+            raise TenantControlError("tenant_lifecycle_denied") from exc
+        finally:
+            self.engine.dispose()
+
+    def read(self, *, principal_id: str, host: str, kind: str) -> dict[str, Any]:
+        functions = {
+            "status": "tenant_control_read_state",
+            "audit": "tenant_control_read_audit",
+            "manifests": "tenant_control_read_manifests",
+        }
+        function = functions.get(kind)
+        if function is None:
+            raise TenantControlError("tenant_read_denied")
+        payload = {"principal_id": principal_id, "host": host.lower()}
+        try:
+            with self.engine.begin() as connection:
+                value = connection.execute(
+                    text(f"SELECT public.{function}(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)},
+                ).scalar_one()
+            return dict(value)
+        except SQLAlchemyError as exc:
+            raise TenantControlError("tenant_read_denied") from exc
+        finally:
+            self.engine.dispose()
+
+    def claim_outbox(self, *, outbox_uid: str, worker_id: str, lease_seconds: str = "30") -> dict[str, Any]:
+        payload = {"outbox_uid": outbox_uid, "worker_id": worker_id, "lease_seconds": lease_seconds}
+        try:
+            with self.engine.begin() as connection:
+                value = connection.execute(
+                    text("SELECT public.tenant_control_worker_claim(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)},
+                ).scalar_one()
+            return dict(value)
+        except SQLAlchemyError as exc:
+            raise TenantControlError("tenant_worker_scope_denied") from exc
+        finally:
+            self.engine.dispose()
+
+    def settle_outbox(self, *, outbox_uid: str, worker_id: str, lease_fence: int, payload_digest: str, outcome: str) -> dict[str, Any]:
+        payload = {"outbox_uid": outbox_uid, "worker_id": worker_id, "lease_fence": str(lease_fence), "payload_digest": payload_digest, "outcome": outcome}
+        try:
+            with self.engine.begin() as connection:
+                value = connection.execute(text("SELECT public.tenant_control_worker_settle(CAST(:payload AS jsonb))"), {"payload": json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)}).scalar_one()
+            return dict(value)
+        except SQLAlchemyError as exc:
+            raise TenantControlError("tenant_worker_settle_denied") from exc
+        finally:
+            self.engine.dispose()

+ 160 - 0
app/core/system/tenant_control_service.py

@@ -0,0 +1,160 @@
+"""Authenticated application boundary for tenant-control claims."""
+
+from __future__ import annotations
+
+import os
+import uuid
+from typing import Any
+
+from app.core.system.tenant_approval_repository import (
+    SqlAlchemyTenantApprovalRepository,
+)
+from app.core.system.tenant_context import (
+    DeliveryMode,
+    TenantScope,
+    validate_tenant_event_envelope,
+)
+from app.core.system.tenant_resources import build_tenant_namespace
+
+
+class TenantControlError(ValueError):
+    pass
+
+
+class TenantControlService:
+    _REQUEST_FIELDS = frozenset({"quota_name", "amount", "idempotency_key"})
+    _LIFECYCLE_FIELDS = frozenset({"expected_fence", "idempotency_key", "approval_ref", "backup_digest", "retention_seconds"})
+    _LIFECYCLE_OPERATIONS = frozenset({"activate", "freeze", "begin_recovery", "mark_deletion_candidate", "rollback", "delete"})
+
+    def __init__(self, repository: Any) -> None:
+        self.repository = repository
+
+    def provision_private(self, *, principal_id: str, host: str, request: dict[str, Any]) -> dict[str, Any]:
+        if set(request) != {"idempotency_key"} or not isinstance(request.get("idempotency_key"), str):
+            raise TenantControlError("tenant_payload_closed")
+        tenant_id = os.environ.get("PRIVATE_SINGLE_TENANT_ID", "")
+        trusted_host = os.environ.get("PRIVATE_SINGLE_TENANT_HOST", "").lower()
+        if not tenant_id or not trusted_host:
+            raise TenantControlError("tenant_default_not_approved")
+        return self.repository.provision(
+            tenant_id=tenant_id,
+            principal_id=principal_id,
+            host=trusted_host,
+            idempotency_key=request["idempotency_key"],
+        )
+
+    def issue_quota_claim(self, *, principal_id: str, host: str, request: dict[str, Any]) -> dict[str, Any]:
+        if set(request) != self._REQUEST_FIELDS:
+            raise TenantControlError("tenant_payload_closed")
+        if not isinstance(principal_id, str) or not principal_id or not isinstance(host, str) or not host:
+            raise TenantControlError("tenant_identity_required")
+        claim = self.repository.issue_claim(
+            principal_id=principal_id,
+            host=host,
+            action="quota_reserve",
+            request=request,
+        )
+        # This is the actual HTTP-service propagation point: the tenant is
+        # returned only after the database has matched principal+route, then
+        # copied into closed task/event and external-store namespace contracts.
+        tenant_id = claim.get("tenant_id")
+        digest = claim.get("request_digest")
+        if isinstance(tenant_id, str) and isinstance(digest, str):
+            scope = TenantScope(tenant_id, DeliveryMode.PRIVATE_SINGLE_TENANT)
+            event = validate_tenant_event_envelope(
+                scope,
+                {"schema_version": 1, "tenant_id": tenant_id, "event_type": "tenant.quota.claim", "payload_digest": digest},
+            )
+            claim["propagation"] = {
+                "task": event,
+                "event": event,
+                "namespaces": {
+                    resource: build_tenant_namespace(scope, resource, "quota-claim")
+                    for resource in ("object", "graph", "cache", "index")
+                },
+            }
+        return claim
+
+    def lifecycle_transition(
+        self, *, principal_id: str, host: str, operation: str, request: dict[str, Any]
+    ) -> dict[str, Any]:
+        if operation not in self._LIFECYCLE_OPERATIONS or not isinstance(request, dict):
+            raise TenantControlError("tenant_payload_closed")
+        if not set(request).issubset(self._LIFECYCLE_FIELDS) or set(request) < {"expected_fence", "idempotency_key"}:
+            raise TenantControlError("tenant_payload_closed")
+        if not isinstance(request["expected_fence"], str) or not isinstance(request["idempotency_key"], str):
+            raise TenantControlError("tenant_payload_closed")
+        normalized = {
+            "operation": operation,
+            "expected_fence": request["expected_fence"],
+            "idempotency_key": request["idempotency_key"],
+            "approval_ref": request.get("approval_ref"),
+            "backup_digest": request.get("backup_digest"),
+            "retention_seconds": request.get("retention_seconds"),
+        }
+        return self.repository.lifecycle_transition(
+            principal_id=principal_id, host=host, operation=operation, request=normalized
+        )
+
+    def read(self, *, principal_id: str, host: str, kind: str) -> dict[str, Any]:
+        if kind not in {"status", "audit", "manifests"}:
+            raise TenantControlError("tenant_read_denied")
+        return self.repository.read(principal_id=principal_id, host=host, kind=kind)
+
+    def issue_lifecycle_approval(self, *, reviewer_id: str, request: dict[str, Any]) -> dict[str, Any]:
+        allowed = frozenset({"operation", "expected_fence", "idempotency_key", "backup_digest", "retention_seconds"})
+        if not isinstance(request, dict) or not set(request).issubset(allowed) or set(request) < {"operation", "expected_fence", "idempotency_key"}:
+            raise TenantControlError("tenant_payload_closed")
+        operation = request["operation"]
+        if operation not in {"begin_recovery", "delete"} or not isinstance(reviewer_id, str):
+            raise TenantControlError("tenant_approval_denied")
+        tenant_id = os.environ.get("PRIVATE_SINGLE_TENANT_ID", "")
+        issuer_id = os.environ.get("TENANT_APPROVER_ISSUER_ID", "")
+        if not tenant_id or not issuer_id or issuer_id == reviewer_id:
+            raise TenantControlError("tenant_approval_identity_required")
+        approval_ref = str(uuid.uuid4())
+        normalized = {
+            "operation": operation,
+            "expected_fence": request["expected_fence"],
+            "idempotency_key": request["idempotency_key"],
+            "approval_ref": approval_ref,
+            "backup_digest": request.get("backup_digest"),
+            "retention_seconds": request.get("retention_seconds"),
+        }
+        value = SqlAlchemyTenantApprovalRepository().issue(
+            payload={
+                "approval_uid": approval_ref,
+                "tenant_id": tenant_id,
+                "action": operation,
+                "request": normalized,
+                "lease_fence": request["expected_fence"],
+                "issuer_id": issuer_id,
+                "reviewer_id": reviewer_id,
+                "ttl_seconds": os.environ.get("TENANT_APPROVAL_TTL_SECONDS", "120"),
+            }
+        )
+        return {**value, "approval_ref": approval_ref}
+
+    def claim_outbox(self, *, outbox_uid: str) -> dict[str, Any]:
+        worker_id = os.environ.get("TENANT_CONTROL_WORKER_ID", "")
+        if not worker_id:
+            raise TenantControlError("tenant_worker_identity_required")
+        value = self.repository.claim_outbox(outbox_uid=outbox_uid, worker_id=worker_id)
+        tenant_id = value.get("tenant_id")
+        if not isinstance(tenant_id, str):
+            raise TenantControlError("tenant_worker_scope_denied")
+        scope = TenantScope(tenant_id, DeliveryMode.PRIVATE_SINGLE_TENANT)
+        value["propagation"] = {
+            "event": validate_tenant_event_envelope(
+                scope,
+                {"schema_version": 1, "tenant_id": tenant_id, "event_type": value["event_type"], "payload_digest": value["payload_digest"]},
+            ),
+            "namespaces": {resource: build_tenant_namespace(scope, resource, "worker") for resource in ("object", "graph", "cache", "index")},
+        }
+        return value
+
+    def settle_outbox(self, *, outbox_uid: str, lease_fence: int, payload_digest: str, outcome: str) -> dict[str, Any]:
+        worker_id = os.environ.get("TENANT_CONTROL_WORKER_ID", "")
+        if not worker_id:
+            raise TenantControlError("tenant_worker_identity_required")
+        return self.repository.settle_outbox(outbox_uid=outbox_uid, worker_id=worker_id, lease_fence=lease_fence, payload_digest=payload_digest, outcome=outcome)

+ 113 - 0
app/core/system/tenant_lifecycle.py

@@ -0,0 +1,113 @@
+"""Deterministic lifecycle and fence contract used before tenant activation."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import re
+from dataclasses import dataclass, field
+from typing import Any
+
+
+class TenantLifecycleError(ValueError):
+    pass
+
+
+_DIGEST = re.compile(r"^[0-9a-f]{64}$")
+_TRANSITIONS = {
+    ("provisioning", "activate"): "active",
+    ("active", "freeze"): "frozen",
+    ("frozen", "begin_recovery"): "recovering",
+    ("recovering", "activate"): "active",
+    ("active", "mark_deletion_candidate"): "deletion_candidate",
+    ("frozen", "mark_deletion_candidate"): "deletion_candidate",
+    ("deletion_candidate", "delete"): "deleted",
+    ("deletion_candidate", "rollback"): "frozen",
+}
+
+
+@dataclass(frozen=True)
+class TenantLifecycleRecord:
+    tenant_id: str
+    state: str
+    fence: int
+
+
+@dataclass
+class TenantLifecycle:
+    tenant_id: str
+    state: str = "provisioning"
+    fence: int = 0
+    _replays: dict[str, tuple[str, TenantLifecycleRecord]] = field(default_factory=dict)
+
+    @classmethod
+    def provision(cls, tenant_id: str) -> TenantLifecycle:
+        return cls(tenant_id=tenant_id)
+
+    def _digest(self, action: str, expected_fence: int, values: dict[str, Any]) -> str:
+        payload = json.dumps(
+            {"action": action, "expected_fence": expected_fence, **values},
+            sort_keys=True,
+            separators=(",", ":"),
+        )
+        return hashlib.sha256(payload.encode("utf-8")).hexdigest()
+
+    def transition(
+        self,
+        action: str,
+        *,
+        expected_fence: int,
+        idempotency_key: str,
+        approval_ref: str | None = None,
+        backup_digest: str | None = None,
+        retention_seconds: int | None = None,
+        hold_active: bool = False,
+    ) -> TenantLifecycleRecord:
+        if not isinstance(expected_fence, int) or isinstance(expected_fence, bool):
+            raise TenantLifecycleError("tenant_fence_conflict")
+        if not isinstance(idempotency_key, str) or not idempotency_key:
+            raise TenantLifecycleError("tenant_replay_conflict")
+        values = {
+            "approval_ref": approval_ref,
+            "backup_digest": backup_digest,
+            "retention_seconds": retention_seconds,
+            "hold_active": hold_active,
+        }
+        request_digest = self._digest(action, expected_fence, values)
+        replay = self._replays.get(idempotency_key)
+        if replay is not None:
+            if replay[0] != request_digest:
+                raise TenantLifecycleError("tenant_replay_conflict")
+            return replay[1]
+        if expected_fence != self.fence:
+            raise TenantLifecycleError("tenant_fence_conflict")
+        target = _TRANSITIONS.get((self.state, action))
+        if target is None:
+            raise TenantLifecycleError("tenant_transition_denied")
+        if action in {"begin_recovery", "activate"} and self.state == "recovering" and not approval_ref:
+            raise TenantLifecycleError("tenant_recovery_approval_required")
+        if action in {"mark_deletion_candidate", "delete"}:
+            if not approval_ref:
+                raise TenantLifecycleError("tenant_deletion_approval_required")
+            if hold_active:
+                raise TenantLifecycleError("tenant_hold_active")
+            if not isinstance(backup_digest, str) or not _DIGEST.fullmatch(backup_digest):
+                raise TenantLifecycleError("tenant_backup_required")
+            if (
+                not isinstance(retention_seconds, int)
+                or isinstance(retention_seconds, bool)
+                or retention_seconds < 1
+            ):
+                raise TenantLifecycleError("tenant_retention_required")
+        self.state = target
+        self.fence += 1
+        result = TenantLifecycleRecord(self.tenant_id, self.state, self.fence)
+        self._replays[idempotency_key] = (request_digest, result)
+        return result
+
+    def require_active_task_scope(self, *, expected_fence: int) -> TenantLifecycleRecord:
+        if expected_fence != self.fence:
+            raise TenantLifecycleError("tenant_fence_conflict")
+        if self.state != "active":
+            raise TenantLifecycleError("tenant_not_active")
+        return TenantLifecycleRecord(self.tenant_id, self.state, self.fence)

+ 102 - 0
app/core/system/tenant_repository.py

@@ -0,0 +1,102 @@
+"""Runtime-only repository for the WP10 PostgreSQL tenant foundation."""
+
+from __future__ import annotations
+
+import json
+from typing import Any
+
+from sqlalchemy import text
+from sqlalchemy.exc import SQLAlchemyError
+
+
+class SqlAlchemyTenantFoundationRepository:
+    def __init__(self, connection: Any) -> None:
+        # 501--503 accepted tenant_id from a caller payload.  Those functions
+        # were revoked in 504 and this compatibility facade must never become
+        # a fallback around the claim-bound control identity introduced later.
+        raise RuntimeError("tenant_legacy_gateway_sealed")
+
+    def _set_runtime_scope(self, tenant_id: str) -> None:
+        if self._tenant_scope is not None and self._tenant_scope != tenant_id:
+            raise ValueError("tenant_scope_denied")
+        self.connection.execute(text("SET LOCAL ROLE dataops_app_runtime"))
+        self.connection.execute(
+            text("SELECT set_config('dataops.tenant_id', :tenant_id, true)"),
+            {"tenant_id": tenant_id},
+        )
+        self._tenant_scope = tenant_id
+
+    def _write(self, action: str, payload: dict[str, Any]) -> dict[str, Any]:
+        tenant_id = payload.get("tenant_id")
+        if not isinstance(tenant_id, str):
+            raise ValueError("tenant_scope_denied")
+        self._set_runtime_scope(tenant_id)
+        try:
+            with self.connection.begin_nested():
+                value = self.connection.execute(
+                    text("SELECT public.tenant_foundation_write(:action, CAST(:payload AS jsonb))"),
+                    {"action": action, "payload": json.dumps(payload, ensure_ascii=False)},
+                ).scalar_one()
+            return dict(value)
+        except SQLAlchemyError as error:
+            message = str(error)
+            for code in (
+                "tenant_quota_exhausted",
+                "tenant_not_active",
+                "tenant_replay_conflict",
+                "tenant_fence_conflict",
+                "tenant_payload_closed",
+                "tenant_payload_invalid",
+            ):
+                if code in message:
+                    raise ValueError(code) from error
+            raise ValueError("tenant_gateway_denied") from error
+
+    def provision(self, tenant_id: str, delivery_mode: str, idempotency_key: str) -> dict[str, Any]:
+        return self._write("provision", {"tenant_id": tenant_id, "delivery_mode": delivery_mode, "idempotency_key": idempotency_key})
+
+    def reserve_quota(self, tenant_id: str, quota_name: str, amount: int | float, idempotency_key: str) -> int:
+        if isinstance(amount, bool) or not isinstance(amount, (int, float)):
+            raise ValueError("tenant_payload_invalid")
+        payload = {"tenant_id": tenant_id, "quota_name": quota_name, "amount": amount, "idempotency_key": idempotency_key}
+        self._set_runtime_scope(tenant_id)
+        try:
+            with self.connection.begin_nested():
+                value = self.connection.execute(text("SELECT public.tenant_quota_reserve(CAST(:payload AS jsonb))"), {"payload": json.dumps(payload, ensure_ascii=False)}).scalar_one()
+            result = dict(value)
+        except SQLAlchemyError as error:
+            message = str(error)
+            for code in ("tenant_quota_exhausted", "tenant_not_active", "tenant_replay_conflict", "tenant_fence_conflict", "tenant_payload_closed", "tenant_payload_invalid"):
+                if code in message:
+                    raise ValueError(code) from error
+            raise ValueError("tenant_gateway_denied") from error
+        return int(result["lease_fence"])
+
+    def transition_lifecycle(
+        self,
+        tenant_id: str,
+        operation: str,
+        expected_fence: int,
+        idempotency_key: str,
+        *,
+        approval_ref: str | None = None,
+    ) -> int:
+        self._set_runtime_scope(tenant_id)
+        try:
+            with self.connection.begin_nested():
+                result = self.connection.execute(
+                    text("SELECT public.tenant_lifecycle_transition(CAST(:payload AS jsonb))"),
+                    {"payload": json.dumps({"tenant_id": tenant_id, "operation": operation, "expected_fence": expected_fence, "idempotency_key": idempotency_key, "approval_ref": approval_ref}, ensure_ascii=False)},
+                ).scalar_one()
+            return int(dict(result)["lease_fence"])
+        except SQLAlchemyError as error:
+            message = str(error)
+            for code in ("tenant_not_active", "tenant_replay_conflict", "tenant_fence_conflict", "tenant_payload_closed", "tenant_payload_invalid", "tenant_recovery_approval_required"):
+                if code in message:
+                    raise ValueError(code) from error
+            raise ValueError("tenant_lifecycle_denied") from error
+
+    def list_audit(self, tenant_id: str) -> list[dict[str, Any]]:
+        self._set_runtime_scope(tenant_id)
+        rows = self.connection.execute(text("SELECT tenant_id,event_type,payload_digest,lease_fence,created_at FROM public.tenant_audit_events ORDER BY created_at,event_uid")).mappings()
+        return [dict(row) for row in rows]

+ 66 - 0
app/core/system/tenant_resources.py

@@ -0,0 +1,66 @@
+"""Tenant namespace and hash-only manifest contracts for non-PostgreSQL stores."""
+
+from __future__ import annotations
+
+import re
+from typing import Any
+
+from app.core.system.tenant_context import TenantScope
+
+
+class TenantResourceError(ValueError):
+    pass
+
+
+_RESOURCE_TYPES = frozenset(
+    {
+        "object",
+        "graph",
+        "cache",
+        "index",
+        "keys",
+        "connectors",
+        "models",
+        "plugins",
+        "backup",
+        "audit_export",
+    }
+)
+_RESOURCE_NAME = re.compile(r"^[a-z0-9][a-z0-9._-]{0,120}$")
+_DIGEST = re.compile(r"^[0-9a-f]{64}$")
+
+
+def _resource_token(value: str, *, allowed: frozenset[str] | None = None) -> str:
+    if not isinstance(value, str) or not _RESOURCE_NAME.fullmatch(value):
+        raise TenantResourceError("tenant_resource_invalid")
+    if allowed is not None and value not in allowed:
+        raise TenantResourceError("tenant_resource_invalid")
+    return value
+
+
+def build_tenant_namespace(scope: TenantScope, resource: str, name: str) -> str:
+    """Return an opaque prefix; callers must not concatenate their own paths."""
+    resource = _resource_token(resource, allowed=_RESOURCE_TYPES)
+    name = _resource_token(name)
+    return f"{scope.tenant_id}/{resource}/v{scope.version}/{name}"
+
+
+def build_tenant_resource_manifest(
+    scope: TenantScope,
+    *,
+    resource: str,
+    name: str,
+    digest: str,
+) -> dict[str, Any]:
+    if not isinstance(digest, str) or not _DIGEST.fullmatch(digest):
+        raise TenantResourceError("tenant_resource_invalid")
+    namespace = build_tenant_namespace(scope, resource, name)
+    return {
+        "schema_version": 1,
+        "tenant_id": scope.tenant_id,
+        "delivery_mode": scope.mode.value,
+        "resource": resource,
+        "namespace": namespace,
+        "digest": f"sha256:{digest}",
+        "provider_enabled": False,
+    }

+ 14 - 4
app/core/system/tokens.py

@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from datetime import datetime, timedelta, timezone
+from datetime import UTC, datetime, timedelta
 
 import jwt
 
@@ -18,8 +18,11 @@ def issue_access_token(
     secret: str,
     now: datetime | None = None,
     lifetime: timedelta = timedelta(minutes=30),
+    session_uid: str | None = None,
+    token_version: int | None = None,
+    identity_source: str | None = None,
 ) -> str:
-    now = now or datetime.now(timezone.utc)
+    now = now or datetime.now(UTC)
     claims = {
         "sub": str(user_id),
         "roles": sorted(set(roles)),
@@ -27,6 +30,12 @@ def issue_access_token(
         "exp": int((now + lifetime).timestamp()),
         "jti": new_governance_uid(),
     }
+    if session_uid is not None:
+        claims["sid"] = str(session_uid)
+    if token_version is not None:
+        claims["token_version"] = int(token_version)
+    if identity_source is not None:
+        claims["identity_source"] = identity_source
     return jwt.encode(claims, secret, algorithm="HS256")
 
 
@@ -46,11 +55,12 @@ def decode_access_token(
     except jwt.PyJWTError as exc:
         raise TokenError("invalid access token") from exc
 
-    current = int((now or datetime.now(timezone.utc)).timestamp())
+    current = int((now or datetime.now(UTC)).timestamp())
     if int(claims["exp"]) <= current:
         raise TokenError("access token expired")
     if int(claims["iat"]) > current + 30:
         raise TokenError("access token issued in the future")
     if not isinstance(claims.get("roles"), list):
         raise TokenError("invalid role claims")
-    return {key: claims[key] for key in ("sub", "roles", "iat", "exp", "jti")}
+    allowed = ("sub", "roles", "iat", "exp", "jti", "sid", "token_version", "identity_source")
+    return {key: claims[key] for key in allowed if key in claims}

+ 670 - 0
app/core/system/trusted_delivery.py

@@ -0,0 +1,670 @@
+"""Closed, default-deny contracts for fine-grained trusted delivery.
+
+This module intentionally never executes a data query or contacts a target.
+It produces a bounded authorization projection and digest-only provider envelope.
+Concrete enterprise providers remain external integration work.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import json
+import re
+import uuid
+from collections.abc import Callable, Mapping
+from datetime import UTC, datetime
+from typing import Any
+
+from app.core.common.identifiers import new_governance_uid
+from app.core.common.timezone_utils import now_china
+
+_PROVIDERS = frozenset({"database", "api", "file", "iam", "kms", "dlp", "siem"})
+_ENVIRONMENTS = frozenset({"development", "test", "production"})
+_ACTIONS = frozenset({"read", "use", "display", "export"})
+_CLASSIFICATIONS = frozenset({"public", "internal", "sensitive", "highly_sensitive"})
+_FIELD = re.compile(r"^[A-Za-z_][A-Za-z0-9_.-]{0,199}$")
+_CODE = re.compile(r"^[A-Z][A-Z0-9_]{2,119}$")
+_DIGEST = re.compile(r"^[0-9a-f]{64}$")
+_UNSAFE_TEXT = re.compile(
+    r"(?i)(?:https?://|ftp://|file://|www\.|\b(?:secret|token|password|private[ _-]?key)\b|"
+    r"-----BEGIN|<script|javascript:|;)")
+_UNSAFE_SQL = re.compile(r"(?i)\b(?:select|insert|update|delete|drop|alter)\s+")
+
+
+def _closed(value: Any, allowed: set[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise ValueError(f"{label} must be an object")
+    extra = sorted(set(value) - allowed)
+    if extra:
+        raise ValueError(f"{label} contains unsupported fields: {', '.join(extra)}")
+    result = copy.deepcopy(value)
+    _assert_safe_value(result, label)
+    return result
+
+
+def _assert_safe_value(value: Any, label: str) -> None:
+    """Reject values that could turn an authorization reference into payload data."""
+    if isinstance(value, str) and _UNSAFE_TEXT.search(value):
+        raise ValueError(f"{label} contains unsafe content")
+    if isinstance(value, dict):
+        for item in value.values():
+            _assert_safe_value(item, label)
+    elif isinstance(value, list):
+        for item in value:
+            _assert_safe_value(item, label)
+
+
+def _text(value: Any, label: str, maximum: int = 300) -> str:
+    if not isinstance(value, str) or not value.strip():
+        raise ValueError(f"{label} is required")
+    result = value.strip()
+    if len(result) > maximum:
+        raise ValueError(f"{label} exceeds {maximum} characters")
+    return result
+
+
+def _uid(value: Any, label: str) -> str:
+    try:
+        return str(uuid.UUID(str(value)))
+    except (TypeError, ValueError, AttributeError) as exc:
+        raise ValueError(f"{label} must be a UUID") from exc
+
+
+def _time(value: Any, label: str) -> datetime:
+    try:
+        result = value if isinstance(value, datetime) else datetime.fromisoformat(str(value).replace("Z", "+00:00"))
+    except (TypeError, ValueError) as exc:
+        raise ValueError(f"{label} must be ISO-8601") from exc
+    if result.tzinfo is None:
+        raise ValueError(f"{label} must include a timezone")
+    return result.astimezone(UTC)
+
+
+def _values(value: Any, label: str, *, minimum: int = 1, maximum: int = 200) -> list[Any]:
+    if not isinstance(value, list) or not minimum <= len(value) <= maximum:
+        raise ValueError(f"{label} must contain between {minimum} and {maximum} items")
+    return copy.deepcopy(value)
+
+
+def _digest(value: Any) -> str:
+    return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
+
+
+def _digest_value(value: Any, label: str) -> str:
+    result = _text(value, label, 64).lower()
+    if not _DIGEST.fullmatch(result):
+        raise ValueError(f"{label} must be SHA-256")
+    return result
+
+
+def _field(value: Any, label: str) -> str:
+    result = _text(value, label, 200)
+    if not _FIELD.fullmatch(result):
+        raise ValueError(f"{label} is invalid")
+    return result
+
+
+def _simple_values(value: Any, label: str, allowed: frozenset[str]) -> list[str]:
+    result = sorted({_text(item, label, 100) for item in _values(value, label)})
+    if not set(result) <= allowed:
+        raise ValueError(f"unsupported {label}")
+    return result
+
+
+def _row_filter(value: Any) -> dict[str, Any] | None:
+    if value is None:
+        return None
+    body = _closed(value, {"op", "field", "value", "items", "item"}, "row filter")
+    op = _text(body.get("op"), "row filter op", 10)
+    if op not in {"eq", "in", "and", "or", "not"}:
+        raise ValueError("unsupported row filter op")
+    if _UNSAFE_SQL.search(json.dumps(body, sort_keys=True)):
+        raise ValueError("row filter contains unsafe content")
+    if op in {"eq", "in"}:
+        if set(body) - {"op", "field", "value"}:
+            raise ValueError("row filter shape is invalid")
+        field = _field(body.get("field"), "row filter field")
+        raw = body.get("value")
+        if op == "eq" and not isinstance(raw, (str, int, float, bool)):
+            raise ValueError("row filter eq value must be scalar")
+        if op == "in":
+            if not isinstance(raw, list) or not 1 <= len(raw) <= 100 or not all(isinstance(item, (str, int, float, bool)) for item in raw):
+                raise ValueError("row filter in value must be bounded scalar list")
+            raw = sorted(raw, key=lambda item: (type(item).__name__, str(item)))
+        return {"op": op, "field": field, "value": raw}
+    if op == "not":
+        if set(body) != {"op", "item"}:
+            raise ValueError("row filter not shape is invalid")
+        return {"op": op, "item": _row_filter(body["item"])}
+    if set(body) != {"op", "items"}:
+        raise ValueError("row filter boolean shape is invalid")
+    items = [_row_filter(item) for item in _values(body.get("items"), "row filter items", minimum=1, maximum=20)]
+    return {"op": op, "items": items}
+
+
+class ClosedProviderRegistry:
+    """Provider names are a closed enum; tests may inject non-network adapters."""
+
+    def __init__(self, adapters: Mapping[str, Any] | None = None, *, test_mode: bool = False):
+        adapters = dict(adapters or {})
+        if set(adapters) - _PROVIDERS:
+            raise ValueError("unsupported trusted-delivery provider")
+        if adapters and not test_mode:
+            raise PermissionError("enterprise providers require approved external integration")
+        self._adapters = adapters
+        self._test_mode = test_mode
+
+    @classmethod
+    def for_tests(cls, adapters: Mapping[str, Any]):
+        return cls(adapters, test_mode=True)
+
+    @property
+    def is_enterprise_ready(self) -> bool:
+        return False
+
+    def dependency_status(self) -> dict[str, str]:
+        return dict.fromkeys(sorted(_PROVIDERS), "TBD_EXTERNAL")
+
+    def get(self, provider: str):
+        provider = _text(provider, "provider", 30)
+        if provider not in _PROVIDERS:
+            raise ValueError("unsupported trusted-delivery provider")
+        adapter = self._adapters.get(provider)
+        if adapter is None:
+            raise PermissionError(f"trusted-delivery provider {provider} is disabled")
+        return adapter
+
+
+class TrustedDeliveryService:
+    """Durable-service façade. Repositories own the database transaction boundary."""
+
+    def __init__(
+        self, repository, *, provider_registry: ClosedProviderRegistry | None = None,
+        uid_factory: Callable[[], str] = new_governance_uid,
+        now_factory: Callable[[], datetime] = now_china,
+    ):
+        self.repository = repository
+        self.provider_registry = provider_registry or ClosedProviderRegistry()
+        self.uid_factory = uid_factory
+        self.now_factory = now_factory
+
+    def _actor(self, value: Any) -> str:
+        actor = _uid(value, "actor_uid")
+        if self.repository.users_available({actor}) != {actor}:
+            raise ValueError("trusted-delivery actor is unavailable")
+        return actor
+
+    def _event(self, action: str, actor_uid: str, detail: dict[str, Any]) -> None:
+        if hasattr(self.repository, "add_evidence"):
+            self.repository.add_evidence({
+                "uid": self.uid_factory(), "action": action, "actor_uid": actor_uid,
+                "safe_detail": copy.deepcopy(detail), "created_at": self.now_factory().isoformat(),
+            })
+
+    def create_policy_version(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"code", "version", "selector", "resource_rules"}, "trusted delivery policy")
+        actor = self._actor(actor_uid)
+        code = _text(body.get("code"), "policy code", 120).upper()
+        if not _CODE.fullmatch(code):
+            raise ValueError("policy code is invalid")
+        selector = _closed(body.get("selector"), {"subjects", "roles", "business_domains", "assets", "purposes", "environments", "actions", "classifications", "expires_at"}, "policy selector")
+        subjects = sorted({_uid(item, "policy subject") for item in _values(selector.get("subjects", []), "policy subjects", minimum=0)})
+        roles = sorted({_text(item, "policy role", 80) for item in _values(selector.get("roles", []), "policy roles", minimum=0)})
+        if not subjects and not roles:
+            raise ValueError("policy selector requires a subject or role")
+        if subjects and self.repository.users_available(set(subjects)) != set(subjects):
+            raise ValueError("policy selector contains unavailable users")
+        rules = _closed(body.get("resource_rules"), {"row_filter", "field_rules"}, "resource rules")
+        field_rules = []
+        for item in _values(rules.get("field_rules"), "field rules", maximum=500):
+            field_rule = _closed(item, {"field", "effect", "mask_ref"}, "field rule")
+            effect = _text(field_rule.get("effect"), "field effect", 10)
+            if effect not in {"allow", "deny"}:
+                raise ValueError("unsupported field effect")
+            mask_ref = field_rule.get("mask_ref")
+            if mask_ref is not None:
+                mask_ref = _text(mask_ref, "mask ref", 120)
+                if not re.fullmatch(r"[a-z][a-z0-9-]{2,119}-v[1-9][0-9]*", mask_ref):
+                    raise ValueError("mask ref is invalid")
+            field_rules.append({"field": _field(field_rule.get("field"), "field rule field"), "effect": effect, "mask_ref": mask_ref})
+        if len({item["field"] for item in field_rules}) != len(field_rules):
+            raise ValueError("field rules must not duplicate fields")
+        expires_at = _time(selector.get("expires_at"), "policy expiry")
+        now = self.now_factory().astimezone(UTC)
+        if expires_at <= now:
+            raise ValueError("policy expiry must be in the future")
+        record = {
+            "uid": self.uid_factory(), "code": code, "version": _text(body.get("version"), "policy version", 30),
+            # A policy never becomes effective by creation alone.  This keeps
+            # both first and subsequent versions behind the durable approval
+            # transition, rather than treating an empty policy history as an
+            # implicit approval.
+            "status": "draft", "selector": {
+                "subjects": subjects, "roles": roles,
+                "business_domains": sorted({_uid(item, "business domain") for item in _values(selector.get("business_domains"), "business domains")}),
+                "assets": sorted({_uid(item, "asset") for item in _values(selector.get("assets"), "assets")}),
+                "purposes": sorted({_text(item, "purpose", 100) for item in _values(selector.get("purposes"), "purposes")}),
+                "environments": _simple_values(selector.get("environments"), "environments", _ENVIRONMENTS),
+                "actions": _simple_values(selector.get("actions"), "actions", _ACTIONS),
+                "classifications": _simple_values(selector.get("classifications"), "classifications", _CLASSIFICATIONS),
+                "expires_at": expires_at.isoformat(),
+            },
+            "resource_rules": {"row_filter": _row_filter(rules.get("row_filter")), "field_rules": sorted(field_rules, key=lambda item: item["field"])},
+            "created_by": actor, "created_at": now.isoformat(), "current_version": 1,
+        }
+        saved = self.repository.create_policy_version(record)
+        self._event("policy_version_created", actor, {"policy_code": code, "version": record["version"]})
+        return saved
+
+    def _lifecycle_request(self, payload: Any, *, label: str) -> tuple[dict[str, Any], str, str, str]:
+        body = _closed(payload, {"code", "version", "approval_ref", "approval_digest", "idempotency_key"}, label)
+        actor = self._actor(payload.get("actor_uid")) if isinstance(payload, dict) and "actor_uid" in payload else None
+        # Callers supply the actor separately.  This branch only keeps direct
+        # service use closed if a malformed payload is passed.
+        if actor is not None:
+            raise ValueError("actor_uid must not be in lifecycle payload")
+        return (
+            body,
+            _text(body.get("code"), "policy code", 120).upper(),
+            _text(body.get("version"), "policy version", 30),
+            _text(body.get("idempotency_key"), "idempotency key", 160),
+        )
+
+    def _policy_transition(self, payload: Any, *, actor_uid: str, operation: str) -> dict[str, Any]:
+        body = _closed(payload, {"code", "version", "approval_ref", "approval_digest", "idempotency_key"}, "policy transition")
+        actor = self._actor(actor_uid)
+        code = _text(body.get("code"), "policy code", 120).upper()
+        if not _CODE.fullmatch(code):
+            raise ValueError("policy code is invalid")
+        version = _text(body.get("version"), "policy version", 30)
+        approval_ref = _text(body.get("approval_ref"), "approval reference", 200)
+        approval_digest = _digest_value(body.get("approval_digest"), "approval digest")
+        key = _text(body.get("idempotency_key"), "idempotency key", 160)
+        request_digest = _digest({"operation": operation, "code": code, "version": version, "approval_ref": approval_ref, "approval_digest": approval_digest})
+        if hasattr(self.repository, "transition_policy"):
+            saved = self.repository.transition_policy(code, version, operation, key, request_digest, actor, approval_ref, approval_digest)
+        elif hasattr(self.repository, "policies"):
+            # Small test repositories intentionally do not emulate PostgreSQL;
+            # preserve the same state rules for service-level tests.
+            history = getattr(self.repository, "policy_transitions", {})
+            existing = history.get(key)
+            if existing:
+                if existing["request_digest"] != request_digest:
+                    raise RuntimeError("trusted delivery idempotency conflict")
+                return copy.deepcopy(existing["result"])
+            target = next((item for item in self.repository.policies.values() if item["code"] == code and item["version"] == version), None)
+            if target is None:
+                raise LookupError("trusted delivery policy version was not found")
+            for item in self.repository.policies.values():
+                if item["code"] == code and item["uid"] != target["uid"] and item["status"] == "active":
+                    item["status"] = "retired"
+                    item["current_version"] += 1
+            if target["status"] != "active":
+                target["status"] = "active"
+                target["current_version"] += 1
+            saved = {key: target[key] for key in ("uid", "code", "version", "status", "current_version")}
+            history[key] = {"request_digest": request_digest, "result": copy.deepcopy(saved)}
+            self.repository.policy_transitions = history
+        else:
+            raise RuntimeError("durable policy transition repository is required")
+        self._event(f"policy_{operation}", actor, {"policy_code": code, "version": version, "approval_digest": approval_digest})
+        return saved
+
+    def activate_policy_version(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        return self._policy_transition(payload, actor_uid=actor_uid, operation="activate")
+
+    def rollback_policy_version(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        return self._policy_transition(payload, actor_uid=actor_uid, operation="rollback")
+
+    def evaluate_gateway(self, payload: Any) -> dict[str, Any]:
+        body = _closed(payload, {"subject_uid", "roles", "business_domain_uid", "asset_uid", "purpose", "environment", "action", "classification", "requested_fields"}, "gateway request")
+        context = {
+            "subject_uid": _uid(body.get("subject_uid"), "subject_uid"),
+            "roles": sorted({_text(item, "role", 80) for item in _values(body.get("roles"), "roles", minimum=0)}),
+            "business_domain_uid": _uid(body.get("business_domain_uid"), "business_domain_uid"),
+            "asset_uid": _uid(body.get("asset_uid"), "asset_uid"), "purpose": _text(body.get("purpose"), "purpose", 100),
+            "environment": _text(body.get("environment"), "environment", 30), "action": _text(body.get("action"), "action", 20),
+            "classification": _text(body.get("classification"), "classification", 30),
+            "requested_fields": sorted({_field(item, "requested field") for item in _values(body.get("requested_fields"), "requested fields", maximum=500)}),
+        }
+        if context["environment"] not in _ENVIRONMENTS or context["action"] not in _ACTIONS or context["classification"] not in _CLASSIFICATIONS:
+            raise ValueError("unsupported gateway environment, action, or classification")
+        if context["action"] == "export" and self.repository.active_hold_for_asset(context["asset_uid"]):
+            return {"decision": "deny", "reason_code": "active_legal_hold"}
+        if context["action"] == "export" and context["classification"] == "highly_sensitive":
+            return {"decision": "deny", "reason_code": "highly_sensitive_export_denied"}
+        now = self.now_factory().astimezone(UTC)
+        for policy in self.repository.active_policies():
+            selector = policy["selector"]
+            if _time(selector["expires_at"], "policy expiry") <= now:
+                continue
+            if not (context["subject_uid"] in selector["subjects"] or set(context["roles"]) & set(selector["roles"])):
+                continue
+            selector_keys = {
+                "business_domain_uid": "business_domains", "asset_uid": "assets",
+                "purpose": "purposes", "environment": "environments",
+                "action": "actions", "classification": "classifications",
+            }
+            if any(context[key] not in selector[selector_keys[key]] for key in selector_keys):
+                continue
+            rules = {rule["field"]: rule for rule in policy["resource_rules"]["field_rules"]}
+            if any(field not in rules or rules[field]["effect"] != "allow" for field in context["requested_fields"]):
+                continue
+            masks = {field: rules[field]["mask_ref"] for field in context["requested_fields"] if rules[field]["mask_ref"]}
+            return {"decision": "allow", "reason_code": "policy_allowed", "policy_code": policy["code"], "policy_version": policy["version"], "projected_fields": context["requested_fields"], "row_filter": policy["resource_rules"]["row_filter"], "field_masks": masks}
+        return {"decision": "deny", "reason_code": "default_deny"}
+
+    def create_grant(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"policy_uid", "approval_ref", "approval_digest", "subject_uid", "asset_uid", "provider", "expires_at", "decision"}, "trusted delivery grant")
+        actor = self._actor(actor_uid)
+        decision = _closed(body.get("decision"), {"decision", "reason_code", "policy_code", "policy_version", "projected_fields", "row_filter", "field_masks"}, "grant decision")
+        if decision.get("decision") != "allow":
+            raise PermissionError("trusted delivery grant requires allowed decision")
+        provider = _text(body.get("provider"), "provider", 30)
+        self.provider_registry.get(provider)
+        expires_at = _time(body.get("expires_at"), "grant expiry")
+        if expires_at <= self.now_factory().astimezone(UTC):
+            raise ValueError("grant expiry must be in the future")
+        record = {
+            "uid": self.uid_factory(), "policy_uid": _uid(body.get("policy_uid"), "policy_uid"),
+            "approval_ref": _text(body.get("approval_ref"), "approval reference", 200),
+            "approval_digest": _digest_value(body.get("approval_digest"), "approval digest"),
+            "subject_uid": _uid(body.get("subject_uid"), "subject_uid"), "asset_uid": _uid(body.get("asset_uid"), "asset_uid"),
+            "provider": provider, "expires_at": expires_at.isoformat(), "status": "active", "reason_code": "approval_bound",
+            "decision_digest": _digest(decision), "current_version": 1, "created_by": actor, "created_at": self.now_factory().isoformat(),
+        }
+        saved = self.repository.create_grant(record)
+        result = self.repository.get_grant(saved["uid"]) if hasattr(self.repository, "get_grant") else saved
+        self._event("grant_created", actor, {"grant_uid": result["uid"], "provider": provider, "decision_digest": record["decision_digest"]})
+        return result
+
+    def dispatch_grant(self, grant_uid: str, *, actor_uid: str) -> dict[str, Any]:
+        actor = self._actor(actor_uid)
+        if hasattr(self.repository, "get_grant"):
+            grant = self.repository.get_grant(_uid(grant_uid, "grant_uid"))
+        elif hasattr(self.repository, "grants"):
+            grant = copy.deepcopy(self.repository.grants.get(_uid(grant_uid, "grant_uid")))
+        else:
+            raise RuntimeError("durable dispatch repository is required")
+        if not grant or grant["status"] != "active":
+            raise LookupError("active trusted delivery grant was not found")
+        response = self.provider_registry.get(grant["provider"]).apply({
+            "schema": "dataops.trusted-delivery.v1", "grant_uid": grant["uid"], "provider": grant["provider"],
+            "subject_uid": grant["subject_uid"], "asset_uid": grant["asset_uid"], "expires_at": grant["expires_at"], "decision_digest": grant["decision_digest"],
+        })
+        response = _closed(response, {"status", "receipt_code", "response_digest"}, "provider receipt")
+        if response.get("status") != "applied":
+            raise RuntimeError("trusted delivery provider failed closed")
+        receipt = {"grant_uid": grant["uid"], "status": "applied", "receipt_code": _text(response.get("receipt_code"), "receipt code", 120), "response_digest": _digest_value(response.get("response_digest"), "provider response digest")}
+        self._event("grant_dispatched", actor, receipt)
+        return receipt
+
+    def provision(self, payload: Any, *, actor_uid: str, subject_roles: list[str] | None = None) -> dict[str, Any]:
+        """Create and apply one approval-bound, digest-only capability grant."""
+        body = _closed(payload, {"policy_uid", "approval_ref", "approval_digest", "subject_uid", "asset_uid", "business_domain_uid", "purpose", "environment", "action", "classification", "requested_fields", "expires_at", "target_capability", "provider", "idempotency_key"}, "trusted delivery provision")
+        actor = self._actor(actor_uid)
+        provider = _text(body.get("provider"), "provider", 30)
+        adapter = self.provider_registry.get(provider)
+        capability = _closed(body.get("target_capability"), {"actions", "fields"}, "target capability")
+        target_capability = {
+            "actions": _simple_values(capability.get("actions"), "capability actions", _ACTIONS),
+            "fields": sorted({_field(item, "capability field") for item in _values(capability.get("fields"), "capability fields", maximum=500)}),
+        }
+        approval_ref = _text(body.get("approval_ref"), "approval reference", 200)
+        approval_digest = _digest_value(body.get("approval_digest"), "approval digest")
+        policy_uid = _uid(body.get("policy_uid"), "policy_uid")
+        subject_uid = _uid(body.get("subject_uid"), "subject_uid")
+        asset_uid = _uid(body.get("asset_uid"), "asset_uid")
+        context = {
+            "subject_uid": subject_uid,
+            "roles": list(subject_roles or []),
+            "business_domain_uid": _uid(body.get("business_domain_uid"), "business_domain_uid"),
+            "asset_uid": asset_uid,
+            "purpose": _text(body.get("purpose"), "purpose", 100),
+            "environment": _text(body.get("environment"), "environment", 30),
+            "action": _text(body.get("action"), "action", 20),
+            "classification": _text(body.get("classification"), "classification", 30),
+            "requested_fields": sorted({_field(item, "requested field") for item in _values(body.get("requested_fields"), "requested fields", maximum=500)}),
+        }
+        # Re-evaluate from the durable, active policy version.  A caller's
+        # policy UID, capability and approval labels are only references; none
+        # of them is allowed to enlarge the authorisation projection.
+        decision = self.evaluate_gateway(context)
+        active = next((item for item in self.repository.active_policies() if item.get("uid") == policy_uid), None)
+        if active is None or decision.get("decision") != "allow" or active["code"] != decision.get("policy_code") or active["version"] != decision.get("policy_version"):
+            raise PermissionError("trusted delivery provision is not allowed by active policy")
+        if set(target_capability["actions"]) != {context["action"]} or set(target_capability["fields"]) != set(decision["projected_fields"]):
+            raise PermissionError("target capability exceeds server policy projection")
+        expires_at = _time(body.get("expires_at"), "grant expiry")
+        if expires_at <= self.now_factory().astimezone(UTC):
+            raise ValueError("grant expiry must be in the future")
+        policy_expiry = _time(active["selector"]["expires_at"], "policy expiry")
+        if expires_at > policy_expiry:
+            raise PermissionError("grant expiry exceeds active policy")
+        if "export" in target_capability["actions"] and self.repository.active_hold_for_asset(asset_uid):
+            raise PermissionError("active legal hold blocks export provision")
+        projection = {
+            "fields": decision["projected_fields"],
+            "row_predicate": decision["row_filter"],
+            "masking": decision["field_masks"],
+        }
+        decision_digest = _digest({"policy_uid": policy_uid, "policy_code": active["code"], "policy_version": active["version"], "context": context, "projection": projection, "approval_digest": approval_digest})
+        record = {
+            "uid": self.uid_factory(), "policy_uid": policy_uid,
+            "approval_ref": approval_ref, "approval_digest": approval_digest,
+            "subject_uid": subject_uid, "asset_uid": asset_uid,
+            "purpose": context["purpose"], "environment": context["environment"],
+            "target_capability": target_capability, "provider": provider,
+            "idempotency_key": _text(body.get("idempotency_key"), "idempotency key", 160),
+            "expires_at": expires_at.isoformat(), "status": "active", "reason_code": "approval_bound",
+            "request_digest": _digest({"decision_digest": decision_digest, "provider": provider, "expires_at": expires_at.isoformat(), "idempotency_key": body.get("idempotency_key")}),
+            "decision_digest": decision_digest,
+            "current_version": 1, "created_by": actor,
+        }
+        if hasattr(self.repository, "enqueue_grant"):
+            grant = self.repository.enqueue_grant(record)
+            grant_uid = grant["uid"]
+            if hasattr(self.repository, "get_grant"):
+                stored = self.repository.get_grant(grant_uid)
+                if stored and stored.get("status") == "reclaimed":
+                    raise RuntimeError("trusted delivery grant was reclaimed")
+            if hasattr(self.repository, "get_provision_receipt"):
+                prior_receipt = self.repository.get_provision_receipt(grant_uid, record["idempotency_key"])
+                if prior_receipt:
+                    return {"grant_uid": grant_uid, "status": "applied", "receipt": prior_receipt}
+        else:
+            raise RuntimeError("durable provision repository is required")
+        # Persist a fenced, idempotent outbox intent before the adapter is
+        # permitted to see the request.  A process crash now leaves a retryable
+        # pending record rather than an unprovable external side effect.
+        if hasattr(self.repository, "prepare_provision_operation"):
+            operation = self.repository.prepare_provision_operation(
+                grant_uid, record["idempotency_key"], record["request_digest"]
+            )
+            if operation.get("completed"):
+                return {"grant_uid": grant_uid, "status": "applied", "receipt": operation["receipt"]}
+        elif hasattr(self.repository, "session"):
+            raise RuntimeError("durable provision outbox repository is required")
+        envelope = {"schema": "dataops.trusted-delivery.v2", "operation": "provision", "grant_uid": grant_uid, "policy_uid": record["policy_uid"], "provider": provider, "subject_uid": record["subject_uid"], "asset_uid": record["asset_uid"], "purpose": record["purpose"], "environment": record["environment"], "expires_at": record["expires_at"], "projection": projection, "decision_digest": decision_digest, "request_digest": record["request_digest"]}
+        claim = None
+        if hasattr(self.repository, "claim_provision_operation"):
+            worker = f"provision:{self.uid_factory()}"
+            claim = self.repository.claim_provision_operation(operation["delivery_uid"], worker)
+            if claim.get("state") == "completed":
+                return {"grant_uid": grant_uid, "status": "applied", "receipt": claim["receipt"]}
+            if claim.get("state") != "claimed":
+                # Another process owns the external side effect.  Returning a
+                # pending state is intentionally safer than performing a
+                # duplicate call while that DB-time lease is alive.
+                return {"grant_uid": grant_uid, "status": "pending", "retryable": True}
+        try:
+            response = _closed(adapter.apply(copy.deepcopy(envelope)), {"status", "receipt_code", "response_digest"}, "provider receipt")
+            if response.get("status") != "applied":
+                raise RuntimeError("trusted delivery provider failed closed")
+        except Exception:
+            if claim is not None:
+                # Commit the retryable outbox state before the HTTP boundary
+                # rolls back the failed request transaction.
+                self.repository.fail_provision_operation(operation["delivery_uid"], worker, claim["lease_fence"])
+                self.repository.session.commit()
+            raise
+        receipt = {"grant_uid": grant_uid, "status": "applied", "receipt_code": _text(response.get("receipt_code"), "receipt code", 120), "response_digest": _digest_value(response.get("response_digest"), "provider response digest"), "diff_digest": _digest({"operation": "provision", "request_digest": record["request_digest"], "response_digest": response.get("response_digest")})}
+        if claim is not None:
+            saved = self.repository.complete_provision_operation(
+                grant_uid, operation["delivery_uid"], worker, claim["lease_fence"], receipt
+            )
+            receipt = {**receipt, "grant_uid": saved.get("uid", grant_uid)}
+        elif hasattr(self.repository, "record_provision_receipt"):
+            receipt = self.repository.record_provision_receipt(grant_uid, record["idempotency_key"], record["request_digest"], receipt)
+        self._event("provision_applied", actor, {"grant_uid": grant_uid, "provider": provider, "request_digest": record["request_digest"], "response_digest": receipt["response_digest"]})
+        return {"grant_uid": grant_uid, "status": "applied", "receipt": receipt}
+
+    def create_legal_hold(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"asset_uid", "operation", "approver_refs", "evidence_digest"}, "legal hold")
+        actor = self._actor(actor_uid)
+        asset_uid = _uid(body.get("asset_uid"), "asset_uid")
+        if self.repository.active_hold_for_asset(asset_uid):
+            raise RuntimeError("active legal hold already exists")
+        operation = _text(body.get("operation"), "legal hold operation", 30)
+        if operation not in {"freeze", "export_review", "destruction_review"}:
+            raise ValueError("unsupported legal hold operation")
+        approvers = sorted({_text(item, "approver reference", 200) for item in _values(body.get("approver_refs"), "approver references", minimum=2, maximum=2)})
+        if len(approvers) != 2:
+            raise ValueError("legal hold requires two distinct approver references")
+        record = {
+            "uid": self.uid_factory(), "asset_uid": asset_uid, "operation": operation,
+            # The SQL schema intentionally stores the two approvals in separate
+            # columns so a parameter mapping cannot silently collapse them.
+            "approver_ref_one": approvers[0], "approver_ref_two": approvers[1],
+            "approver_refs": approvers, "evidence_digest": _digest_value(body.get("evidence_digest"), "evidence digest"),
+            "status": "active", "created_by": actor, "created_at": self.now_factory().isoformat(), "current_version": 1,
+        }
+        saved = self.repository.create_legal_hold(record)
+        self._event("legal_hold_active", actor, {"hold_uid": saved["uid"], "operation": operation})
+        return saved
+
+    def release_legal_hold(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"hold_uid", "approval_ref", "approval_digest", "idempotency_key"}, "legal hold release")
+        actor = self._actor(actor_uid)
+        hold_uid = _uid(body.get("hold_uid"), "hold_uid")
+        approval_ref = _text(body.get("approval_ref"), "approval reference", 200)
+        approval_digest = _digest_value(body.get("approval_digest"), "approval digest")
+        key = _text(body.get("idempotency_key"), "idempotency key", 160)
+        request_digest = _digest({"hold_uid": hold_uid, "approval_ref": approval_ref, "approval_digest": approval_digest})
+        if hasattr(self.repository, "release_legal_hold"):
+            saved = self.repository.release_legal_hold(hold_uid, key, request_digest, actor, approval_ref, approval_digest)
+        elif hasattr(self.repository, "holds"):
+            releases = getattr(self.repository, "hold_releases", {})
+            existing = releases.get(key)
+            if existing:
+                if existing["request_digest"] != request_digest:
+                    raise RuntimeError("trusted delivery idempotency conflict")
+                return copy.deepcopy(existing["result"])
+            hold = self.repository.holds.get(hold_uid)
+            if not hold:
+                raise LookupError("trusted delivery legal hold was not found")
+            if hold["created_by"] == actor or approval_ref in hold["approver_refs"]:
+                raise PermissionError("legal hold release requires an independent approval")
+            hold["status"] = "released"
+            hold["current_version"] += 1
+            saved = {"uid": hold_uid, "status": "released", "current_version": hold["current_version"]}
+            releases[key] = {"request_digest": request_digest, "result": copy.deepcopy(saved)}
+            self.repository.hold_releases = releases
+        else:
+            raise RuntimeError("durable legal hold repository is required")
+        self._event("legal_hold_released", actor, {"hold_uid": hold_uid, "approval_digest": approval_digest})
+        return saved
+
+    def execute_reclaim(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        """Request provider revocation first; never delete or reclaim optimistically."""
+        body = _closed(payload, {"grant_uid", "idempotency_key"}, "trusted delivery reclaim")
+        actor = self._actor(actor_uid)
+        grant_uid = _uid(body.get("grant_uid"), "grant_uid")
+        key = _text(body.get("idempotency_key"), "idempotency key", 160)
+        if not hasattr(self.repository, "get_grant"):
+            raise RuntimeError("durable reclaim repository is required")
+        grant = self.repository.get_grant(grant_uid)
+        if not grant:
+            raise LookupError("trusted delivery grant was not found")
+        now = self.now_factory().astimezone(UTC)
+        if grant.get("status") == "reclaimed":
+            return {"grant_uid": grant_uid, "status": "reclaimed", "idempotent": True}
+        if grant.get("status") != "active" or _time(grant["expires_at"], "grant expiry") > now:
+            raise ValueError("trusted delivery grant is not reclaimable")
+        if self.repository.active_hold_for_asset(grant["asset_uid"]):
+            raise PermissionError("active legal hold blocks reclaim")
+        expiry = _time(grant["expires_at"], "grant expiry").isoformat()
+        request_digest = _digest({"operation": "revoke", "grant_uid": grant_uid, "provider": grant["provider"], "expires_at": expiry})
+        worker_id = f"reclaim-{uuid.uuid4()}"
+        claim = None
+        if hasattr(self.repository, "claim_reclaim_operation"):
+            claim = self.repository.claim_reclaim_operation(grant_uid, key, request_digest, worker_id)
+            if claim["state"] == "completed":
+                return {"grant_uid": grant_uid, "status": "reclaimed", "idempotent": True}
+            if claim["state"] == "leased":
+                raise RuntimeError("trusted delivery reclaim is already leased")
+        try:
+            adapter = self.provider_registry.get(grant["provider"])
+        except PermissionError:
+            self._event("reclaim_provider_unavailable", actor, {"grant_uid": grant_uid, "provider": grant["provider"], "request_digest": request_digest})
+            raise
+        envelope = {"schema": "dataops.trusted-delivery.v2", "operation": "revoke", "grant_uid": grant_uid, "provider": grant["provider"], "asset_uid": grant["asset_uid"], "request_digest": request_digest}
+        revoke = getattr(adapter, "revoke", None)
+        if not callable(revoke):
+            self._event("reclaim_provider_unavailable", actor, {"grant_uid": grant_uid, "provider": grant["provider"], "request_digest": request_digest})
+            raise PermissionError("trusted delivery provider revoke is disabled")
+        try:
+            response = _closed(revoke(copy.deepcopy(envelope)), {"status", "receipt_code", "response_digest"}, "provider receipt")
+        except Exception:
+            if claim:
+                self.repository.fail_reclaim_operation(claim["delivery_uid"], worker_id, claim["lease_fence"])
+            raise
+        if response.get("status") != "revoked":
+            if claim:
+                self.repository.fail_reclaim_operation(claim["delivery_uid"], worker_id, claim["lease_fence"])
+            raise RuntimeError("trusted delivery provider failed closed")
+        receipt = {"grant_uid": grant_uid, "status": "reclaimed", "receipt_code": _text(response.get("receipt_code"), "receipt code", 120), "response_digest": _digest_value(response.get("response_digest"), "provider response digest"), "diff_digest": _digest({"operation": "revoke", "request_digest": request_digest, "response_digest": response.get("response_digest")})}
+        if claim:
+            saved = self.repository.complete_claimed_reclaim(grant_uid, claim["delivery_uid"], worker_id, claim["lease_fence"], receipt)
+        elif hasattr(self.repository, "complete_reclaim"):
+            saved = self.repository.complete_reclaim(grant_uid, key, request_digest, receipt)
+        else:
+            saved = self.repository.update_grant_status(grant_uid, grant["current_version"], "reclaimed", "expired_reclaimed")
+        self._event("grant_reclaimed", actor, {"grant_uid": grant_uid, "provider": grant["provider"], "request_digest": request_digest, "response_digest": receipt["response_digest"]})
+        return {"grant_uid": grant_uid, "status": saved["status"], "receipt": receipt}
+
+    def preview_reclaim(self, *, as_of: Any, actor_uid: str) -> list[dict[str, Any]]:
+        """Return eligible identifiers only; this query has no state transition."""
+        self._actor(actor_uid)
+        instant = _time(as_of, "as_of")
+        candidates = []
+        for grant in self.repository.grants_due_for_revoke(instant):
+            if grant.get("status") != "active" or _time(grant["expires_at"], "grant expiry") > instant:
+                continue
+            if self.repository.active_hold_for_asset(grant["asset_uid"]):
+                continue
+            candidates.append({"grant_uid": grant["uid"], "provider": grant.get("provider", "unknown"), "status": "eligible"})
+        return candidates
+
+    def revoke_expired(self, *, as_of: datetime, actor_uid: str) -> list[dict[str, Any]]:
+        actor = self._actor(actor_uid)
+        instant = _time(as_of, "as_of")
+        revoked = []
+        for grant in self.repository.grants_due_for_revoke(instant):
+            if _time(grant["expires_at"], "grant expiry") > instant or self.repository.active_hold_for_asset(grant["asset_uid"]):
+                continue
+            saved = self.repository.update_grant_status(grant["uid"], grant["current_version"], "revoked", "expired")
+            self._event("grant_revoked", actor, {"grant_uid": saved["uid"], "reason_code": "expired"})
+            revoked.append(saved)
+        return revoked
+
+    @staticmethod
+    def mask_preview(payload: Any) -> dict[str, Any]:
+        body = _closed(payload, {"mode", "mask_ref", "value"}, "mask preview")
+        mode = _text(body.get("mode"), "mask mode", 20)
+        if mode not in {"static", "dynamic", "display", "export"}:
+            raise ValueError("unsupported mask mode")
+        mask_ref = _text(body.get("mask_ref"), "mask ref", 120)
+        value = _text(body.get("value"), "mask value", 1000)
+        return {"mode": mode, "mask_ref": mask_ref, "masked_value": "***-****", "token_digest": _digest({"mask_ref": mask_ref, "value": value})}

+ 293 - 0
app/core/system/trusted_delivery_controls.py

@@ -0,0 +1,293 @@
+"""Fail-closed control-profile and trusted-delivery gate contracts.
+
+The module deliberately models only safe identifiers, digests and decision
+codes.  It neither stores scan payloads or credentials nor claims that an
+external legal, identity, network or industry assessment has happened.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import hmac
+import json
+import re
+from collections.abc import Callable, Mapping
+from datetime import UTC, datetime
+from typing import Any
+
+
+class TrustedDeliveryControlError(ValueError):
+    """The closed trusted-delivery controls rejected a caller supplied value."""
+
+
+_DIGEST = re.compile(r"^[0-9a-f]{64}$")
+_REF = re.compile(r"^[a-z][a-z0-9-]{2,40}:[A-Za-z0-9._-]{1,160}$")
+_ID = re.compile(r"^[A-Za-z][A-Za-z0-9._-]{2,159}$")
+_VERSION = re.compile(r"^[A-Za-z][A-Za-z0-9._-]{0,39}$")
+_UNSAFE = re.compile(
+    r"(?i)(?:https?://|ftp://|file://|\b(?:credential|secret|token|password|private[ _-]?key)\b|"
+    r"-----BEGIN|<script|javascript:|;|\b(?:select|insert|update|delete|drop|alter)\b)"
+)
+_CAPABILITIES = {
+    "kms": frozenset({"kms-key-wrap", "kms-evidence-seal"}),
+    "dlp": frozenset({"dlp-export-check"}),
+    "siem": frozenset({"siem-security-event"}),
+    "iam": frozenset({"iam-entitlement-provision"}),
+    "target": frozenset({"target-db-provision", "target-api-provision", "target-file-provision"}),
+}
+
+
+def _canonical(value: Any) -> str:
+    return json.dumps(value, sort_keys=True, separators=(",", ":"))
+
+
+def _digest(value: Any) -> str:
+    return hashlib.sha256(_canonical(value).encode()).hexdigest()
+
+
+def _closed(value: Any, allowed: set[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise TrustedDeliveryControlError(f"{label} must be an object")
+    extra = set(value) - allowed
+    if extra:
+        raise TrustedDeliveryControlError(f"{label} contains unsupported fields")
+    return copy.deepcopy(value)
+
+
+def _text(value: Any, label: str, maximum: int = 160) -> str:
+    if not isinstance(value, str) or not value.strip():
+        raise TrustedDeliveryControlError(f"{label} is required")
+    result = value.strip()
+    if len(result) > maximum:
+        raise TrustedDeliveryControlError(f"{label} exceeds {maximum} characters")
+    if _UNSAFE.search(result):
+        raise TrustedDeliveryControlError(f"{label} contains unsafe content")
+    return result
+
+
+def _sha256(value: Any, label: str) -> str:
+    result = _text(value, label, 64).lower()
+    if not _DIGEST.fullmatch(result):
+        raise TrustedDeliveryControlError(f"{label} must be a SHA-256 digest")
+    return result
+
+
+def _instant(value: Any, label: str) -> datetime:
+    try:
+        result = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
+    except (TypeError, ValueError) as error:
+        raise TrustedDeliveryControlError(f"{label} must be ISO-8601") from error
+    if result.tzinfo is None:
+        raise TrustedDeliveryControlError(f"{label} must include a timezone")
+    return result.astimezone(UTC)
+
+
+class ControlProfileCatalog:
+    """Versioned engineering evidence mappings, never external certification."""
+
+    _profiles = {
+        "china_general": ("SEC-04", "SEC-05", "SEC-06", "SEC-07", "SEC-14", "SEC-16", "SEC-20"),
+        "finance": ("SEC-04", "SEC-05", "SEC-06", "SEC-07", "SEC-14", "SEC-17", "SEC-20", "SEC-21"),
+        "medical": ("SEC-04", "SEC-05", "SEC-06", "SEC-07", "SEC-14", "SEC-18", "SEC-20", "SEC-21"),
+        "government": ("SEC-04", "SEC-05", "SEC-06", "SEC-07", "SEC-14", "SEC-19", "SEC-20", "SEC-22"),
+    }
+
+    def resolve(self, profile_id: Any, version: Any) -> dict[str, Any]:
+        name = _text(profile_id, "control profile", 40)
+        release = _text(version, "control profile version", 20)
+        if release != "1.0" or name not in self._profiles:
+            raise TrustedDeliveryControlError("unknown control profile version")
+        modules = self._profiles[name]
+        return {
+            "profile_id": name,
+            "version": release,
+            "conformance_state": "ENGINEERING_EVIDENCE_ONLY",
+            "external_required": True,
+            "external_dependencies": ["security_legal", "identity", "network"],
+            "evidence_mapping": [
+                {"module_id": module_id, "evidence_kind": "engineering_control_reference"}
+                for module_id in modules
+            ],
+        }
+
+
+class TrustedCapabilityRegistry:
+    """Closed, default-disabled capabilities with locally verifiable fake receipts."""
+
+    def __init__(self, approved: Mapping[str, Mapping[str, str]] | None = None, *, test_mode: bool = False):
+        values = dict(approved or {})
+        if values and not test_mode:
+            raise TrustedDeliveryControlError("enterprise capabilities require approved external integration")
+        if set(values) - set(_CAPABILITIES):
+            raise TrustedDeliveryControlError("unsupported capability provider")
+        self._approved: dict[str, dict[str, str]] = {}
+        for provider, descriptor in values.items():
+            body = _closed(descriptor, {"version", "config_digest"}, "capability descriptor")
+            version = _text(body.get("version"), "capability version", 30)
+            if not _VERSION.fullmatch(version):
+                raise TrustedDeliveryControlError("capability version is invalid")
+            self._approved[provider] = {
+                "version": version,
+                "config_digest": _sha256(body.get("config_digest"), "capability config digest"),
+            }
+        self._replay: dict[str, str] = {}
+
+    @classmethod
+    def for_tests(cls, approved: Mapping[str, Mapping[str, str]]):
+        return cls(approved, test_mode=True)
+
+    def dependency_status(self) -> dict[str, str]:
+        return {
+            provider: "LOCAL_FAKE_ONLY" if provider in self._approved else "TBD_EXTERNAL"
+            for provider in sorted(_CAPABILITIES)
+        }
+
+    def _key(self, provider: str) -> bytes:
+        descriptor = self._approved[provider]
+        return hashlib.sha256(
+            f"trusted-delivery-local-fake:{provider}:{descriptor['version']}:{descriptor['config_digest']}".encode()
+        ).digest()
+
+    def issue_envelope(self, provider: Any, capability: Any, payload: Any) -> dict[str, str]:
+        provider_name = _text(provider, "capability provider", 30)
+        operation = _text(capability, "capability", 80)
+        if provider_name not in _CAPABILITIES or operation not in _CAPABILITIES[provider_name]:
+            raise TrustedDeliveryControlError("unsupported capability")
+        if provider_name not in self._approved:
+            raise TrustedDeliveryControlError("capability provider is disabled")
+        body = _closed(payload, {"request_digest", "expires_at"}, "capability envelope request")
+        request_digest = _sha256(body.get("request_digest"), "request digest")
+        expires_at = _instant(body.get("expires_at"), "capability envelope expiry")
+        if expires_at <= datetime.now(UTC):
+            raise TrustedDeliveryControlError("capability envelope is expired")
+        replay_key = f"{provider_name}:{operation}:{request_digest}"
+        request_fingerprint = _digest({"request_digest": request_digest, "expires_at": expires_at.isoformat()})
+        if replay_key in self._replay:
+            if self._replay[replay_key] != request_fingerprint:
+                raise TrustedDeliveryControlError("capability replay conflict")
+            raise TrustedDeliveryControlError("capability replay rejected")
+        descriptor = self._approved[provider_name]
+        envelope = {
+            "capability": operation,
+            "version": descriptor["version"],
+            "config_digest": descriptor["config_digest"],
+            "request_digest": request_digest,
+            "expires_at": expires_at.isoformat(),
+            "receipt": f"LOCAL_FAKE_{_digest([provider_name, operation, request_digest])[:20].upper()}",
+        }
+        envelope["signature"] = hmac.new(self._key(provider_name), _canonical(envelope).encode(), hashlib.sha256).hexdigest()
+        self._replay[replay_key] = request_fingerprint
+        return envelope
+
+    def verify_envelope(self, envelope: Any) -> bool:
+        body = _closed(envelope, {"capability", "version", "config_digest", "request_digest", "expires_at", "receipt", "signature"}, "capability receipt")
+        operation = _text(body.get("capability"), "capability", 80)
+        provider = next((name for name, allowed in _CAPABILITIES.items() if operation in allowed), None)
+        if provider is None or provider not in self._approved:
+            raise TrustedDeliveryControlError("capability receipt is disabled")
+        descriptor = self._approved[provider]
+        if body.get("version") != descriptor["version"] or body.get("config_digest") != descriptor["config_digest"]:
+            raise TrustedDeliveryControlError("capability receipt binding is invalid")
+        _sha256(body.get("request_digest"), "request digest")
+        if _instant(body.get("expires_at"), "capability receipt expiry") <= datetime.now(UTC):
+            raise TrustedDeliveryControlError("capability receipt is expired")
+        receipt = _text(body.get("receipt"), "receipt", 80)
+        signature = _sha256(body.get("signature"), "receipt signature")
+        signed = {key: body[key] for key in body if key != "signature"}
+        expected = hmac.new(self._key(provider), _canonical(signed).encode(), hashlib.sha256).hexdigest()
+        if not hmac.compare_digest(signature, expected):
+            raise TrustedDeliveryControlError("capability receipt signature is invalid")
+        if not receipt.startswith("LOCAL_FAKE_"):
+            raise TrustedDeliveryControlError("capability receipt is invalid")
+        return True
+
+
+class SupplyChainReleaseGate:
+    """Digest-only release gate; a passing result is not a production approval."""
+
+    def __init__(self, *, now_factory: Callable[[], datetime] | None = None):
+        self.now_factory = now_factory or (lambda: datetime.now(UTC))
+
+    def evaluate(self, payload: Any) -> dict[str, str]:
+        body = _closed(payload, {
+            "artifact_digest", "sbom_digest", "license_policy_digest", "vulnerability_scan_digest",
+            "approval_id", "approval_version", "scan_decision", "evidence_expires_at",
+        }, "release gate")
+        required = (
+            "artifact_digest", "sbom_digest", "license_policy_digest", "vulnerability_scan_digest",
+            "approval_id", "approval_version", "scan_decision", "evidence_expires_at",
+        )
+        if any(key not in body or body[key] in (None, "") for key in required):
+            raise TrustedDeliveryControlError("missing release gate evidence")
+        digests = {key: _sha256(body[key], key.replace("_", " ")) for key in required[:4]}
+        approval_id = _text(body["approval_id"], "approval id", 160)
+        approval_version = _text(body["approval_version"], "approval version", 40)
+        if not _ID.fullmatch(approval_id) or not _VERSION.fullmatch(approval_version):
+            raise TrustedDeliveryControlError("release approval is invalid")
+        expiry = _instant(body["evidence_expires_at"], "release gate evidence expiry")
+        if expiry <= self.now_factory().astimezone(UTC):
+            raise TrustedDeliveryControlError("release gate evidence is expired")
+        if body["scan_decision"] != "passed":
+            raise TrustedDeliveryControlError("release gate rejected")
+        return {
+            "decision_code": "RELEASE_GATE_APPROVED",
+            **digests,
+            "approval_id": approval_id,
+            "approval_version": approval_version,
+            "evidence_expires_at": expiry.isoformat(),
+        }
+
+
+class EvidenceLedger:
+    """In-memory append-only contract used before its repository migration lands."""
+
+    def __init__(self):
+        self._entries: list[dict[str, str]] = []
+        self._audit: list[dict[str, str]] = []
+
+    def append(self, payload: Any) -> dict[str, str]:
+        body = _closed(payload, {"evidence_ref", "evidence_digest", "decision_code"}, "evidence entry")
+        if set(body) != {"evidence_ref", "evidence_digest", "decision_code"}:
+            raise TrustedDeliveryControlError("evidence entry is incomplete")
+        reference = _text(body["evidence_ref"], "evidence reference", 200)
+        if not _REF.fullmatch(reference):
+            raise TrustedDeliveryControlError("evidence reference is invalid")
+        entry = {
+            "evidence_ref": reference,
+            "evidence_digest": _sha256(body["evidence_digest"], "evidence digest"),
+            "decision_code": _text(body["decision_code"], "decision code", 120),
+        }
+        if any(item["evidence_ref"] == entry["evidence_ref"] for item in self._entries):
+            raise TrustedDeliveryControlError("evidence is immutable")
+        self._entries.append(entry)
+        return dict(entry)
+
+    def export_summary(self) -> list[dict[str, str]]:
+        return [dict(item) for item in self._entries]
+
+    def approve_destruction(self, payload: Any) -> dict[str, str]:
+        body = _closed(payload, {"approval_refs", "active_hold"}, "destruction approval")
+        refs = body.get("approval_refs")
+        if not isinstance(refs, list) or len(refs) != 2:
+            self._audit.append({"decision_code": "DESTRUCTION_REJECTED"})
+            raise TrustedDeliveryControlError("destruction requires two independent approvals")
+        approvals = [_text(item, "destruction approval reference", 160) for item in refs]
+        if approvals[0] == approvals[1]:
+            self._audit.append({"decision_code": "DESTRUCTION_REJECTED"})
+            raise TrustedDeliveryControlError("destruction requires two independent approvals")
+        if body.get("active_hold") is not False:
+            self._audit.append({"decision_code": "DESTRUCTION_HELD"})
+            raise TrustedDeliveryControlError("destruction blocked by active legal hold")
+        result = {"decision_code": "DESTRUCTION_DUAL_APPROVED", "approval_digest": _digest(sorted(approvals))}
+        self._audit.append(dict(result))
+        return result
+
+    def tamper(self, payload: Any) -> None:
+        # Never preserve an attempted replacement value, which may itself be sensitive.
+        _closed(payload, {"evidence_ref", "replacement"}, "tamper attempt")
+        self._audit.append({"decision_code": "TAMPER_REJECTED"})
+        raise TrustedDeliveryControlError("evidence ledger is immutable")
+
+    def audit_events(self) -> list[dict[str, str]]:
+        return [dict(item) for item in self._audit]

+ 150 - 0
app/core/system/trusted_delivery_controls_repository.py

@@ -0,0 +1,150 @@
+"""PostgreSQL persistence for digest-only trusted-delivery control evidence."""
+
+from __future__ import annotations
+
+import json
+import uuid
+from typing import Any
+
+from sqlalchemy import text
+
+
+def _json(value: Any) -> str:
+    return json.dumps(value, sort_keys=True, separators=(",", ":"))
+
+
+def _row(value) -> dict[str, Any] | None:
+    return None if value is None else dict(value)
+
+
+class SqlAlchemyTrustedDeliveryControlsRepository:
+    """Closed-schema writes; service validation remains the public boundary."""
+
+    def __init__(self, session):
+        self.session = session
+
+    def _trusted_delivery_runtime_write(self, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
+        result = self.session.execute(text("""
+            SELECT public.trusted_delivery_runtime_write(:operation, CAST(:payload AS jsonb))
+        """), {"operation": operation, "payload": _json(payload)}).scalar_one()
+        return dict(result)
+
+    def _trusted_delivery_runtime_control_write(self, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
+        result = self.session.execute(text("SELECT public.trusted_delivery_runtime_control_write(:operation, CAST(:payload AS jsonb))"), {"operation": operation, "payload": _json(payload)}).scalar_one()
+        return dict(result)
+
+    def _runtime_restricted(self) -> bool:
+        return not self.session.execute(text("""
+            SELECT has_table_privilege(current_user,
+              'public.trusted_delivery_control_profile_versions', 'INSERT')
+        """)).scalar_one()
+
+    def users_available(self, values):
+        values = sorted(set(values))
+        if not values:
+            return set()
+        return set(self.session.execute(text("SELECT id::text FROM public.users WHERE status='active' AND id=ANY(CAST(:values AS uuid[]))"), {"values": values}).scalars())
+
+    def create_profile(self, record):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("create_control_profile", record))
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_control_profile_versions
+          (uid,profile_id,version,status,profile,profile_digest,created_by,current_version)
+          VALUES(CAST(:uid AS uuid),:profile_id,:version,'draft',CAST(:profile AS jsonb),:profile_digest,CAST(:created_by AS uuid),1)
+          RETURNING uid::text,profile_id,version,status,current_version"""), {**record, "profile": _json(record["profile"])}).mappings().one()
+        return _row(row)
+
+    def profile_exists(self, profile_id, version):
+        return self.session.execute(text("SELECT 1 FROM public.trusted_delivery_control_profile_versions WHERE profile_id=:profile AND version=:version"), {"profile": profile_id, "version": version}).scalar_one_or_none() is not None
+
+    def activate_profile(self, profile_id, version, idempotency_key, request_digest, actor_uid, approval_ref, approval_digest, operation="activate"):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("activate_control_profile", {
+                "uid": str(uuid.uuid4()), "profile_id": profile_id, "version": version,
+                "idempotency_key": idempotency_key, "request_digest": request_digest,
+                "actor_uid": actor_uid, "approval_ref": approval_ref,
+                "approval_digest": approval_digest, "transition_operation": operation,
+            }))
+        existing = self.session.execute(text("SELECT profile_uid::text,request_digest FROM public.trusted_delivery_control_profile_transitions WHERE idempotency_key=:key"), {"key": idempotency_key}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != request_digest:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            row = self.session.execute(text("SELECT uid::text,profile_id,version,status,current_version FROM public.trusted_delivery_control_profile_versions WHERE uid=CAST(:uid AS uuid)"), {"uid": existing["profile_uid"]}).mappings().one()
+            return _row(row)
+        target = self.session.execute(text("SELECT uid::text FROM public.trusted_delivery_control_profile_versions WHERE profile_id=:profile AND version=:version FOR UPDATE"), {"profile": profile_id, "version": version}).mappings().one_or_none()
+        if target is None:
+            raise LookupError("control profile version was not found")
+        self.session.execute(text("SELECT uid FROM public.trusted_delivery_control_profile_versions WHERE profile_id=:profile AND status='active' FOR UPDATE"), {"profile": profile_id})
+        self.session.execute(text("UPDATE public.trusted_delivery_control_profile_versions SET status='retired',current_version=current_version+1,updated_at=clock_timestamp() WHERE profile_id=:profile AND status='active' AND uid<>CAST(:uid AS uuid)"), {"profile": profile_id, "uid": target["uid"]})
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_control_profile_versions
+          SET status='active',current_version=current_version+CASE WHEN status='active' THEN 0 ELSE 1 END,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) RETURNING uid::text,profile_id,version,status,current_version"""), {"uid": target["uid"]}).mappings().one()
+        self.session.execute(text("""INSERT INTO public.trusted_delivery_control_profile_transitions
+          (uid,profile_uid,operation,idempotency_key,request_digest,approval_ref,approval_digest,actor_uid)
+          VALUES(CAST(:uid AS uuid),CAST(:profile_uid AS uuid),:operation,:key,:request_digest,:approval_ref,:approval_digest,CAST(:actor AS uuid))"""), {"uid": str(uuid.uuid4()), "profile_uid": target["uid"], "operation": operation, "key": idempotency_key, "request_digest": request_digest, "approval_ref": approval_ref, "approval_digest": approval_digest, "actor": actor_uid})
+        return _row(row)
+
+    def active_profile_summary(self, profile_id):
+        row = self.session.execute(text("SELECT profile_id,version,profile_digest,status,current_version FROM public.trusted_delivery_control_profile_versions WHERE profile_id=:profile AND status='active'"), {"profile": profile_id}).mappings().one_or_none()
+        return _row(row)
+
+    def approve_capability(self, record):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_control_write("approve_capability", record))
+        existing = self.session.execute(text("SELECT uid::text,request_digest FROM public.trusted_delivery_capability_approvals WHERE idempotency_key=:key"), {"key": record["idempotency_key"]}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != record["request_digest"]:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            return {"uid": existing["uid"], "status": "approved"}
+        self.session.execute(text("UPDATE public.trusted_delivery_capability_approvals SET status='revoked' WHERE provider=:provider AND capability=:capability AND status='approved'"), record)
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_capability_approvals
+          (uid,provider,capability,version,config_digest,status,idempotency_key,request_digest,created_by,approval_ref,approval_digest)
+          VALUES(CAST(:uid AS uuid),:provider,:capability,:version,:config_digest,'approved',:idempotency_key,:request_digest,CAST(:created_by AS uuid),:approval_ref,:approval_digest)
+          RETURNING uid::text,provider,capability,version,config_digest,status"""), record).mappings().one()
+        return _row(row)
+
+    def record_release_gate(self, record):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_control_write("record_release_gate", record))
+        existing = self.session.execute(text("SELECT uid::text,request_digest,decision_code FROM public.trusted_delivery_supply_chain_gates WHERE idempotency_key=:key"), {"key": record["idempotency_key"]}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != record["request_digest"]:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            return _row(existing)
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_supply_chain_gates
+          (uid,artifact_digest,sbom_digest,license_policy_digest,vulnerability_scan_digest,approval_id,approval_version,evidence_expires_at,decision_code,idempotency_key,request_digest,created_by,approval_ref,approval_digest)
+          VALUES(CAST(:uid AS uuid),:artifact_digest,:sbom_digest,:license_policy_digest,:vulnerability_scan_digest,:approval_id,:approval_version,CAST(:evidence_expires_at AS timestamptz),:decision_code,:idempotency_key,:request_digest,CAST(:created_by AS uuid),:approval_ref,:approval_digest)
+          RETURNING uid::text,decision_code,artifact_digest,sbom_digest,license_policy_digest,vulnerability_scan_digest,approval_id,approval_version,evidence_expires_at"""), record).mappings().one()
+        return _row(row)
+
+    def create_evidence(self, record):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_control_write("create_control_evidence", record))
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_control_evidence
+          (uid,evidence_ref,evidence_digest,decision_code,gate_uid,actor_uid,approval_ref,approval_digest)
+          VALUES(CAST(:uid AS uuid),:evidence_ref,:evidence_digest,:decision_code,CAST(:gate_uid AS uuid),CAST(:actor_uid AS uuid),:approval_ref,:approval_digest)
+          ON CONFLICT(evidence_ref) DO NOTHING
+          RETURNING uid::text,evidence_ref,evidence_digest,decision_code,gate_uid::text"""), record).mappings().one_or_none()
+        if row is None:
+            row = self.session.execute(text("SELECT uid::text,evidence_ref,evidence_digest,decision_code,gate_uid::text FROM public.trusted_delivery_control_evidence WHERE evidence_ref=:evidence_ref"), {"evidence_ref": record["evidence_ref"]}).mappings().one()
+        return _row(row)
+
+    def evidence_summary(self, limit=100):
+        rows = self.session.execute(text("SELECT evidence_ref,evidence_digest,decision_code,gate_uid::text FROM public.trusted_delivery_control_evidence ORDER BY created_at,uid LIMIT :limit"), {"limit": int(limit)}).mappings()
+        return [_row(row) for row in rows]
+
+    def approve_destruction(self, record):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("approve_destruction", record))
+        existing = self.session.execute(text("SELECT uid::text,request_digest,status FROM public.trusted_delivery_destruction_approvals WHERE idempotency_key=:key"), {"key": record["idempotency_key"]}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != record["request_digest"]:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            return _row(existing)
+        if self.session.execute(text("SELECT 1 FROM public.trusted_delivery_legal_holds WHERE asset_uid=CAST(:asset AS uuid) AND status='active'"), {"asset": record["asset_uid"]}).scalar_one_or_none():
+            raise PermissionError("destruction blocked by active legal hold")
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_destruction_approvals
+          (uid,asset_uid,approval_ref_one,approval_ref_two,evidence_digest,status,idempotency_key,request_digest,created_by)
+          VALUES(CAST(:uid AS uuid),CAST(:asset_uid AS uuid),:approval_ref_one,:approval_ref_two,:evidence_digest,'approved',:idempotency_key,:request_digest,CAST(:created_by AS uuid))
+          RETURNING uid::text,status"""), record).mappings().one()
+        return _row(row)

+ 103 - 0
app/core/system/trusted_delivery_controls_service.py

@@ -0,0 +1,103 @@
+"""Service boundary for durable trusted-delivery control profiles and gates."""
+
+from __future__ import annotations
+
+import hashlib
+import uuid
+from typing import Any
+
+from app.core.system.trusted_delivery_controls import (
+    ControlProfileCatalog,
+    EvidenceLedger,
+    SupplyChainReleaseGate,
+    TrustedDeliveryControlError,
+    _closed,
+    _digest,
+    _sha256,
+    _text,
+)
+
+
+class TrustedDeliveryControlsService:
+    def __init__(self, repository, *, catalog=None, gate=None):
+        self.repository = repository
+        self.catalog = catalog or ControlProfileCatalog()
+        self.gate = gate or SupplyChainReleaseGate()
+
+    def _actor(self, actor_uid: str) -> str:
+        if self.repository.users_available({actor_uid}) != {actor_uid}:
+            raise TrustedDeliveryControlError("control actor is unavailable")
+        return actor_uid
+
+    def _transition(self, payload: Any, *, actor_uid: str, operation: str) -> dict[str, Any]:
+        body = _closed(payload, {"profile_id", "version", "approval_ref", "approval_digest", "idempotency_key"}, "control profile transition")
+        actor = self._actor(actor_uid)
+        profile = self.catalog.resolve(body.get("profile_id"), body.get("version"))
+        digest = _digest(profile)
+        profile_id = profile["profile_id"]
+        version = profile["version"]
+        if not self.repository.profile_exists(profile_id, version):
+            self.repository.create_profile({"uid": str(uuid.uuid4()), "profile_id": profile_id, "version": version, "profile_digest": digest, "profile": profile, "created_by": actor})
+        request = {
+            "profile_id": profile_id, "version": version,
+            "approval_ref": _text(body.get("approval_ref"), "approval reference", 200),
+            "approval_digest": _sha256(body.get("approval_digest"), "approval digest"),
+            "idempotency_key": _text(body.get("idempotency_key"), "idempotency key", 160),
+            "operation": operation,
+        }
+        return self.repository.activate_profile(profile_id, version, request["idempotency_key"], _digest(request), actor, request["approval_ref"], request["approval_digest"], operation)
+
+    def activate_profile(self, payload: Any, *, actor_uid: str, operation: str = "activate") -> dict[str, Any]:
+        return self._transition(payload, actor_uid=actor_uid, operation=operation)
+
+    def active_profile_summary(self, profile_id: Any) -> dict[str, Any] | None:
+        return self.repository.active_profile_summary(_text(profile_id, "control profile", 40))
+
+    def approve_capability(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"provider", "capability", "version", "config_digest", "approval_ref", "approval_digest", "idempotency_key"}, "capability approval")
+        actor = self._actor(actor_uid)
+        provider = _text(body.get("provider"), "capability provider", 20)
+        capability = _text(body.get("capability"), "capability", 80)
+        allowed = {
+            "kms": {"kms-key-wrap", "kms-evidence-seal"}, "dlp": {"dlp-export-check"},
+            "siem": {"siem-security-event"}, "iam": {"iam-entitlement-provision"},
+            "target": {"target-db-provision", "target-api-provision", "target-file-provision"},
+        }
+        if provider not in allowed or capability not in allowed[provider]:
+            raise TrustedDeliveryControlError("unsupported capability")
+        record = {"uid": str(uuid.uuid4()), "provider": provider, "capability": capability, "version": _text(body.get("version"), "capability version", 40), "config_digest": _sha256(body.get("config_digest"), "config digest"), "approval_ref": _text(body.get("approval_ref"), "approval reference", 200), "approval_digest": _sha256(body.get("approval_digest"), "approval digest"), "idempotency_key": _text(body.get("idempotency_key"), "idempotency key", 160), "created_by": actor}
+        record["request_digest"] = _digest({key: record[key] for key in ("provider", "capability", "version", "config_digest", "idempotency_key")})
+        return self.repository.approve_capability(record)
+
+    def evaluate_release_gate(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"artifact_digest", "sbom_digest", "license_policy_digest", "vulnerability_scan_digest", "approval_id", "approval_version", "scan_decision", "evidence_expires_at", "approval_ref", "approval_digest", "idempotency_key"}, "release gate request")
+        actor = self._actor(actor_uid)
+        # Approval proof binds the durable write, but is not an input to the
+        # release-policy evaluator's closed schema.
+        summary = self.gate.evaluate({key: body[key] for key in (
+            "artifact_digest", "sbom_digest", "license_policy_digest",
+            "vulnerability_scan_digest", "approval_id", "approval_version",
+            "scan_decision", "evidence_expires_at",
+        )})
+        record = {"uid": str(uuid.uuid4()), **{key: summary[key] for key in ("artifact_digest", "sbom_digest", "license_policy_digest", "vulnerability_scan_digest", "approval_id", "approval_version", "evidence_expires_at", "decision_code")}, "approval_ref": _text(body.get("approval_ref"), "approval reference", 200), "approval_digest": _sha256(body.get("approval_digest"), "approval digest"), "idempotency_key": _text(body.get("idempotency_key"), "idempotency key", 160), "created_by": actor}
+        record["request_digest"] = _digest({key: record[key] for key in record if key not in {"uid", "created_by"}})
+        result = self.repository.record_release_gate(record)
+        self.repository.create_evidence({"uid": str(uuid.uuid4()), "evidence_ref": f"trusted-evidence:gate-{result['uid']}", "evidence_digest": hashlib.sha256(result["uid"].encode()).hexdigest(), "decision_code": result["decision_code"], "gate_uid": result["uid"], "actor_uid": actor, "approval_ref": record["approval_ref"], "approval_digest": record["approval_digest"]})
+        return result
+
+    def evidence_summary(self) -> list[dict[str, Any]]:
+        return self.repository.evidence_summary()
+
+    def approve_destruction(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"asset_uid", "approval_refs", "evidence_digest", "idempotency_key"}, "destruction approval")
+        actor = self._actor(actor_uid)
+        refs = body.get("approval_refs")
+        ledger = EvidenceLedger()
+        ledger.approve_destruction({"approval_refs": refs, "active_hold": False})
+        try:
+            asset_uid = str(uuid.UUID(str(body.get("asset_uid"))))
+        except (TypeError, ValueError, AttributeError) as error:
+            raise TrustedDeliveryControlError("asset uid must be a UUID") from error
+        record = {"uid": str(uuid.uuid4()), "asset_uid": asset_uid, "approval_ref_one": refs[0], "approval_ref_two": refs[1], "evidence_digest": _sha256(body.get("evidence_digest"), "evidence digest"), "idempotency_key": _text(body.get("idempotency_key"), "idempotency key", 160), "created_by": actor}
+        record["request_digest"] = _digest({key: record[key] for key in ("asset_uid", "approval_ref_one", "approval_ref_two", "evidence_digest", "idempotency_key")})
+        return self.repository.approve_destruction(record)

+ 534 - 0
app/core/system/trusted_delivery_repository.py

@@ -0,0 +1,534 @@
+"""PostgreSQL repository for trusted-delivery state and digest-only receipts."""
+
+from __future__ import annotations
+
+import json
+import uuid
+from datetime import datetime
+from typing import Any
+
+from sqlalchemy import text
+
+
+def _json(value: Any) -> str:
+    return json.dumps(value, sort_keys=True, separators=(",", ":"))
+
+
+def _row(row) -> dict[str, Any] | None:
+    return None if row is None else dict(row)
+
+
+class SqlAlchemyTrustedDeliveryRepository:
+    """All writes are parameterized and transactions are owned by the caller."""
+
+    def __init__(self, session):
+        self.session = session
+
+    def _trusted_delivery_gateway(self, operation: str, payload: dict[str, Any]) -> dict[str, Any] | None:
+        """Use the database definer gateway when this is a runtime session.
+
+        Migration/test identities retain direct DML while the long-running
+        runtime login receives only EXECUTE on the closed operation gateway.
+        The decision is derived from PostgreSQL privileges, not a caller flag.
+        """
+        direct_dml = self.session.execute(text("""
+            SELECT has_table_privilege(current_user,
+              'public.trusted_delivery_policy_versions', 'INSERT')
+        """)).scalar_one()
+        if direct_dml:
+            return None
+        result = self.session.execute(text("""
+            SELECT public.trusted_delivery_write(:operation, CAST(:payload AS jsonb))
+        """), {"operation": operation, "payload": _json(payload)}).scalar_one()
+        return dict(result)
+
+    def _trusted_delivery_runtime_write(self, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
+        """Execute one fixed database-side WP06 lifecycle operation."""
+        result = self.session.execute(text("""
+            SELECT public.trusted_delivery_runtime_write(:operation, CAST(:payload AS jsonb))
+        """), {"operation": operation, "payload": _json(payload)}).scalar_one()
+        return dict(result)
+
+    def _trusted_delivery_runtime_reclaim_write(self, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
+        """Use the atomic revoke outbox gateway for a restricted runtime login."""
+        result = self.session.execute(text("""
+            SELECT public.trusted_delivery_runtime_reclaim_write(
+              :operation, CAST(:payload AS jsonb)
+            )
+        """), {"operation": operation, "payload": _json(payload)}).scalar_one()
+        return dict(result)
+
+    def _runtime_restricted(self) -> bool:
+        return not self.session.execute(text("""
+            SELECT has_table_privilege(current_user,
+              'public.trusted_delivery_grants', 'INSERT')
+        """)).scalar_one()
+
+    def users_available(self, values):
+        values = sorted(set(values))
+        if not values:
+            return set()
+        return set(self.session.execute(text("SELECT id::text FROM public.users WHERE status='active' AND id=ANY(CAST(:values AS uuid[]))"), {"values": values}).scalars())
+
+    def create_policy_version(self, record):
+        gateway = self._trusted_delivery_gateway("create_policy", {
+            key: record[key] for key in ("uid", "code", "version", "selector", "resource_rules", "created_by", "current_version") if key in record
+        })
+        if gateway is not None:
+            return gateway
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_policy_versions
+          (uid,code,version,status,selector,resource_rules,created_by,current_version)
+          VALUES(CAST(:uid AS uuid),:code,:version,:status,CAST(:selector AS jsonb),CAST(:resource_rules AS jsonb),CAST(:created_by AS uuid),:current_version)
+          RETURNING uid::text,code,version,status,current_version"""), {**record, "selector": _json(record["selector"]), "resource_rules": _json(record["resource_rules"])}).mappings().one()
+        # Compatibility for pre-WP06 fixture importers only: production policy
+        # creation always enters as draft and is activated through
+        # ``transition_policy``.  A repository caller that imports an already
+        # active historic record still receives an auditable bootstrap approval,
+        # so the DB active-grant trigger is never bypassed.
+        if record["status"] == "active":
+            self.session.execute(text("""INSERT INTO public.trusted_delivery_policy_transitions
+              (uid,policy_uid,operation,idempotency_key,request_digest,approval_ref,approval_digest,actor_uid)
+              VALUES(CAST(:uid AS uuid),CAST(:policy AS uuid),'activate',:key,:digest,:approval_ref,:digest,CAST(:actor AS uuid))
+              ON CONFLICT (idempotency_key) DO NOTHING"""), {
+                "uid": str(uuid.uuid4()), "policy": record["uid"],
+                "key": f"bootstrap:{record['uid']}", "digest": "0" * 64,
+                "approval_ref": f"bootstrap:{record['uid']}", "actor": record["created_by"],
+            })
+        return _row(row)
+
+    def transition_policy(self, code, version, operation, idempotency_key, request_digest, actor_uid, approval_ref, approval_digest):
+        gateway = self._trusted_delivery_gateway("transition_policy", {
+            "code": code, "version": version, "operation": operation,
+            "idempotency_key": idempotency_key, "request_digest": request_digest,
+            "actor_uid": actor_uid, "approval_ref": approval_ref,
+            "approval_digest": approval_digest,
+        })
+        if gateway is not None:
+            return gateway
+        existing = self.session.execute(text("SELECT policy_uid::text,request_digest FROM public.trusted_delivery_policy_transitions WHERE idempotency_key=:key"), {"key": idempotency_key}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != request_digest:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            row = self.session.execute(text("SELECT uid::text,code,version,status,current_version FROM public.trusted_delivery_policy_versions WHERE uid=CAST(:uid AS uuid)"), {"uid": existing["policy_uid"]}).mappings().one()
+            return _row(row)
+        target = self.session.execute(text("SELECT uid::text,code,version,status,current_version FROM public.trusted_delivery_policy_versions WHERE code=:code AND version=:version FOR UPDATE"), {"code": code, "version": version}).mappings().one_or_none()
+        if target is None:
+            raise LookupError("trusted delivery policy version was not found")
+        # Lock all candidate active rows before changing the partial unique index.
+        self.session.execute(text("SELECT uid FROM public.trusted_delivery_policy_versions WHERE code=:code AND status='active' FOR UPDATE"), {"code": code})
+        self.session.execute(text("UPDATE public.trusted_delivery_policy_versions SET status='retired',current_version=current_version+1 WHERE code=:code AND status='active' AND uid<>CAST(:uid AS uuid)"), {"code": code, "uid": target["uid"]})
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_policy_versions
+          SET status='active',current_version=current_version+CASE WHEN status='active' THEN 0 ELSE 1 END
+          WHERE uid=CAST(:uid AS uuid)
+          RETURNING uid::text,code,version,status,current_version"""), {"uid": target["uid"]}).mappings().one()
+        self.session.execute(text("""INSERT INTO public.trusted_delivery_policy_transitions
+          (uid,policy_uid,operation,idempotency_key,request_digest,approval_ref,approval_digest,actor_uid)
+          VALUES(CAST(:uid AS uuid),CAST(:policy_uid AS uuid),:operation,:key,:request_digest,:approval_ref,:approval_digest,CAST(:actor_uid AS uuid))"""), {"uid": str(uuid.uuid4()), "policy_uid": target["uid"], "operation": operation, "key": idempotency_key, "request_digest": request_digest, "approval_ref": approval_ref, "approval_digest": approval_digest, "actor_uid": actor_uid})
+        return _row(row)
+
+    def active_policies(self):
+        rows = self.session.execute(text("SELECT uid::text,code,version,status,selector,resource_rules FROM public.trusted_delivery_policy_versions WHERE status='active' ORDER BY created_at,uid")).mappings()
+        return [{**_row(row), "selector": dict(row["selector"]), "resource_rules": dict(row["resource_rules"])} for row in rows]
+
+    def enqueue_grant(self, record):
+        if record.get("approval_ref") and record.get("approval_digest") and record.get("target_capability") is not None:
+            gateway = self._trusted_delivery_gateway("create_grant", {
+                key: record[key] for key in (
+                    "uid", "policy_uid", "subject_uid", "asset_uid", "provider",
+                    "idempotency_key", "request_digest", "expires_at", "status",
+                    "reason_code", "current_version", "created_by", "approval_ref",
+                    "approval_digest", "purpose", "environment", "target_capability",
+                ) if key in record
+            })
+            if gateway is not None:
+                return {"uid": gateway["uid"]}
+        existing = self.session.execute(text("SELECT uid::text,request_digest FROM public.trusted_delivery_grants WHERE idempotency_key=:key"), {"key": record["idempotency_key"]}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != record["request_digest"]:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            return {"uid": existing["uid"]}
+        # This method is also used by controlled legacy import tests.  Public
+        # provision paths always provide the real server-derived values; the
+        # import fallback remains database-validated and cannot be reproduced
+        # by direct SQL.
+        legacy_digest = record.get("request_digest")
+        values = {
+            "approval_ref": f"legacy-import:{record['uid']}", "approval_digest": legacy_digest,
+            "purpose": "legacy_import", "environment": "test", "target_capability": {}, **record,
+        }
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_grants
+          (uid,policy_uid,subject_uid,asset_uid,provider,idempotency_key,request_digest,expires_at,status,reason_code,current_version,created_by,approval_ref,approval_digest,purpose,environment,target_capability)
+          VALUES(CAST(:uid AS uuid),CAST(:policy_uid AS uuid),CAST(:subject_uid AS uuid),CAST(:asset_uid AS uuid),:provider,:idempotency_key,:request_digest,CAST(:expires_at AS timestamptz),:status,:reason_code,:current_version,CAST(:created_by AS uuid),:approval_ref,:approval_digest,:purpose,:environment,CAST(:target_capability AS jsonb))
+          RETURNING uid::text,status,current_version"""), {**values, "target_capability": None if values["target_capability"] is None else _json(values["target_capability"])}).mappings().one()
+        return _row(row)
+
+    def create_grant(self, record):
+        # Service-created grants retain closed-schema provenance as an idempotency input.
+        return self.enqueue_grant({
+            **record, "idempotency_key": f"grant:{record['uid']}", "request_digest": record["decision_digest"],
+        })
+
+    def get_grant(self, uid):
+        row = self.session.execute(text("""SELECT uid::text,policy_uid::text,subject_uid::text,asset_uid::text,provider,expires_at,status,reason_code,current_version,request_digest AS decision_digest,
+          approval_ref,approval_digest,purpose,environment,target_capability
+          FROM public.trusted_delivery_grants WHERE uid=CAST(:uid AS uuid)"""), {"uid": uid}).mappings().one_or_none()
+        value = _row(row)
+        if value and value.get("target_capability") is not None:
+            value["target_capability"] = dict(value["target_capability"])
+        return value
+
+    def enqueue_delivery(self, grant_uid, idempotency_key, request_digest, operation="provision"):
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("enqueue_delivery", {
+                "uid": str(uuid.uuid4()), "grant_uid": grant_uid,
+                "idempotency_key": idempotency_key, "request_digest": request_digest,
+                "operation": operation,
+            })
+        existing = self.session.execute(text("SELECT uid::text,request_digest FROM public.trusted_delivery_deliveries WHERE idempotency_key=:key"), {"key": idempotency_key}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != request_digest:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            return {"uid": existing["uid"]}
+        if operation == "provision":
+            # The partial unique index is a last line of defence.  Taking a
+            # per-grant transaction advisory lock first lets different HTTP
+            # keys converge on the same intent without making a second worker
+            # guess whether an IntegrityError represented a replay or a
+            # conflicting entitlement.
+            self.session.execute(text("SELECT pg_advisory_xact_lock(hashtextextended(CAST(:grant AS text),494))"), {"grant": grant_uid})
+            existing = self.session.execute(text("""SELECT uid::text,request_digest
+              FROM public.trusted_delivery_deliveries
+              WHERE grant_uid=CAST(:grant AS uuid) AND operation='provision' FOR UPDATE"""), {"grant": grant_uid}).mappings().one_or_none()
+            if existing:
+                if existing["request_digest"] != request_digest:
+                    raise RuntimeError("trusted delivery idempotency conflict")
+                return {"uid": existing["uid"]}
+        uid = str(uuid.uuid4())
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_deliveries(uid,grant_uid,idempotency_key,request_digest,status,operation)
+          VALUES(CAST(:uid AS uuid),CAST(:grant_uid AS uuid),:key,:digest,'pending',:operation) RETURNING uid::text,status,lease_fence"""), {"uid": uid, "grant_uid": grant_uid, "key": idempotency_key, "digest": request_digest, "operation": operation}).mappings().one()
+        return _row(row)
+
+    def claim_delivery(self, uid, worker):
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("claim_delivery", {"uid": uid, "worker": worker})
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_deliveries SET status='processing',lease_owner=:worker,
+          lease_fence=lease_fence+1,lease_expires_at=clock_timestamp()+interval '300 seconds',updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND ((status='pending' AND next_attempt_at<=clock_timestamp()) OR (status='processing' AND lease_expires_at<=clock_timestamp()))
+          RETURNING uid::text,grant_uid::text,status,lease_fence,lease_owner"""), {"uid": uid, "worker": worker}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery lease unavailable")
+        return _row(row)
+
+    def complete_delivery(self, uid, worker, fence, status, receipt_code, response_digest, diff_digest):
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("complete_delivery", {
+                "uid": uid, "worker": worker, "fence": fence, "status": status,
+                "receipt_uid": str(uuid.uuid4()), "receipt_code": receipt_code,
+                "response_digest": response_digest, "diff_digest": diff_digest,
+            })
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_deliveries SET status=:status,lease_owner=NULL,lease_expires_at=NULL,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND status='processing' AND lease_owner=:worker AND lease_fence=:fence AND lease_expires_at>clock_timestamp()
+          RETURNING uid::text,grant_uid::text,request_digest,status"""), {"uid": uid, "worker": worker, "fence": int(fence), "status": status}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery lease conflict")
+        self.session.execute(text("""INSERT INTO public.trusted_delivery_receipts(uid,delivery_uid,grant_uid,status,receipt_code,request_digest,response_digest,diff_digest)
+          VALUES(CAST(:uid AS uuid),CAST(:delivery_uid AS uuid),CAST(:grant_uid AS uuid),:status,:receipt_code,:request_digest,:response_digest,:diff_digest)"""), {"uid": str(uuid.uuid4()), "delivery_uid": row["uid"], "grant_uid": row["grant_uid"], "status": status, "receipt_code": receipt_code, "request_digest": row["request_digest"], "response_digest": response_digest, "diff_digest": diff_digest})
+        return {"uid": row["uid"], "status": row["status"]}
+
+    def record_provision_receipt(self, grant_uid, idempotency_key, request_digest, receipt):
+        key = f"provision:{idempotency_key}"
+        delivery = self.enqueue_delivery(grant_uid, key, request_digest, operation="provision")
+        existing = self.session.execute(text("""SELECT status,receipt_code,response_digest,diff_digest FROM public.trusted_delivery_receipts
+          WHERE delivery_uid=CAST(:uid AS uuid)"""), {"uid": delivery["uid"]}).mappings().one_or_none()
+        if existing:
+            return {"grant_uid": grant_uid, "status": existing["status"], "receipt_code": existing["receipt_code"], "response_digest": existing["response_digest"], "diff_digest": existing["diff_digest"]}
+        claimed = self.claim_delivery(delivery["uid"], "provision-sync")
+        self.complete_delivery(delivery["uid"], "provision-sync", claimed["lease_fence"], "applied", receipt["receipt_code"], receipt["response_digest"], receipt["diff_digest"])
+        return receipt
+
+    def prepare_provision_operation(self, grant_uid, idempotency_key, request_digest):
+        """Persist the outbox intent before any provider adapter is invoked.
+
+        The API boundary owns ordinary commits, but external I/O is different:
+        an adapter must never run for an intent that can still be rolled back.
+        This deliberately commits only the durable intent and leaves completion
+        for the receipt path.  A retry sees the same delivery/receipt.
+        """
+        delivery = self.enqueue_delivery(
+            grant_uid, f"provision:{idempotency_key}", request_digest, operation="provision"
+        )
+        existing = self.session.execute(text("""SELECT status,receipt_code,response_digest,diff_digest
+          FROM public.trusted_delivery_receipts WHERE delivery_uid=CAST(:uid AS uuid)"""), {
+            "uid": delivery["uid"],
+        }).mappings().one_or_none()
+        self.session.commit()
+        if existing:
+            return {"delivery_uid": delivery["uid"], "completed": True, "receipt": {
+                "grant_uid": grant_uid, "status": existing["status"],
+                "receipt_code": existing["receipt_code"],
+                "response_digest": existing["response_digest"], "diff_digest": existing["diff_digest"],
+            }}
+        return {"delivery_uid": delivery["uid"], "completed": False}
+
+    def claim_provision_operation(self, delivery_uid, worker_id):
+        """Lease one persisted provision intent using PostgreSQL's clock.
+
+        This is deliberately separate from ``prepare_provision_operation``:
+        intent durability is committed before any process is entitled to call
+        an external provider, while a crashed worker can be replaced after its
+        database-time lease expires.
+        """
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("claim_provision", {
+                "uid": delivery_uid, "worker": worker_id,
+            })
+        completed = self.session.execute(text("""SELECT r.status,r.receipt_code,r.response_digest,r.diff_digest
+          FROM public.trusted_delivery_deliveries d
+          JOIN public.trusted_delivery_receipts r ON r.delivery_uid=d.uid
+          WHERE d.uid=CAST(:uid AS uuid) AND d.operation='provision'"""), {"uid": delivery_uid}).mappings().one_or_none()
+        if completed:
+            return {"state": "completed", "receipt": _row(completed)}
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_deliveries
+          SET status='processing',lease_owner=:worker,lease_fence=lease_fence+1,
+              lease_expires_at=clock_timestamp()+interval '300 seconds',updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND operation='provision' AND (
+              (status='pending' AND next_attempt_at<=clock_timestamp()) OR
+              (status='processing' AND lease_expires_at<=clock_timestamp())
+          )
+          RETURNING uid::text,grant_uid::text,lease_fence,lease_owner,lease_expires_at"""), {
+            "uid": delivery_uid, "worker": worker_id,
+        }).mappings().one_or_none()
+        return ({"state": "claimed", **_row(row)} if row else {"state": "leased"})
+
+    def fail_provision_operation(self, delivery_uid, worker_id, fence):
+        """Release only the still-current provision lease for a safe retry."""
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("fail_provision", {
+                "uid": delivery_uid, "worker": worker_id, "fence": int(fence),
+            })
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_deliveries
+          SET status='pending',lease_owner=NULL,lease_expires_at=NULL,
+              next_attempt_at=clock_timestamp(),updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND operation='provision' AND status='processing'
+            AND lease_owner=:worker AND lease_fence=:fence AND lease_expires_at>clock_timestamp()
+          RETURNING uid::text"""), {
+            "uid": delivery_uid, "worker": worker_id, "fence": int(fence),
+        }).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery lease conflict")
+        return _row(row)
+
+    def complete_provision_operation(self, grant_uid, delivery_uid, worker_id, fence, receipt):
+        """Atomically append a receipt, complete delivery, and mark its grant."""
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("complete_provision", {
+                "grant_uid": grant_uid, "uid": delivery_uid, "worker": worker_id,
+                "fence": int(fence), "receipt_uid": str(uuid.uuid4()),
+                "receipt_code": receipt["receipt_code"],
+                "response_digest": receipt["response_digest"],
+                "diff_digest": receipt["diff_digest"],
+            })
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_deliveries
+          SET status='applied',lease_owner=NULL,lease_expires_at=NULL,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND grant_uid=CAST(:grant AS uuid)
+            AND operation='provision' AND status='processing' AND lease_owner=:worker
+            AND lease_fence=:fence AND lease_expires_at>clock_timestamp()
+          RETURNING uid::text,grant_uid::text,request_digest,status"""), {
+            "uid": delivery_uid, "grant": grant_uid, "worker": worker_id, "fence": int(fence),
+        }).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery lease conflict")
+        self.session.execute(text("""INSERT INTO public.trusted_delivery_receipts
+          (uid,delivery_uid,grant_uid,status,receipt_code,request_digest,response_digest,diff_digest)
+          VALUES(CAST(:uid AS uuid),CAST(:delivery AS uuid),CAST(:grant AS uuid),'applied',:code,:request,:response,:diff)"""), {
+            "uid": str(uuid.uuid4()), "delivery": row["uid"], "grant": row["grant_uid"],
+            "code": receipt["receipt_code"], "request": row["request_digest"],
+            "response": receipt["response_digest"], "diff": receipt["diff_digest"],
+        })
+        grant = self.session.execute(text("""UPDATE public.trusted_delivery_grants
+          SET reason_code='provision_applied',current_version=current_version+1,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND status='active'
+          RETURNING uid::text,status,reason_code,current_version"""), {"uid": grant_uid}).mappings().one_or_none()
+        if grant is None:
+            raise RuntimeError("trusted delivery grant version conflict")
+        return {
+            "delivery_uid": row["uid"], "status": "applied",
+            "grant_uid": grant["uid"], "grant_status": grant["status"],
+            "reason_code": grant["reason_code"], "current_version": grant["current_version"],
+        }
+
+    def get_provision_receipt(self, grant_uid, idempotency_key):
+        row = self.session.execute(text("""SELECT r.status,r.receipt_code,r.response_digest,r.diff_digest
+          FROM public.trusted_delivery_deliveries d JOIN public.trusted_delivery_receipts r ON r.delivery_uid=d.uid
+          WHERE d.grant_uid=CAST(:grant_uid AS uuid) AND d.idempotency_key=:key"""), {"grant_uid": grant_uid, "key": f"provision:{idempotency_key}"}).mappings().one_or_none()
+        if row is None:
+            return None
+        return {"grant_uid": grant_uid, "status": row["status"], "receipt_code": row["receipt_code"], "response_digest": row["response_digest"], "diff_digest": row["diff_digest"]}
+
+    def complete_reclaim(self, grant_uid, idempotency_key, request_digest, receipt):
+        grant = self.session.execute(text("SELECT uid::text,status,current_version FROM public.trusted_delivery_grants WHERE uid=CAST(:uid AS uuid) FOR UPDATE"), {"uid": grant_uid}).mappings().one_or_none()
+        if grant is None:
+            raise LookupError("trusted delivery grant was not found")
+        if grant["status"] == "reclaimed":
+            return {"uid": grant_uid, "status": "reclaimed", "current_version": grant["current_version"]}
+        key = f"reclaim:{idempotency_key}"
+        delivery = self.enqueue_delivery(grant_uid, key, request_digest, operation="revoke")
+        existing = self.session.execute(text("SELECT status FROM public.trusted_delivery_receipts WHERE delivery_uid=CAST(:uid AS uuid)"), {"uid": delivery["uid"]}).mappings().one_or_none()
+        if existing:
+            row = self.session.execute(text("SELECT uid::text,status,current_version FROM public.trusted_delivery_grants WHERE uid=CAST(:uid AS uuid)"), {"uid": grant_uid}).mappings().one()
+            return _row(row)
+        claimed = self.claim_delivery(delivery["uid"], "reclaim-sync")
+        # The provider receipt is append-only evidence.  Store it before the
+        # final grant transition; both writes share the caller transaction, so
+        # a later conflict rolls the receipt back rather than exposing a claim
+        # that was never completed.
+        self.complete_delivery(delivery["uid"], "reclaim-sync", claimed["lease_fence"], "reclaimed", receipt["receipt_code"], receipt["response_digest"], receipt["diff_digest"])
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_grants SET status='reclaimed',reason_code='expired_reclaimed',
+          current_version=current_version+1,updated_at=clock_timestamp() WHERE uid=CAST(:uid AS uuid) AND status='active'
+          RETURNING uid::text,status,current_version"""), {"uid": grant_uid}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery version conflict")
+        return _row(row)
+
+    def claim_reclaim_operation(self, grant_uid, idempotency_key, request_digest, worker_id):
+        """Durably lease the one revoke outbox operation before provider I/O."""
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_reclaim_write("claim_reclaim", {
+                "grant_uid": grant_uid, "idempotency_key": idempotency_key,
+                "request_digest": request_digest, "worker": worker_id,
+            })
+        grant = self.session.execute(text("""SELECT uid::text,asset_uid::text,status,expires_at
+          FROM public.trusted_delivery_grants WHERE uid=CAST(:uid AS uuid) FOR UPDATE"""), {"uid": grant_uid}).mappings().one_or_none()
+        if grant is None:
+            raise LookupError("trusted delivery grant was not found")
+        self.session.execute(text("SELECT pg_advisory_xact_lock(hashtextextended(CAST(:asset AS text),485))"), {"asset": grant["asset_uid"]})
+        if grant["status"] == "reclaimed":
+            return {"state": "completed", "grant_uid": grant_uid}
+        if grant["status"] != "active" or grant["expires_at"] > self.session.execute(text("SELECT clock_timestamp()")).scalar_one():
+            raise ValueError("trusted delivery grant is not reclaimable")
+        if self.active_hold_for_asset(grant["asset_uid"]):
+            raise PermissionError("active legal hold blocks reclaim")
+        row = self.session.execute(text("""SELECT uid::text,request_digest,status,lease_owner,lease_fence,lease_expires_at
+          FROM public.trusted_delivery_deliveries WHERE grant_uid=CAST(:grant AS uuid) AND operation='revoke' FOR UPDATE"""), {"grant": grant_uid}).mappings().one_or_none()
+        if row is None:
+            uid = str(uuid.uuid4())
+            row = self.session.execute(text("""INSERT INTO public.trusted_delivery_deliveries
+              (uid,grant_uid,idempotency_key,request_digest,status,operation,lease_owner,lease_fence,lease_expires_at)
+              VALUES(CAST(:uid AS uuid),CAST(:grant AS uuid),:key,:digest,'processing','revoke',:worker,1,clock_timestamp()+interval '300 seconds')
+              RETURNING uid::text,lease_fence"""), {"uid": uid, "grant": grant_uid, "key": f"reclaim:{idempotency_key}", "digest": request_digest, "worker": worker_id}).mappings().one()
+            return {"state": "claimed", "delivery_uid": row["uid"], "lease_fence": row["lease_fence"]}
+        if row["request_digest"] != request_digest:
+            raise RuntimeError("trusted delivery idempotency conflict")
+        if row["status"] == "reclaimed":
+            return {"state": "completed", "grant_uid": grant_uid}
+        claimed = self.session.execute(text("""UPDATE public.trusted_delivery_deliveries SET status='processing',lease_owner=:worker,
+          lease_fence=lease_fence+1,lease_expires_at=clock_timestamp()+interval '300 seconds',updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND (status='pending' OR (status='processing' AND lease_expires_at<=clock_timestamp()))
+          RETURNING uid::text,lease_fence"""), {"uid": row["uid"], "worker": worker_id}).mappings().one_or_none()
+        return {"state": "claimed", "delivery_uid": claimed["uid"], "lease_fence": claimed["lease_fence"]} if claimed else {"state": "leased"}
+
+    def fail_reclaim_operation(self, delivery_uid, worker_id, fence):
+        if self._runtime_restricted():
+            self._trusted_delivery_runtime_reclaim_write("fail_reclaim", {
+                "delivery_uid": delivery_uid, "worker": worker_id, "fence": fence,
+            })
+            return
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_deliveries SET status='pending',lease_owner=NULL,
+          lease_expires_at=NULL,next_attempt_at=clock_timestamp()+interval '5 seconds',updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND status='processing' AND lease_owner=:worker AND lease_fence=:fence AND lease_expires_at>clock_timestamp()
+          RETURNING uid::text"""), {"uid": delivery_uid, "worker": worker_id, "fence": int(fence)}).one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery lease conflict")
+
+    def complete_claimed_reclaim(self, grant_uid, delivery_uid, worker_id, fence, receipt):
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_reclaim_write("complete_reclaim", {
+                "grant_uid": grant_uid, "delivery_uid": delivery_uid,
+                "worker": worker_id, "fence": fence, "receipt_uid": str(uuid.uuid4()),
+                "receipt_code": receipt["receipt_code"],
+                "response_digest": receipt["response_digest"],
+                "diff_digest": receipt["diff_digest"],
+            })
+        self.complete_delivery(delivery_uid, worker_id, fence, "reclaimed", receipt["receipt_code"], receipt["response_digest"], receipt["diff_digest"])
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_grants SET status='reclaimed',reason_code='expired_reclaimed',current_version=current_version+1,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND status='active' RETURNING uid::text,status,current_version"""), {"uid": grant_uid}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery version conflict")
+        return _row(row)
+
+    def create_legal_hold(self, record):
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("create_hold", {
+                **record, "hold_operation": record["operation"],
+            })
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_legal_holds
+          (uid,asset_uid,operation,status,approver_ref_one,approver_ref_two,evidence_digest,created_by,current_version)
+          VALUES(CAST(:uid AS uuid),CAST(:asset_uid AS uuid),:operation,:status,:approver_ref_one,:approver_ref_two,:evidence_digest,CAST(:created_by AS uuid),:current_version)
+          RETURNING uid::text,status"""), record).mappings().one()
+        return _row(row)
+
+    def release_legal_hold(self, hold_uid, idempotency_key, request_digest, actor_uid, approval_ref, approval_digest):
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("release_hold", {
+                "uid": str(uuid.uuid4()), "hold_uid": hold_uid,
+                "idempotency_key": idempotency_key, "request_digest": request_digest,
+                "actor_uid": actor_uid, "approval_ref": approval_ref,
+                "approval_digest": approval_digest,
+            })
+        existing = self.session.execute(text("SELECT hold_uid::text,request_digest FROM public.trusted_delivery_hold_releases WHERE idempotency_key=:key"), {"key": idempotency_key}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != request_digest:
+                raise RuntimeError("trusted delivery idempotency conflict")
+            row = self.session.execute(text("SELECT uid::text,status,current_version FROM public.trusted_delivery_legal_holds WHERE uid=CAST(:uid AS uuid)"), {"uid": existing["hold_uid"]}).mappings().one()
+            return _row(row)
+        hold = self.session.execute(text("""SELECT uid::text,status,current_version,created_by::text,approver_ref_one,approver_ref_two
+          FROM public.trusted_delivery_legal_holds WHERE uid=CAST(:uid AS uuid) FOR UPDATE"""), {"uid": hold_uid}).mappings().one_or_none()
+        if hold is None:
+            raise LookupError("trusted delivery legal hold was not found")
+        if hold["created_by"] == actor_uid or approval_ref in {hold["approver_ref_one"], hold["approver_ref_two"]}:
+            raise PermissionError("legal hold release requires an independent approval")
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_legal_holds SET status='released',current_version=current_version+1
+          WHERE uid=CAST(:uid AS uuid) AND status='active' RETURNING uid::text,status,current_version"""), {"uid": hold_uid}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery legal hold is not active")
+        self.session.execute(text("""INSERT INTO public.trusted_delivery_hold_releases
+          (uid,hold_uid,idempotency_key,request_digest,approval_ref,approval_digest,actor_uid)
+          VALUES(CAST(:uid AS uuid),CAST(:hold_uid AS uuid),:key,:digest,:approval_ref,:approval_digest,CAST(:actor_uid AS uuid))"""), {"uid": str(uuid.uuid4()), "hold_uid": hold_uid, "key": idempotency_key, "digest": request_digest, "approval_ref": approval_ref, "approval_digest": approval_digest, "actor_uid": actor_uid})
+        return _row(row)
+
+    def active_hold_for_asset(self, asset_uid):
+        row = self.session.execute(text("SELECT uid::text,asset_uid::text,status FROM public.trusted_delivery_legal_holds WHERE asset_uid=CAST(:asset AS uuid) AND status='active'"), {"asset": asset_uid}).mappings().one_or_none()
+        return _row(row)
+
+    def grants_due_for_revoke(self, instant: datetime):
+        rows = self.session.execute(text("SELECT uid::text,asset_uid::text,expires_at,status,current_version FROM public.trusted_delivery_grants WHERE status='active' AND expires_at<=CAST(:instant AS timestamptz)"), {"instant": instant}).mappings()
+        return [_row(row) for row in rows]
+
+    def update_grant_status(self, uid, expected_version, status, reason_code):
+        if self._runtime_restricted():
+            return self._trusted_delivery_runtime_write("update_grant_status", {
+                "uid": uid, "expected_version": expected_version, "status": status,
+                "reason_code": reason_code,
+            })
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_grants SET status=:status,reason_code=:reason,current_version=current_version+1,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND current_version=:expected RETURNING uid::text,status,reason_code,current_version"""), {"uid": uid, "expected": int(expected_version), "status": status, "reason": reason_code}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("trusted delivery version conflict")
+        return _row(row)
+
+    def expire_grants(self, instant, actor_uid):
+        expired = []
+        for grant in self.grants_due_for_revoke(instant):
+            if self.active_hold_for_asset(grant["asset_uid"]):
+                continue
+            expired.append(self.update_grant_status(grant["uid"], grant["current_version"], "revoked", "expired"))
+        return expired
+
+    def add_evidence(self, record):
+        if self._runtime_restricted():
+            self._trusted_delivery_runtime_write("subscription_evidence", {
+                "uid": record["uid"], "action": record["action"],
+                "actor_uid": record["actor_uid"], "safe_detail": record["safe_detail"],
+            })
+            return
+        self.session.execute(text("INSERT INTO public.trusted_delivery_evidence(uid,action,actor_uid,safe_detail) VALUES(CAST(:uid AS uuid),:action,CAST(:actor AS uuid),CAST(:detail AS jsonb))"), {"uid": record["uid"], "action": record["action"], "actor": record["actor_uid"], "detail": _json(record["safe_detail"])})

+ 203 - 0
app/core/system/trusted_delivery_subscription_repository.py

@@ -0,0 +1,203 @@
+"""PostgreSQL storage for fenced trusted-delivery subscription deliveries."""
+
+from __future__ import annotations
+
+import json
+from typing import Any
+
+from sqlalchemy import text
+
+
+def _json(value: Any) -> str:
+    return json.dumps(value, sort_keys=True, separators=(",", ":"))
+
+
+def _row(value) -> dict[str, Any] | None:
+    if value is None:
+        return None
+    result = dict(value)
+    for key, item in tuple(result.items()):
+        if hasattr(item, "isoformat"):
+            result[key] = item.isoformat()
+        elif hasattr(item, "hex"):
+            result[key] = str(item)
+    if result.get("trigger") is not None:
+        result["trigger"] = dict(result["trigger"])
+    return result
+
+
+class SqlAlchemyTrustedDeliverySubscriptionRepository:
+    def __init__(self, session):
+        self.session = session
+
+    def _trusted_delivery_gateway(self, operation: str, payload: dict[str, Any]) -> dict[str, Any] | None:
+        direct_dml = self.session.execute(text("""
+            SELECT has_table_privilege(current_user,
+              'public.trusted_delivery_subscriptions', 'INSERT')
+        """)).scalar_one()
+        if direct_dml:
+            return None
+        result = self.session.execute(text("""
+            SELECT public.trusted_delivery_write(:operation, CAST(:payload AS jsonb))
+        """), {"operation": operation, "payload": _json(payload)}).scalar_one()
+        return dict(result)
+
+    def _trusted_delivery_runtime_write(self, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
+        result = self.session.execute(text("""
+            SELECT public.trusted_delivery_runtime_write(:operation, CAST(:payload AS jsonb))
+        """), {"operation": operation, "payload": _json(payload)}).scalar_one()
+        return dict(result)
+
+    def _runtime_restricted(self) -> bool:
+        return not self.session.execute(text("""
+            SELECT has_table_privilege(current_user,
+              'public.trusted_delivery_subscriptions', 'INSERT')
+        """)).scalar_one()
+
+    def users_available(self, values):
+        values = sorted(set(values))
+        if not values:
+            return set()
+        return set(self.session.execute(text("SELECT id::text FROM public.users WHERE status='active' AND id=ANY(CAST(:values AS uuid[]))"), {"values": values}).scalars())
+
+    def create_subscription(self, record):
+        gateway = self._trusted_delivery_gateway("create_subscription", {
+            key: record[key] for key in (
+                "uid", "grant_uid", "asset_uid", "trigger", "purpose", "expires_at",
+                "idempotency_key", "request_digest", "status", "current_version", "created_by",
+            ) if key in record
+        })
+        if gateway is not None:
+            return _row(gateway)
+        existing = self.session.execute(text("SELECT uid::text,request_digest FROM public.trusted_delivery_subscriptions WHERE idempotency_key=:key"), {"key": record["idempotency_key"]}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != record["request_digest"]:
+                raise RuntimeError("subscription idempotency conflict")
+            return _row(self.session.execute(text("SELECT uid::text,grant_uid::text,asset_uid::text,trigger,purpose,expires_at,status,current_version FROM public.trusted_delivery_subscriptions WHERE uid=CAST(:uid AS uuid)"), {"uid": existing["uid"]}).mappings().one())
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_subscriptions
+          (uid,grant_uid,asset_uid,trigger,purpose,expires_at,idempotency_key,request_digest,status,current_version,created_by)
+          VALUES(CAST(:uid AS uuid),CAST(:grant_uid AS uuid),CAST(:asset_uid AS uuid),CAST(:trigger AS jsonb),:purpose,CAST(:expires_at AS timestamptz),:idempotency_key,:request_digest,:status,:current_version,CAST(:created_by AS uuid))
+          RETURNING uid::text,grant_uid::text,asset_uid::text,trigger,purpose,expires_at,status,current_version"""), {**record, "trigger": _json(record["trigger"])}).mappings().one()
+        return _row(row)
+
+    def get_grant_for_subscription(self, uid):
+        row = self.session.execute(text("""SELECT uid::text,asset_uid::text,purpose,expires_at,status
+          FROM public.trusted_delivery_grants WHERE uid=CAST(:uid AS uuid) FOR KEY SHARE"""), {"uid": uid}).mappings().one_or_none()
+        return _row(row)
+
+    def transition_subscription(self, uid, expected_status, new_status):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("transition_subscription", {
+                "uid": uid, "expected_status": sorted(expected_status), "status": new_status,
+            }))
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_subscriptions SET status=:target,current_version=current_version+1,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND status=ANY(CAST(:expected AS varchar[]))
+          RETURNING uid::text,grant_uid::text,asset_uid::text,trigger,purpose,expires_at,status,current_version"""), {"uid": uid, "expected": sorted(expected_status), "target": new_status}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("subscription transition conflict")
+        return _row(row)
+
+    def enqueue_subscription_delivery(self, record):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("enqueue_subscription_delivery", record))
+        existing = self.session.execute(text("SELECT uid::text,request_digest FROM public.trusted_delivery_subscription_deliveries WHERE idempotency_key=:key"), {"key": record["idempotency_key"]}).mappings().one_or_none()
+        if existing:
+            if existing["request_digest"] != record["request_digest"]:
+                raise RuntimeError("subscription idempotency conflict")
+            return _row(self.session.execute(text("SELECT uid::text,subscription_uid::text,trigger_ref,event_digest,status,attempt_count,lease_fence FROM public.trusted_delivery_subscription_deliveries WHERE uid=CAST(:uid AS uuid)"), {"uid": existing["uid"]}).mappings().one())
+        state = self.session.execute(text("SELECT status FROM public.trusted_delivery_subscriptions WHERE uid=CAST(:uid AS uuid) FOR KEY SHARE"), {"uid": record["subscription_uid"]}).scalar_one_or_none()
+        if state != "active":
+            raise RuntimeError("subscription is not active")
+        row = self.session.execute(text("""INSERT INTO public.trusted_delivery_subscription_deliveries
+          (uid,subscription_uid,trigger_ref,event_digest,idempotency_key,request_digest,status,attempt_count,lease_fence)
+          VALUES(CAST(:uid AS uuid),CAST(:subscription_uid AS uuid),:trigger_ref,:event_digest,:idempotency_key,:request_digest,:status,:attempt_count,:lease_fence)
+          RETURNING uid::text,subscription_uid::text,trigger_ref,event_digest,status,attempt_count,lease_fence"""), record).mappings().one()
+        return _row(row)
+
+    def claim_subscription_delivery(self, uid, worker):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("claim_subscription_delivery", {
+                "uid": uid, "worker": worker,
+            }))
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_subscription_deliveries SET status='processing',lease_owner=:worker,lease_fence=lease_fence+1,
+          lease_expires_at=clock_timestamp()+interval '300 seconds',updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND ((status='pending' AND next_attempt_at<=clock_timestamp()) OR (status='processing' AND lease_expires_at<=clock_timestamp()))
+          RETURNING uid::text,subscription_uid::text,status,attempt_count,lease_owner,lease_fence,lease_expires_at"""), {"uid": uid, "worker": worker}).mappings().one_or_none()
+        return _row(row)
+
+    def record_subscription_attempt(self, uid, worker, fence, delivered):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("subscription_attempt", {
+                "uid": uid, "worker": worker, "fence": fence, "delivered": delivered,
+            }))
+        if delivered:
+            statement = """UPDATE public.trusted_delivery_subscription_deliveries SET status='delivered',lease_owner=NULL,lease_expires_at=NULL,updated_at=clock_timestamp()
+              WHERE uid=CAST(:uid AS uuid) AND status='processing' AND lease_owner=:worker AND lease_fence=:fence AND lease_expires_at>clock_timestamp()
+              RETURNING uid::text,subscription_uid::text,status,attempt_count,lease_fence"""
+            parameters = {"uid": uid, "worker": worker, "fence": fence}
+        else:
+            statement = """UPDATE public.trusted_delivery_subscription_deliveries SET attempt_count=attempt_count+1,lease_owner=NULL,lease_expires_at=NULL,
+              status=CASE WHEN attempt_count+1>=3 THEN 'dead_letter' ELSE 'pending' END,
+              next_attempt_at=clock_timestamp()+(LEAST(3600,60*(attempt_count+1))*interval '1 second'),last_reason_code='provider_unavailable',updated_at=clock_timestamp()
+              WHERE uid=CAST(:uid AS uuid) AND status='processing' AND lease_owner=:worker AND lease_fence=:fence AND lease_expires_at>clock_timestamp()
+              RETURNING uid::text,subscription_uid::text,status,attempt_count,lease_fence"""
+            parameters = {"uid": uid, "worker": worker, "fence": fence}
+        return _row(self.session.execute(text(statement), parameters).mappings().one_or_none())
+
+    def compensate_subscription_delivery(self, uid, reason_code, receipt_code):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("compensate_subscription_delivery", {
+                "uid": uid, "reason_code": reason_code, "receipt_code": receipt_code,
+            }))
+        current = self.session.execute(text("SELECT uid::text,status,compensation_reason_code,compensation_receipt_code FROM public.trusted_delivery_subscription_deliveries WHERE uid=CAST(:uid AS uuid) FOR UPDATE"), {"uid": uid}).mappings().one_or_none()
+        if current is None or current["status"] not in {"dead_letter", "compensated"}:
+            raise RuntimeError("subscription delivery is not compensable")
+        if current["status"] == "compensated":
+            if (current["compensation_reason_code"], current["compensation_receipt_code"]) != (reason_code, receipt_code):
+                raise RuntimeError("subscription compensation conflict")
+            return _row(current)
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_subscription_deliveries SET status='compensated',compensation_reason_code=:reason,
+          compensation_receipt_code=:receipt,updated_at=clock_timestamp() WHERE uid=CAST(:uid AS uuid) AND status='dead_letter'
+          RETURNING uid::text,subscription_uid::text,status,attempt_count,compensation_reason_code,compensation_receipt_code"""), {"uid": uid, "reason": reason_code, "receipt": receipt_code}).mappings().one()
+        return _row(row)
+
+    def add_subscription_evidence(self, record):
+        if self._runtime_restricted():
+            self._trusted_delivery_runtime_write("subscription_evidence", record)
+            return
+        self.session.execute(text("INSERT INTO public.trusted_delivery_evidence(uid,action,actor_uid,safe_detail) VALUES(CAST(:uid AS uuid),:action,CAST(:actor_uid AS uuid),CAST(:detail AS jsonb))"), {**record, "detail": _json(record["safe_detail"])})
+
+    def incident_exists(self, incident_uid):
+        return self.session.execute(text("SELECT 1 FROM public.data_incidents WHERE uid=CAST(:uid AS uuid)"), {"uid": incident_uid}).scalar_one_or_none() is not None
+
+    def link_anomaly_incident(self, incident_uid, asset_uid, label):
+        if self._runtime_restricted():
+            self.session.execute(text("""
+                SELECT public.trusted_delivery_runtime_incident_impact(
+                  CAST(:incident_uid AS uuid), CAST(:asset_uid AS uuid), :label
+                )
+            """), {"incident_uid": incident_uid, "asset_uid": asset_uid, "label": label})
+            return
+        self.session.execute(text("""INSERT INTO public.data_incident_impacts(incident_uid,target_type,target_uid,label)
+          VALUES(CAST(:incident_uid AS uuid),'asset',:asset_uid,:label)
+          ON CONFLICT(incident_uid,target_type,target_uid) DO NOTHING"""), {"incident_uid": incident_uid, "asset_uid": asset_uid, "label": label})
+
+    def subscriptions_due_for_reclaim(self, instant):
+        rows = self.session.execute(text("""SELECT s.uid::text,s.grant_uid::text,s.asset_uid::text,s.status,s.current_version
+          FROM public.trusted_delivery_subscriptions s
+          WHERE s.status IN ('active','expired') AND s.expires_at<=CAST(:instant AS timestamptz)
+          AND NOT EXISTS(SELECT 1 FROM public.trusted_delivery_legal_holds h WHERE h.asset_uid=s.asset_uid AND h.status='active')
+          ORDER BY s.expires_at,s.uid"""), {"instant": instant}).mappings()
+        return [_row(row) for row in rows]
+
+    def mark_subscription_reclaimed(self, uid, expected_version):
+        if self._runtime_restricted():
+            return _row(self._trusted_delivery_runtime_write("mark_subscription_reclaimed", {
+                "uid": uid, "expected_version": expected_version,
+            }))
+        row = self.session.execute(text("""UPDATE public.trusted_delivery_subscriptions SET status='reclaimed',current_version=current_version+1,updated_at=clock_timestamp()
+          WHERE uid=CAST(:uid AS uuid) AND status='expired' AND current_version=:version
+          RETURNING uid::text,grant_uid::text,asset_uid::text,status,current_version"""), {"uid": uid, "version": expected_version}).mappings().one_or_none()
+        if row is None:
+            raise RuntimeError("subscription reclaim conflict")
+        return _row(row)

+ 255 - 0
app/core/system/trusted_delivery_subscriptions.py

@@ -0,0 +1,255 @@
+"""Closed subscription and delivery controls for trusted data delivery.
+
+The service never carries data rows, target URLs, credentials, or provider
+configuration.  It persists only the minimal delivery control-plane facts and
+safe incident references; P3 production operations remains the incident source
+of truth.
+"""
+
+from __future__ import annotations
+
+import copy
+import hashlib
+import json
+import re
+import uuid
+from collections.abc import Callable
+from datetime import UTC, datetime
+from typing import Any
+
+from app.core.common.identifiers import new_governance_uid
+from app.core.common.timezone_utils import now_china
+
+_SAFE_CODE = re.compile(r"^[A-Za-z][A-Za-z0-9_.:-]{0,159}$")
+_DIGEST = re.compile(r"^[0-9a-f]{64}$")
+_UNSAFE = re.compile(
+    r"(?i)(?:https?://|ftp://|file://|www\.|\b(?:secret|token|password|private[ _-]?key)\b|"
+    r"-----BEGIN|<script|javascript:|;|\bselect\s+.+\bfrom\b)"
+)
+_ANOMALIES = frozenset({"unusual_access", "quota_exceeded", "purpose_breach", "secondary_propagation"})
+
+
+def _uid(value: Any, label: str) -> str:
+    try:
+        return str(uuid.UUID(str(value)))
+    except (TypeError, ValueError, AttributeError) as exc:
+        raise ValueError(f"{label} must be a UUID") from exc
+
+
+def _text(value: Any, label: str, maximum: int = 160) -> str:
+    if not isinstance(value, str) or not value.strip() or len(value.strip()) > maximum:
+        raise ValueError(f"{label} is invalid")
+    result = value.strip()
+    if _UNSAFE.search(result):
+        raise ValueError(f"{label} contains unsafe content")
+    return result
+
+
+def _instant(value: Any, label: str) -> str:
+    try:
+        result = value if isinstance(value, datetime) else datetime.fromisoformat(str(value).replace("Z", "+00:00"))
+    except (TypeError, ValueError) as exc:
+        raise ValueError(f"{label} must be ISO-8601") from exc
+    if result.tzinfo is None:
+        raise ValueError(f"{label} must include a timezone")
+    return result.astimezone(UTC).isoformat()
+
+
+def _digest(value: Any) -> str:
+    return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
+
+
+def _closed(value: Any, allowed: set[str], label: str) -> dict[str, Any]:
+    if not isinstance(value, dict):
+        raise ValueError(f"{label} must be an object")
+    extra = set(value) - allowed
+    if extra:
+        raise ValueError(f"{label} contains unsupported fields")
+    return copy.deepcopy(value)
+
+
+class TrustedDeliverySubscriptionService:
+    """State machine façade; the repository owns DB time and transaction scope."""
+
+    def __init__(
+        self,
+        repository,
+        *,
+        uid_factory: Callable[[], str] = new_governance_uid,
+        now_factory: Callable[[], datetime] = now_china,
+    ):
+        self.repository = repository
+        self.uid_factory = uid_factory
+        self.now_factory = now_factory
+
+    def _actor(self, value: Any) -> str:
+        actor = _uid(value, "actor_uid")
+        if self.repository.users_available({actor}) != {actor}:
+            raise ValueError("trusted delivery actor is unavailable")
+        return actor
+
+    def _evidence(self, action: str, actor_uid: str, detail: dict[str, str]) -> None:
+        self.repository.add_subscription_evidence({
+            "uid": self.uid_factory(), "action": action, "actor_uid": actor_uid,
+            "safe_detail": copy.deepcopy(detail), "created_at": self.now_factory().astimezone(UTC).isoformat(),
+        })
+
+    def _trigger(self, value: Any) -> dict[str, str]:
+        body = _closed(value, {"kind", "schedule_ref", "event_type"}, "subscription trigger")
+        kind = _text(body.get("kind"), "trigger kind", 20)
+        if kind == "schedule":
+            if set(body) != {"kind", "schedule_ref"}:
+                raise ValueError("schedule trigger has unsupported fields")
+            return {"kind": kind, "schedule_ref": _text(body["schedule_ref"], "schedule ref", 120)}
+        if kind == "event":
+            if set(body) != {"kind", "event_type"}:
+                raise ValueError("event trigger has unsupported fields")
+            return {"kind": kind, "event_type": _text(body["event_type"], "event type", 120)}
+        raise ValueError("trigger kind is unsupported")
+
+    def create_subscription(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"grant_uid", "asset_uid", "trigger", "purpose", "expires_at", "idempotency_key"}, "subscription")
+        actor = self._actor(actor_uid)
+        expiry = _instant(body.get("expires_at"), "subscription expiry")
+        if datetime.fromisoformat(expiry) <= self.now_factory().astimezone(UTC):
+            raise ValueError("subscription expiry must be in the future")
+        key = _text(body.get("idempotency_key"), "idempotency key", 160)
+        if not _SAFE_CODE.fullmatch(key):
+            raise ValueError("idempotency key is invalid")
+        trigger = self._trigger(body.get("trigger"))
+        record = {
+            "uid": self.uid_factory(), "grant_uid": _uid(body.get("grant_uid"), "grant_uid"),
+            "asset_uid": _uid(body.get("asset_uid"), "asset_uid"), "trigger": trigger,
+            "purpose": _text(body.get("purpose"), "purpose", 100), "expires_at": expiry,
+            "idempotency_key": key, "status": "draft", "current_version": 1, "created_by": actor,
+        }
+        if not hasattr(self.repository, "get_grant_for_subscription"):
+            raise RuntimeError("durable trusted-delivery grant repository is required")
+        grant = self.repository.get_grant_for_subscription(record["grant_uid"])
+        if not grant or grant.get("status") != "active":
+            raise PermissionError("subscription grant is unavailable")
+        if grant.get("asset_uid") != record["asset_uid"] or grant.get("purpose") != record["purpose"]:
+            raise PermissionError("subscription grant does not bind asset and purpose")
+        if _instant(grant.get("expires_at"), "grant expiry") < record["expires_at"]:
+            raise PermissionError("subscription expiry exceeds grant")
+        record["request_digest"] = _digest({key: record[key] for key in ("grant_uid", "asset_uid", "trigger", "purpose", "expires_at", "idempotency_key")})
+        result = self.repository.create_subscription(record)
+        self._evidence("subscription_created", actor, {"subscription_uid": result["uid"], "trigger_kind": trigger["kind"]})
+        return result
+
+    def _transition(self, uid: Any, *, expected: set[str], target: str, actor_uid: str) -> dict[str, Any]:
+        actor = self._actor(actor_uid)
+        result = self.repository.transition_subscription(_uid(uid, "subscription_uid"), expected, target)
+        self._evidence(f"subscription_{target}", actor, {"subscription_uid": result["uid"], "status": target})
+        return result
+
+    def activate_subscription(self, uid: Any, *, actor_uid: str) -> dict[str, Any]:
+        return self._transition(uid, expected={"draft", "paused"}, target="active", actor_uid=actor_uid)
+
+    def pause_subscription(self, uid: Any, *, actor_uid: str) -> dict[str, Any]:
+        return self._transition(uid, expected={"active"}, target="paused", actor_uid=actor_uid)
+
+    def resume_subscription(self, uid: Any, *, actor_uid: str) -> dict[str, Any]:
+        return self._transition(uid, expected={"paused"}, target="active", actor_uid=actor_uid)
+
+    def terminate_subscription(self, uid: Any, *, actor_uid: str) -> dict[str, Any]:
+        return self._transition(uid, expected={"draft", "active", "paused"}, target="terminated", actor_uid=actor_uid)
+
+    def enqueue_delivery(self, payload: Any, *, actor_uid: str) -> dict[str, Any]:
+        body = _closed(payload, {"subscription_uid", "trigger_ref", "event_digest"}, "subscription delivery")
+        actor = self._actor(actor_uid)
+        event_digest = _text(body.get("event_digest"), "event digest", 64).lower()
+        if not _DIGEST.fullmatch(event_digest):
+            raise ValueError("event digest must be SHA-256")
+        subscription_uid = _uid(body.get("subscription_uid"), "subscription_uid")
+        trigger_ref = _text(body.get("trigger_ref"), "trigger reference", 160)
+        if not _SAFE_CODE.fullmatch(trigger_ref):
+            raise ValueError("trigger reference is invalid")
+        record = {
+            "uid": self.uid_factory(), "subscription_uid": subscription_uid, "trigger_ref": trigger_ref,
+            "event_digest": event_digest, "status": "pending", "attempt_count": 0,
+            "lease_owner": None, "lease_fence": 0,
+        }
+        record["idempotency_key"] = f"{subscription_uid}:{trigger_ref}"
+        record["request_digest"] = _digest({key: record[key] for key in ("subscription_uid", "trigger_ref", "event_digest")})
+        result = self.repository.enqueue_subscription_delivery(record)
+        self._evidence("subscription_delivery_enqueued", actor, {"delivery_uid": result["uid"], "subscription_uid": subscription_uid})
+        return result
+
+    def claim_delivery(self, uid: Any, *, worker_id: str) -> dict[str, Any]:
+        result = self.repository.claim_subscription_delivery(_uid(uid, "delivery_uid"), _text(worker_id, "worker id", 80))
+        if result is None:
+            raise ValueError("subscription delivery is not claimable")
+        return result
+
+    def record_delivery_attempt(self, uid: Any, *, worker_id: str, lease_fence: Any, delivered: Any, actor_uid: str) -> dict[str, Any] | None:
+        actor = self._actor(actor_uid)
+        if not isinstance(lease_fence, int) or lease_fence < 1 or not isinstance(delivered, bool):
+            raise ValueError("subscription delivery attempt is invalid")
+        result = self.repository.record_subscription_attempt(_uid(uid, "delivery_uid"), _text(worker_id, "worker id", 80), lease_fence, delivered)
+        if result is not None:
+            self._evidence("subscription_delivery_delivered" if delivered else "subscription_delivery_failed", actor, {"delivery_uid": result["uid"], "status": result["status"]})
+        return result
+
+    def compensate_dead_letter(self, uid: Any, *, reason_code: Any, receipt_code: Any, actor_uid: str) -> dict[str, Any]:
+        actor = self._actor(actor_uid)
+        reason = _text(reason_code, "compensation reason", 64)
+        receipt = _text(receipt_code, "compensation receipt", 64)
+        if reason not in {"operator_review", "provider_recovered", "duplicate_suppressed"} or not re.fullmatch(r"[A-Z][A-Z0-9_]{0,63}", receipt):
+            raise ValueError("subscription compensation is invalid")
+        result = self.repository.compensate_subscription_delivery(_uid(uid, "delivery_uid"), reason, receipt)
+        self._evidence("subscription_dead_letter_compensated", actor, {"delivery_uid": result["uid"], "receipt_code": receipt})
+        return result
+
+    def report_anomaly(self, payload: Any, *, actor_uid: str) -> dict[str, str]:
+        body = _closed(payload, {"kind", "asset_uid", "subscription_uid", "incident_uid", "summary", "correlation_id"}, "subscription anomaly")
+        actor = self._actor(actor_uid)
+        kind = _text(body.get("kind"), "anomaly kind", 40)
+        if kind not in _ANOMALIES:
+            raise ValueError("anomaly kind is unsupported")
+        record = {
+            "kind": kind, "asset_uid": _uid(body.get("asset_uid"), "asset_uid"),
+            "subscription_uid": _uid(body.get("subscription_uid"), "subscription_uid"),
+            "incident_uid": _uid(body.get("incident_uid"), "incident_uid"),
+            "summary": _text(body.get("summary"), "anomaly summary", 160),
+            "correlation_id": _uid(body.get("correlation_id"), "correlation_id"),
+        }
+        if hasattr(self.repository, "incident_exists") and not self.repository.incident_exists(record["incident_uid"]):
+            raise ValueError("referenced incident is unavailable")
+        if not hasattr(self.repository, "link_anomaly_incident"):
+            raise RuntimeError("canonical incident linkage is unavailable")
+        # The P2 incident ledger remains the sole incident truth.  Trusted
+        # delivery only creates a bounded, idempotent asset impact link; it
+        # never creates a parallel incident record or accepts an unverified ID.
+        self.repository.link_anomaly_incident(
+            record["incident_uid"], record["asset_uid"], f"trusted_delivery:{kind}"
+        )
+        self._evidence("subscription_anomaly_reported", actor, {"kind": kind, "incident_uid": record["incident_uid"], "correlation_id": record["correlation_id"]})
+        return record
+
+    def expire_and_reclaim(self, *, as_of: Any, actor_uid: str, reclaim: Callable[[str, str], dict[str, Any]]) -> list[dict[str, Any]]:
+        """Reuse the trusted-delivery revocation path before changing state.
+
+        A legal hold is filtered by the repository; provider failures leave the
+        subscription unchanged so the next scheduler pass can retry safely.
+        """
+        actor = self._actor(actor_uid)
+        instant = _instant(as_of, "as_of")
+        results = []
+        for subscription in self.repository.subscriptions_due_for_reclaim(instant):
+            expired = subscription
+            if subscription.get("status", "active") == "active":
+                expired = self.repository.transition_subscription(subscription["uid"], {"active"}, "expired")
+            key = f"subscription-reclaim:{subscription['uid']}"
+            try:
+                receipt = reclaim(subscription["grant_uid"], key)
+            except (PermissionError, RuntimeError, ValueError):
+                self._evidence("subscription_reclaim_deferred", actor, {"subscription_uid": subscription["uid"], "grant_uid": subscription["grant_uid"]})
+                continue
+            if not isinstance(receipt, dict) or receipt.get("status") != "reclaimed":
+                self._evidence("subscription_reclaim_deferred", actor, {"subscription_uid": subscription["uid"], "grant_uid": subscription["grant_uid"]})
+                continue
+            saved = self.repository.mark_subscription_reclaimed(subscription["uid"], expired["current_version"])
+            self._evidence("subscription_reclaimed", actor, {"subscription_uid": saved["uid"], "grant_uid": subscription["grant_uid"]})
+            results.append(saved)
+        return results

+ 19 - 0
app/edge_gateway/__init__.py

@@ -0,0 +1,19 @@
+"""Pull-only enterprise edge runtime.
+
+The package intentionally exposes no listener or inbound server.  All control
+plane communication is initiated by :class:`EdgeTransport`.
+"""
+
+from .agent import EdgeAgent, EdgeRunnerAdapter, SignedReleaseManager
+from .bootstrap import EdgeBootstrapConfig
+from .transport import EdgeAuthenticationStopped, EdgeTransport, EdgeTransportError
+
+__all__ = [
+    "EdgeAgent",
+    "EdgeAuthenticationStopped",
+    "EdgeBootstrapConfig",
+    "EdgeRunnerAdapter",
+    "EdgeTransport",
+    "EdgeTransportError",
+    "SignedReleaseManager",
+]

+ 5 - 0
app/edge_gateway/__main__.py

@@ -0,0 +1,5 @@
+"""Run the enterprise edge process."""
+
+from app.edge_gateway.runtime import run
+
+raise SystemExit(run())

+ 1273 - 0
app/edge_gateway/agent.py

@@ -0,0 +1,1273 @@
+"""Durable pull-only edge agent, closed runner adapter, and release verifier."""
+
+from __future__ import annotations
+
+import base64
+import hashlib
+import os
+import re
+import sqlite3
+import stat
+from collections.abc import Callable, Mapping
+from datetime import UTC, datetime, timedelta
+from pathlib import Path, PurePosixPath
+from types import MappingProxyType
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
+from cryptography.hazmat.primitives.ciphers.aead import AESGCM
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
+
+from app.core.edge_gateway.contracts import (
+    EdgeContractError,
+    EdgeEventContract,
+    EdgeTaskContract,
+    SignedTaskEnvelope,
+    canonical_json_bytes,
+    canonical_sha256,
+    canonical_timestamp,
+    stable_event_id,
+)
+from app.core.edge_gateway.policy import EdgeEgressPolicy, EdgePolicyError
+from app.core.edge_gateway.queue import (
+    EdgeQueueConflictError,
+    EdgeQueueLeaseError,
+    SqliteEdgeQueue,
+)
+from app.edge_gateway.bootstrap import EdgeBootstrapConfig
+from app.edge_gateway.transport import EdgeAuthenticationStopped, EdgeTransportError
+
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+_VERSION = re.compile(r"^(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})$")
+_RELEASE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,254}$")
+
+EDGE_NODE_BY_OPERATION = MappingProxyType(
+    {
+        "collect": "edge.collect",
+        "profile": "edge.profile",
+        "quality": "quality.check",
+        "lineage": "edge.lineage",
+        "controlled_query": "edge.controlled_query",
+    }
+)
+EVENT_CLASSIFICATION_BY_OPERATION = MappingProxyType(
+    {
+        "collect": "desensitized_metadata",
+        "profile": "statistics",
+        "quality": "evidence",
+        "lineage": "lineage",
+        "controlled_query": "statistics",
+    }
+)
+DEFAULT_GOVERNED_PURPOSES = frozenset(
+    {
+        "inventory",
+        "governed-inventory",
+        "metadata-inventory",
+        "profile-statistics",
+        "quality-evaluation",
+        "lineage-capture",
+        "controlled-query",
+    }
+)
+_RUNNER_REQUEST_FIELDS = frozenset(
+    {
+        "task_id",
+        "operation",
+        "purpose",
+        "classification",
+        "environment",
+        "network_zone",
+        "idempotency_key",
+        "deadline_at",
+    }
+)
+_RUNNER_RESULT_FIELDS = frozenset(
+    {"classification", "payload", "local_artifact_digest", "local_artifact_ref"}
+)
+
+
+class EdgeAgentError(RuntimeError):
+    """Bounded edge runtime failure."""
+
+
+class EdgeTaskCancelled(EdgeAgentError):
+    """Cooperative cancellation was observed before a terminal event."""
+
+
+class EdgeReleaseError(EdgeAgentError):
+    """A candidate release failed a closed verification or activation gate."""
+
+
+class LocalArtifactStore:
+    """Validate and delete local artifacts without exposing their paths outbound."""
+
+    def __init__(self, root: str | Path, *, clock: Callable[[], object] | None = None):
+        candidate = Path(root)
+        if not candidate.is_absolute() or not candidate.is_dir() or candidate.is_symlink():
+            raise ValueError("artifact root must be an existing absolute directory")
+        self.root = candidate.resolve()
+        self.clock = clock or (lambda: datetime.now(UTC))
+
+    def inspect(self, reference: object, expected_digest: object) -> dict[str, str]:
+        if (
+            not isinstance(reference, str)
+            or not reference
+            or "\x00" in reference
+            or len(reference.encode("utf-8")) > 2_048
+            or not isinstance(expected_digest, str)
+            or not _SHA256.fullmatch(expected_digest)
+        ):
+            raise EdgePolicyError("local artifact reference is invalid")
+        raw = Path(reference)
+        path = raw if raw.is_absolute() else self.root / raw
+        try:
+            resolved = path.resolve(strict=True)
+            resolved.relative_to(self.root)
+            current = self.root
+            for part in resolved.relative_to(self.root).parts:
+                current = current / part
+                if current.is_symlink():
+                    raise EdgePolicyError("local artifact symlink is forbidden")
+            metadata = resolved.lstat()
+        except (OSError, ValueError) as exc:
+            raise EdgePolicyError("local artifact escapes its configured root") from exc
+        if not stat.S_ISREG(metadata.st_mode):
+            raise EdgePolicyError("local artifact must be a regular file")
+        digest = hashlib.sha256()
+        try:
+            with resolved.open("rb") as stream:
+                for chunk in iter(lambda: stream.read(1_048_576), b""):
+                    digest.update(chunk)
+        except OSError as exc:
+            raise EdgePolicyError("local artifact cannot be read") from exc
+        if digest.hexdigest() != expected_digest:
+            raise EdgePolicyError("local artifact digest does not match content")
+        canonical_ref = str(resolved)
+        return {
+            "artifact_digest": expected_digest,
+            "artifact_ref": canonical_ref,
+            "artifact_ref_hash": canonical_sha256(canonical_ref),
+        }
+
+    def delete(self, reference: object, expected_digest: object) -> dict[str, str]:
+        inspected = self.inspect(reference, expected_digest)
+        try:
+            Path(inspected["artifact_ref"]).unlink()
+        except OSError as exc:
+            raise EdgePolicyError("local artifact deletion failed") from exc
+        return {
+            "artifact_digest": inspected["artifact_digest"],
+            "artifact_ref_hash": inspected["artifact_ref_hash"],
+            "deleted_at": canonical_timestamp(
+                _now(self.clock).isoformat().replace("+00:00", "Z")
+            ),
+            "status": "deleted",
+        }
+
+    def receipt_for_missing(
+        self, reference: object, expected_digest: object, expected_ref_hash: object
+    ) -> dict[str, str]:
+        if (
+            not isinstance(reference, str)
+            or not isinstance(expected_digest, str)
+            or not _SHA256.fullmatch(expected_digest)
+            or not isinstance(expected_ref_hash, str)
+            or not _SHA256.fullmatch(expected_ref_hash)
+        ):
+            raise EdgePolicyError("missing artifact recovery metadata is invalid")
+        path = Path(reference)
+        try:
+            path.parent.resolve(strict=True).relative_to(self.root)
+            canonical_ref = str(path.resolve(strict=False))
+        except (OSError, ValueError) as exc:
+            raise EdgePolicyError("missing artifact escapes its configured root") from exc
+        if path.exists() or path.is_symlink() or canonical_sha256(canonical_ref) != expected_ref_hash:
+            raise EdgePolicyError("missing artifact recovery proof is invalid")
+        return {
+            "artifact_digest": expected_digest,
+            "artifact_ref_hash": expected_ref_hash,
+            "deleted_at": canonical_timestamp(
+                _now(self.clock).isoformat().replace("+00:00", "Z")
+            ),
+            "status": "deleted",
+        }
+
+
+def _now(clock: Callable[[], object]) -> datetime:
+    value = clock()
+    if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
+        raise ValueError("edge clock must return a timezone-aware datetime")
+    return value.astimezone(UTC)
+
+
+def _timestamp(clock: Callable[[], object]) -> str:
+    return canonical_timestamp(_now(clock).isoformat().replace("+00:00", "Z"))
+
+
+def _remote_lease_codec(config: EdgeBootstrapConfig):
+    """Protect recoverable control leases without persisting the credential."""
+
+    key = HKDF(
+        algorithm=hashes.SHA256(),
+        length=32,
+        salt=bytes.fromhex(config.certificate_sha256),
+        info=(
+            f"dataops-edge-remote-lease-v1:{config.gateway_id}:"
+            f"{config.environment}:{config.network_zone}"
+        ).encode(),
+    ).derive(config.credential.encode())
+    cipher = AESGCM(key)
+    aad = f"{config.gateway_id}:{config.policy_digest}".encode()
+
+    def encode(token: str) -> str:
+        nonce = os.urandom(12)
+        protected = cipher.encrypt(nonce, token.encode("utf-8"), aad)
+        return base64.urlsafe_b64encode(nonce + protected).decode("ascii")
+
+    def decode(value: str) -> str:
+        raw = base64.b64decode(value.encode("ascii"), altchars=b"-_", validate=True)
+        if len(raw) < 29:
+            raise ValueError("protected remote lease is invalid")
+        return cipher.decrypt(raw[:12], raw[12:], aad).decode("utf-8")
+
+    return encode, decode
+
+
+class EdgeRunnerAdapter:
+    """Map governed operations to exact local runner handlers.
+
+    No control-plane supplied parameters, module names, commands, SQL, paths, or
+    network destinations are accepted by this adapter.  Local handlers resolve
+    their governed configuration using the immutable task identity.
+    """
+
+    def __init__(
+        self,
+        executor: Callable[[str, Mapping[str, object], Callable[[], bool]], object],
+        *,
+        policy: EdgeEgressPolicy | None = None,
+        allowed_purposes: set[str] | frozenset[str] = DEFAULT_GOVERNED_PURPOSES,
+    ) -> None:
+        if not callable(executor):
+            raise ValueError("an explicit edge runner executor is required")
+        self._executor = executor
+        self.policy = policy or EdgeEgressPolicy(allowed_control_hosts=set())
+        if not isinstance(allowed_purposes, (set, frozenset)) or not allowed_purposes:
+            raise ValueError("explicit governed purposes are required")
+        if any(
+            not isinstance(item, str)
+            or not item
+            or len(item.encode("utf-8")) > 255
+            for item in allowed_purposes
+        ):
+            raise ValueError("governed purpose allowlist is invalid")
+        self.allowed_purposes = frozenset(allowed_purposes)
+
+    def execute(
+        self, task: EdgeTaskContract, cancel_requested: Callable[[], bool]
+    ) -> dict[str, object]:
+        task = EdgeTaskContract.from_mapping(task.to_mapping())
+        node = EDGE_NODE_BY_OPERATION.get(task.operation)
+        if node is None:
+            raise EdgePolicyError("edge task operation is not runner-approved")
+        if task.purpose not in self.allowed_purposes:
+            raise EdgePolicyError("edge task purpose is not runner-approved")
+        if cancel_requested():
+            raise EdgeTaskCancelled("edge task was cancelled")
+        request = {
+            "task_id": task.task_id,
+            "operation": task.operation,
+            "purpose": task.purpose,
+            "classification": task.classification,
+            "environment": task.environment,
+            "network_zone": task.network_zone,
+            "idempotency_key": task.idempotency_key,
+            "deadline_at": task.deadline_at,
+        }
+        if set(request) != _RUNNER_REQUEST_FIELDS:  # pragma: no cover - invariant
+            raise EdgePolicyError("edge runner request schema is invalid")
+        try:
+            raw = self._executor(node, MappingProxyType(request), cancel_requested)
+        except EdgeTaskCancelled:
+            raise
+        except (MemoryError, RecursionError) as exc:
+            raise EdgePolicyError("edge runner result exceeds safe limits") from exc
+        if cancel_requested():
+            raise EdgeTaskCancelled("edge task was cancelled")
+        if not isinstance(raw, Mapping):
+            raise EdgePolicyError("edge runner result must be a mapping")
+        unknown = set(raw) - _RUNNER_RESULT_FIELDS
+        missing = {"classification", "payload"} - set(raw)
+        if unknown or missing:
+            raise EdgePolicyError("edge runner result schema is invalid")
+        classification = raw["classification"]
+        expected = EVENT_CLASSIFICATION_BY_OPERATION[task.operation]
+        if classification != expected:
+            raise EdgePolicyError("edge runner result classification is invalid")
+        payload = raw["payload"]
+        if not isinstance(payload, Mapping):
+            raise EdgePolicyError("edge runner result payload must be a mapping")
+        try:
+            self.policy.validate_approved_payload(expected, payload)
+        except (EdgePolicyError, EdgeContractError, MemoryError, RecursionError) as exc:
+            raise EdgePolicyError("edge runner result is not approved for egress") from exc
+        artifact_digest = raw.get("local_artifact_digest")
+        artifact_ref = raw.get("local_artifact_ref")
+        if (artifact_digest is None) != (artifact_ref is None):
+            raise EdgePolicyError("local artifact digest and reference must be paired")
+        if artifact_digest is not None:
+            if not isinstance(artifact_digest, str) or not _SHA256.fullmatch(artifact_digest):
+                raise EdgePolicyError("local artifact digest is invalid")
+            if (
+                not isinstance(artifact_ref, str)
+                or not artifact_ref
+                or "\x00" in artifact_ref
+                or len(artifact_ref.encode("utf-8")) > 2_048
+            ):
+                raise EdgePolicyError("local artifact reference is invalid")
+        return {
+            "classification": expected,
+            "payload": dict(payload),
+            "local_artifact_digest": artifact_digest,
+            "local_artifact_ref": artifact_ref,
+        }
+
+
+class SignedReleaseManager:
+    """Verify Ed25519 manifests and activate candidates through injected primitives."""
+
+    _MANIFEST_FIELDS = frozenset(
+        {
+            "release_id",
+            "version",
+            "rollback_version",
+            "artifact_digest",
+            "artifact_name",
+            "deadline_at",
+            "signature_algorithm",
+            "key_id",
+            "manifest_digest",
+            "signature",
+            "status",
+        }
+    )
+
+    def __init__(
+        self,
+        *,
+        current_version: str,
+        trusted_keys: Mapping[str, str],
+        artifact_loader: Callable[[Mapping[str, object]], bytes],
+        activator: Callable[[str, bytes], bool],
+        rollback: Callable[[str], bool],
+        clock: Callable[[], object] | None = None,
+    ) -> None:
+        if not _VERSION.fullmatch(current_version):
+            raise ValueError("current release version is invalid")
+        if any(int(part) > 2_147_483_647 for part in current_version.split(".")):
+            raise ValueError("current release version is invalid")
+        if not trusted_keys:
+            raise ValueError("trusted release keys are required")
+        self.current_version = current_version
+        self.trusted_keys = dict(trusted_keys)
+        self.artifact_loader = artifact_loader
+        self.activator = activator
+        self.rollback = rollback
+        self.clock = clock or (lambda: datetime.now(UTC))
+        self._seen: dict[str, str] = {}
+
+    @staticmethod
+    def _version_tuple(value: str) -> tuple[int, ...]:
+        return tuple(int(item) for item in value.split("."))
+
+    def verify(
+        self,
+        value: Mapping[str, object],
+        *,
+        allow_recovery: bool = False,
+    ) -> dict[str, object]:
+        if not isinstance(value, Mapping) or set(value) != self._MANIFEST_FIELDS:
+            raise EdgeReleaseError("release manifest schema is invalid")
+        manifest = dict(value)
+        release_id = manifest["release_id"]
+        version = manifest["version"]
+        rollback_version = manifest["rollback_version"]
+        if not isinstance(release_id, str) or not _RELEASE_ID.fullmatch(release_id):
+            raise EdgeReleaseError("release identifier is invalid")
+        status = manifest.get("status")
+        if status not in {"offered", "accepted", "failed", "installed", "rolled_back"}:
+            raise EdgeReleaseError("release state is invalid")
+        if (
+            not isinstance(version, str)
+            or not isinstance(rollback_version, str)
+            or not _VERSION.fullmatch(version)
+            or not _VERSION.fullmatch(rollback_version)
+            or any(int(part) > 2_147_483_647 for part in version.split("."))
+            or any(int(part) > 2_147_483_647 for part in rollback_version.split("."))
+        ):
+            raise EdgeReleaseError("release downgrade or rollback binding was rejected")
+        if not allow_recovery and status in {"offered", "accepted"} and (
+            rollback_version != self.current_version
+            or self._version_tuple(version) <= self._version_tuple(self.current_version)
+        ):
+            raise EdgeReleaseError("release downgrade or rollback binding was rejected")
+        artifact_name = manifest["artifact_name"]
+        if (
+            not isinstance(artifact_name, str)
+            or not artifact_name
+            or len(artifact_name.encode("utf-8")) > 255
+            or PurePosixPath(artifact_name).name != artifact_name
+            or artifact_name in {".", ".."}
+            or any(character in artifact_name for character in ("/", "\\", "\x00"))
+        ):
+            raise EdgeReleaseError("release artifact path was rejected")
+        if manifest["signature_algorithm"] != "Ed25519":
+            raise EdgeReleaseError("release signature algorithm is not approved")
+        key_id = manifest["key_id"]
+        key_hex = self.trusted_keys.get(key_id) if isinstance(key_id, str) else None
+        if key_hex is None:
+            raise EdgeReleaseError("release signing key is not trusted")
+        for field in ("artifact_digest", "manifest_digest"):
+            if not isinstance(manifest[field], str) or not _SHA256.fullmatch(manifest[field]):
+                raise EdgeReleaseError("release digest is invalid")
+        try:
+            deadline = datetime.fromisoformat(str(manifest["deadline_at"]).replace("Z", "+00:00"))
+        except ValueError as exc:
+            raise EdgeReleaseError("release deadline is invalid") from exc
+        if deadline.tzinfo is None or deadline.astimezone(UTC) <= _now(self.clock):
+            raise EdgeReleaseError("release deadline has expired")
+        try:
+            canonical_deadline = canonical_timestamp(manifest["deadline_at"], "deadline_at")
+        except EdgeContractError as exc:
+            raise EdgeReleaseError("release deadline is invalid") from exc
+        if canonical_deadline != manifest["deadline_at"]:
+            raise EdgeReleaseError("release deadline must use canonical UTC form")
+        unsigned = {
+            key: manifest[key]
+            for key in sorted(self._MANIFEST_FIELDS - {"manifest_digest", "signature"})
+        }
+        expected_digest = canonical_sha256(unsigned)
+        if expected_digest != manifest["manifest_digest"]:
+            raise EdgeReleaseError("release manifest digest does not match")
+        content_digest = canonical_sha256(
+            {
+                key: manifest[key]
+                for key in (
+                    "release_id",
+                    "version",
+                    "rollback_version",
+                    "artifact_digest",
+                    "artifact_name",
+                    "deadline_at",
+                )
+            }
+        )
+        prior = self._seen.get(release_id)
+        if prior is not None and prior != content_digest:
+            raise EdgeReleaseError("release identifier replay changed its digest")
+        try:
+            signature = bytes.fromhex(str(manifest["signature"]))
+            Ed25519PublicKey.from_public_bytes(bytes.fromhex(key_hex)).verify(
+                signature, canonical_json_bytes(unsigned)
+            )
+        except (ValueError, InvalidSignature) as exc:
+            raise EdgeReleaseError("release signature verification failed") from exc
+        self._seen[release_id] = content_digest
+        return manifest
+
+    def install(self, value: Mapping[str, object]) -> dict[str, object]:
+        manifest = self.verify(value)
+        release_id = str(manifest["release_id"])
+        previous = self.current_version
+        try:
+            artifact = self.artifact_loader(MappingProxyType(manifest))
+            if not isinstance(artifact, bytes) or hashlib.sha256(artifact).hexdigest() != manifest["artifact_digest"]:
+                raise EdgeReleaseError("release artifact digest does not match")
+            if not self.activator(str(manifest["version"]), artifact):
+                raise EdgeReleaseError("release candidate health confirmation failed")
+        except Exception:
+            rolled_back = bool(self.rollback(previous))
+            return {
+                "release_id": release_id,
+                "outcome": "rollback" if rolled_back else "failed",
+                "summary": {
+                    "release_status": "rolled_back" if rolled_back else "failed",
+                    "version": previous,
+                },
+            }
+        self.current_version = str(manifest["version"])
+        return {
+            "release_id": release_id,
+            "outcome": "installed",
+            "summary": {"release_status": "installed", "version": self.current_version},
+        }
+
+
+class EdgeAgent:
+    """One durable worker for pull, execute, persist, and acknowledge cycles."""
+
+    def __init__(
+        self,
+        config: EdgeBootstrapConfig,
+        transport,
+        runner: EdgeRunnerAdapter,
+        *,
+        worker_id: str = "edge-worker-1",
+        clock: Callable[[], object] | None = None,
+        random: Callable[[], float] | None = None,
+        release_manager: SignedReleaseManager | None = None,
+        artifact_store: LocalArtifactStore | None = None,
+    ) -> None:
+        if not isinstance(config, EdgeBootstrapConfig):
+            raise ValueError("validated edge bootstrap configuration is required")
+        if not isinstance(runner, EdgeRunnerAdapter):
+            raise ValueError("validated edge runner adapter is required")
+        if not isinstance(worker_id, str) or not worker_id or len(worker_id.encode()) > 255:
+            raise ValueError("worker_id is invalid")
+        self.config = config
+        self.transport = transport
+        self.runner = runner
+        self.worker_id = worker_id
+        self.clock = clock or (lambda: datetime.now(UTC))
+        self.random = random or __import__("random").random
+        self.policy = EdgeEgressPolicy(
+            allowed_control_hosts=set(config.allowed_control_hosts),
+            allowed_proxy_hosts=set(config.allowed_proxy_hosts),
+        )
+        lease_encoder, lease_decoder = _remote_lease_codec(config)
+        self.queue = SqliteEdgeQueue(
+            config.queue_path,
+            clock=self.clock,
+            egress_policy=self.policy,
+            random_source=self.random,
+            remote_lease_encoder=lease_encoder,
+            remote_lease_decoder=lease_decoder,
+        )
+        self.artifact_store = artifact_store or LocalArtifactStore(
+            config.artifact_root, clock=self.clock
+        )
+        self.release_manager = release_manager
+        self._cancelled: set[str] = set()
+        self._stopped = False
+        self._needs_remote_lease_task: str | None = None
+        self._trusted_task_keys = {
+            key_id: bytes.fromhex(public_key)
+            for key_id, public_key in config.trusted_task_keys.items()
+        }
+        self.gateway_id_digest = hashlib.sha256(config.gateway_id.encode()).hexdigest()
+
+    def retry_delay(self, attempt: int) -> float:
+        if isinstance(attempt, bool) or not isinstance(attempt, int) or attempt < 1:
+            raise ValueError("retry attempt is invalid")
+        base = min(300.0, float(2 ** min(attempt - 1, 8)))
+        jitter = self.random()
+        if not isinstance(jitter, (int, float)) or not 0 <= jitter <= 1:
+            raise ValueError("random source returned an invalid value")
+        return min(300.0, base * (0.75 + 0.5 * float(jitter)))
+
+    def _validate_binding(self, contract: EdgeTaskContract) -> None:
+        expected = (
+            self.config.gateway_id,
+            self.config.environment,
+            self.config.network_zone,
+            self.config.policy_digest,
+        )
+        actual = (
+            contract.gateway_id,
+            contract.environment,
+            contract.network_zone,
+            contract.policy_digest,
+        )
+        if actual != expected:
+            raise EdgePolicyError("pulled task binding does not match this edge gateway")
+
+    def accept_pulled_task(
+        self,
+        envelope_value: Mapping[str, object],
+        lease_token: str,
+        lease_expires_at: str,
+    ):
+        if not isinstance(lease_token, str) or not lease_token.startswith("dopl_") or len(lease_token) > 128:
+            raise EdgePolicyError("control task lease is invalid")
+        try:
+            envelope = SignedTaskEnvelope.verify_mapping(
+                envelope_value,
+                authority_keys=self._trusted_task_keys,
+                now=_now(self.clock),
+                allowed_future_skew_seconds=(
+                    self.config.task_authority_clock_skew_seconds
+                ),
+            )
+        except EdgeContractError as exc:
+            raise EdgePolicyError("signed task authority was rejected") from exc
+        self._validate_binding(envelope.task)
+        return self.queue.accept_signed_task(
+            envelope,
+            remote_lease_token=lease_token,
+            remote_lease_expires_at=lease_expires_at,
+            remote_attempt=envelope.task.attempt,
+        )
+
+    def _cancel_requested(self, task_id: str) -> bool:
+        if task_id in self._cancelled:
+            return True
+        probe = getattr(self.transport, "cancel_requested", None)
+        if callable(probe):
+            try:
+                if bool(probe(task_id)):
+                    self._cancelled.add(task_id)
+                    return True
+            except EdgeAuthenticationStopped:
+                self._stopped = True
+                raise
+            except EdgeTransportError:
+                return task_id in self._cancelled
+        return False
+
+    def _event(self, task: EdgeTaskContract, result: Mapping[str, object]) -> EdgeEventContract:
+        identity = {
+            "task_id": task.task_id,
+            "gateway_id": task.gateway_id,
+            "environment": task.environment,
+            "network_zone": task.network_zone,
+            "purpose": task.purpose,
+            "classification": result["classification"],
+            "contract_version": task.contract_version,
+            "occurred_at": _timestamp(self.clock),
+            "attempt": task.attempt,
+            "idempotency_key": task.idempotency_key,
+            "policy_digest": task.policy_digest,
+            "payload": result["payload"],
+        }
+        return EdgeEventContract(event_id=stable_event_id(identity), **identity)  # type: ignore[arg-type]
+
+    def _artifact_metadata(
+        self,
+        task: EdgeTaskContract,
+        result: Mapping[str, object],
+    ) -> tuple[dict[str, object], ...]:
+        digest = result.get("local_artifact_digest")
+        reference = result.get("local_artifact_ref")
+        if digest is None or reference is None:
+            return ()
+        inspected = self.artifact_store.inspect(reference, digest)
+        retention_class = (
+            task.classification
+            if task.classification in {"raw", "recent_detail", "evidence"}
+            else "raw"
+            if task.classification == "restricted"
+            else "metadata"
+        )
+        retention_until = _now(self.clock) + timedelta(
+            days=self.policy.retention_days(retention_class)
+        )
+        return (
+            {
+                "artifact_digest": inspected["artifact_digest"],
+                "artifact_ref": inspected["artifact_ref"],
+                "artifact_ref_hash": inspected["artifact_ref_hash"],
+                "classification": task.classification,
+                "retention_until": canonical_timestamp(
+                    retention_until.isoformat().replace("+00:00", "Z")
+                ),
+            },
+        )
+
+    def _drain_artifact_cleanup(self) -> str | None:
+        artifact = self.queue.claim_artifact_cleanup(self.worker_id)
+        if artifact is None:
+            return None
+        if artifact.artifact_ref is None:
+            self.queue.fail_artifact_cleanup(
+                artifact.artifact_digest,
+                lease_token=str(artifact.lease_token),
+                error_code="artifact_reference_missing",
+            )
+            return None
+        try:
+            receipt = self.artifact_store.delete(
+                artifact.artifact_ref, artifact.artifact_digest
+            )
+            self.queue.acknowledge_artifact_cleanup(
+                artifact.artifact_digest,
+                lease_token=str(artifact.lease_token),
+                receipt=receipt,
+            )
+            return artifact.artifact_digest
+        except EdgePolicyError:
+            try:
+                receipt = self.artifact_store.receipt_for_missing(
+                    artifact.artifact_ref,
+                    artifact.artifact_digest,
+                    artifact.artifact_ref_hash,
+                )
+                self.queue.acknowledge_artifact_cleanup(
+                    artifact.artifact_digest,
+                    lease_token=str(artifact.lease_token),
+                    receipt=receipt,
+                )
+                return artifact.artifact_digest
+            except EdgePolicyError:
+                pass
+            self.queue.fail_artifact_cleanup(
+                artifact.artifact_digest,
+                lease_token=str(artifact.lease_token),
+                error_code="artifact_cleanup_failed",
+            )
+            return None
+
+    def _drain_event(self) -> str | None:
+        event = self.queue.claim_event(self.worker_id)
+        if event is None:
+            return None
+        task_id = str(event.event["task_id"])
+        task = self.queue.get_task(task_id)
+        if task is None:
+            self.queue.fail_event(
+                event.event_id,
+                lease_token=str(event.lease_token),
+                error_code="control_lease_unavailable",
+            )
+            return None
+        expiry = task.remote_lease_expires_at
+        expired = (
+            expiry is None
+            or datetime.fromisoformat(expiry.replace("Z", "+00:00")) <= _now(self.clock)
+        )
+        if expired:
+            self._needs_remote_lease_task = task_id
+            self.queue.fail_event(
+                event.event_id,
+                lease_token=str(event.lease_token),
+                error_code="remote_lease_expired",
+            )
+            return None
+        try:
+            control_lease = self.queue.recover_remote_lease(task_id)
+        except EdgeQueueLeaseError:
+            self._needs_remote_lease_task = task_id
+            self.queue.fail_event(
+                event.event_id,
+                lease_token=str(event.lease_token),
+                error_code="control_lease_unavailable",
+            )
+            return None
+        try:
+            ack = self.transport.send_event(event.event, control_lease)
+            self.queue.acknowledge_event(
+                event.event_id,
+                lease_token=str(event.lease_token),
+                acknowledgement=ack,
+            )
+            return event.event_id
+        except EdgeAuthenticationStopped:
+            if not expired:
+                self._stopped = True
+            else:
+                self._needs_remote_lease_task = task_id
+            self.queue.fail_event(
+                event.event_id,
+                lease_token=str(event.lease_token),
+                error_code=(
+                    "credential_stopped" if not expired else "remote_lease_expired"
+                ),
+            )
+            if not expired:
+                raise
+            return None
+        except EdgeTransportError:
+            self.queue.fail_event(
+                event.event_id,
+                lease_token=str(event.lease_token),
+                error_code="control_unavailable",
+            )
+            return None
+
+    def _has_unresolved_events(self) -> bool:
+        connection = sqlite3.connect(f"file:{self.queue.db_path}?mode=ro", uri=True)
+        try:
+            count = connection.execute(
+                "SELECT COUNT(*) FROM edge_outbound_events WHERE status IN ('pending','sending')"
+            ).fetchone()[0]
+        finally:
+            connection.close()
+        return bool(count)
+
+    def _expired_pending_remote_task(self) -> str | None:
+        connection = sqlite3.connect(f"file:{self.queue.db_path}?mode=ro", uri=True)
+        try:
+            row = connection.execute(
+                """SELECT task_id FROM edge_tasks
+                WHERE status='pending' AND (
+                    remote_lease_expires_at IS NULL
+                    OR julianday(remote_lease_expires_at) <= julianday(?)
+                ) ORDER BY created_at,task_id LIMIT 1""",
+                (_timestamp(self.clock),),
+            ).fetchone()
+        finally:
+            connection.close()
+        return str(row[0]) if row is not None else None
+
+    def _apply_reconcile(self, value: Mapping[str, object]) -> tuple[list[str], list[dict]]:
+        legacy = {"cancelled_task_ids", "release_offers"}
+        paged = {
+            "cancelled_task_ids", "cancel_next_cursor", "release_offers",
+            "release_next_cursor", "release_baseline",
+        }
+        if (
+            not isinstance(value, Mapping)
+            or (set(value) != legacy and set(value) != paged)
+        ):
+            raise EdgeTransportError("reconcile response schema is invalid")
+        cancelled = value["cancelled_task_ids"]
+        releases = value["release_offers"]
+        if not isinstance(cancelled, list) or not isinstance(releases, list) or len(cancelled) > 100 or len(releases) > 100:
+            raise EdgeTransportError("reconcile response exceeds safe limits")
+        applied: list[str] = []
+        for task_id in cancelled:
+            if not isinstance(task_id, str) or len(task_id.encode()) > 255:
+                raise EdgeTransportError("cancel reconciliation is invalid")
+            self._cancelled.add(task_id)
+            queued = self.queue.get_task(task_id)
+            if queued is not None and queued.status in {"pending", "leased"}:
+                self.queue.cancel_with_outcome(task_id)
+                applied.append(task_id)
+        release_values: list[dict] = []
+        for release in releases:
+            if not isinstance(release, Mapping):
+                raise EdgeTransportError("release reconciliation is invalid")
+            release_values.append(dict(release))
+        if set(value) == paged and value["release_baseline"] is not None:
+            baseline = value["release_baseline"]
+            if not isinstance(baseline, Mapping):
+                raise EdgeTransportError("release baseline is invalid")
+            if all(
+                item.get("release_id") != baseline.get("release_id")
+                for item in release_values
+            ):
+                release_values.append(dict(baseline))
+        return applied, release_values
+
+    def _drain_outcome(self) -> str | None:
+        outcome = self.queue.claim_outcome(self.worker_id)
+        if outcome is None:
+            return None
+        try:
+            control_lease = self.queue.recover_remote_lease(outcome.task_id)
+            sender = getattr(self.transport, "task_outcome", None)
+            if not callable(sender):
+                raise EdgeTransportError("task outcome transport is unavailable")
+            acknowledgement = sender(
+                outcome.task_id,
+                outcome.outcome["outcome"],
+                control_lease,
+                outcome.outcome["safe_summary"],
+            )
+            self.queue.acknowledge_outcome(
+                outcome.task_id,
+                lease_token=str(outcome.lease_token),
+                acknowledgement=acknowledgement,
+            )
+            return outcome.task_id
+        except EdgeAuthenticationStopped:
+            self._stopped = True
+            self.queue.fail_outcome(
+                outcome.task_id,
+                lease_token=str(outcome.lease_token),
+                error_code="credential_stopped",
+            )
+            raise
+        except (EdgeTransportError, EdgeQueueLeaseError):
+            self.queue.fail_outcome(
+                outcome.task_id,
+                lease_token=str(outcome.lease_token),
+                error_code="control_unavailable",
+            )
+            return None
+
+    def _process_releases(self, releases: list[dict]) -> None:
+        if not releases:
+            return
+        if self.release_manager is None:
+            raise EdgeReleaseError("signed release manager is not configured")
+        for release in releases:
+            verified = self.release_manager.verify(release, allow_recovery=True)
+            release_id = str(verified["release_id"])
+            version = str(verified["version"])
+            rollback_version = str(verified["rollback_version"])
+            content_digest = canonical_sha256(
+                {
+                    key: verified[key]
+                    for key in (
+                        "release_id",
+                        "version",
+                        "rollback_version",
+                        "artifact_digest",
+                        "artifact_name",
+                        "deadline_at",
+                    )
+                }
+            )
+            existing = self.queue.get_release_state(release_id)
+            state = self.queue.put_release_state(
+                release_id=release_id,
+                manifest_digest=content_digest,
+                version=version,
+                rollback_version=rollback_version,
+            )
+            server_status = str(verified["status"])
+            if (
+                existing is None
+                and server_status == "offered"
+                and (
+                    rollback_version != self.release_manager.current_version
+                    or self.release_manager._version_tuple(version)
+                    <= self.release_manager._version_tuple(
+                        self.release_manager.current_version
+                    )
+                )
+            ):
+                raise EdgeReleaseError(
+                    "release downgrade or rollback binding was rejected"
+                )
+
+            if server_status == "rolled_back":
+                while state.status != "rolled_back":
+                    target = (
+                        "accepted"
+                        if state.status == "offered"
+                        else "candidate"
+                        if state.status == "accepted"
+                        else "failed"
+                        if state.status in {"candidate", "installed"}
+                        else "rolled_back"
+                    )
+                    state = self.queue.compare_and_set_release_state(
+                        release_id,
+                        expected_status=state.status,
+                        target_status=target,
+                        previous_version=rollback_version,
+                        current_version=rollback_version,
+                    )
+                self.release_manager.current_version = rollback_version
+                continue
+
+            if server_status == "installed":
+                if state.status in {"offered", "accepted"}:
+                    if state.status == "offered":
+                        state = self.queue.compare_and_set_release_state(
+                            release_id,
+                            expected_status="offered",
+                            target_status="accepted",
+                            previous_version=rollback_version,
+                        )
+                    state = self.queue.compare_and_set_release_state(
+                        release_id,
+                        expected_status="accepted",
+                        target_status="candidate",
+                        previous_version=rollback_version,
+                    )
+                if state.status == "candidate":
+                    state = self.queue.compare_and_set_release_state(
+                        release_id,
+                        expected_status="candidate",
+                        target_status="installed",
+                        previous_version=rollback_version,
+                        current_version=version,
+                    )
+                self.release_manager.current_version = version
+                continue
+
+            if server_status == "offered":
+                if state.status == "offered":
+                    state = self.queue.compare_and_set_release_state(
+                        release_id,
+                        expected_status="offered",
+                        target_status="accepted",
+                        previous_version=rollback_version,
+                    )
+                self.transport.acknowledge_release(
+                    release_id,
+                    "accepted",
+                    {"release_status": "accepted", "version": version},
+                )
+
+            if state.status == "installed":
+                self.transport.acknowledge_release(
+                    release_id,
+                    "installed",
+                    {"release_status": "installed", "version": version},
+                )
+                self.release_manager.current_version = version
+                continue
+            if state.status == "rolled_back":
+                if server_status == "accepted":
+                    self.transport.acknowledge_release(
+                        release_id,
+                        "failed",
+                        {"release_status": "failed", "version": version},
+                    )
+                self.transport.acknowledge_release(
+                    release_id,
+                    "rollback",
+                    {"release_status": "rolled_back", "version": rollback_version},
+                )
+                continue
+            if state.status == "failed" or server_status == "failed":
+                if state.status != "failed":
+                    state = self.queue.compare_and_set_release_state(
+                        release_id,
+                        expected_status=state.status,
+                        target_status="failed",
+                        previous_version=rollback_version,
+                    )
+                if not self.release_manager.rollback(rollback_version):
+                    raise EdgeReleaseError("release rollback health confirmation failed")
+                state = self.queue.compare_and_set_release_state(
+                    release_id,
+                    expected_status="failed",
+                    target_status="rolled_back",
+                    previous_version=rollback_version,
+                    current_version=rollback_version,
+                )
+                self.release_manager.current_version = rollback_version
+                if server_status != "failed":
+                    self.transport.acknowledge_release(
+                        release_id,
+                        "failed",
+                        {"release_status": "failed", "version": version},
+                    )
+                self.transport.acknowledge_release(
+                    release_id,
+                    "rollback",
+                    {"release_status": "rolled_back", "version": rollback_version},
+                )
+                continue
+
+            if state.status == "candidate":
+                if not self.release_manager.rollback(rollback_version):
+                    raise EdgeReleaseError("uncertain candidate rollback failed")
+                state = self.queue.compare_and_set_release_state(
+                    release_id,
+                    expected_status="candidate",
+                    target_status="failed",
+                    previous_version=rollback_version,
+                )
+                state = self.queue.compare_and_set_release_state(
+                    release_id,
+                    expected_status="failed",
+                    target_status="rolled_back",
+                    previous_version=rollback_version,
+                    current_version=rollback_version,
+                )
+                self.transport.acknowledge_release(
+                    release_id,
+                    "failed",
+                    {"release_status": "failed", "version": version},
+                )
+                self.transport.acknowledge_release(
+                    release_id,
+                    "rollback",
+                    {"release_status": "rolled_back", "version": rollback_version},
+                )
+                continue
+
+            state = self.queue.compare_and_set_release_state(
+                release_id,
+                expected_status="accepted",
+                target_status="candidate",
+                previous_version=rollback_version,
+            )
+            result = self.release_manager.install(release)
+            if result["outcome"] == "installed":
+                self.queue.compare_and_set_release_state(
+                    release_id,
+                    expected_status="candidate",
+                    target_status="installed",
+                    previous_version=rollback_version,
+                    current_version=version,
+                )
+                self.transport.acknowledge_release(
+                    release_id, "installed", result["summary"]
+                )
+            else:
+                self.queue.compare_and_set_release_state(
+                    release_id,
+                    expected_status="candidate",
+                    target_status="failed",
+                    previous_version=rollback_version,
+                )
+                self.queue.compare_and_set_release_state(
+                    release_id,
+                    expected_status="failed",
+                    target_status="rolled_back",
+                    previous_version=rollback_version,
+                    current_version=rollback_version,
+                )
+                self.transport.acknowledge_release(
+                    release_id,
+                    "failed",
+                    {"release_status": "failed", "version": version},
+                )
+                self.transport.acknowledge_release(
+                    release_id, "rollback", result["summary"]
+                )
+
+    def heartbeat_once(self) -> Mapping[str, object]:
+        """Send only the policy-approved bounded health summary."""
+
+        summary = self.safe_diagnostic()
+        self.policy.validate_approved_payload("health_summary", summary)
+        return self.transport.heartbeat(summary)
+
+    def run_once(self) -> dict[str, object]:
+        if self._stopped:
+            raise EdgeAuthenticationStopped("edge agent is stopped")
+        self._drain_artifact_cleanup()
+        try:
+            cancelled, releases = self._apply_reconcile(self.transport.reconcile())
+            self._drain_outcome()
+            self._process_releases(releases)
+            sent = self._drain_event()
+            if sent is not None:
+                return {"cancelled": cancelled, "sent": sent, "executed": None}
+            if self._has_unresolved_events() and self._needs_remote_lease_task is None:
+                return {"cancelled": cancelled, "sent": None, "executed": None}
+            pulled = self.transport.pull_task()
+            if pulled is not None:
+                if not isinstance(pulled, tuple) or len(pulled) != 3:
+                    raise EdgeTransportError("pulled task response is invalid")
+                accepted = self.accept_pulled_task(pulled[0], pulled[1], pulled[2])
+                if self._needs_remote_lease_task is not None:
+                    if accepted.task_id == self._needs_remote_lease_task:
+                        self._needs_remote_lease_task = None
+                    return {
+                        "cancelled": cancelled,
+                        "sent": None,
+                        "executed": None,
+                    }
+            elif self._needs_remote_lease_task is not None:
+                return {"cancelled": cancelled, "sent": None, "executed": None}
+            expired_pending = self._expired_pending_remote_task()
+            if expired_pending is not None:
+                self._needs_remote_lease_task = expired_pending
+                return {"cancelled": cancelled, "sent": None, "executed": None}
+        except EdgeAuthenticationStopped:
+            self._stopped = True
+            raise
+        task_row = self.queue.claim(self.worker_id)
+        if task_row is None:
+            return {"cancelled": cancelled, "sent": None, "executed": None}
+        contract = EdgeTaskContract.from_mapping(task_row.task)
+        now = _now(self.clock)
+        if (
+            datetime.fromisoformat(contract.deadline_at.replace("Z", "+00:00")) <= now
+            or task_row.lease_expires_at is None
+            or datetime.fromisoformat(
+                task_row.lease_expires_at.replace("Z", "+00:00")
+            ) <= now
+            or task_row.remote_lease_expires_at is None
+            or datetime.fromisoformat(
+                task_row.remote_lease_expires_at.replace("Z", "+00:00")
+            ) <= now
+        ):
+            self.queue.fail(
+                contract.task_id,
+                lease_token=str(task_row.lease_token),
+                error_code="execution_lease_expired",
+                retryable=False,
+            )
+            self._needs_remote_lease_task = contract.task_id
+            return {"cancelled": cancelled, "sent": None, "executed": None}
+        try:
+            control_lease = self.queue.recover_remote_lease(contract.task_id)
+        except EdgeQueueLeaseError:
+            self.queue.fail(
+                contract.task_id,
+                lease_token=str(task_row.lease_token),
+                error_code="control_lease_unavailable",
+            )
+            return {"cancelled": cancelled, "sent": None, "executed": None}
+        try:
+            result = self.runner.execute(
+                contract, lambda: self._cancel_requested(contract.task_id)
+            )
+            if self._cancel_requested(contract.task_id):
+                raise EdgeTaskCancelled("edge task was cancelled")
+            event = self._event(contract, result)
+            self.queue.complete_with_event(
+                contract.task_id,
+                lease_token=str(task_row.lease_token),
+                remote_lease_token=control_lease,
+                event=event,
+                artifacts=self._artifact_metadata(contract, result),
+            )
+            self._drain_artifact_cleanup()
+        except EdgeTaskCancelled:
+            self.queue.cancel_with_outcome(contract.task_id)
+            self._drain_outcome()
+            return {"cancelled": sorted(set(cancelled) | {contract.task_id}), "sent": None, "executed": None}
+        except (EdgePolicyError, EdgeContractError, EdgeQueueConflictError):
+            self.queue.fail(
+                contract.task_id,
+                lease_token=str(task_row.lease_token),
+                error_code="execution_boundary_rejected",
+                retryable=False,
+            )
+            raise
+        except EdgeQueueLeaseError:
+            current = self.queue.get_task(contract.task_id)
+            if current is not None and current.status == "cancelled":
+                return {
+                    "cancelled": sorted(set(cancelled) | {contract.task_id}),
+                    "sent": None,
+                    "executed": None,
+                }
+            if current is not None and current.status == "leased":
+                self.queue.fail(
+                    contract.task_id,
+                    lease_token=str(task_row.lease_token),
+                    error_code="execution_lease_expired",
+                    retryable=False,
+                )
+            self._needs_remote_lease_task = contract.task_id
+            return {"cancelled": cancelled, "sent": None, "executed": None}
+        except EdgeAuthenticationStopped:
+            self._stopped = True
+            self.queue.fail(
+                contract.task_id,
+                lease_token=str(task_row.lease_token),
+                error_code="credential_stopped",
+            )
+            raise
+        except Exception as exc:
+            self.queue.fail(
+                contract.task_id,
+                lease_token=str(task_row.lease_token),
+                error_code="runner_execution_failed",
+                retryable=False,
+            )
+            raise EdgeAgentError("edge runner execution failed") from exc
+        sent = self._drain_event()
+        return {"cancelled": cancelled, "sent": sent, "executed": contract.task_id}
+
+    def safe_diagnostic(self) -> dict[str, object]:
+        connection = sqlite3.connect(f"file:{self.queue.db_path}?mode=ro", uri=True)
+        try:
+            pending = connection.execute(
+                "SELECT COUNT(*) FROM edge_tasks WHERE status IN ('pending','leased')"
+            ).fetchone()[0]
+            events = connection.execute(
+                "SELECT COUNT(*) FROM edge_outbound_events WHERE status != 'acknowledged'"
+            ).fetchone()[0]
+        finally:
+            connection.close()
+        return {
+            "agent_status": "stopped" if self._stopped else "ready",
+            "gateway_id_digest": self.gateway_id_digest,
+            "queue_pending_count": int(pending),
+            "queue_unacknowledged_count": int(events),
+            "version": self.config.version,
+        }

+ 333 - 0
app/edge_gateway/bootstrap.py

@@ -0,0 +1,333 @@
+"""Fail-closed bootstrap configuration for an enterprise edge agent."""
+
+from __future__ import annotations
+
+import re
+import ssl
+import stat
+from collections.abc import Mapping, Sequence
+from dataclasses import dataclass, field
+from datetime import UTC, datetime
+from pathlib import Path
+from types import MappingProxyType
+
+from cryptography import x509
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, padding, rsa
+
+from app.core.edge_gateway.policy import EdgeEgressPolicy
+
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,254}$")
+_VERSION = re.compile(r"^(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})\.(0|[1-9][0-9]{0,9})$")
+_PLACEHOLDERS = {"change-me", "changeme", "example", "placeholder", "tbd", "todo"}
+_REQUIRED = frozenset(
+    {
+        "queue_path",
+        "artifact_root",
+        "gateway_id",
+        "credential",
+        "certificate_sha256",
+        "client_certificate_path",
+        "client_private_key_path",
+        "ca_bundle_path",
+        "generation",
+        "environment",
+        "network_zone",
+        "policy_digest",
+        "control_url",
+        "proxy_url",
+        "allowed_control_hosts",
+        "allowed_proxy_hosts",
+        "trusted_release_keys",
+        "trusted_task_keys",
+        "task_authority_clock_skew_seconds",
+        "version",
+    }
+)
+_OPTIONAL = frozenset({"server_crl_path", "allowed_control_origins", "allowed_proxy_origins"})
+
+
+def _text(value: object, label: str, maximum: int = 255) -> str:
+    if (
+        not isinstance(value, str)
+        or not value
+        or value.strip() != value
+        or "\x00" in value
+        or len(value.encode("utf-8")) > maximum
+        or value.casefold() in _PLACEHOLDERS
+    ):
+        raise ValueError(f"{label} is not explicitly configured")
+    return value
+
+
+def _identifier(value: object, label: str) -> str:
+    candidate = _text(value, label)
+    if not _IDENTIFIER.fullmatch(candidate):
+        raise ValueError(f"{label} is invalid")
+    return candidate
+
+
+def _hosts(value: object, label: str, *, required: bool) -> frozenset[str]:
+    if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
+        raise ValueError(f"{label} must be an explicit sequence")
+    items = [_text(item, label) for item in value]
+    if any(item != item.lower() for item in items):
+        raise ValueError(f"{label} must contain lowercase exact hosts")
+    result = frozenset(items)
+    if required and not result:
+        raise ValueError(f"{label} is required")
+    if len(result) != len(value):
+        raise ValueError(f"{label} contains duplicates")
+    return result
+
+
+@dataclass(frozen=True, slots=True)
+class EdgeBootstrapConfig:
+    queue_path: str
+    artifact_root: str
+    gateway_id: str
+    credential: str = field(repr=False)
+    certificate_sha256: str
+    client_certificate_path: str
+    client_private_key_path: str = field(repr=False)
+    ca_bundle_path: str
+    generation: int
+    environment: str
+    network_zone: str
+    policy_digest: str
+    control_url: str
+    proxy_url: str | None
+    allowed_control_hosts: frozenset[str]
+    allowed_proxy_hosts: frozenset[str]
+    trusted_release_keys: Mapping[str, str] = field(repr=False)
+    trusted_task_keys: Mapping[str, str] = field(repr=False)
+    version: str
+    task_authority_clock_skew_seconds: int = 0
+    server_crl_path: str | None = None
+    allowed_control_origins: frozenset[str] = frozenset()
+    allowed_proxy_origins: frozenset[str] = frozenset()
+
+    @classmethod
+    def from_mapping(cls, value: Mapping[str, object]) -> EdgeBootstrapConfig:
+        if (
+            not isinstance(value, Mapping)
+            or not set(value) >= _REQUIRED
+            or not set(value) <= _REQUIRED | _OPTIONAL
+        ):
+            raise ValueError("edge bootstrap requires the exact configuration fields")
+        queue = _text(value["queue_path"], "queue_path", 2_048)
+        if queue == ":memory:" or "mode=memory" in queue:
+            raise ValueError("edge queue must use an explicit disk path")
+        queue_path = Path(queue).expanduser()
+        if not queue_path.is_absolute():
+            raise ValueError("edge queue path must be absolute")
+        artifact_root = Path(
+            _text(value["artifact_root"], "artifact_root", 2_048)
+        ).expanduser()
+        if (
+            not artifact_root.is_absolute()
+            or not artifact_root.is_dir()
+            or artifact_root.is_symlink()
+        ):
+            raise ValueError("artifact_root must be an existing absolute directory")
+        credential = _text(value["credential"], "credential", 160)
+        if (
+            not credential.startswith("dopg_")
+            or len(credential) < 16
+            or any(marker in credential.casefold() for marker in _PLACEHOLDERS)
+        ):
+            raise ValueError("credential is invalid")
+        certificate = _text(value["certificate_sha256"], "certificate_sha256", 64)
+        policy_digest = _text(value["policy_digest"], "policy_digest", 64)
+        if not _SHA256.fullmatch(certificate) or not _SHA256.fullmatch(policy_digest):
+            raise ValueError("edge digest binding is invalid")
+        material: dict[str, Path] = {}
+        for field_name in (
+            "client_certificate_path",
+            "client_private_key_path",
+            "ca_bundle_path",
+        ):
+            candidate = Path(_text(value[field_name], field_name, 2_048)).expanduser()
+            if not candidate.is_absolute() or not candidate.is_file():
+                raise ValueError(f"{field_name} must be an existing absolute file")
+            material[field_name] = candidate.resolve()
+        key_mode = stat.S_IMODE(material["client_private_key_path"].stat().st_mode)
+        if key_mode & 0o077:
+            raise ValueError("client private key permissions are not secure")
+        try:
+            certificate_object = x509.load_pem_x509_certificate(
+                material["client_certificate_path"].read_bytes()
+            )
+            ca_object = x509.load_pem_x509_certificate(
+                material["ca_bundle_path"].read_bytes()
+            )
+            private_key = serialization.load_pem_private_key(
+                material["client_private_key_path"].read_bytes(), password=None
+            )
+        except (ValueError, OSError) as exc:
+            raise ValueError("mTLS material is invalid") from exc
+        actual_fingerprint = certificate_object.fingerprint(hashes.SHA256()).hex()
+        if actual_fingerprint != certificate:
+            raise ValueError("client certificate fingerprint does not match configuration")
+        public_encoding = {
+            "encoding": serialization.Encoding.DER,
+            "format": serialization.PublicFormat.SubjectPublicKeyInfo,
+        }
+        if certificate_object.public_key().public_bytes(**public_encoding) != private_key.public_key().public_bytes(**public_encoding):
+            raise ValueError("client private key does not match certificate")
+        now = datetime.now(UTC)
+        if certificate_object.not_valid_before_utc > now or certificate_object.not_valid_after_utc <= now:
+            raise ValueError("client certificate is not currently valid")
+        if ca_object.not_valid_after_utc <= now:
+            raise ValueError("CA bundle certificate is expired")
+        generation = value["generation"]
+        if isinstance(generation, bool) or not isinstance(generation, int) or generation < 1:
+            raise ValueError("generation is invalid")
+        control_hosts = _hosts(value["allowed_control_hosts"], "allowed_control_hosts", required=True)
+        proxy_hosts = _hosts(value["allowed_proxy_hosts"], "allowed_proxy_hosts", required=False)
+        proxy_url = value["proxy_url"]
+        if proxy_url is not None:
+            proxy_url = _text(proxy_url, "proxy_url", 2_048)
+        control_url = _text(value["control_url"], "control_url", 2_048)
+        def origins(field_name: str) -> frozenset[str]:
+            raw = value.get(field_name, [])
+            if not isinstance(raw, Sequence) or isinstance(raw, (str, bytes)):
+                raise ValueError(f"{field_name} must be an explicit sequence")
+            result = frozenset(_text(item, field_name, 2_048) for item in raw)
+            if len(result) != len(raw) or any(not item.startswith("https://") for item in result):
+                raise ValueError(f"{field_name} is invalid")
+            return result
+
+        control_origins = origins("allowed_control_origins")
+        proxy_origins = origins("allowed_proxy_origins")
+        EdgeEgressPolicy(
+            allowed_control_hosts=set(control_hosts),
+            allowed_proxy_hosts=set(proxy_hosts),
+            allowed_control_origins=set(control_origins) or None,
+            allowed_proxy_origins=set(proxy_origins) or None,
+        ).validate_destination(control_url, proxy_url=proxy_url)
+        def trusted_keys(field_name: str) -> Mapping[str, str]:
+            raw_keys = value[field_name]
+            if not isinstance(raw_keys, Mapping) or not raw_keys:
+                raise ValueError(f"{field_name} is required")
+            keys: dict[str, str] = {}
+            for raw_key_id, raw_public_key in raw_keys.items():
+                key_id = _identifier(raw_key_id, "signing key id")
+                public_key = _text(raw_public_key, "signing public key", 128)
+                try:
+                    decoded = bytes.fromhex(public_key)
+                except ValueError as exc:
+                    raise ValueError("signing public key must be lowercase hex") from exc
+                if (
+                    public_key != public_key.lower()
+                    or len(decoded) != 32
+                    or decoded == bytes(32)
+                ):
+                    raise ValueError("signing public key must be a 32-byte Ed25519 key")
+                keys[key_id] = public_key
+            return MappingProxyType(keys)
+
+        release_keys = trusted_keys("trusted_release_keys")
+        task_keys = trusted_keys("trusted_task_keys")
+        task_authority_clock_skew_seconds = value[
+            "task_authority_clock_skew_seconds"
+        ]
+        if (
+            isinstance(task_authority_clock_skew_seconds, bool)
+            or not isinstance(task_authority_clock_skew_seconds, int)
+            or not 0 <= task_authority_clock_skew_seconds <= 300
+        ):
+            raise ValueError("task authority clock skew is invalid")
+        version = _text(value["version"], "version", 80)
+        if (
+            not _VERSION.fullmatch(version)
+            or any(int(part) > 2_147_483_647 for part in version.split("."))
+        ):
+            raise ValueError("version is invalid")
+        server_crl_path: str | None = None
+        if "server_crl_path" in value:
+            candidate = Path(
+                _text(value["server_crl_path"], "server_crl_path", 2_048)
+            ).expanduser()
+            if not candidate.is_absolute() or not candidate.is_file():
+                raise ValueError("server_crl_path must be an existing absolute file")
+            server_crl_path = str(candidate.resolve())
+            validate_server_crl(str(material["ca_bundle_path"]), server_crl_path)
+        return cls(
+            queue_path=str(queue_path.resolve()),
+            artifact_root=str(artifact_root.resolve()),
+            gateway_id=_identifier(value["gateway_id"], "gateway_id"),
+            credential=credential,
+            certificate_sha256=certificate,
+            client_certificate_path=str(material["client_certificate_path"]),
+            client_private_key_path=str(material["client_private_key_path"]),
+            ca_bundle_path=str(material["ca_bundle_path"]),
+            generation=generation,
+            environment=_identifier(value["environment"], "environment"),
+            network_zone=_identifier(value["network_zone"], "network_zone"),
+            policy_digest=policy_digest,
+            control_url=control_url.rstrip("/"),
+            proxy_url=proxy_url,
+            allowed_control_hosts=control_hosts,
+            allowed_proxy_hosts=proxy_hosts,
+            trusted_release_keys=release_keys,
+            trusted_task_keys=task_keys,
+            version=version,
+            task_authority_clock_skew_seconds=task_authority_clock_skew_seconds,
+            server_crl_path=server_crl_path,
+            allowed_control_origins=control_origins,
+            allowed_proxy_origins=proxy_origins,
+        )
+
+
+def _verify_crl_signature(crl: x509.CertificateRevocationList, issuer) -> None:
+    public_key = issuer.public_key()
+    try:
+        if isinstance(public_key, rsa.RSAPublicKey):
+            public_key.verify(
+                crl.signature, crl.tbs_certlist_bytes, padding.PKCS1v15(),
+                crl.signature_hash_algorithm,
+            )
+        elif isinstance(public_key, ec.EllipticCurvePublicKey):
+            public_key.verify(
+                crl.signature, crl.tbs_certlist_bytes,
+                ec.ECDSA(crl.signature_hash_algorithm),
+            )
+        elif isinstance(public_key, (ed25519.Ed25519PublicKey, ed448.Ed448PublicKey)):
+            public_key.verify(crl.signature, crl.tbs_certlist_bytes)
+        else:
+            raise ValueError("server CRL issuer key is unsupported")
+    except Exception as exc:
+        raise ValueError("server CRL signature is invalid") from exc
+
+
+def validate_server_crl(ca_bundle_path: str, server_crl_path: str) -> datetime:
+    try:
+        authorities = x509.load_pem_x509_certificates(Path(ca_bundle_path).read_bytes())
+        crl = x509.load_pem_x509_crl(Path(server_crl_path).read_bytes())
+    except (OSError, ValueError) as exc:
+        raise ValueError("server CRL material is invalid") from exc
+    issuer = next((item for item in authorities if item.subject == crl.issuer), None)
+    if issuer is None:
+        raise ValueError("server CRL issuer is not in the CA bundle")
+    _verify_crl_signature(crl, issuer)
+    now = datetime.now(UTC)
+    if (
+        crl.last_update_utc > now
+        or crl.next_update_utc is None
+        or crl.next_update_utc <= now
+    ):
+        raise ValueError("server CRL is not fresh")
+    return crl.next_update_utc
+
+
+def build_server_crl_ssl_context(
+    ca_bundle_path: str, server_crl_path: str
+) -> ssl.SSLContext:
+    validate_server_crl(ca_bundle_path, server_crl_path)
+    context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=ca_bundle_path)
+    context.load_verify_locations(cafile=server_crl_path)
+    context.verify_flags |= ssl.VERIFY_CRL_CHECK_LEAF
+    context.check_hostname = True
+    return context

+ 32 - 0
app/edge_gateway/healthcheck.py

@@ -0,0 +1,32 @@
+"""Bounded container health probe for the edge runtime."""
+
+from __future__ import annotations
+
+import json
+import os
+from datetime import UTC, datetime, timedelta
+from pathlib import Path
+
+
+def main() -> int:
+    path = Path(os.environ.get("EDGE_HEALTH_PATH", "/run/edge/tmp/health.json"))
+    try:
+        value = json.loads(path.read_text(encoding="utf-8"))
+        checked = datetime.fromisoformat(str(value["checked_at"]).replace("Z", "+00:00"))
+        if (
+            set(value) - {
+                "checked_at", "crl_next_update", "gateway_id_digest", "last_cycle",
+                "queue", "status",
+            }
+            or value["status"] not in {"healthy", "degraded"}
+            or checked.tzinfo is None
+            or datetime.now(UTC) - checked.astimezone(UTC) > timedelta(seconds=90)
+        ):
+            return 1
+    except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError):
+        return 1
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(main())

+ 276 - 0
app/edge_gateway/runtime.py

@@ -0,0 +1,276 @@
+"""Runnable pull-only edge process with mTLS server-CRL fail-stop semantics."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import os
+import signal
+import stat
+import threading
+from datetime import UTC, datetime
+from pathlib import Path
+
+import requests
+from requests.adapters import HTTPAdapter
+
+from app.core.edge_gateway.policy import EdgePolicyError
+from app.edge_gateway.agent import EdgeAgent, EdgeRunnerAdapter
+from app.edge_gateway.bootstrap import (
+    EdgeBootstrapConfig,
+    build_server_crl_ssl_context,
+    validate_server_crl,
+)
+from app.edge_gateway.transport import (
+    EdgeAuthenticationStopped,
+    EdgeTransport,
+    EdgeTransportError,
+)
+
+MAX_CONFIG_BYTES = 262_144
+
+
+def _now() -> str:
+    return datetime.now(UTC).isoformat().replace("+00:00", "Z")
+
+
+def _read_regular(path: Path, *, maximum: int, private: bool = False) -> bytes:
+    if not path.is_absolute() or path.is_symlink():
+        raise ValueError("runtime material path is invalid")
+    flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0)
+    if hasattr(os, "O_NOFOLLOW"):
+        flags |= os.O_NOFOLLOW
+    descriptor = os.open(path, flags)
+    try:
+        metadata = os.fstat(descriptor)
+        if (
+            not stat.S_ISREG(metadata.st_mode)
+            or metadata.st_nlink != 1
+            or metadata.st_size > maximum
+            or (private and stat.S_IMODE(metadata.st_mode) & 0o077)
+        ):
+            raise ValueError("runtime material metadata is unsafe")
+        chunks = bytearray()
+        while len(chunks) <= maximum:
+            chunk = os.read(descriptor, min(65_536, maximum + 1 - len(chunks)))
+            if not chunk:
+                break
+            chunks.extend(chunk)
+        if len(chunks) > maximum or os.fstat(descriptor) != metadata:
+            raise ValueError("runtime material changed while reading")
+        return bytes(chunks)
+    finally:
+        os.close(descriptor)
+
+
+def _read_json(path: Path) -> dict[str, object]:
+    try:
+        value = json.loads(_read_regular(path, maximum=MAX_CONFIG_BYTES).decode("utf-8"))
+    except (UnicodeError, json.JSONDecodeError) as exc:
+        raise ValueError("runtime JSON is invalid") from exc
+    if not isinstance(value, dict):
+        raise ValueError("runtime JSON must be an object")
+    return value
+
+
+def _atomic_health(path: Path, value: dict[str, object]) -> None:
+    encoded = json.dumps(value, sort_keys=True, separators=(",", ":")).encode() + b"\n"
+    path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
+    temporary = path.parent / f".{path.name}.{os.getpid()}.tmp"
+    flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_CLOEXEC", 0)
+    if hasattr(os, "O_NOFOLLOW"):
+        flags |= os.O_NOFOLLOW
+    descriptor = os.open(temporary, flags, 0o600)
+    try:
+        os.write(descriptor, encoded)
+        os.fsync(descriptor)
+    finally:
+        os.close(descriptor)
+    os.replace(temporary, path)
+    directory = os.open(path.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
+    try:
+        os.fsync(directory)
+    finally:
+        os.close(directory)
+
+
+class ServerCrlHttpAdapter(HTTPAdapter):
+    def __init__(self, ca_bundle_path: str, server_crl_path: str):
+        self._ssl_context = build_server_crl_ssl_context(
+            ca_bundle_path, server_crl_path
+        )
+        super().__init__()
+
+    def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
+        pool_kwargs["ssl_context"] = self._ssl_context
+        return super().init_poolmanager(connections, maxsize, block, **pool_kwargs)
+
+
+class ServerCrlSession(requests.Session):
+    """Reload the TLS pool whenever the operator atomically replaces the CRL."""
+
+    def __init__(self, ca_bundle_path: str, server_crl_path: str):
+        super().__init__()
+        self.trust_env = False
+        self._ca_bundle_path = ca_bundle_path
+        self._server_crl_path = server_crl_path
+        self._server_crl_digest = ""
+        self.refresh_server_crl()
+
+    @property
+    def server_crl_digest(self) -> str:
+        return self._server_crl_digest
+
+    def refresh_server_crl(self) -> bool:
+        validate_server_crl(self._ca_bundle_path, self._server_crl_path)
+        digest = hashlib.sha256(
+            _read_regular(Path(self._server_crl_path), maximum=1_048_576)
+        ).hexdigest()
+        if digest == self._server_crl_digest:
+            return False
+        replacement = ServerCrlHttpAdapter(
+            self._ca_bundle_path, self._server_crl_path
+        )
+        previous = self.adapters.get("https://")
+        self.mount("https://", replacement)
+        self._server_crl_digest = digest
+        if previous is not None:
+            previous.close()
+        return True
+
+    def request(self, *args, **kwargs):
+        self.refresh_server_crl()
+        return super().request(*args, **kwargs)
+
+
+def _runtime_self_check(node, request, cancel_requested):
+    if (
+        node != "quality.check"
+        or request.get("operation") != "quality"
+        or request.get("purpose") != "quality-evaluation"
+        or request.get("classification") != "evidence"
+    ):
+        raise EdgePolicyError("enterprise source runner handler is not provisioned")
+    if cancel_requested():
+        raise EdgePolicyError("runtime self-check was cancelled")
+    return {
+        "classification": "evidence",
+        "payload": {
+            "check_count": 1,
+            "passed_count": 1,
+            "scope": "edge_runtime",
+            "status": "passed",
+        },
+    }
+
+
+def build_runtime_runner() -> EdgeRunnerAdapter:
+    """Provide one closed engineering self-check; source handlers remain external."""
+
+    return EdgeRunnerAdapter(_runtime_self_check)
+
+
+def build_runtime_from_environment() -> tuple[EdgeAgent, Path, float]:
+    config_path = Path(os.environ.get("EDGE_CONFIG_PATH", ""))
+    credential_path = Path(os.environ.get("EDGE_CREDENTIAL_FILE", ""))
+    task_keys_path = Path(os.environ.get("EDGE_TASK_SIGNING_KEYS_PATH", ""))
+    release_keys_path = Path(os.environ.get("EDGE_RELEASE_SIGNING_KEYS_PATH", ""))
+    server_crl_path = Path(os.environ.get("EDGE_SERVER_CRL_PATH", ""))
+    health_path = Path(os.environ.get("EDGE_HEALTH_PATH", "/run/edge/tmp/health.json"))
+    if any(not path.is_absolute() for path in (
+        config_path, credential_path, task_keys_path, release_keys_path,
+        server_crl_path, health_path,
+    )):
+        raise ValueError("edge runtime paths must be absolute")
+    raw = _read_json(config_path)
+    credential = _read_regular(
+        credential_path, maximum=160, private=True
+    ).decode("utf-8").strip()
+    task_keys = _read_json(task_keys_path)
+    release_keys = _read_json(release_keys_path)
+    raw.update({
+        "credential": credential,
+        "trusted_task_keys": task_keys,
+        "trusted_release_keys": release_keys,
+        "server_crl_path": str(server_crl_path),
+    })
+    config = EdgeBootstrapConfig.from_mapping(raw)
+    if config.server_crl_path is None:
+        raise ValueError("server CRL is required by the runtime")
+    session = ServerCrlSession(config.ca_bundle_path, config.server_crl_path)
+    transport = EdgeTransport(
+        base_url=config.control_url,
+        gateway_id=config.gateway_id,
+        environment=config.environment,
+        network_zone=config.network_zone,
+        generation=config.generation,
+        credential=config.credential,
+        certificate_sha256=config.certificate_sha256,
+        allowed_control_hosts=config.allowed_control_hosts,
+        allowed_proxy_hosts=config.allowed_proxy_hosts,
+        allowed_control_origins=config.allowed_control_origins or None,
+        allowed_proxy_origins=config.allowed_proxy_origins or None,
+        proxy_url=config.proxy_url,
+        client_certificate_path=config.client_certificate_path,
+        client_private_key_path=config.client_private_key_path,
+        ca_bundle_path=config.ca_bundle_path,
+        client=session,
+        server_crl_path=config.server_crl_path,
+    )
+    runner = build_runtime_runner()
+    interval = float(os.environ.get("EDGE_POLL_INTERVAL_SECONDS", "5"))
+    if not 1 <= interval <= 60:
+        raise ValueError("EDGE_POLL_INTERVAL_SECONDS must be between 1 and 60")
+    return EdgeAgent(config, transport, runner), health_path, interval
+
+
+def run() -> int:
+    stop = threading.Event()
+    for event in (signal.SIGINT, signal.SIGTERM):
+        signal.signal(event, lambda _signum, _frame: stop.set())
+    try:
+        agent, health_path, interval = build_runtime_from_environment()
+    except Exception:
+        return 2
+    stopped = False
+    while not stop.is_set():
+        try:
+            if agent.config.server_crl_path is None:  # pragma: no cover - invariant
+                raise EdgeAuthenticationStopped("server CRL is missing")
+            crl_next_update = validate_server_crl(
+                agent.config.ca_bundle_path, agent.config.server_crl_path
+            )
+            agent.heartbeat_once()
+            cycle = agent.run_once()
+            _atomic_health(health_path, {
+                "checked_at": _now(),
+                "crl_next_update": crl_next_update.isoformat().replace("+00:00", "Z"),
+                "gateway_id_digest": agent.gateway_id_digest,
+                "last_cycle": cycle,
+                "queue": agent.safe_diagnostic(),
+                "status": "healthy",
+            })
+        except EdgeAuthenticationStopped:
+            stopped = True
+            _atomic_health(health_path, {
+                "checked_at": _now(), "gateway_id_digest": agent.gateway_id_digest,
+                "status": "stopped",
+            })
+        except (EdgeTransportError, OSError, ValueError):
+            _atomic_health(health_path, {
+                "checked_at": _now(), "gateway_id_digest": agent.gateway_id_digest,
+                "status": "degraded",
+            })
+        if stopped:
+            stop.wait(interval)
+        else:
+            stop.wait(interval)
+    _atomic_health(health_path, {
+        "checked_at": _now(), "gateway_id_digest": agent.gateway_id_digest,
+        "status": "stopped",
+    })
+    return 0
+
+
+if __name__ == "__main__":
+    raise SystemExit(run())

+ 427 - 0
app/edge_gateway/transport.py

@@ -0,0 +1,427 @@
+"""Bounded pull-only HTTPS transport for the enterprise edge agent."""
+
+from __future__ import annotations
+
+import hashlib
+import json
+import logging
+import re
+from collections.abc import Mapping
+from urllib.parse import quote, urlsplit
+
+from app.core.edge_gateway.contracts import (
+    EdgeContractError,
+    EdgeEventContract,
+    canonical_sha256,
+    preflight_json,
+    strict_json_bytes,
+)
+from app.core.edge_gateway.policy import EdgeEgressPolicy, EdgePolicyError
+from app.edge_gateway.bootstrap import validate_server_crl
+
+LOGGER = logging.getLogger(__name__)
+
+_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,254}$")
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+
+
+class EdgeTransportError(RuntimeError):
+    """A bounded transport failure safe for logs and retry decisions."""
+
+
+class EdgeAuthenticationStopped(EdgeTransportError):
+    """Credential/certificate binding is no longer usable; stop the agent."""
+
+
+class EdgeTransportConflict(EdgeTransportError):
+    """The control plane rejected an idempotent replay with changed content."""
+
+
+class EdgeTransport:
+    MAX_REQUEST_BYTES = 262_144
+    MAX_RESPONSE_BYTES = 1_048_576
+
+    def __init__(
+        self,
+        *,
+        base_url: str,
+        gateway_id: str,
+        environment: str,
+        network_zone: str,
+        generation: int,
+        credential: str,
+        certificate_sha256: str,
+        allowed_control_hosts: set[str] | frozenset[str],
+        allowed_proxy_hosts: set[str] | frozenset[str] | None = None,
+        allowed_control_origins: set[str] | frozenset[str] | None = None,
+        allowed_proxy_origins: set[str] | frozenset[str] | None = None,
+        proxy_url: str | None = None,
+        client_certificate_path: str,
+        client_private_key_path: str,
+        ca_bundle_path: str,
+        client,
+        server_crl_path: str | None = None,
+        connect_timeout_seconds: int = 5,
+        read_timeout_seconds: int = 20,
+    ) -> None:
+        self.policy = EdgeEgressPolicy(
+            allowed_control_hosts=set(allowed_control_hosts),
+            allowed_proxy_hosts=set(allowed_proxy_hosts or set()),
+            allowed_control_origins=allowed_control_origins,
+            allowed_proxy_origins=allowed_proxy_origins,
+        )
+        self.policy.validate_destination(base_url, proxy_url=proxy_url)
+        parsed = urlsplit(base_url)
+        if parsed.query or parsed.fragment or parsed.path not in {"", "/"}:
+            raise EdgePolicyError("control base URL must not contain a path or query")
+        if not isinstance(gateway_id, str) or not _IDENTIFIER.fullmatch(gateway_id):
+            raise ValueError("gateway_id is invalid")
+        if not isinstance(certificate_sha256, str) or not _SHA256.fullmatch(certificate_sha256):
+            raise ValueError("certificate_sha256 is invalid")
+        if (
+            not isinstance(credential, str)
+            or not credential.startswith("dopg_")
+            or len(credential) > 160
+        ):
+            raise ValueError("credential is invalid")
+        if isinstance(generation, bool) or not isinstance(generation, int) or generation < 1:
+            raise ValueError("generation is invalid")
+        for label, timeout in {
+            "connect_timeout_seconds": connect_timeout_seconds,
+            "read_timeout_seconds": read_timeout_seconds,
+        }.items():
+            if isinstance(timeout, bool) or not isinstance(timeout, int) or not 1 <= timeout <= 120:
+                raise ValueError(f"{label} is invalid")
+        if client is None or not callable(getattr(client, "request", None)):
+            raise ValueError("an explicit bounded HTTP client is required")
+        if hasattr(client, "trust_env") and client.trust_env is not False:
+            raise ValueError("HTTP client must disable implicit environment proxies")
+        self.base_url = base_url.rstrip("/")
+        self.proxy_url = proxy_url
+        self.gateway_id = gateway_id
+        self.environment = environment
+        self.network_zone = network_zone
+        self.generation = generation
+        self._credential = credential
+        self.certificate_sha256 = certificate_sha256
+        self.client = client
+        for label, path in {
+            "client_certificate_path": client_certificate_path,
+            "client_private_key_path": client_private_key_path,
+            "ca_bundle_path": ca_bundle_path,
+        }.items():
+            if not isinstance(path, str) or not path.startswith("/") or "\x00" in path:
+                raise ValueError(f"{label} is invalid")
+        self.client_certificate_path = client_certificate_path
+        self._client_private_key_path = client_private_key_path
+        self.ca_bundle_path = ca_bundle_path
+        if server_crl_path is not None and (
+            not isinstance(server_crl_path, str)
+            or not server_crl_path.startswith("/")
+            or "\x00" in server_crl_path
+        ):
+            raise ValueError("server_crl_path is invalid")
+        self.server_crl_path = server_crl_path
+        if server_crl_path is not None:
+            validate_server_crl(ca_bundle_path, server_crl_path)
+        self.timeout = (connect_timeout_seconds, read_timeout_seconds)
+
+    def _binding(self) -> dict[str, object]:
+        return {
+            "environment": self.environment,
+            "network_zone": self.network_zone,
+            "generation": self.generation,
+        }
+
+    def _request(self, path: str, payload: Mapping[str, object]) -> dict[str, object]:
+        if self.server_crl_path is not None:
+            try:
+                validate_server_crl(self.ca_bundle_path, self.server_crl_path)
+            except ValueError as exc:
+                raise EdgeAuthenticationStopped("control-plane server CRL is not fresh") from exc
+        if not path.startswith("/") or ".." in path or "//" in path:
+            raise EdgeTransportError("control path is invalid")
+        url = f"{self.base_url}{path}"
+        self.policy.validate_destination(url, proxy_url=self.proxy_url)
+        try:
+            encoded = strict_json_bytes(payload)
+        except EdgeContractError as exc:
+            raise EdgeTransportError("control request is invalid") from exc
+        if len(encoded) > self.MAX_REQUEST_BYTES:
+            raise EdgeTransportError("control request exceeds the byte limit")
+        headers = {
+            "Accept": "application/json",
+            "Content-Type": "application/json",
+            "X-Edge-Credential": self._credential,
+            "X-Edge-Certificate-SHA256": self.certificate_sha256,
+        }
+        try:
+            response = self.client.request(
+                "POST",
+                url,
+                data=encoded,
+                headers=headers,
+                timeout=self.timeout,
+                allow_redirects=False,
+                proxies=(
+                    {"https": self.proxy_url}
+                    if self.proxy_url is not None
+                    else {"http": None, "https": None}
+                ),
+                verify=self.ca_bundle_path,
+                cert=(self.client_certificate_path, self._client_private_key_path),
+                stream=True,
+            )
+        except Exception as exc:
+            LOGGER.warning(
+                "edge control request failed category=%s", type(exc).__name__
+            )
+            raise EdgeTransportError("control request failed") from exc
+        try:
+            return self._read_response(response)
+        finally:
+            close = getattr(response, "close", None)
+            if callable(close):
+                close()
+
+    def _read_response(self, response) -> dict[str, object]:
+        status = getattr(response, "status_code", None)
+        if not isinstance(status, int):
+            raise EdgeTransportError("control response is invalid")
+        if 300 <= status < 400:
+            raise EdgeTransportError("control redirects are forbidden")
+        headers_value = getattr(response, "headers", {})
+        content_length = headers_value.get("Content-Length") if isinstance(headers_value, Mapping) else None
+        if content_length is not None:
+            try:
+                declared_length = int(content_length)
+            except (TypeError, ValueError) as exc:
+                raise EdgeTransportError("control response content length is invalid") from exc
+            if declared_length < 0 or declared_length > self.MAX_RESPONSE_BYTES:
+                raise EdgeTransportError("control response exceeds the byte limit")
+        iterator = getattr(response, "iter_content", None)
+        if not callable(iterator):
+            raise EdgeTransportError("control response streaming is unavailable")
+        chunks = bytearray()
+        try:
+            for chunk in iterator(chunk_size=65_536):
+                if not isinstance(chunk, bytes):
+                    raise EdgeTransportError("control response chunk is invalid")
+                chunks.extend(chunk)
+                if len(chunks) > self.MAX_RESPONSE_BYTES:
+                    raise EdgeTransportError("control response exceeds the byte limit")
+        except EdgeTransportError:
+            raise
+        except Exception as exc:
+            raise EdgeTransportError("control response stream failed") from exc
+        body = bytes(chunks)
+        if len(body) > self.MAX_RESPONSE_BYTES:
+            raise EdgeTransportError("control response exceeds the byte limit")
+        if status in {401, 403}:
+            raise EdgeAuthenticationStopped("edge credential or certificate binding was rejected")
+        if status == 409:
+            raise EdgeTransportConflict("control plane idempotency conflict")
+        if not 200 <= status < 300:
+            raise EdgeTransportError("control plane rejected the request")
+        try:
+            value = json.loads(body.decode("utf-8"))
+            preflight_json(
+                value,
+                max_depth=32,
+                max_nodes=5_000,
+                max_string_bytes=131_072,
+                max_total_bytes=self.MAX_RESPONSE_BYTES,
+            )
+        except (UnicodeDecodeError, json.JSONDecodeError, EdgeContractError) as exc:
+            raise EdgeTransportError("control response is invalid") from exc
+        if not isinstance(value, dict):
+            raise EdgeTransportError("control response is invalid")
+        if set(value) == {"code", "message", "data"}:
+            if value["code"] != 200:
+                raise EdgeTransportError("control response envelope is invalid")
+            value = value["data"]
+        if not isinstance(value, dict):
+            raise EdgeTransportError("control response data is invalid")
+        return value
+
+    def reconcile(self) -> dict[str, object]:
+        cancelled: list[str] = []
+        releases: list[dict[str, object]] = []
+        release_digests: dict[str, str] = {}
+        cancel_cursor: str | None = None
+        release_cursor: str | None = None
+        cancel_done = False
+        release_done = False
+        for _page in range(100):
+            value = self._request(
+                f"/api/datasource/edge/gateways/{quote(self.gateway_id, safe='')}/reconcile",
+                {
+                    **self._binding(),
+                    "limit": 50,
+                    "cancel_cursor": cancel_cursor,
+                    "release_cursor": release_cursor,
+                },
+            )
+            required = {
+                "cancelled_task_ids", "cancel_next_cursor", "release_offers",
+                "release_next_cursor", "release_baseline",
+            }
+            if set(value) != required:
+                raise EdgeTransportError("reconcile response schema is invalid")
+            page_cancelled = value["cancelled_task_ids"]
+            page_releases = value["release_offers"]
+            if (
+                not isinstance(page_cancelled, list)
+                or not isinstance(page_releases, list)
+                or len(page_cancelled) > 50
+                or len(page_releases) > 50
+            ):
+                raise EdgeTransportError("reconcile page exceeds safe limits")
+            for task_id in page_cancelled:
+                if not isinstance(task_id, str) or not _IDENTIFIER.fullmatch(task_id):
+                    raise EdgeTransportError("cancel reconciliation is invalid")
+                if task_id not in cancelled:
+                    cancelled.append(task_id)
+            baseline = value["release_baseline"]
+            candidates = list(page_releases)
+            if baseline is not None:
+                candidates.append(baseline)
+            for release in candidates:
+                if not isinstance(release, Mapping):
+                    raise EdgeTransportError("release reconciliation is invalid")
+                release_id = release.get("release_id")
+                if not isinstance(release_id, str) or not _IDENTIFIER.fullmatch(release_id):
+                    raise EdgeTransportError("release reconciliation is invalid")
+                digest = canonical_sha256(release)
+                prior = release_digests.get(release_id)
+                if prior is not None and prior != digest:
+                    raise EdgeTransportConflict("release page replay changed content")
+                if prior is None:
+                    release_digests[release_id] = digest
+                    releases.append(dict(release))
+            next_cancel = value["cancel_next_cursor"]
+            next_release = value["release_next_cursor"]
+            if next_cancel is not None and (
+                not isinstance(next_cancel, str) or not _IDENTIFIER.fullmatch(next_cancel)
+            ):
+                raise EdgeTransportError("cancel cursor is invalid")
+            if next_release is not None and (
+                not isinstance(next_release, str) or not _IDENTIFIER.fullmatch(next_release)
+            ):
+                raise EdgeTransportError("release cursor is invalid")
+            cancel_done = next_cancel is None
+            release_done = next_release is None
+            cancel_cursor = next_cancel or (
+                page_cancelled[-1] if page_cancelled else cancel_cursor
+            )
+            release_cursor = next_release or (
+                str(page_releases[-1].get("release_id"))
+                if page_releases and isinstance(page_releases[-1], Mapping)
+                else release_cursor
+            )
+            if len(cancelled) > 5_000 or len(releases) > 5_000:
+                raise EdgeTransportError("reconcile result exceeds safe limits")
+            if cancel_done and release_done:
+                return {
+                    "cancelled_task_ids": cancelled,
+                    "release_offers": releases,
+                }
+        raise EdgeTransportError("reconcile pagination did not converge")
+
+    def cancel_requested(self, task_id: str) -> bool:
+        if not isinstance(task_id, str) or not _IDENTIFIER.fullmatch(task_id):
+            raise EdgeTransportError("cancel task identity is invalid")
+        value = self.reconcile()
+        cancelled = value.get("cancelled_task_ids")
+        if not isinstance(cancelled, list) or len(cancelled) > 100:
+            raise EdgeTransportError("cancel reconciliation is invalid")
+        if any(not isinstance(item, str) or not _IDENTIFIER.fullmatch(item) for item in cancelled):
+            raise EdgeTransportError("cancel reconciliation is invalid")
+        return task_id in cancelled
+
+    def pull_task(self):
+        value = self._request(
+            f"/api/datasource/edge/gateways/{quote(self.gateway_id, safe='')}/tasks/pull",
+            self._binding(),
+        )
+        envelope = value.get("signed_task_envelope")
+        task = value.get("task")
+        if envelope is None and task is None:
+            return None
+        lease_token = value.get("lease_token")
+        lease_expires_at = value.get("lease_expires_at")
+        if (
+            not isinstance(envelope, Mapping)
+            or not isinstance(task, Mapping)
+            or envelope.get("task") != task
+            or not isinstance(lease_token, str)
+            or not isinstance(lease_expires_at, str)
+        ):
+            raise EdgeTransportError("pulled task response is invalid")
+        return dict(envelope), lease_token, lease_expires_at
+
+    def send_event(self, event: Mapping[str, object], lease_token: str) -> dict[str, object]:
+        try:
+            normalized_event = EdgeEventContract.from_mapping(event).to_mapping()
+        except EdgeContractError as exc:
+            raise EdgeTransportError("outbound event contract is invalid") from exc
+        value = self._request(
+            f"/api/datasource/edge/gateways/{quote(self.gateway_id, safe='')}/events",
+            {
+                **self._binding(),
+                "event": normalized_event,
+                "lease_token": lease_token,
+            },
+        )
+        expected_digest = hashlib.sha256(lease_token.encode("utf-8")).hexdigest()
+        if (
+            value.get("status") != "accepted"
+            or value.get("event_id") != normalized_event.get("event_id")
+            or value.get("event_digest") != canonical_sha256(normalized_event)
+            or value.get("remote_lease_digest") != expected_digest
+            or not isinstance(value.get("received_at"), str)
+        ):
+            raise EdgeTransportError("event acknowledgement is invalid")
+        return {
+            "event_id": value["event_id"],
+            "event_digest": value["event_digest"],
+            "remote_lease_digest": value["remote_lease_digest"],
+            "received_at": value["received_at"],
+            "status": "accepted",
+        }
+
+    def task_outcome(self, task_id: str, outcome: str, lease_token: str, summary: Mapping[str, object]):
+        value = self._request(
+            f"/api/datasource/edge/gateways/{quote(self.gateway_id, safe='')}/tasks/{quote(task_id, safe='')}/outcome",
+            {
+                **self._binding(),
+                "outcome": outcome,
+                "lease_token": lease_token,
+                "safe_summary": dict(summary),
+            },
+        )
+        if (
+            set(value) != {"task_id", "status", "replayed"}
+            or value.get("task_id") != task_id
+            or value.get("status") != outcome
+            or not isinstance(value.get("replayed"), bool)
+        ):
+            raise EdgeTransportError("task outcome acknowledgement is invalid")
+        return value
+
+    def heartbeat(self, summary: Mapping[str, object]):
+        return self._request(
+            f"/api/datasource/edge/gateways/{quote(self.gateway_id, safe='')}/heartbeat",
+            {**self._binding(), "version": summary.get("version"), "safe_summary": dict(summary)},
+        )
+
+    def acknowledge_release(self, release_id: str, outcome: str, summary: Mapping[str, object]):
+        return self._request(
+            f"/api/datasource/edge/gateways/{quote(self.gateway_id, safe='')}/releases/ack",
+            {
+                **self._binding(),
+                "release_id": release_id,
+                "outcome": outcome,
+                "safe_summary": dict(summary),
+            },
+        )

+ 30 - 0
app/models/__init__.py

@@ -11,6 +11,12 @@ from app.models.active_metadata import (
     ActiveMetadataPlan,
     ActiveMetadataRun,
 )
+from app.models.connectors import (
+    ConnectorManifestRecord,
+    ConnectorPrincipal,
+    ConnectorRun,
+    ConnectorSourceBinding,
+)
 from app.models.data_product import DataOrder, DataProduct
 from app.models.data_research import (
     CandidateDecisionRecord,
@@ -27,6 +33,17 @@ from app.models.data_research import (
     OntologyVersionModel,
     SourceArtifact,
 )
+from app.models.edge_gateway import (
+    EdgeGateway,
+    EdgeGatewayAudit,
+    EdgeGatewayCredential,
+    EdgeGatewayEnrollment,
+    EdgeGatewayEvent,
+    EdgeGatewayFailureWindow,
+    EdgeGatewayRelease,
+    EdgeGatewayRotationRequest,
+    EdgeGatewayTask,
+)
 from app.models.governance_template import (
     GovernanceDomainTemplate,
     GovernanceDomainTemplateImport,
@@ -55,6 +72,19 @@ __all__ = [
     "ActiveMetadataCorrectionAudit",
     "DataOrder",
     "DataProduct",
+    "ConnectorManifestRecord",
+    "ConnectorPrincipal",
+    "ConnectorRun",
+    "ConnectorSourceBinding",
+    "EdgeGatewayEnrollment",
+    "EdgeGateway",
+    "EdgeGatewayCredential",
+    "EdgeGatewayRotationRequest",
+    "EdgeGatewayTask",
+    "EdgeGatewayEvent",
+    "EdgeGatewayRelease",
+    "EdgeGatewayAudit",
+    "EdgeGatewayFailureWindow",
     "MetadataReviewRecord",
     "MetadataVersionHistory",
     "SemanticAsset",

+ 83 - 0
app/models/connectors.py

@@ -0,0 +1,83 @@
+"""Read models for the enterprise connector control plane."""
+
+from sqlalchemy.dialects.postgresql import ARRAY, JSONB, UUID
+
+from app import db
+from app.core.common.identifiers import new_governance_uid
+
+
+class ConnectorManifestRecord(db.Model):
+    __tablename__ = "connector_manifests"
+    __table_args__ = (
+        db.UniqueConstraint("connector_id", "connector_version"),
+        {"schema": "public"},
+    )
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    connector_id = db.Column(db.String(64), nullable=False)
+    connector_version = db.Column(db.String(40), nullable=False)
+    sdk_version = db.Column(db.String(20), nullable=False)
+    display_name = db.Column(db.String(200), nullable=False)
+    capabilities = db.Column(JSONB, nullable=False)
+    config_schema = db.Column(JSONB, nullable=False)
+    status = db.Column(db.String(20), nullable=False)
+
+
+class ConnectorPrincipal(db.Model):
+    __tablename__ = "connector_principals"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    connector_id = db.Column(db.String(64), nullable=False)
+    connector_version = db.Column(db.String(40), nullable=False)
+    source_uid = db.Column(UUID(as_uuid=False), nullable=False)
+    business_domain_uid = db.Column(UUID(as_uuid=False), nullable=False)
+    environment = db.Column(db.String(20), nullable=False)
+    allowed_operations = db.Column(ARRAY(db.Text), nullable=False)
+    allowed_scopes = db.Column(JSONB, nullable=False)
+    status = db.Column(db.String(20), nullable=False)
+    source_binding_uid = db.Column(UUID(as_uuid=False))
+    source_binding_version = db.Column(db.Integer)
+
+
+class ConnectorSourceBinding(db.Model):
+    __tablename__ = "connector_source_bindings"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    binding_version = db.Column(db.Integer, primary_key=True)
+    connector_id = db.Column(db.String(64), nullable=False)
+    connector_version = db.Column(db.String(40), nullable=False)
+    source_uid = db.Column(UUID(as_uuid=False), nullable=False)
+    business_domain_uid = db.Column(UUID(as_uuid=False), nullable=False)
+    environment = db.Column(db.String(20), nullable=False)
+    approved_base_url = db.Column(db.String(2048))
+    allowed_host = db.Column(db.String(253))
+    credential_ref = db.Column(db.String(300))
+    approved_config = db.Column(JSONB, nullable=False)
+    status = db.Column(db.String(20), nullable=False)
+    approved_by = db.Column(UUID(as_uuid=False), nullable=False)
+
+
+class ConnectorRun(db.Model):
+    __tablename__ = "connector_runs"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    idempotency_key = db.Column(db.String(64), nullable=False, unique=True)
+    connector_id = db.Column(db.String(64), nullable=False)
+    connector_version = db.Column(db.String(40), nullable=False)
+    source_uid = db.Column(UUID(as_uuid=False), nullable=False)
+    principal_uid = db.Column(UUID(as_uuid=False))
+    business_domain_uid = db.Column(UUID(as_uuid=False))
+    environment = db.Column(db.String(20))
+    process_key = db.Column(db.String(300))
+    operation = db.Column(db.String(30), nullable=False)
+    status = db.Column(db.String(20), nullable=False)
+    attempt_count = db.Column(db.Integer, nullable=False)
+    checkpoint = db.Column(JSONB, nullable=False)
+    cursor = db.Column(JSONB, nullable=False)
+    safe_config = db.Column(JSONB, nullable=False)
+    scope = db.Column(JSONB, nullable=False)
+    request_hash = db.Column(db.String(64), nullable=False)
+    client_hint_hash = db.Column(db.String(64))
+    source_binding_uid = db.Column(UUID(as_uuid=False))
+    source_binding_version = db.Column(db.Integer)
+    attempt_lease_token = db.Column(UUID(as_uuid=False))
+    cancel_requested = db.Column(db.Boolean, nullable=False, default=False)

+ 133 - 0
app/models/edge_gateway.py

@@ -0,0 +1,133 @@
+"""Read models for the enterprise edge-gateway control plane."""
+
+from sqlalchemy.dialects.postgresql import ARRAY, JSONB, UUID
+
+from app import db
+from app.core.common.identifiers import new_governance_uid
+
+
+class EdgeGatewayEnrollment(db.Model):
+    __tablename__ = "edge_gateway_enrollments"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    gateway_id = db.Column(UUID(as_uuid=False), nullable=False, unique=True)
+    gateway_name = db.Column(db.String(200), nullable=False)
+    environment = db.Column(db.String(20), nullable=False)
+    network_zone = db.Column(db.String(120), nullable=False)
+    policy_digest = db.Column(db.String(64), nullable=False)
+    expected_certificate_sha256 = db.Column(db.String(64), nullable=False)
+    allowed_control_hosts = db.Column(ARRAY(db.Text), nullable=False)
+    allowed_proxy_hosts = db.Column(ARRAY(db.Text), nullable=False)
+    token_hash = db.Column(db.String(64), nullable=False, unique=True)
+    status = db.Column(db.String(20), nullable=False)
+    expires_at = db.Column(db.DateTime(timezone=True), nullable=False)
+    consumed_at = db.Column(db.DateTime(timezone=True))
+
+
+class EdgeGateway(db.Model):
+    __tablename__ = "edge_gateways"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    name = db.Column(db.String(200), nullable=False)
+    environment = db.Column(db.String(20), nullable=False)
+    network_zone = db.Column(db.String(120), nullable=False)
+    status = db.Column(db.String(20), nullable=False)
+    current_generation = db.Column(db.Integer, nullable=False)
+    policy_digest = db.Column(db.String(64), nullable=False)
+    allowed_control_hosts = db.Column(ARRAY(db.Text), nullable=False)
+    allowed_proxy_hosts = db.Column(ARRAY(db.Text), nullable=False)
+    last_heartbeat_at = db.Column(db.DateTime(timezone=True))
+    runtime_version = db.Column(db.String(80))
+    safe_health_summary = db.Column(JSONB, nullable=False)
+
+
+class EdgeGatewayCredential(db.Model):
+    __tablename__ = "edge_gateway_credentials"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    gateway_id = db.Column(UUID(as_uuid=False), nullable=False)
+    generation = db.Column(db.Integer, nullable=False)
+    credential_hash = db.Column(db.String(64), nullable=False, unique=True)
+    certificate_sha256 = db.Column(db.String(64), nullable=False)
+    status = db.Column(db.String(20), nullable=False)
+    expires_at = db.Column(db.DateTime(timezone=True), nullable=False)
+
+
+class EdgeGatewayRotationRequest(db.Model):
+    __tablename__ = "edge_gateway_rotation_requests"
+    __table_args__ = {"schema": "public"}
+    gateway_id = db.Column(UUID(as_uuid=False), primary_key=True)
+    request_id = db.Column(db.String(255), primary_key=True)
+    request_digest = db.Column(db.String(64), nullable=False)
+    generation = db.Column(db.Integer, nullable=False)
+
+
+class EdgeGatewayTask(db.Model):
+    __tablename__ = "edge_gateway_tasks"
+    __table_args__ = {"schema": "public"}
+    task_id = db.Column(db.String(255), primary_key=True)
+    gateway_id = db.Column(UUID(as_uuid=False), nullable=False)
+    idempotency_key = db.Column(db.String(255), nullable=False)
+    contract = db.Column(JSONB, nullable=False)
+    contract_digest = db.Column(db.String(64), nullable=False)
+    authority = db.Column(JSONB, nullable=False)
+    status = db.Column(db.String(24), nullable=False)
+    result_summary = db.Column(JSONB)
+
+
+class EdgeGatewayEvent(db.Model):
+    __tablename__ = "edge_gateway_events"
+    __table_args__ = {"schema": "public"}
+    event_id = db.Column(db.String(80), primary_key=True)
+    gateway_id = db.Column(UUID(as_uuid=False), nullable=False)
+    task_id = db.Column(db.String(255), nullable=False)
+    classification = db.Column(db.String(40), nullable=False)
+    contract = db.Column(JSONB, nullable=False)
+    contract_digest = db.Column(db.String(64), nullable=False)
+    ack = db.Column(JSONB, nullable=False)
+
+
+class EdgeGatewayRelease(db.Model):
+    __tablename__ = "edge_gateway_releases"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    gateway_id = db.Column(UUID(as_uuid=False), nullable=False)
+    version = db.Column(db.String(80), nullable=False)
+    artifact_digest = db.Column(db.String(64), nullable=False)
+    rollback_version = db.Column(db.String(80), nullable=False)
+    request_id = db.Column(db.String(255), nullable=False)
+    request_digest = db.Column(db.String(64), nullable=False)
+    status = db.Column(db.String(24), nullable=False)
+    safe_summary = db.Column(JSONB, nullable=False)
+    artifact_name = db.Column(db.String(255), nullable=False)
+    deadline_at = db.Column(db.DateTime(timezone=True), nullable=False)
+    signature_algorithm = db.Column(db.String(20), nullable=False)
+    key_id = db.Column(db.String(128), nullable=False)
+    manifest_digest = db.Column(db.String(64), nullable=False)
+    signature = db.Column(db.String(128), nullable=False)
+    signed_manifest = db.Column(JSONB, nullable=False)
+
+
+class EdgeGatewayAudit(db.Model):
+    __tablename__ = "edge_gateway_audits"
+    __table_args__ = {"schema": "public"}
+    uid = db.Column(UUID(as_uuid=False), primary_key=True, default=new_governance_uid)
+    gateway_id = db.Column(UUID(as_uuid=False))
+    event_type = db.Column(db.String(80), nullable=False)
+    actor_uid = db.Column(UUID(as_uuid=False))
+    success = db.Column(db.Boolean, nullable=False)
+    safe_detail = db.Column(JSONB, nullable=False)
+    failure_fingerprint = db.Column(db.String(64))
+    window_started_at = db.Column(db.DateTime(timezone=True))
+
+
+class EdgeGatewayFailureWindow(db.Model):
+    __tablename__ = "edge_gateway_failure_windows"
+    __table_args__ = {"schema": "public"}
+    failure_fingerprint = db.Column(db.String(64), primary_key=True)
+    window_started_at = db.Column(db.DateTime(timezone=True), primary_key=True)
+    gateway_id = db.Column(UUID(as_uuid=False))
+    event_type = db.Column(db.String(80), nullable=False)
+    occurrence_count = db.Column(db.Integer, nullable=False)
+    first_seen_at = db.Column(db.DateTime(timezone=True), nullable=False)
+    last_seen_at = db.Column(db.DateTime(timezone=True), nullable=False)

+ 51 - 0
app/runner/api.py

@@ -9,6 +9,57 @@ from flask import Flask, jsonify, request
 from app.runner.auth import TaskTokenExpired, TaskTokenInvalid
 from app.runner.nodes import NodeExecutionError
 
+EDGE_RUNNER_NODES = frozenset(
+    {
+        "edge.collect",
+        "edge.profile",
+        "quality.check",
+        "edge.lineage",
+        "edge.controlled_query",
+    }
+)
+EDGE_RUNNER_REQUEST_FIELDS = frozenset(
+    {
+        "task_id",
+        "operation",
+        "purpose",
+        "classification",
+        "environment",
+        "network_zone",
+        "idempotency_key",
+        "deadline_at",
+    }
+)
+
+
+def execute_edge_adapter(handlers, node, edge_request, cancel_requested):
+    """Execute a closed local edge request without opening an HTTP surface.
+
+    Handlers are locally configured callables.  The control plane cannot name a
+    module, command, path, network target, script, or SQL statement through
+    this schema.
+    """
+
+    if node not in EDGE_RUNNER_NODES:
+        raise NodeExecutionError("edge runner node is not approved")
+    if not isinstance(edge_request, dict) or set(edge_request) != EDGE_RUNNER_REQUEST_FIELDS:
+        raise NodeExecutionError("edge runner request is invalid")
+    if not callable(cancel_requested):
+        raise NodeExecutionError("edge cancellation probe is invalid")
+    handler = handlers.get(node) if isinstance(handlers, dict) else None
+    if not callable(handler):
+        raise NodeExecutionError("edge runner node is unavailable")
+    if cancel_requested():
+        from app.edge_gateway.agent import EdgeTaskCancelled
+
+        raise EdgeTaskCancelled("edge task was cancelled")
+    result = handler(dict(edge_request), cancel_requested)
+    if cancel_requested():
+        from app.edge_gateway.agent import EdgeTaskCancelled
+
+        raise EdgeTaskCancelled("edge task was cancelled")
+    return result
+
 
 def create_runner_app(*, verifier, ledger, registry):
     app = Flask("dataops-runner")

+ 15 - 1
app/runner/bootstrap.py

@@ -12,7 +12,7 @@ from app.core.data_source.runtime import (
     DataSourceRuntimeConfig,
     build_standalone_data_source_runtime,
 )
-from app.runner.api import create_runner_app
+from app.runner.api import create_runner_app, execute_edge_adapter
 from app.runner.artifacts import ArtifactStore, PostgresArtifactResolver
 from app.runner.auth import TaskTokenVerifier
 from app.runner.ledger import PostgresTaskLedger
@@ -79,6 +79,20 @@ class RunnerSettings:
     max_query_rows: int = 1000
 
 
+def build_edge_runner_adapter(handlers):
+    """Build the local-only edge adapter around explicitly wired handlers."""
+
+    if not isinstance(handlers, dict) or not handlers:
+        raise ValueError("explicit edge runner handlers are required")
+    from app.edge_gateway.agent import EdgeRunnerAdapter
+
+    return EdgeRunnerAdapter(
+        lambda node, request, cancel: execute_edge_adapter(
+            handlers, node, dict(request), cancel
+        )
+    )
+
+
 def register_artifact_cleanup_cli(
     app, artifact_resolver, evidence_writer=None
 ):

Vissa filer visades inte eftersom för många filer har ändrats