engineering-platform 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +32 -0
  2. engineering_platform/ENGINEERING_PLATFORM_VERSION.json +15 -0
  3. engineering_platform/__init__.py +1 -0
  4. engineering_platform/__main__.py +7 -0
  5. engineering_platform/agent_state.py +530 -0
  6. engineering_platform/agent_trust.py +174 -0
  7. engineering_platform/assets/dashboard.css +1317 -0
  8. engineering_platform/assets/dashboard.js +8534 -0
  9. engineering_platform/assets/dashboard_locales.mjs +4049 -0
  10. engineering_platform/assets/dashboard_status_store.mjs +41 -0
  11. engineering_platform/assets/operations-console/apple-touch-icon-dark.png +0 -0
  12. engineering_platform/assets/operations-console/apple-touch-icon-light.png +0 -0
  13. engineering_platform/assets/operations-console/icon-dark.png +0 -0
  14. engineering_platform/assets/operations-console/icon-light.png +0 -0
  15. engineering_platform/assets/operations-console/icon-transparent.png +0 -0
  16. engineering_platform/assets/operations-console/manifest.webmanifest +11 -0
  17. engineering_platform/capability_preflight.py +285 -0
  18. engineering_platform/capability_review.py +261 -0
  19. engineering_platform/central_data_transfer.py +195 -0
  20. engineering_platform/central_database.py +245 -0
  21. engineering_platform/central_store_migration.py +1672 -0
  22. engineering_platform/codex_capacity.py +81 -0
  23. engineering_platform/codex_chat.py +226 -0
  24. engineering_platform/codex_observability.py +153 -0
  25. engineering_platform/component_lock.py +40 -0
  26. engineering_platform/component_logging.py +420 -0
  27. engineering_platform/console_presentation.py +14 -0
  28. engineering_platform/console_route_ownership.py +83 -0
  29. engineering_platform/contracts/__init__.py +38 -0
  30. engineering_platform/contracts/ep_consumer.py +391 -0
  31. engineering_platform/contracts/models.py +105 -0
  32. engineering_platform/contracts/projection.py +401 -0
  33. engineering_platform/dashboard_browser_validation.py +206 -0
  34. engineering_platform/dashboard_state.py +630 -0
  35. engineering_platform/dashboard_supervisor.swift +105 -0
  36. engineering_platform/dashboard_translation.py +129 -0
  37. engineering_platform/dependabot_producer.py +349 -0
  38. engineering_platform/drift_diagnostics.py +144 -0
  39. engineering_platform/emergency_recovery.py +268 -0
  40. engineering_platform/engineering_memory.py +139 -0
  41. engineering_platform/ep_consumer_credentials.py +473 -0
  42. engineering_platform/evidence_projection.py +213 -0
  43. engineering_platform/execution_activity.py +218 -0
  44. engineering_platform/execution_context.py +132 -0
  45. engineering_platform/execution_errors.py +42 -0
  46. engineering_platform/execution_evidence.py +24 -0
  47. engineering_platform/execution_executor.py +730 -0
  48. engineering_platform/execution_finalization.py +44 -0
  49. engineering_platform/execution_host.py +3306 -0
  50. engineering_platform/execution_lease.py +365 -0
  51. engineering_platform/execution_lifecycle.py +447 -0
  52. engineering_platform/execution_models.py +43 -0
  53. engineering_platform/execution_readiness.py +166 -0
  54. engineering_platform/execution_reporting.py +1607 -0
  55. engineering_platform/execution_repository.py +253 -0
  56. engineering_platform/execution_timeout_policy.py +56 -0
  57. engineering_platform/execution_timing.py +440 -0
  58. engineering_platform/execution_transaction.py +28 -0
  59. engineering_platform/external_producer_binding.py +235 -0
  60. engineering_platform/file_inbox.py +249 -0
  61. engineering_platform/forensic_attribution.py +338 -0
  62. engineering_platform/forensic_attribution_v2.py +134 -0
  63. engineering_platform/forensic_delta.py +299 -0
  64. engineering_platform/golden_scenario.py +63 -0
  65. engineering_platform/historical_dashboard_configuration.py +171 -0
  66. engineering_platform/host_admin.py +199 -0
  67. engineering_platform/host_preflight.py +231 -0
  68. engineering_platform/installation_relocation.py +122 -0
  69. engineering_platform/investigation_ledger.py +89 -0
  70. engineering_platform/legacy_inbox_migration.py +79 -0
  71. engineering_platform/lifecycle_worker.py +223 -0
  72. engineering_platform/live_status.py +267 -0
  73. engineering_platform/local_api.py +209 -0
  74. engineering_platform/local_api_keychain.py +51 -0
  75. engineering_platform/local_repository_binding.py +138 -0
  76. engineering_platform/managed_autonomy.py +509 -0
  77. engineering_platform/managed_codex_runtime.py +105 -0
  78. engineering_platform/parity_context.py +203 -0
  79. engineering_platform/parity_lifecycle_dispatcher.py +488 -0
  80. engineering_platform/platform_admin.py +13 -0
  81. engineering_platform/platform_api.py +428 -0
  82. engineering_platform/platform_bootstrap.py +385 -0
  83. engineering_platform/platform_components.py +65 -0
  84. engineering_platform/platform_version.py +171 -0
  85. engineering_platform/pr_check_repair.py +276 -0
  86. engineering_platform/pr_evidence_backfill.py +278 -0
  87. engineering_platform/producer.py +209 -0
  88. engineering_platform/project_agent.py +366 -0
  89. engineering_platform/project_agent_service.py +244 -0
  90. engineering_platform/project_topology.py +126 -0
  91. engineering_platform/prompt_history.py +591 -0
  92. engineering_platform/provider_context.py +136 -0
  93. engineering_platform/provider_context_benchmark.py +41 -0
  94. engineering_platform/provider_context_scope.py +90 -0
  95. engineering_platform/provider_interruption.py +168 -0
  96. engineering_platform/provider_process_identity.py +80 -0
  97. engineering_platform/provider_readiness.py +138 -0
  98. engineering_platform/provider_recovery.py +647 -0
  99. engineering_platform/provider_usage.py +497 -0
  100. engineering_platform/providers.py +471 -0
  101. engineering_platform/qualification.py +220 -0
  102. engineering_platform/recommendation_handoff.py +238 -0
  103. engineering_platform/report_analysis.py +193 -0
  104. engineering_platform/repository_attachment.py +171 -0
  105. engineering_platform/repository_handoff.py +95 -0
  106. engineering_platform/resources.py +38 -0
  107. engineering_platform/reviewer_evidence.py +70 -0
  108. engineering_platform/schemas/repository-attachment.schema.json +61 -0
  109. engineering_platform/server.py +3679 -0
  110. engineering_platform/server_console_services.py +2024 -0
  111. engineering_platform/server_relay.py +172 -0
  112. engineering_platform/server_service.py +122 -0
  113. engineering_platform/status_model.py +135 -0
  114. engineering_platform/status_reconciliation.py +34 -0
  115. engineering_platform/storage.py +2440 -0
  116. engineering_platform/submission_cli.py +77 -0
  117. engineering_platform/submission_intake.py +45 -0
  118. engineering_platform/submission_service.py +317 -0
  119. engineering_platform/telemetry.py +951 -0
  120. engineering_platform/templates/workspace-config.json +25 -0
  121. engineering_platform/validation_identity.py +50 -0
  122. engineering_platform/validation_profile.py +211 -0
  123. engineering_platform/workspace_preflight.py +263 -0
  124. engineering_platform/worktree_provenance.py +147 -0
  125. engineering_platform/worktree_tooling.py +18 -0
  126. engineering_platform-2.2.0.dist-info/METADATA +18 -0
  127. engineering_platform-2.2.0.dist-info/RECORD +130 -0
  128. engineering_platform-2.2.0.dist-info/WHEEL +5 -0
  129. engineering_platform-2.2.0.dist-info/entry_points.txt +6 -0
  130. engineering_platform-2.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,391 @@
1
+ """Deterministic, transport-free EP consumer v1 contract values.
2
+
3
+ This module defines only the consumer boundary. It neither dispatches an
4
+ operation nor authenticates a credential, opens a listener, reads storage, or
5
+ depends on a consumer repository.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ import json
12
+ import re
13
+ import unicodedata
14
+ from typing import Any, Mapping
15
+
16
+
17
+ EP_CONSUMER_CONTRACT_VERSION = "1.0"
18
+ EP_CONSUMER_REQUEST_TYPE = "contract.foundation"
19
+ MAX_PROJECT_ID_LENGTH = 128
20
+ MAX_CONSUMER_ID_LENGTH = 128
21
+ MAX_REQUEST_ID_LENGTH = 128
22
+ MAX_CREDENTIAL_LENGTH = 4096
23
+ MAX_PAYLOAD_BYTES = 8192
24
+
25
+ _IDENTIFIER_PATTERN = re.compile(r"[a-z][a-z0-9-]*\Z")
26
+ _CREDENTIAL_PATTERN = re.compile(r"[\x21-\x7e]+\Z")
27
+ _REQUEST_FIELDS = frozenset(
28
+ {"contract_version", "request_type", "request_id", "project_id", "consumer", "auth", "payload"}
29
+ )
30
+ _CONSUMER_FIELDS = frozenset({"consumer_id"})
31
+ _AUTH_FIELDS = frozenset({"scheme", "credential"})
32
+ _RESPONSE_FIELDS = frozenset({"contract_version", "request_id", "status", "payload"})
33
+ _ERROR_ENVELOPE_FIELDS = frozenset({"contract_version", "request_id", "status", "error"})
34
+ _ERROR_FIELDS = frozenset({"code", "message", "field", "path"})
35
+
36
+
37
+ class ErrorCode:
38
+ """Stable, public EP consumer v1 error codes."""
39
+
40
+ INVALID_CONTRACT_VERSION = "INVALID_CONTRACT_VERSION"
41
+ MISSING_PROJECT_ID = "MISSING_PROJECT_ID"
42
+ INVALID_PROJECT_ID = "INVALID_PROJECT_ID"
43
+ INVALID_CONSUMER_IDENTITY = "INVALID_CONSUMER_IDENTITY"
44
+ INVALID_AUTH_ENVELOPE = "INVALID_AUTH_ENVELOPE"
45
+ UNKNOWN_FIELD = "UNKNOWN_FIELD"
46
+ UNSUPPORTED_REQUEST_TYPE = "UNSUPPORTED_REQUEST_TYPE"
47
+ INVALID_NORMALIZATION = "INVALID_NORMALIZATION"
48
+ VALUE_TOO_LARGE = "VALUE_TOO_LARGE"
49
+ MALFORMED_REQUEST = "MALFORMED_REQUEST"
50
+ UNAUTHENTICATED = "UNAUTHENTICATED"
51
+ PROJECT_NOT_AUTHORIZED = "PROJECT_NOT_AUTHORIZED"
52
+ SERVICE_NOT_READY = "SERVICE_NOT_READY"
53
+
54
+
55
+ _MESSAGES = {
56
+ ErrorCode.INVALID_CONTRACT_VERSION: "The EP consumer contract version is invalid or unsupported.",
57
+ ErrorCode.MISSING_PROJECT_ID: "project_id is required.",
58
+ ErrorCode.INVALID_PROJECT_ID: "project_id is invalid.",
59
+ ErrorCode.INVALID_CONSUMER_IDENTITY: "consumer identity is invalid.",
60
+ ErrorCode.INVALID_AUTH_ENVELOPE: "authentication envelope is invalid.",
61
+ ErrorCode.UNKNOWN_FIELD: "The envelope contains an unknown field.",
62
+ ErrorCode.UNSUPPORTED_REQUEST_TYPE: "request_type is unsupported.",
63
+ ErrorCode.INVALID_NORMALIZATION: "The value is not in canonical normalized form.",
64
+ ErrorCode.VALUE_TOO_LARGE: "The value exceeds the contract limit.",
65
+ ErrorCode.MALFORMED_REQUEST: "The request is malformed.",
66
+ ErrorCode.UNAUTHENTICATED: "Authentication is required or invalid.",
67
+ ErrorCode.PROJECT_NOT_AUTHORIZED: "The credential is not authorized for this project.",
68
+ ErrorCode.SERVICE_NOT_READY: "The EP consumer boundary is not ready.",
69
+ }
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class ContractError(ValueError):
74
+ """A safe, stable validation failure; submitted values are never retained."""
75
+
76
+ code: str
77
+ field: str | None = None
78
+ path: str | None = None
79
+
80
+ def __post_init__(self) -> None:
81
+ if self.code not in _MESSAGES:
82
+ raise ValueError("unknown EP consumer error code")
83
+
84
+ @property
85
+ def message(self) -> str:
86
+ return _MESSAGES[self.code]
87
+
88
+ def __str__(self) -> str:
89
+ return self.message
90
+
91
+ def to_error_envelope(self, request_id: str | None = None) -> "ErrorEnvelope":
92
+ return ErrorEnvelope(
93
+ request_id=request_id, code=self.code, field=self.field, path=self.path
94
+ )
95
+
96
+
97
+ def _require_mapping(value: object, *, code: str, field: str) -> Mapping[str, Any]:
98
+ if not isinstance(value, Mapping) or not all(isinstance(key, str) for key in value):
99
+ raise ContractError(code, field=field, path=field)
100
+ return value
101
+
102
+
103
+ def _reject_unknown_fields(value: Mapping[str, Any], allowed: frozenset[str], *, path: str) -> None:
104
+ if set(value) - allowed:
105
+ # Never reflect arbitrary submitted keys into a diagnostic: a key is
106
+ # itself consumer-controlled and could carry sensitive text.
107
+ raise ContractError(ErrorCode.UNKNOWN_FIELD, field=path, path=path)
108
+
109
+
110
+ def _identifier(
111
+ value: object, *, field: str, invalid_code: str, missing_code: str | None = None, limit: int
112
+ ) -> str:
113
+ if value is None and missing_code:
114
+ raise ContractError(missing_code, field=field, path=field)
115
+ if not isinstance(value, str) or not value:
116
+ raise ContractError(invalid_code, field=field, path=field)
117
+ if len(value) > limit:
118
+ raise ContractError(ErrorCode.VALUE_TOO_LARGE, field=field, path=field)
119
+ if unicodedata.normalize("NFC", value) != value:
120
+ raise ContractError(ErrorCode.INVALID_NORMALIZATION, field=field, path=field)
121
+ if not _IDENTIFIER_PATTERN.fullmatch(value):
122
+ raise ContractError(invalid_code, field=field, path=field)
123
+ return value
124
+
125
+
126
+ def _normalize_payload(value: object, *, path: str = "payload", depth: int = 0) -> object:
127
+ if depth > 4:
128
+ raise ContractError(ErrorCode.VALUE_TOO_LARGE, field="payload", path=path)
129
+ if value is None or isinstance(value, (bool, int, float)):
130
+ return value
131
+ if isinstance(value, str):
132
+ return unicodedata.normalize("NFC", value.replace("\r\n", "\n").replace("\r", "\n"))
133
+ if isinstance(value, list):
134
+ if len(value) > 64:
135
+ raise ContractError(ErrorCode.VALUE_TOO_LARGE, field="payload", path=path)
136
+ return [
137
+ _normalize_payload(item, path=f"{path}[{index}]", depth=depth + 1)
138
+ for index, item in enumerate(value)
139
+ ]
140
+ if isinstance(value, Mapping) and all(isinstance(key, str) for key in value):
141
+ if len(value) > 64:
142
+ raise ContractError(ErrorCode.VALUE_TOO_LARGE, field="payload", path=path)
143
+ normalized: dict[str, object] = {}
144
+ for key in sorted(value):
145
+ normalized_key = unicodedata.normalize(
146
+ "NFC", key.replace("\r\n", "\n").replace("\r", "\n")
147
+ )
148
+ if not normalized_key or normalized_key in normalized:
149
+ raise ContractError(ErrorCode.INVALID_NORMALIZATION, field="payload", path=path)
150
+ normalized[normalized_key] = _normalize_payload(
151
+ value[key], path=f"{path}.{normalized_key}", depth=depth + 1
152
+ )
153
+ return normalized
154
+ raise ContractError(ErrorCode.MALFORMED_REQUEST, field="payload", path=path)
155
+
156
+
157
+ def _payload(value: object) -> dict[str, object]:
158
+ payload = _require_mapping(value, code=ErrorCode.MALFORMED_REQUEST, field="payload")
159
+ normalized = _normalize_payload(payload)
160
+ assert isinstance(normalized, dict)
161
+ try:
162
+ encoded = json.dumps(
163
+ normalized, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
164
+ ).encode("utf-8")
165
+ except (TypeError, ValueError):
166
+ raise ContractError(ErrorCode.MALFORMED_REQUEST, field="payload", path="payload") from None
167
+ if len(encoded) > MAX_PAYLOAD_BYTES:
168
+ raise ContractError(ErrorCode.VALUE_TOO_LARGE, field="payload", path="payload")
169
+ return normalized
170
+
171
+
172
+ @dataclass(frozen=True)
173
+ class ConsumerEnvelope:
174
+ consumer_id: str
175
+
176
+ @classmethod
177
+ def parse(cls, value: object) -> "ConsumerEnvelope":
178
+ consumer = _require_mapping(
179
+ value, code=ErrorCode.INVALID_CONSUMER_IDENTITY, field="consumer"
180
+ )
181
+ _reject_unknown_fields(consumer, _CONSUMER_FIELDS, path="consumer")
182
+ return cls(
183
+ consumer_id=_identifier(
184
+ consumer.get("consumer_id"),
185
+ field="consumer_id",
186
+ invalid_code=ErrorCode.INVALID_CONSUMER_IDENTITY,
187
+ limit=MAX_CONSUMER_ID_LENGTH,
188
+ )
189
+ )
190
+
191
+ def to_dict(self) -> dict[str, str]:
192
+ return {"consumer_id": self.consumer_id}
193
+
194
+
195
+ @dataclass(frozen=True)
196
+ class AuthEnvelope:
197
+ scheme: str
198
+ credential: str
199
+
200
+ @classmethod
201
+ def parse(cls, value: object) -> "AuthEnvelope":
202
+ auth = _require_mapping(value, code=ErrorCode.INVALID_AUTH_ENVELOPE, field="auth")
203
+ _reject_unknown_fields(auth, _AUTH_FIELDS, path="auth")
204
+ scheme, credential = auth.get("scheme"), auth.get("credential")
205
+ if scheme != "bearer" or not isinstance(credential, str) or not credential:
206
+ raise ContractError(ErrorCode.INVALID_AUTH_ENVELOPE, field="auth", path="auth")
207
+ if len(credential) > MAX_CREDENTIAL_LENGTH:
208
+ raise ContractError(
209
+ ErrorCode.VALUE_TOO_LARGE, field="credential", path="auth.credential"
210
+ )
211
+ if not _CREDENTIAL_PATTERN.fullmatch(credential):
212
+ raise ContractError(
213
+ ErrorCode.INVALID_AUTH_ENVELOPE, field="credential", path="auth.credential"
214
+ )
215
+ return cls(scheme=scheme, credential=credential)
216
+
217
+ def to_dict(self, *, safe: bool = False) -> dict[str, str]:
218
+ return {"scheme": self.scheme, "credential": "[REDACTED]" if safe else self.credential}
219
+
220
+
221
+ @dataclass(frozen=True)
222
+ class RequestEnvelope:
223
+ contract_version: str
224
+ request_type: str
225
+ request_id: str
226
+ project_id: str
227
+ consumer: ConsumerEnvelope
228
+ auth: AuthEnvelope
229
+ payload: dict[str, object]
230
+
231
+ @classmethod
232
+ def parse(cls, value: object) -> "RequestEnvelope":
233
+ request = _require_mapping(value, code=ErrorCode.MALFORMED_REQUEST, field="request")
234
+ _reject_unknown_fields(request, _REQUEST_FIELDS, path="request")
235
+ version = request.get("contract_version")
236
+ if version != EP_CONSUMER_CONTRACT_VERSION:
237
+ raise ContractError(
238
+ ErrorCode.INVALID_CONTRACT_VERSION,
239
+ field="contract_version",
240
+ path="contract_version",
241
+ )
242
+ request_type = request.get("request_type")
243
+ if request_type != EP_CONSUMER_REQUEST_TYPE:
244
+ raise ContractError(
245
+ ErrorCode.UNSUPPORTED_REQUEST_TYPE, field="request_type", path="request_type"
246
+ )
247
+ request_id = _identifier(
248
+ request.get("request_id"),
249
+ field="request_id",
250
+ invalid_code=ErrorCode.MALFORMED_REQUEST,
251
+ limit=MAX_REQUEST_ID_LENGTH,
252
+ )
253
+ return cls(
254
+ contract_version=version,
255
+ request_type=request_type,
256
+ request_id=request_id,
257
+ project_id=_identifier(
258
+ request.get("project_id"),
259
+ field="project_id",
260
+ invalid_code=ErrorCode.INVALID_PROJECT_ID,
261
+ missing_code=ErrorCode.MISSING_PROJECT_ID,
262
+ limit=MAX_PROJECT_ID_LENGTH,
263
+ ),
264
+ consumer=ConsumerEnvelope.parse(request.get("consumer")),
265
+ auth=AuthEnvelope.parse(request.get("auth")),
266
+ payload=_payload(request.get("payload")),
267
+ )
268
+
269
+ def to_dict(self, *, safe: bool = False) -> dict[str, object]:
270
+ return {
271
+ "contract_version": self.contract_version,
272
+ "request_type": self.request_type,
273
+ "request_id": self.request_id,
274
+ "project_id": self.project_id,
275
+ "consumer": self.consumer.to_dict(),
276
+ "auth": self.auth.to_dict(safe=safe),
277
+ "payload": self.payload,
278
+ }
279
+
280
+ def serialize(self) -> str:
281
+ return serialize(self.to_dict())
282
+
283
+ def safe_dict(self) -> dict[str, object]:
284
+ return self.to_dict(safe=True)
285
+
286
+
287
+ @dataclass(frozen=True)
288
+ class ResponseEnvelope:
289
+ request_id: str
290
+ payload: dict[str, object]
291
+ contract_version: str = EP_CONSUMER_CONTRACT_VERSION
292
+ status: str = "success"
293
+
294
+ def __post_init__(self) -> None:
295
+ if self.contract_version != EP_CONSUMER_CONTRACT_VERSION or self.status != "success":
296
+ raise ContractError(ErrorCode.INVALID_CONTRACT_VERSION, field="contract_version")
297
+ _identifier(
298
+ self.request_id,
299
+ field="request_id",
300
+ invalid_code=ErrorCode.MALFORMED_REQUEST,
301
+ limit=MAX_REQUEST_ID_LENGTH,
302
+ )
303
+ object.__setattr__(self, "payload", _payload(self.payload))
304
+
305
+ def to_dict(self) -> dict[str, object]:
306
+ return {
307
+ "contract_version": self.contract_version,
308
+ "request_id": self.request_id,
309
+ "status": self.status,
310
+ "payload": self.payload,
311
+ }
312
+
313
+ def serialize(self) -> str:
314
+ return serialize(self.to_dict())
315
+
316
+
317
+ @dataclass(frozen=True)
318
+ class ErrorEnvelope:
319
+ request_id: str | None
320
+ code: str
321
+ field: str | None = None
322
+ path: str | None = None
323
+ contract_version: str = EP_CONSUMER_CONTRACT_VERSION
324
+ status: str = "error"
325
+
326
+ def __post_init__(self) -> None:
327
+ if (
328
+ self.contract_version != EP_CONSUMER_CONTRACT_VERSION
329
+ or self.status != "error"
330
+ or self.code not in _MESSAGES
331
+ ):
332
+ raise ContractError(ErrorCode.MALFORMED_REQUEST)
333
+ if self.request_id is not None:
334
+ _identifier(
335
+ self.request_id,
336
+ field="request_id",
337
+ invalid_code=ErrorCode.MALFORMED_REQUEST,
338
+ limit=MAX_REQUEST_ID_LENGTH,
339
+ )
340
+ for value in (self.field, self.path):
341
+ if value is not None and (
342
+ not isinstance(value, str)
343
+ or len(value) > 128
344
+ or not re.fullmatch(r"[a-z][a-z0-9_.\[\]]*", value)
345
+ ):
346
+ raise ContractError(ErrorCode.MALFORMED_REQUEST)
347
+
348
+ def to_dict(self) -> dict[str, object]:
349
+ error: dict[str, object] = {"code": self.code, "message": _MESSAGES[self.code]}
350
+ if self.field is not None:
351
+ error["field"] = self.field
352
+ if self.path is not None:
353
+ error["path"] = self.path
354
+ return {
355
+ "contract_version": self.contract_version,
356
+ "request_id": self.request_id,
357
+ "status": self.status,
358
+ "error": error,
359
+ }
360
+
361
+ def serialize(self) -> str:
362
+ return serialize(self.to_dict())
363
+
364
+
365
+ def serialize(value: object) -> str:
366
+ """Return canonical JSON for an already validated contract value."""
367
+ if isinstance(value, (RequestEnvelope, ResponseEnvelope, ErrorEnvelope)):
368
+ value = value.to_dict()
369
+ return json.dumps(
370
+ value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
371
+ )
372
+
373
+
374
+ def deserialize_request(serialized: str | bytes | bytearray) -> RequestEnvelope:
375
+ """Decode JSON without duplicate-key ambiguity, then validate the request."""
376
+
377
+ def reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]:
378
+ result: dict[str, object] = {}
379
+ for key, item in pairs:
380
+ if key in result:
381
+ raise ContractError(ErrorCode.MALFORMED_REQUEST, field=key, path=key)
382
+ result[key] = item
383
+ return result
384
+
385
+ try:
386
+ decoded = json.loads(serialized, object_pairs_hook=reject_duplicate_keys)
387
+ except ContractError:
388
+ raise
389
+ except (TypeError, UnicodeDecodeError, json.JSONDecodeError):
390
+ raise ContractError(ErrorCode.MALFORMED_REQUEST) from None
391
+ return RequestEnvelope.parse(decoded)
@@ -0,0 +1,105 @@
1
+ """JSON-compatible, versioned public contract value objects.
2
+
3
+ These objects describe evidence and policy decisions only. They contain no
4
+ filesystem locations, executable commands, provider output, or prompt text.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import asdict, dataclass
10
+ from typing import Any
11
+
12
+
13
+ CONTRACT_MAJOR_VERSION = 1
14
+ CONTRACT_VERSION = "1.0"
15
+
16
+
17
+ class ContractVersionError(ValueError):
18
+ """Raised when an incompatible external contract version is requested."""
19
+
20
+
21
+ def require_compatible_version(version: object) -> None:
22
+ """Fail closed for unknown major contract versions."""
23
+ if not isinstance(version, str) or version.split(".", 1)[0] != str(CONTRACT_MAJOR_VERSION):
24
+ raise ContractVersionError("Engineering contract version is incompatible.")
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class _ContractValue:
29
+ contract_version: str = CONTRACT_VERSION
30
+
31
+ def to_dict(self) -> dict[str, Any]:
32
+ require_compatible_version(self.contract_version)
33
+ return asdict(self)
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class EvidenceReference(_ContractValue):
38
+ """A bounded pointer to canonical evidence, never the evidence payload."""
39
+
40
+ id: str = ""
41
+ source_type: str = "UNAVAILABLE"
42
+ authority: str = "UNAVAILABLE"
43
+ observed_at: str = "UNAVAILABLE"
44
+ freshness: str = "UNKNOWN"
45
+ subject: str = "UNAVAILABLE"
46
+ snapshot_identity: str = "UNAVAILABLE"
47
+ safe_summary_code: str = "UNAVAILABLE"
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class AllowedAction(_ContractValue):
52
+ """A capability descriptor; it intentionally has no executable payload."""
53
+
54
+ action_id: str = ""
55
+ action_namespace: str = ""
56
+ action_version: str = CONTRACT_VERSION
57
+ run_id: str = ""
58
+ target_identity: str = "RUN"
59
+ classification: str = "READ_ONLY"
60
+ mutation_level: str = "NONE"
61
+ authority_required: str = "EP_POLICY"
62
+ confirmation_required: bool = False
63
+ allowed: bool = False
64
+ reason_code: str = "UNAVAILABLE"
65
+ evidence_version: str = "UNAVAILABLE"
66
+ expires_at: str = "BOUNDARY_SENSITIVE"
67
+ scope: str = "RUN_SCOPED"
68
+ expected_effect_code: str = "READ_CANONICAL_EVIDENCE"
69
+ blocked_reason_code: str | None = None
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class ActionPolicyDecision(_ContractValue):
74
+ action_id: str = ""
75
+ run_id: str = ""
76
+ decision: str = "UNAVAILABLE"
77
+ reason_code: str = "UNAVAILABLE"
78
+ policy_version: str = CONTRACT_VERSION
79
+ evaluated_at: str = "UNAVAILABLE"
80
+ evidence_version: str = "UNAVAILABLE"
81
+ authority: str = "EP_POLICY"
82
+ confirmation_required: bool = False
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class ActionAuditRecord(_ContractValue):
87
+ """Append-only audit shape for a future governed action gateway.
88
+
89
+ No persistence or execution behaviour is introduced by this declaration.
90
+ """
91
+
92
+ audit_id: str = ""
93
+ run_id: str = ""
94
+ action_id: str = ""
95
+ action_version: str = CONTRACT_VERSION
96
+ policy_version: str = CONTRACT_VERSION
97
+ evidence_version: str = "UNAVAILABLE"
98
+ actor_type: str = "UNAVAILABLE"
99
+ authority: str = "UNAVAILABLE"
100
+ requested_at: str = "UNAVAILABLE"
101
+ confirmed_at: str | None = None
102
+ executed_at: str | None = None
103
+ result: str = "UNAVAILABLE"
104
+ resulting_state_code: str = "UNAVAILABLE"
105
+ validation_evidence_reference: str | None = None