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,235 @@
1
+ """CENTRAL-owned identity bindings for bounded Server-owned producers."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from datetime import datetime, timezone
6
+ import json
7
+ import re
8
+ import sqlite3
9
+ from uuid import uuid4
10
+
11
+ from .platform_admin import require_installation_owner
12
+
13
+ DEPENDABOT = "DEPENDABOT"
14
+ GITHUB_REPOSITORY = "GITHUB_REPOSITORY"
15
+ _GITHUB = re.compile(r"^[a-z0-9][a-z0-9_.-]*/[a-z0-9][a-z0-9_.-]*$")
16
+
17
+
18
+ class ProducerBindingError(ValueError):
19
+ """Stable, fail-closed binding result."""
20
+
21
+ def __init__(self, code: str) -> None:
22
+ super().__init__(code)
23
+ self.code = code
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class ExternalProducerBinding:
28
+ binding_id: str
29
+ project_id: str
30
+ repository_id: str
31
+ version: int
32
+
33
+
34
+ def _validate_binding_kind(producer_type: object, external_resource_type: object) -> None:
35
+ """Keep the first registry deliberately bounded to approved producers."""
36
+ if producer_type != DEPENDABOT or external_resource_type != GITHUB_REPOSITORY:
37
+ raise ProducerBindingError("UNSUPPORTED_PRODUCER_BINDING")
38
+
39
+
40
+ def normalize_github_repository(value: object) -> str:
41
+ """Normalize an external identity only; never inspect a local Git remote."""
42
+ if not isinstance(value, str):
43
+ raise ProducerBindingError("INVALID_EXTERNAL_IDENTITY")
44
+ normalized = value.strip().removesuffix("/").removesuffix(".git").casefold()
45
+ if normalized.startswith("https://github.com/"):
46
+ normalized = normalized.removeprefix("https://github.com/")
47
+ if normalized.startswith("git@github.com:"):
48
+ normalized = normalized.removeprefix("git@github.com:")
49
+ if not _GITHUB.fullmatch(normalized):
50
+ raise ProducerBindingError("INVALID_EXTERNAL_IDENTITY")
51
+ return normalized
52
+
53
+
54
+ def resolve(
55
+ connection: sqlite3.Connection,
56
+ *,
57
+ producer_type: str,
58
+ external_resource_type: str,
59
+ external_resource_identity: object,
60
+ ) -> ExternalProducerBinding:
61
+ _validate_binding_kind(producer_type, external_resource_type)
62
+ identity = normalize_github_repository(external_resource_identity)
63
+ rows = connection.execute("""SELECT b.binding_id,b.project_id,b.repository_id,b.version,p.status,r.project_id
64
+ FROM ep_external_producer_bindings b JOIN ep_project_registrations p ON p.project_id=b.project_id
65
+ JOIN ep_repository_registrations r ON r.repository_id=b.repository_id
66
+ WHERE b.producer_type=? AND b.external_resource_type=? AND b.external_resource_identity=? AND b.status='ACTIVE'""", (producer_type, external_resource_type, identity)).fetchall()
67
+ if len(rows) != 1:
68
+ raise ProducerBindingError("BINDING_NOT_FOUND" if not rows else "BINDING_CONFLICT")
69
+ binding_id, project_id, repository_id, version, project_status, repository_project = rows[0]
70
+ if project_status != "ACTIVE":
71
+ raise ProducerBindingError("PROJECT_INACTIVE")
72
+ if project_id != repository_project:
73
+ raise ProducerBindingError("REPOSITORY_NOT_AUTHORIZED")
74
+ return ExternalProducerBinding(str(binding_id), str(project_id), str(repository_id), int(version))
75
+
76
+
77
+ def active_external_identities(
78
+ connection: sqlite3.Connection,
79
+ *,
80
+ producer_type: str,
81
+ external_resource_type: str,
82
+ ) -> tuple[str, ...]:
83
+ """Return only active observation targets for a bounded Server producer."""
84
+ _validate_binding_kind(producer_type, external_resource_type)
85
+ return tuple(
86
+ str(row[0])
87
+ for row in connection.execute(
88
+ """SELECT external_resource_identity
89
+ FROM ep_external_producer_bindings
90
+ WHERE producer_type=? AND external_resource_type=? AND status='ACTIVE'
91
+ ORDER BY external_resource_identity""",
92
+ (producer_type, external_resource_type),
93
+ )
94
+ )
95
+
96
+
97
+ def register(
98
+ connection: sqlite3.Connection,
99
+ *,
100
+ data_root: object,
101
+ producer_type: str,
102
+ external_resource_type: str,
103
+ external_resource_identity: object,
104
+ project_id: str,
105
+ repository_id: str,
106
+ reason: str,
107
+ ) -> ExternalProducerBinding:
108
+ """Register a binding through the installation-owner-only admin boundary."""
109
+ from pathlib import Path
110
+ actor = require_installation_owner(Path(data_root))
111
+ _validate_binding_kind(producer_type, external_resource_type)
112
+ if not isinstance(project_id, str) or not project_id or not isinstance(repository_id, str) or not repository_id:
113
+ raise ProducerBindingError("INVALID_BINDING")
114
+ if not isinstance(reason, str) or not reason.strip() or len(reason.strip()) > 512:
115
+ raise ProducerBindingError("INVALID_BINDING")
116
+ identity = normalize_github_repository(external_resource_identity)
117
+ target = connection.execute(
118
+ """SELECT p.status,r.project_id
119
+ FROM ep_project_registrations AS p
120
+ LEFT JOIN ep_repository_registrations AS r ON r.repository_id=?
121
+ WHERE p.project_id=?""",
122
+ (repository_id, project_id),
123
+ ).fetchone()
124
+ if target is None or target[0] != "ACTIVE" or target[1] != project_id:
125
+ raise ProducerBindingError("REPOSITORY_NOT_AUTHORIZED")
126
+ now, binding_id = datetime.now(timezone.utc).isoformat(), "binding-" + uuid4().hex
127
+ try:
128
+ connection.execute(
129
+ """INSERT INTO ep_external_producer_bindings(
130
+ binding_id,producer_type,external_resource_type,external_resource_identity,
131
+ project_id,repository_id,status,version,created_at,created_by,updated_at,provenance
132
+ ) VALUES(?,?,?,?,?,?, 'ACTIVE',1,?,?,?,?)""",
133
+ (
134
+ binding_id,
135
+ producer_type,
136
+ external_resource_type,
137
+ identity,
138
+ project_id,
139
+ repository_id,
140
+ now,
141
+ actor,
142
+ now,
143
+ json.dumps({"reason": reason.strip()}, sort_keys=True),
144
+ ),
145
+ )
146
+ except sqlite3.IntegrityError as error:
147
+ raise ProducerBindingError("BINDING_CONFLICT") from error
148
+ connection.execute(
149
+ """INSERT INTO ep_external_producer_binding_audit(
150
+ binding_id,action,actor,reason,payload,recorded_at
151
+ ) VALUES(?,?,?,?,?,?)""",
152
+ (
153
+ binding_id,
154
+ "REGISTER",
155
+ actor,
156
+ reason.strip(),
157
+ json.dumps(
158
+ {"identity": identity, "project_id": project_id, "repository_id": repository_id},
159
+ sort_keys=True,
160
+ ),
161
+ now,
162
+ ),
163
+ )
164
+ return ExternalProducerBinding(binding_id, project_id, repository_id, 1)
165
+
166
+
167
+ def list_bindings(connection: sqlite3.Connection, *, data_root: object) -> list[dict[str, object]]:
168
+ """Return bounded binding metadata to the installation-owner admin only."""
169
+ from pathlib import Path
170
+
171
+ require_installation_owner(Path(data_root))
172
+ rows = connection.execute(
173
+ """SELECT binding_id,producer_type,external_resource_type,external_resource_identity,
174
+ project_id,repository_id,status,version,created_at,updated_at
175
+ FROM ep_external_producer_bindings
176
+ ORDER BY producer_type,external_resource_type,external_resource_identity"""
177
+ ).fetchall()
178
+ columns = (
179
+ "binding_id",
180
+ "producer_type",
181
+ "external_resource_type",
182
+ "external_resource_identity",
183
+ "project_id",
184
+ "repository_id",
185
+ "status",
186
+ "version",
187
+ "created_at",
188
+ "updated_at",
189
+ )
190
+ return [dict(zip(columns, row, strict=True)) for row in rows]
191
+
192
+
193
+ def deactivate(
194
+ connection: sqlite3.Connection,
195
+ *,
196
+ data_root: object,
197
+ binding_id: object,
198
+ reason: str,
199
+ ) -> ExternalProducerBinding:
200
+ """Deactivate exactly one active binding; historical provenance remains intact."""
201
+ from pathlib import Path
202
+
203
+ actor = require_installation_owner(Path(data_root))
204
+ if not isinstance(binding_id, str) or not binding_id or not isinstance(reason, str) or not reason.strip() or len(reason.strip()) > 512:
205
+ raise ProducerBindingError("INVALID_BINDING")
206
+ row = connection.execute(
207
+ """SELECT binding_id,project_id,repository_id,version
208
+ FROM ep_external_producer_bindings
209
+ WHERE binding_id=? AND status='ACTIVE'""",
210
+ (binding_id,),
211
+ ).fetchone()
212
+ if row is None:
213
+ raise ProducerBindingError("BINDING_NOT_ACTIVE")
214
+ now = datetime.now(timezone.utc).isoformat()
215
+ next_version = int(row[3]) + 1
216
+ connection.execute(
217
+ """UPDATE ep_external_producer_bindings
218
+ SET status='INACTIVE',version=?,updated_at=?
219
+ WHERE binding_id=? AND status='ACTIVE'""",
220
+ (next_version, now, binding_id),
221
+ )
222
+ connection.execute(
223
+ """INSERT INTO ep_external_producer_binding_audit(
224
+ binding_id,action,actor,reason,payload,recorded_at
225
+ ) VALUES(?,?,?,?,?,?)""",
226
+ (
227
+ binding_id,
228
+ "DEACTIVATE",
229
+ actor,
230
+ reason.strip(),
231
+ json.dumps({"previous_version": int(row[3])}, sort_keys=True),
232
+ now,
233
+ ),
234
+ )
235
+ return ExternalProducerBinding(str(row[0]), str(row[1]), str(row[2]), next_version)
@@ -0,0 +1,249 @@
1
+ """Thin file ingress for canonical CENTRAL submissions.
2
+
3
+ This module owns only physical delivery acknowledgement. It does not open a
4
+ database, select work, or retain any run/lifecycle state. An explicit project
5
+ id is required in every file; a checkout name and current directory are never
6
+ consulted for authority.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from datetime import datetime, timezone
11
+ import hashlib
12
+ import json
13
+ import os
14
+ from pathlib import Path
15
+ import threading
16
+ import time
17
+ from collections.abc import Callable
18
+ from urllib.error import HTTPError, URLError
19
+ from urllib.request import Request, urlopen
20
+ from .submission_intake import SubmissionIntakeError, normalize_human_file
21
+
22
+
23
+ MAX_FILE_BYTES = 131072
24
+ MAX_REASON_LENGTH = 160
25
+ HEARTBEAT_FILENAME = "file-inbox-heartbeat.json"
26
+ QUALIFICATION_FAULT_ENVIRONMENT = "EP_FILE_INBOX_QUALIFICATION_FAULT"
27
+ SUPPORTED_FILE_SUFFIXES = frozenset({".json", ".txt", ".md"})
28
+
29
+
30
+ class FileInboxError(ValueError):
31
+ """A bounded, terminal file-transport rejection."""
32
+
33
+
34
+ Admission = Callable[[dict[str, object], str, str], dict[str, object]]
35
+
36
+
37
+ def _utcnow() -> str:
38
+ return datetime.now(timezone.utc).isoformat()
39
+
40
+
41
+ def _layout(root: Path) -> dict[str, Path]:
42
+ folders = {name: root / name for name in ("incoming", "processing", "accepted", "quarantine")}
43
+ for folder in folders.values():
44
+ folder.mkdir(mode=0o700, parents=True, exist_ok=True)
45
+ return folders
46
+
47
+
48
+ def _reason(value: object) -> str:
49
+ rendered = str(value).replace("\n", " ").replace("\r", " ")
50
+ return rendered[:MAX_REASON_LENGTH] or "MALFORMED_FILE"
51
+
52
+
53
+ def _read_envelope(path: Path) -> tuple[dict[str, object], bytes, str]:
54
+ raw = path.read_bytes()
55
+ if not 0 < len(raw) <= MAX_FILE_BYTES or b"\0" in raw:
56
+ raise FileInboxError("MALFORMED_FILE")
57
+ try:
58
+ envelope = json.loads(raw.decode("utf-8")) if path.suffix.lower() == ".json" else normalize_human_file(path)
59
+ except (UnicodeDecodeError, json.JSONDecodeError, SubmissionIntakeError) as error:
60
+ raise FileInboxError("MALFORMED_FILE") from error
61
+ if not isinstance(envelope, dict) or set(envelope) != {"project_id", "submission"}:
62
+ raise FileInboxError("MALFORMED_FILE")
63
+ if not isinstance(envelope["project_id"], str) or not envelope["project_id"]:
64
+ raise FileInboxError("MALFORMED_FILE")
65
+ if not isinstance(envelope["submission"], dict):
66
+ raise FileInboxError("MALFORMED_FILE")
67
+ digest = hashlib.sha256(raw).hexdigest()
68
+ return envelope, raw, digest
69
+
70
+
71
+ def _receipt_path(folder: Path, digest: str) -> Path:
72
+ return folder / f"{digest}.receipt.json"
73
+
74
+
75
+ def _write_receipt(path: Path, payload: dict[str, object]) -> None:
76
+ temporary = path.with_suffix(".partial")
77
+ temporary.write_text(json.dumps(payload, sort_keys=True) + "\n", encoding="utf-8")
78
+ temporary.chmod(0o600)
79
+ os.replace(temporary, path)
80
+
81
+
82
+ def _move(source: Path, destination: Path) -> None:
83
+ if destination.exists():
84
+ source.unlink()
85
+ else:
86
+ os.replace(source, destination)
87
+
88
+
89
+ def _submit(server: str, credential: str, envelope: dict[str, object], *, receipt_id: str, received_at: str) -> dict[str, object]:
90
+ project_id = str(envelope["project_id"])
91
+ submission = dict(envelope["submission"])
92
+ # This physical-file digest is the delivery key. It survives a restart
93
+ # between CENTRAL acceptance and archive acknowledgement.
94
+ submission["idempotency_key"] = receipt_id
95
+ submission["transport_receipt_id"] = receipt_id
96
+ submission["transport_received_at"] = received_at
97
+ request = Request(
98
+ server.rstrip("/") + f"/v1/projects/{project_id}/submissions",
99
+ data=json.dumps(submission, sort_keys=True).encode("utf-8"), method="POST",
100
+ headers={"Content-Type": "application/json", "Authorization": f"Bearer {credential}", "EP-Submission-Transport": "FILE_INBOX"},
101
+ )
102
+ try:
103
+ with urlopen(request, timeout=15) as response: # nosec B310 -- configured CENTRAL endpoint
104
+ result = json.loads(response.read().decode("utf-8"))
105
+ except HTTPError as error:
106
+ if error.code >= 500:
107
+ raise URLError("CENTRAL_UNAVAILABLE") from error
108
+ try:
109
+ detail = json.loads(error.read().decode("utf-8")).get("error", "CENTRAL_REJECTED")
110
+ except (UnicodeDecodeError, json.JSONDecodeError):
111
+ detail = "CENTRAL_REJECTED"
112
+ raise FileInboxError(_reason(detail)) from error
113
+ except (URLError, TimeoutError) as error:
114
+ raise URLError("CENTRAL_UNAVAILABLE") from error
115
+ if not isinstance(result, dict) or not isinstance(result.get("submission_id"), str):
116
+ raise URLError("CENTRAL_UNAVAILABLE")
117
+ return result
118
+
119
+
120
+ def process_once(
121
+ root: Path, *, server: str | None = None, credential: str | None = None,
122
+ admission: Admission | None = None,
123
+ ) -> dict[str, int]:
124
+ """Deliver every pending file once; unavailable CENTRAL leaves it retryable.
125
+
126
+ The installed Server child supplies ``admission``. It is an in-process
127
+ call to the same canonical application service used after HTTP/CLI caller
128
+ authentication; File Inbox never gets a project bearer credential.
129
+ ``server``/``credential`` remain private test seams for the transport's
130
+ public HTTP equivalence tests and are not an installed executable surface.
131
+ """
132
+ folders = _layout(root)
133
+ counts = {"accepted": 0, "quarantined": 0, "retryable": 0}
134
+ for source in sorted(path for path in folders["incoming"].iterdir() if path.suffix.lower() in SUPPORTED_FILE_SUFFIXES):
135
+ claimed = folders["processing"] / source.name
136
+ _move(source, claimed)
137
+ for claimed in sorted(path for path in folders["processing"].iterdir() if path.suffix.lower() in SUPPORTED_FILE_SUFFIXES):
138
+ try:
139
+ envelope, _raw, digest = _read_envelope(claimed)
140
+ # Installed qualification only: terminate the real Server process
141
+ # at a named transport boundary. Normal runtime never sets this.
142
+ if os.environ.get(QUALIFICATION_FAULT_ENVIRONMENT) == "AFTER_CLAIM_BEFORE_SUBMIT":
143
+ os._exit(86) # nosec B605 -- deliberate crash-canary boundary
144
+ receipt_id, received_at = f"file:{digest}", _utcnow()
145
+ if admission is not None:
146
+ receipt = admission(envelope, receipt_id, received_at)
147
+ elif server is not None and credential is not None:
148
+ receipt = _submit(server, credential, envelope, receipt_id=receipt_id, received_at=received_at)
149
+ else:
150
+ raise FileInboxError("FILE_INBOX_AUTH_UNAVAILABLE")
151
+ if os.environ.get(QUALIFICATION_FAULT_ENVIRONMENT) == "AFTER_CENTRAL_ACCEPT_BEFORE_ARCHIVE":
152
+ os._exit(87) # nosec B605 -- deliberate crash-canary boundary
153
+ submission = envelope["submission"]
154
+ constraints = submission.get("constraints") if isinstance(submission, dict) else {}
155
+ normalized = json.dumps(envelope, sort_keys=True, separators=(",", ":")).encode("utf-8")
156
+ _write_receipt(_receipt_path(folders["accepted"], digest), {
157
+ "transport": "FILE_INBOX", "receipt_id": receipt_id, "received_at": received_at,
158
+ "source_digest": digest, "project_id": envelope["project_id"],
159
+ "repository_id": submission.get("repository_id") if isinstance(submission, dict) else None,
160
+ "requested_mode": constraints.get("mode") if isinstance(constraints, dict) else None,
161
+ "normalization_method": constraints.get("normalization") if isinstance(constraints, dict) else "STRUCTURED",
162
+ "normalization_version": constraints.get("normalization") if isinstance(constraints, dict) else "STRUCTURED_V1",
163
+ "normalized_submission_digest": hashlib.sha256(normalized).hexdigest(),
164
+ "submission_id": receipt["submission_id"], "duplicate": bool(receipt.get("duplicate")),
165
+ })
166
+ _move(claimed, folders["accepted"] / f"{digest}.json")
167
+ counts["accepted"] += 1
168
+ except FileInboxError as error:
169
+ # A terminal parser/admission error is transport acknowledgement,
170
+ # not a CENTRAL lifecycle mutation.
171
+ reason = _reason(error)
172
+ digest = hashlib.sha256(claimed.name.encode("utf-8")).hexdigest()
173
+ _write_receipt(_receipt_path(folders["quarantine"], digest), {"transport": "FILE_INBOX", "reason": reason})
174
+ _move(claimed, folders["quarantine"] / f"{digest}.json")
175
+ counts["quarantined"] += 1
176
+ except URLError:
177
+ counts["retryable"] += 1
178
+ return counts
179
+
180
+
181
+ def heartbeat_path(root: Path) -> Path:
182
+ """Return the installation-owned liveness record for this adapter.
183
+
184
+ This is deliberately a small, secret-free transport observation. It is
185
+ neither a queue nor a lifecycle store; CENTRAL remains the sole authority
186
+ for accepted submissions and dispatch state.
187
+ """
188
+ return root / HEARTBEAT_FILENAME
189
+
190
+
191
+ def read_heartbeat(root: Path) -> dict[str, object] | None:
192
+ try:
193
+ payload = json.loads(heartbeat_path(root).read_text(encoding="utf-8"))
194
+ except (OSError, json.JSONDecodeError):
195
+ return None
196
+ return payload if isinstance(payload, dict) else None
197
+
198
+
199
+ class FileInboxService:
200
+ """Installed Server-composed File Inbox adapter with a bounded heartbeat."""
201
+
202
+ def __init__(
203
+ self, root: Path, *, admission: Admission | None = None,
204
+ server: str | None = None, credential: str | None = None, interval_seconds: float = 2.0,
205
+ ) -> None:
206
+ self.root, self.admission, self.server, self.credential = root, admission, server, credential
207
+ self.interval_seconds = interval_seconds
208
+ self._stop = threading.Event()
209
+ self._thread: threading.Thread | None = None
210
+ self._counts = {"accepted": 0, "quarantined": 0, "retryable": 0}
211
+ self._recent_error: str | None = None
212
+
213
+ def _write_heartbeat(self) -> None:
214
+ folders = _layout(self.root)
215
+ ready = self.admission is not None
216
+ payload = {
217
+ # A live thread without a submission credential cannot admit a
218
+ # file. Process liveness must never be projected as readiness.
219
+ "state": "READY" if ready else "RUNNING_NOT_READY",
220
+ "readiness": "SUBMISSION_CAPABLE" if ready else "AUTHENTICATION_UNAVAILABLE",
221
+ "updated_at": _utcnow(),
222
+ "watched_location": str(self.root),
223
+ "delivery_retry": "PENDING" if self._counts["retryable"] else "NONE",
224
+ "quarantine_count": len(list(folders["quarantine"].glob("*.json"))),
225
+ "recent_error": self._recent_error or (None if ready else "FILE_INBOX_AUTH_UNAVAILABLE"),
226
+ }
227
+ _write_receipt(heartbeat_path(self.root), payload)
228
+
229
+ def _run(self) -> None:
230
+ while not self._stop.is_set():
231
+ try:
232
+ if self.admission is not None:
233
+ self._counts = process_once(self.root, admission=self.admission)
234
+ self._recent_error = None
235
+ self._write_heartbeat()
236
+ except (OSError, ValueError, URLError) as error:
237
+ self._recent_error = _reason(error)
238
+ self._write_heartbeat()
239
+ self._stop.wait(self.interval_seconds)
240
+
241
+ def start(self) -> None:
242
+ _layout(self.root)
243
+ self._thread = threading.Thread(target=self._run, name="engineering-platform-file-inbox", daemon=True)
244
+ self._thread.start()
245
+
246
+ def stop(self) -> None:
247
+ self._stop.set()
248
+ if self._thread is not None:
249
+ self._thread.join(timeout=self.interval_seconds + 1)