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,299 @@
1
+ """Deterministic, read-only SQLite forensic delta export for Engineering Platform stores."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import closing
6
+ from dataclasses import dataclass
7
+ import hashlib
8
+ import json
9
+ import math
10
+ from pathlib import Path
11
+ import sqlite3
12
+ import unicodedata
13
+
14
+
15
+ REPORT_VERSION = "1.0"
16
+ _REFERENCE_COLUMNS = frozenset({
17
+ "submission_id", "run_id", "execution_run_id", "invocation_id", "consumer_id", "project_id",
18
+ "producer_id", "recovery_id", "original_id", "replacement_id", "qualification_run_id",
19
+ "reconciliation_run_id",
20
+ })
21
+ _SAFE_VALUE_COLUMNS = frozenset({
22
+ "artifact_id", "classification", "consumer_id", "contract_version", "created_at",
23
+ "credential_id", "disabled_at", "execution_id", "execution_mode", "fingerprint",
24
+ "id", "integrity_status", "invocation_id", "issued_at", "lease_id", "mission_id",
25
+ "observed_at", "ordinal", "phase", "producer_id", "producer_type", "project_id",
26
+ "purpose", "received_at", "repository", "run_id", "schema", "status", "submission_id",
27
+ "updated_at", "version",
28
+ }) | _REFERENCE_COLUMNS
29
+ _SENSITIVE_WORDS = frozenset({
30
+ "authorization", "bearer", "credential", "history", "password", "prompt", "raw_audio",
31
+ "secret", "token", "verifier",
32
+ })
33
+ # These are only used after a table has no declared primary or total UNIQUE key.
34
+ # They are canonical EP identities, not a row-order fallback.
35
+ _CANONICAL_COMPOSITE_KEYS = {
36
+ "ep_consumer_registrations": ("consumer_id", "project_id"),
37
+ "local_api_consumer_registrations": ("consumer_id", "project_id"),
38
+ }
39
+
40
+
41
+ class ForensicDeltaError(RuntimeError):
42
+ """Raised when an input cannot be safely inspected read-only."""
43
+
44
+ code = "FORENSIC_DELTA_UNREADABLE"
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class KeyDefinition:
49
+ columns: tuple[str, ...]
50
+ source: str
51
+
52
+ def report(self) -> dict[str, object]:
53
+ return {"columns": list(self.columns), "source": self.source}
54
+
55
+
56
+ def _canonical_json(value: object) -> str:
57
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
58
+
59
+
60
+ def _digest(value: object) -> str:
61
+ return hashlib.sha256(_canonical_json(value).encode("utf-8")).hexdigest()
62
+
63
+
64
+ def _normalise(value: object) -> object:
65
+ if value is None or isinstance(value, (bool, int)):
66
+ return value
67
+ if isinstance(value, float):
68
+ if not math.isfinite(value):
69
+ return {"type": "float", "value": repr(value)}
70
+ return value
71
+ if isinstance(value, bytes):
72
+ return {"type": "blob", "sha256": hashlib.sha256(value).hexdigest(), "size": len(value)}
73
+ if isinstance(value, str):
74
+ text = unicodedata.normalize("NFC", value)
75
+ try:
76
+ parsed = json.loads(text)
77
+ except json.JSONDecodeError:
78
+ return text
79
+ if isinstance(parsed, (dict, list)):
80
+ return {"type": "json", "value": parsed}
81
+ return text
82
+ return {"type": type(value).__name__, "value": str(value)}
83
+
84
+
85
+ def _is_sensitive(column: str) -> bool:
86
+ name = column.casefold()
87
+ if name in {"credential_id", "credential_fingerprint", "fingerprint"}:
88
+ return False
89
+ return any(word in name for word in _SENSITIVE_WORDS)
90
+
91
+
92
+ def _safe_value(column: str, value: object) -> object:
93
+ if _is_sensitive(column):
94
+ return "REDACTED"
95
+ if column.casefold() in _SAFE_VALUE_COLUMNS or column.casefold().endswith(("_at", "_status")):
96
+ return _normalise(value)
97
+ return {"sha256": _digest(_normalise(value))}
98
+
99
+
100
+ def _fingerprint(path: Path) -> dict[str, object]:
101
+ if not path.is_file():
102
+ raise ForensicDeltaError(f"database is not a regular file: {path}")
103
+ state = path.stat()
104
+ payload: dict[str, object] = {
105
+ "path": str(path.resolve()), "size_bytes": state.st_size, "modified_ns": state.st_mtime_ns,
106
+ "sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
107
+ }
108
+ for suffix in ("-wal", "-shm"):
109
+ sidecar = Path(f"{path}{suffix}")
110
+ if sidecar.is_file():
111
+ payload[suffix[1:]] = {
112
+ "size_bytes": sidecar.stat().st_size,
113
+ "sha256": hashlib.sha256(sidecar.read_bytes()).hexdigest(),
114
+ }
115
+ return payload
116
+
117
+
118
+ def _readonly(path: Path) -> sqlite3.Connection:
119
+ return sqlite3.connect(f"file:{path.resolve().as_posix()}?mode=ro", uri=True)
120
+
121
+
122
+ def _quote(identifier: str) -> str:
123
+ return '"' + identifier.replace('"', '""') + '"'
124
+
125
+
126
+ def _tables(connection: sqlite3.Connection) -> list[str]:
127
+ return [str(row[0]) for row in connection.execute(
128
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
129
+ )]
130
+
131
+
132
+ def _columns(connection: sqlite3.Connection, table: str) -> list[str]:
133
+ return [str(row[1]) for row in connection.execute(f"PRAGMA table_info({_quote(table)})")]
134
+
135
+
136
+ def _key_definition(connection: sqlite3.Connection, table: str) -> KeyDefinition | None:
137
+ info = list(connection.execute(f"PRAGMA table_info({_quote(table)})"))
138
+ column_info = {str(row[1]): row for row in info}
139
+ primary = tuple(str(row[1]) for row in sorted(info, key=lambda row: int(row[5])) if int(row[5]) > 0)
140
+ if primary:
141
+ return KeyDefinition(primary, "PRIMARY_KEY")
142
+ for index in connection.execute(f"PRAGMA index_list({_quote(table)})"):
143
+ # seq, name, unique, origin, partial: partial unique indexes are not total row identities.
144
+ if not int(index[2]) or (len(index) > 4 and int(index[4])):
145
+ continue
146
+ name = str(index[1])
147
+ columns = tuple(str(row[2]) for row in connection.execute(f"PRAGMA index_info({_quote(name)})"))
148
+ if columns and all(int(column_info[column][3]) for column in columns):
149
+ return KeyDefinition(columns, "UNIQUE_INDEX")
150
+ registered = _CANONICAL_COMPOSITE_KEYS.get(table)
151
+ if registered and all(column in column_info for column in registered):
152
+ return KeyDefinition(registered, "REGISTERED_COMPOSITE")
153
+ return None
154
+
155
+
156
+ def _rows(connection: sqlite3.Connection, table: str, key: KeyDefinition) -> dict[str, dict[str, object]]:
157
+ columns = _columns(connection, table)
158
+ select = ", ".join(_quote(column) for column in columns)
159
+ result: dict[str, dict[str, object]] = {}
160
+ for values in connection.execute(f"SELECT {select} FROM {_quote(table)}"):
161
+ row = dict(zip(columns, values, strict=True))
162
+ key_value = [ _normalise(row[column]) for column in key.columns ]
163
+ encoded = _canonical_json(key_value)
164
+ if encoded in result:
165
+ raise ForensicDeltaError(f"non-unique discovered key for {table}")
166
+ result[encoded] = row
167
+ return result
168
+
169
+
170
+ def _row_digest(row: dict[str, object]) -> str:
171
+ return _digest({column: _normalise(value) for column, value in sorted(row.items())})
172
+
173
+
174
+ def _evidence(change_type: str, canonical_key: str, baseline: dict[str, object] | None,
175
+ candidate: dict[str, object] | None) -> dict[str, object]:
176
+ row = candidate if candidate is not None else baseline
177
+ assert row is not None
178
+ result: dict[str, object] = {"change_type": change_type, "canonical_key": json.loads(canonical_key)}
179
+ if baseline is not None:
180
+ result["row_digest_baseline"] = _row_digest(baseline)
181
+ if candidate is not None:
182
+ result["row_digest_candidate"] = _row_digest(candidate)
183
+ for column in sorted(row):
184
+ if column in _REFERENCE_COLUMNS or column.endswith("_id") or column in {"purpose", "status"} or column.endswith("_at"):
185
+ result[column] = _safe_value(column, row[column])
186
+ if baseline is not None and candidate is not None:
187
+ changed = []
188
+ for column in sorted(set(baseline) | set(candidate)):
189
+ before, after = baseline.get(column), candidate.get(column)
190
+ if _normalise(before) != _normalise(after):
191
+ changed.append({"column": column, "baseline": _safe_value(column, before), "candidate": _safe_value(column, after)})
192
+ result["changed_fields"] = changed
193
+ return result
194
+
195
+
196
+ def _graph_edges(source: str, table: str, rows: dict[str, dict[str, object]]) -> list[dict[str, object]]:
197
+ edges = []
198
+ for canonical_key, row in rows.items():
199
+ for column in sorted(row):
200
+ if column in _REFERENCE_COLUMNS or column.endswith("_id"):
201
+ if row[column] is not None:
202
+ edges.append({"source": source, "table_name": table, "canonical_key": json.loads(canonical_key),
203
+ "reference_type": column, "reference": _safe_value(column, row[column])})
204
+ return edges
205
+
206
+
207
+ def export_forensic_delta(baseline: Path, candidate: Path, *, migration_id: str) -> dict[str, object]:
208
+ """Compare two SQLite files without opening either through a writable API."""
209
+ baseline, candidate = baseline.resolve(), candidate.resolve()
210
+ before_baseline, before_candidate = _fingerprint(baseline), _fingerprint(candidate)
211
+ table_reports: list[dict[str, object]] = []
212
+ edges: list[dict[str, object]] = []
213
+ try:
214
+ with closing(_readonly(baseline)) as baseline_connection, closing(_readonly(candidate)) as candidate_connection:
215
+ baseline_tables, candidate_tables = _tables(baseline_connection), _tables(candidate_connection)
216
+ all_tables = sorted(set(baseline_tables) | set(candidate_tables))
217
+ for table in all_tables:
218
+ baseline_exists, candidate_exists = table in baseline_tables, table in candidate_tables
219
+ baseline_columns = _columns(baseline_connection, table) if baseline_exists else []
220
+ candidate_columns = _columns(candidate_connection, table) if candidate_exists else []
221
+ baseline_key = _key_definition(baseline_connection, table) if baseline_exists else None
222
+ candidate_key = _key_definition(candidate_connection, table) if candidate_exists else None
223
+ key = candidate_key if candidate_exists else baseline_key
224
+ key_mismatch = (
225
+ baseline_key is not None and candidate_key is not None
226
+ and baseline_key.columns != candidate_key.columns
227
+ )
228
+ report: dict[str, object] = {
229
+ "table_name": table,
230
+ "key_status": "RESOLVED" if key and not key_mismatch else "KEY_UNRESOLVED",
231
+ "key_definition": key.report() if key and not key_mismatch else None,
232
+ "columns_baseline_only": sorted(set(baseline_columns) - set(candidate_columns)),
233
+ "columns_candidate_only": sorted(set(candidate_columns) - set(baseline_columns)),
234
+ "changes": [],
235
+ }
236
+ if key is None or key_mismatch:
237
+ report.update({"baseline_count": None, "candidate_count": None, "unchanged_count": 0,
238
+ "added_count": 0, "removed_count": 0, "modified_count": 0})
239
+ if key_mismatch:
240
+ report["diagnostic"] = "KEY_DEFINITION_MISMATCH"
241
+ table_reports.append(report)
242
+ continue
243
+ baseline_rows = _rows(baseline_connection, table, key) if baseline_exists else {}
244
+ candidate_rows = _rows(candidate_connection, table, key) if candidate_exists else {}
245
+ changes = []
246
+ unchanged = added = removed = modified = 0
247
+ for canonical_key in sorted(set(baseline_rows) | set(candidate_rows)):
248
+ before, after = baseline_rows.get(canonical_key), candidate_rows.get(canonical_key)
249
+ if before is None:
250
+ added += 1
251
+ changes.append(_evidence("ADDED", canonical_key, None, after))
252
+ elif after is None:
253
+ removed += 1
254
+ changes.append(_evidence("REMOVED", canonical_key, before, None))
255
+ elif _row_digest(before) == _row_digest(after):
256
+ unchanged += 1
257
+ else:
258
+ modified += 1
259
+ changes.append(_evidence("MODIFIED", canonical_key, before, after))
260
+ edges.extend(_graph_edges("baseline", table, baseline_rows))
261
+ edges.extend(_graph_edges("candidate", table, candidate_rows))
262
+ report.update({"baseline_count": len(baseline_rows), "candidate_count": len(candidate_rows),
263
+ "unchanged_count": unchanged, "added_count": added, "removed_count": removed,
264
+ "modified_count": modified, "changes": changes})
265
+ table_reports.append(report)
266
+ except sqlite3.Error as error:
267
+ raise ForensicDeltaError(f"read-only SQLite inspection failed: {error}") from error
268
+ after_baseline, after_candidate = _fingerprint(baseline), _fingerprint(candidate)
269
+ if before_baseline != after_baseline or before_candidate != after_candidate:
270
+ raise ForensicDeltaError("input fingerprint changed during read-only export")
271
+ keyed = [table for table in table_reports if table["key_status"] == "RESOLVED"]
272
+ changed = [change for table in keyed for change in table["changes"]]
273
+ unresolved = [table["table_name"] for table in table_reports if table["key_status"] == "KEY_UNRESOLVED"]
274
+ report: dict[str, object] = {
275
+ "report_version": REPORT_VERSION, "migration_id": migration_id,
276
+ "baseline": before_baseline, "candidate": before_candidate,
277
+ "read_only_verified": True,
278
+ "diagnostics": {"key_unresolved_tables": unresolved},
279
+ "schema_difference": {"tables_baseline_only": sorted(set(baseline_tables) - set(candidate_tables)),
280
+ "tables_candidate_only": sorted(set(candidate_tables) - set(baseline_tables))},
281
+ "summary": {"tables_compared": len(keyed), "tables_baseline_only": len(set(baseline_tables) - set(candidate_tables)),
282
+ "tables_candidate_only": len(set(candidate_tables) - set(baseline_tables)),
283
+ "tables_key_unresolved": len(table_reports) - len(keyed),
284
+ "rows_added": sum(table["added_count"] for table in keyed),
285
+ "rows_removed": sum(table["removed_count"] for table in keyed),
286
+ "rows_modified": sum(table["modified_count"] for table in keyed),
287
+ "changed_rows_with_run_id": sum("run_id" in change or "execution_run_id" in change for change in changed),
288
+ "changed_rows_with_submission_id": sum("submission_id" in change for change in changed),
289
+ "changed_rows_with_consumer_project_scope": sum("consumer_id" in change or "project_id" in change for change in changed),
290
+ "graph_edge_count": len(edges)},
291
+ "tables": table_reports, "graph_edges": sorted(edges, key=_canonical_json),
292
+ }
293
+ report["report_digest"] = _digest(report)
294
+ return report
295
+
296
+
297
+ def canonical_report_json(report: dict[str, object]) -> str:
298
+ """Render the canonical byte-stable report representation."""
299
+ return _canonical_json(report)
@@ -0,0 +1,63 @@
1
+ """Deterministic, side-effect-bounded Engineering Platform Golden Scenarios."""
2
+ from __future__ import annotations
3
+
4
+ from datetime import datetime, timezone
5
+ import json
6
+ from pathlib import Path
7
+ import tempfile
8
+
9
+ from .platform_api import PlatformConfiguration, provider_registry
10
+ from .platform_bootstrap import validate_repository
11
+ from .qualification import execute_qualification
12
+
13
+ SCENARIO_ID = "EP-GOLDEN-001"
14
+
15
+
16
+ def run(
17
+ root: Path,
18
+ *,
19
+ fail_phase: str | None = None,
20
+ evidence_directory: Path | None = None,
21
+ ) -> dict[str, object]:
22
+ """Prove the productized lifecycle without altering the repository root.
23
+
24
+ A checkout may deliberately expose ``.engineering`` as a symlink to its
25
+ installation-owned state. Golden qualification must neither follow nor
26
+ replace that link. Callers that need a retained receipt can provide a
27
+ separate, already-authorized evidence directory.
28
+ """
29
+ phases: list[dict[str, object]] = []
30
+ try:
31
+ with tempfile.TemporaryDirectory(prefix="ep-golden-qualification-") as temporary:
32
+ transient_evidence_root = Path(temporary)
33
+ for name, operation in (
34
+ ("repository_bootstrap", lambda: validate_repository(root)),
35
+ # Golden qualification proves the public lifecycle contract. It
36
+ # must not activate a deferred shared-workspace migration while a
37
+ # managed transaction is in progress.
38
+ ("readiness", lambda: True),
39
+ ("configuration", lambda: PlatformConfiguration.load(root)),
40
+ ("providers", lambda: provider_registry(root)),
41
+ ("runtime_execution_simulation", lambda: True),
42
+ ("qualification", lambda: execute_qualification(root, ep_repository_root=root, evidence_root=transient_evidence_root)),
43
+ ("finalization_simulation", lambda: {"state": "MERGED_RECONCILED"}),
44
+ ("repository_handoff_simulation", lambda: {"generated": True}),
45
+ ):
46
+ if fail_phase == name:
47
+ raise RuntimeError("deterministic fixture failure")
48
+ result = operation()
49
+ # EP-GOLDEN-001 validates provider selection and contracts, not
50
+ # host-specific executable availability or private connectivity.
51
+ if name == "qualification" and result["qualification"] != "PASS":
52
+ raise RuntimeError("qualification failed")
53
+ phases.append({"phase": name, "status": "PASS"})
54
+ payload = {"scenario_id": SCENARIO_ID, "result": "ENGINEERING_PLATFORM_GOLDEN_PASS", "executed_at": datetime.now(timezone.utc).isoformat(), "phases": phases, "evidence": ["platform_identity", "workspace_identity", "providers", "readiness", "qualification", "handoff_simulation"]}
55
+ except Exception as error:
56
+ payload = {"scenario_id": SCENARIO_ID, "result": "ENGINEERING_PLATFORM_GOLDEN_FAIL", "executed_at": datetime.now(timezone.utc).isoformat(), "phases": phases, "failed_phase": fail_phase or (phases[-1]["phase"] if phases else "repository_bootstrap"), "diagnostic": str(error), "expected_state": "ENGINEERING_PLATFORM_GOLDEN_PASS", "remediation": "Correct the reported configuration, provider, readiness or qualification failure and rerun EP-GOLDEN-001."}
57
+ if evidence_directory is not None:
58
+ evidence_directory.mkdir(mode=0o700, parents=True, exist_ok=True)
59
+ (evidence_directory / "ep-golden-001.json").write_text(
60
+ json.dumps(payload, indent=2, sort_keys=True) + "\n",
61
+ encoding="utf-8",
62
+ )
63
+ return payload
@@ -0,0 +1,171 @@
1
+ """Bounded, local-only preferences for the private Engineering dashboard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ import os
7
+ from pathlib import Path
8
+ import json
9
+
10
+ from .storage import open_storage
11
+
12
+
13
+ DEFAULTS = {
14
+ "log_retention_days": 30,
15
+ "telemetry_retention_days": 90,
16
+ "log_level": "INFO",
17
+ "inbox_scan_interval_seconds": 15,
18
+ "open_pr_check_interval_seconds": 30,
19
+ "dashboard_stream_interval_seconds": 1,
20
+ "platform_health_refresh_seconds": 15,
21
+ "component_details_refresh_seconds": 5,
22
+ "provider_readiness_refresh_seconds": 300,
23
+ "codex_capacity_reserve_percent": 0,
24
+ }
25
+ OPTIONS = {
26
+ "log_retention_days": frozenset({30, 60, 90, 120, 180, 360}),
27
+ "telemetry_retention_days": frozenset({30, 60, 90, 120, 180, 360}),
28
+ "log_level": frozenset({"INFO", "DEBUG"}),
29
+ "inbox_scan_interval_seconds": frozenset({5, 15, 30, 60}),
30
+ "open_pr_check_interval_seconds": frozenset({30, 60}),
31
+ "dashboard_stream_interval_seconds": frozenset(range(1, 11)),
32
+ "platform_health_refresh_seconds": frozenset({5, 15, 30, 60}),
33
+ "component_details_refresh_seconds": frozenset({5, 15, 30, 60}),
34
+ "provider_readiness_refresh_seconds": frozenset({60, 300, 600}),
35
+ "codex_capacity_reserve_percent": frozenset({0, 5, 10, 15, 20, 25, 50, 75}),
36
+ }
37
+ PREFIX = "dashboard_configuration."
38
+ INBOX_ROOT_KEY = PREFIX + "inbox_root"
39
+ _UNSET = object()
40
+
41
+
42
+ class DashboardConfigurationConflict(ValueError):
43
+ """Raised when a client tries to save over a newer local preference."""
44
+
45
+
46
+ def get(root: Path) -> dict[str, object]:
47
+ connection = open_storage(root)
48
+ try:
49
+ values = dict(DEFAULTS)
50
+ for key, raw in connection.execute(
51
+ "SELECT key,value FROM engineering_metadata WHERE key LIKE ?", (PREFIX + "%",)
52
+ ):
53
+ name = str(key).removeprefix(PREFIX)
54
+ try:
55
+ value = json.loads(raw)
56
+ except (TypeError, json.JSONDecodeError):
57
+ continue
58
+ if name in OPTIONS and value in OPTIONS[name]:
59
+ values[name] = value
60
+ return values
61
+ finally:
62
+ connection.close()
63
+
64
+
65
+ def update(
66
+ root: Path,
67
+ key: str,
68
+ value: object,
69
+ *,
70
+ expected_previous: object = _UNSET,
71
+ ) -> dict[str, object]:
72
+ if key == "codex_capacity_reserve_percent" and (
73
+ not isinstance(value, int) or isinstance(value, bool)
74
+ ):
75
+ raise ValueError("Ongeldige dashboardinstelling.")
76
+ if key not in OPTIONS or value not in OPTIONS[key]:
77
+ raise ValueError("Ongeldige dashboardinstelling.")
78
+ connection = open_storage(root)
79
+ try:
80
+ previous = DEFAULTS[key]
81
+ row = connection.execute(
82
+ "SELECT value FROM engineering_metadata WHERE key=?", (PREFIX + key,)
83
+ ).fetchone()
84
+ if row is not None:
85
+ try:
86
+ stored = json.loads(row[0])
87
+ except (TypeError, json.JSONDecodeError):
88
+ stored = previous
89
+ if stored in OPTIONS[key]:
90
+ previous = stored
91
+ if expected_previous is not _UNSET and expected_previous != previous:
92
+ raise DashboardConfigurationConflict("De instelling is in een ander dashboardvenster gewijzigd.")
93
+ connection.execute(
94
+ "INSERT INTO engineering_metadata(key,value) VALUES(?,?) "
95
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value",
96
+ (PREFIX + key, json.dumps(value)),
97
+ )
98
+ return {"key": key, "previous": previous, "value": value,
99
+ "changed_at": datetime.now(timezone.utc).isoformat()}
100
+ finally:
101
+ connection.close()
102
+
103
+
104
+ def inbox_root(root: Path) -> Path | None:
105
+ """Return the validated host-owned Inbox root override, when configured."""
106
+ connection = open_storage(root)
107
+ try:
108
+ row = connection.execute(
109
+ "SELECT value FROM engineering_metadata WHERE key=?", (INBOX_ROOT_KEY,)
110
+ ).fetchone()
111
+ finally:
112
+ connection.close()
113
+ if row is None:
114
+ return None
115
+ try:
116
+ raw = json.loads(row[0])
117
+ except (TypeError, json.JSONDecodeError):
118
+ return None
119
+ if not isinstance(raw, str):
120
+ return None
121
+ candidate = Path(raw).expanduser()
122
+ if not candidate.is_absolute():
123
+ return None
124
+ return candidate.resolve()
125
+
126
+
127
+ def update_inbox_root(root: Path, value: object) -> dict[str, object]:
128
+ """Persist a writable Inbox root, accepting either the root or its Inbox child."""
129
+ if not isinstance(value, str) or not value.strip():
130
+ raise ValueError("Kies een bestaande lokale Inbox-map.")
131
+ candidate = Path(value).expanduser()
132
+ if not candidate.is_absolute():
133
+ raise ValueError("De Inbox-locatie moet een absoluut lokaal pad zijn.")
134
+ candidate = candidate.resolve()
135
+ inbox = candidate / "Inbox"
136
+ if candidate.name == "Inbox" and candidate.is_dir() and os.access(candidate, os.W_OK):
137
+ candidate, inbox = candidate.parent, candidate
138
+ if not candidate.is_dir() or not inbox.is_dir() or not os.access(inbox, os.W_OK):
139
+ raise ValueError("De gekozen map bevat geen beschrijfbare Inbox-map.")
140
+ previous = inbox_root(root)
141
+ connection = open_storage(root)
142
+ try:
143
+ connection.execute(
144
+ "INSERT INTO engineering_metadata(key,value) VALUES(?,?) "
145
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value",
146
+ (INBOX_ROOT_KEY, json.dumps(str(candidate))),
147
+ )
148
+ finally:
149
+ connection.close()
150
+ return {
151
+ "key": "inbox_root",
152
+ "previous": str(previous) if previous else None,
153
+ "value": str(candidate),
154
+ "changed_at": datetime.now(timezone.utc).isoformat(),
155
+ }
156
+
157
+
158
+ def restore_inbox_root(root: Path, previous: Path | None) -> None:
159
+ """Restore the Inbox-root preference after an unconfirmed route change."""
160
+ connection = open_storage(root)
161
+ try:
162
+ if previous is None:
163
+ connection.execute("DELETE FROM engineering_metadata WHERE key=?", (INBOX_ROOT_KEY,))
164
+ else:
165
+ connection.execute(
166
+ "INSERT INTO engineering_metadata(key,value) VALUES(?,?) "
167
+ "ON CONFLICT(key) DO UPDATE SET value=excluded.value",
168
+ (INBOX_ROOT_KEY, json.dumps(str(previous))),
169
+ )
170
+ finally:
171
+ connection.close()