ultra-builder-pro-cli 5.0.0 → 6.0.0

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 (159) hide show
  1. package/CHANGELOG.md +113 -3
  2. package/README.md +67 -478
  3. package/README.zh-CN.md +49 -422
  4. package/adapters/_shared/frontmatter.cjs +1 -18
  5. package/adapters/_shared/plugin-core.cjs +17 -83
  6. package/adapters/_shared/runtime-assets.cjs +6 -120
  7. package/adapters/claude.js +8 -24
  8. package/adapters/codex.js +16 -38
  9. package/bin/install.js +35 -57
  10. package/docs/PLUGIN-ISOLATION-CONTRACT.md +13 -101
  11. package/docs/examples/drive/claude-reviewer.sh +53 -0
  12. package/docs/examples/drive/claude-writer.sh +43 -0
  13. package/engine/FORMATS.md +1051 -0
  14. package/engine/checks.cjs +139 -0
  15. package/engine/cli.cjs +148 -0
  16. package/engine/completion.cjs +48 -0
  17. package/engine/context.cjs +94 -0
  18. package/engine/drive.cjs +220 -0
  19. package/engine/expect-worker.cjs +5 -0
  20. package/engine/expect.cjs +33 -0
  21. package/engine/formats.cjs +189 -0
  22. package/engine/git-state.cjs +112 -0
  23. package/engine/ledger.cjs +205 -0
  24. package/engine/loading.cjs +28 -0
  25. package/engine/merge.cjs +273 -0
  26. package/engine/milestone.cjs +89 -0
  27. package/engine/plan.cjs +232 -0
  28. package/engine/project.cjs +5 -0
  29. package/engine/signals.cjs +3 -0
  30. package/engine/states.cjs +175 -0
  31. package/hooks/session-start.cjs +54 -0
  32. package/package.json +17 -52
  33. package/skills/ultra/SKILL.md +395 -0
  34. package/.ultra-template/changes/abandoned/.gitkeep +0 -0
  35. package/.ultra-template/changes/active/.gitkeep +0 -1
  36. package/.ultra-template/changes/archive/.gitkeep +0 -1
  37. package/.ultra-template/contexts/TEMPLATE.md +0 -135
  38. package/.ultra-template/decisions/.gitkeep +0 -1
  39. package/.ultra-template/evidence/.gitkeep +0 -1
  40. package/.ultra-template/north-star.md +0 -52
  41. package/.ultra-template/project-brief.md +0 -28
  42. package/.ultra-template/research/.gitkeep +0 -1
  43. package/.ultra-template/specs/architecture.md +0 -81
  44. package/.ultra-template/specs/discovery.md +0 -74
  45. package/.ultra-template/specs/product.md +0 -80
  46. package/.ultra-template/specs/research-distillate.md +0 -57
  47. package/.ultra-template/tasks.json +0 -4
  48. package/.ultra-template/test-report.json +0 -44
  49. package/adapters/_shared/host-profile.cjs +0 -5
  50. package/adapters/grok.js +0 -242
  51. package/adapters/kimi.js +0 -232
  52. package/adapters/opencode.js +0 -408
  53. package/adapters/zcode.js +0 -375
  54. package/bin/delegate-worker.cjs +0 -5
  55. package/bin/delegate.cjs +0 -5
  56. package/docs/ARCHITECTURE.md +0 -274
  57. package/docs/ARTIFACT-AUTHORITY.md +0 -305
  58. package/docs/DECISIONS.md +0 -229
  59. package/docs/PHILOSOPHY.md +0 -261
  60. package/docs/RUNTIME-COMPAT-MATRIX.md +0 -315
  61. package/docs/SKILL-AUTHORING.md +0 -113
  62. package/docs/WORKFLOW-LIFECYCLE.md +0 -350
  63. package/docs/evals/adversarial-review-2026-08-14.md +0 -84
  64. package/docs/evals/ultra-5-0-journey-results.md +0 -62
  65. package/docs/evals/ultra-5-0-live-acp-conformance.md +0 -93
  66. package/docs/evals/ultra-5-0-route-journeys.md +0 -145
  67. package/docs/evals/zcode-automation-2026-08-14.md +0 -103
  68. package/hooks/README.md +0 -52
  69. package/hooks/_common.py +0 -1779
  70. package/hooks/adapters/codex.py +0 -295
  71. package/hooks/adapters/grok.py +0 -134
  72. package/hooks/adapters/kimi.py +0 -156
  73. package/hooks/adapters/zcode.py +0 -227
  74. package/hooks/block_dangerous_commands.py +0 -201
  75. package/hooks/compact_context.py +0 -224
  76. package/hooks/mid_workflow_recall.py +0 -47
  77. package/hooks/post_edit_guard.py +0 -215
  78. package/hooks/session_context.py +0 -741
  79. package/skills/ultra-change/SKILL.md +0 -165
  80. package/skills/ultra-change/references/change-contract.md +0 -199
  81. package/skills/ultra-change/references/execution-grant.md +0 -109
  82. package/skills/ultra-change/references/primary-transfer.md +0 -273
  83. package/skills/ultra-change/scripts/validate_primary_transfer.cjs +0 -1928
  84. package/skills/ultra-delegate/SKILL.md +0 -195
  85. package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +0 -184
  86. package/skills/ultra-delegate/references/delegation-contract.md +0 -138
  87. package/skills/ultra-delegate/scripts/acp-conformance.cjs +0 -287
  88. package/skills/ultra-delegate/scripts/acp-driver.cjs +0 -762
  89. package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +0 -320
  90. package/skills/ultra-delegate/scripts/cli-driver.cjs +0 -61
  91. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-read-only.md +0 -15
  92. package/skills/ultra-delegate/scripts/delegate-profiles/kimi-write.md +0 -17
  93. package/skills/ultra-delegate/scripts/delegate-worker.cjs +0 -328
  94. package/skills/ultra-delegate/scripts/delegate.cjs +0 -690
  95. package/skills/ultra-delegate/scripts/delegate_wait.py +0 -86
  96. package/skills/ultra-delegate/scripts/host-profile.cjs +0 -267
  97. package/skills/ultra-deliver/SKILL.md +0 -194
  98. package/skills/ultra-deliver/references/baseline-reconciliation.md +0 -41
  99. package/skills/ultra-dev/SKILL.md +0 -265
  100. package/skills/ultra-dev/references/debugging.md +0 -22
  101. package/skills/ultra-domain-modeling/SKILL.md +0 -79
  102. package/skills/ultra-grilling/SKILL.md +0 -121
  103. package/skills/ultra-grilling/references/reframing.md +0 -76
  104. package/skills/ultra-init/SKILL.md +0 -143
  105. package/skills/ultra-init/references/brownfield-adoption.md +0 -32
  106. package/skills/ultra-init/scripts/init_project.cjs +0 -1031
  107. package/skills/ultra-plan/SKILL.md +0 -160
  108. package/skills/ultra-plan/references/task-evidence-v2.md +0 -300
  109. package/skills/ultra-plan/scripts/validate_task_evidence.cjs +0 -859
  110. package/skills/ultra-research/SKILL.md +0 -167
  111. package/skills/ultra-research/references/00-problem-validation.md +0 -36
  112. package/skills/ultra-research/references/01-opportunity-discovery.md +0 -33
  113. package/skills/ultra-research/references/02-market-assessment.md +0 -31
  114. package/skills/ultra-research/references/03-alternatives.md +0 -31
  115. package/skills/ultra-research/references/04-product-strategy.md +0 -41
  116. package/skills/ultra-research/references/05-assumptions-validation.md +0 -31
  117. package/skills/ultra-research/references/10-user-personas.md +0 -30
  118. package/skills/ultra-research/references/11-user-scenarios.md +0 -28
  119. package/skills/ultra-research/references/20-user-stories.md +0 -31
  120. package/skills/ultra-research/references/21-features-scope.md +0 -38
  121. package/skills/ultra-research/references/22-success-metrics.md +0 -33
  122. package/skills/ultra-research/references/30-architecture-context.md +0 -30
  123. package/skills/ultra-research/references/31-solution-strategy.md +0 -28
  124. package/skills/ultra-research/references/32-building-blocks.md +0 -28
  125. package/skills/ultra-research/references/40-deployment.md +0 -29
  126. package/skills/ultra-research/references/41-quality-risks.md +0 -28
  127. package/skills/ultra-research/references/99-synthesis.md +0 -59
  128. package/skills/ultra-research/references/north-star-v2.md +0 -168
  129. package/skills/ultra-research/references/wayfinding.md +0 -50
  130. package/skills/ultra-research/scripts/validate_north_star.cjs +0 -1174
  131. package/skills/ultra-review/SKILL.md +0 -275
  132. package/skills/ultra-review/references/adversarial-evaluation.md +0 -31
  133. package/skills/ultra-review/references/code.md +0 -17
  134. package/skills/ultra-review/references/comments.md +0 -12
  135. package/skills/ultra-review/references/design.md +0 -12
  136. package/skills/ultra-review/references/errors.md +0 -13
  137. package/skills/ultra-review/references/review-modes.md +0 -29
  138. package/skills/ultra-review/references/spec.md +0 -18
  139. package/skills/ultra-review/references/tests.md +0 -12
  140. package/skills/ultra-review/references/unified-schema.md +0 -284
  141. package/skills/ultra-review/references/worker-packet.md +0 -183
  142. package/skills/ultra-review/scripts/review_wait.py +0 -2943
  143. package/skills/ultra-status/SKILL.md +0 -209
  144. package/skills/ultra-status/references/route-contract.md +0 -100
  145. package/skills/ultra-tdd/SKILL.md +0 -91
  146. package/skills/ultra-tdd/references/templates/README.md +0 -20
  147. package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +0 -79
  148. package/skills/ultra-tdd/references/templates/persistence-real.ts +0 -78
  149. package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +0 -78
  150. package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +0 -73
  151. package/skills/ultra-tdd/references/templates/vertical-slice.ts +0 -66
  152. package/skills/ultra-tdd/references/test-execution.md +0 -23
  153. package/skills/ultra-test/SKILL.md +0 -244
  154. package/skills/ultra-test/references/export-syntax.md +0 -18
  155. package/skills/ultra-test/scripts/validate_review_transport.cjs +0 -549
  156. package/skills/ultra-test/scripts/worktree_digest.cjs +0 -576
  157. package/skills/ultra-think/SKILL.md +0 -56
  158. package/skills/ultra-think/references/autonomy-boundary.md +0 -48
  159. package/skills/ultra-think/references/interaction-boundary.md +0 -42
@@ -1,2943 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Wait for and validate Ultra review artifacts.
3
-
4
- Usage:
5
- python3 review_wait.py <session_path> packet --packet-digest <sha256>
6
- python3 review_wait.py <session_path> agents --packet-digest <sha256> <artifact-stem> [<artifact-stem> ...]
7
- python3 review_wait.py <session_path> summary --packet-digest <sha256>
8
- python3 review_wait.py <session_path> summary --packet-digest <sha256> --summary-snapshot-digest <sha256>
9
-
10
- New sessions require admitted v4. Add ``--legacy-v4`` immediately after the packet
11
- digest only when reading an immutable pre-admission v4 session, or ``--legacy-v3`` for
12
- an immutable historical v3 session; neither flag writes or upgrades artifacts.
13
-
14
- The agents mode validates the exact named JSON files. The summary mode validates the
15
- two-axis verdict contract. Timeout and poll intervals can be shortened in tests with
16
- UBP_REVIEW_WAIT_TIMEOUT and UBP_REVIEW_WAIT_POLL.
17
- """
18
-
19
- import hashlib
20
- import json
21
- import os
22
- import re
23
- import selectors
24
- import shutil
25
- import stat
26
- import subprocess
27
- import sys
28
- import tempfile
29
- import time
30
- from datetime import datetime
31
- from pathlib import Path, PurePosixPath
32
-
33
- FINDINGS_SCHEMA = "ultra-review-findings-v4"
34
- SUMMARY_SCHEMA = "ultra-review-summary-v4"
35
- LEGACY_FINDINGS_SCHEMA = "ultra-review-findings-v3"
36
- LEGACY_SUMMARY_SCHEMA = "ultra-review-summary-v3"
37
- ADMISSION_SCHEMA_V1 = "ultra-review-admission-v1"
38
- ADMISSION_SCHEMA_V2 = "ultra-review-admission-v2"
39
- ADMISSION_CONTRACT_V1 = "ultra-review-admission-required-v1"
40
- ADMISSION_CONTRACT_V2 = "ultra-review-admission-required-v2"
41
- STRICT_ADMISSION_CONTRACTS = {ADMISSION_CONTRACT_V1, ADMISSION_CONTRACT_V2}
42
- AXES = {"spec_fidelity", "engineering_standards"}
43
- SEVERITIES = {"P0", "P1", "P2", "P3"}
44
- AXIS_VERDICTS = {"PASS", "FAIL", "INCOMPLETE"}
45
- OVERALL_VERDICTS = {"APPROVE", "REQUEST_CHANGES", "INCOMPLETE"}
46
- REVIEW_MODES = {"task", "change", "plan"}
47
- EXECUTION_MODES = {"isolated", "sequential-shared-context"}
48
- SELECTION_STATUSES = {"selected", "skipped"}
49
- REVIEW_WORKERS = {
50
- "review-spec",
51
- "review-code",
52
- "review-tests",
53
- "review-errors",
54
- "review-design",
55
- "review-comments",
56
- }
57
- CANONICAL_WORKERS = (
58
- ("review-spec", "spec_fidelity", "skills/ultra-review/references/spec.md"),
59
- ("review-code", "engineering_standards", "skills/ultra-review/references/code.md"),
60
- ("review-tests", "engineering_standards", "skills/ultra-review/references/tests.md"),
61
- ("review-errors", "engineering_standards", "skills/ultra-review/references/errors.md"),
62
- ("review-design", "engineering_standards", "skills/ultra-review/references/design.md"),
63
- ("review-comments", "engineering_standards", "skills/ultra-review/references/comments.md"),
64
- )
65
- CANONICAL_WORKER_BY_AGENT = {
66
- agent: {"axis": axis, "lens": lens, "order": order}
67
- for order, (agent, axis, lens) in enumerate(CANONICAL_WORKERS)
68
- }
69
- PACKET_REQUIRED_FIELDS = {
70
- "$schema",
71
- "session",
72
- "mode",
73
- "created_at",
74
- "head",
75
- "range",
76
- "change_id",
77
- "task_ids",
78
- "acceptance",
79
- "public_seams",
80
- "north_star_trace",
81
- "context_files",
82
- "workers",
83
- "diff_files",
84
- "output_directory",
85
- }
86
- TRACE_FIELDS = {
87
- "path",
88
- "first_principles",
89
- "serves",
90
- "touches",
91
- "north_star_revision",
92
- "north_star_digest",
93
- }
94
- CONTEXT_FIELDS = {"path", "sha256"}
95
- WORKER_FIELDS = {"agent", "axis", "lens", "output"}
96
- REVIEW_HISTORY_FIELDS = {
97
- "parent_summary_ref",
98
- "parent_summary_digest",
99
- "unresolved_blocking_ids",
100
- }
101
- FOREIGN_REVIEW_SESSION = re.compile(
102
- r"\.ultra/reviews/([A-Za-z0-9][A-Za-z0-9._-]*)/"
103
- )
104
- ARTIFACT_STEM = re.compile(r"^[a-z][a-z0-9-]*$")
105
- FINDING_FIELDS = {
106
- "id", "axis", "severity", "category", "title", "file", "line", "trigger",
107
- "impact", "evidence", "suggestion",
108
- }
109
- MAX_JSON_BYTES = 8 * 1024 * 1024
110
- READ_CHUNK_BYTES = 64 * 1024
111
- MAX_VALIDATOR_REPORT_BYTES = 1024 * 1024
112
- VALIDATOR_TIMEOUT_SECONDS = 5
113
- ADMISSION_FIELDS = {
114
- "$schema",
115
- "version",
116
- "session",
117
- "packet_digest",
118
- "head",
119
- "observations",
120
- "north_star_report",
121
- "subject_digest",
122
- }
123
- ADMISSION_OBSERVATION_FIELDS_V1 = {"role", "path", "sha256"}
124
- ADMISSION_OBSERVATION_FIELDS_V2 = {"role", "path", "sha256", "byte_length"}
125
- PACKET_SUBJECT_OBSERVATION_FIELDS = {"role", "path", "sha256", "byte_length"}
126
- NORTH_STAR_REPORT_RECEIPT_FIELDS = {
127
- "schema",
128
- "sha256",
129
- "input_sha256",
130
- "input_byte_length",
131
- "source_observations",
132
- }
133
- NORTH_STAR_SOURCE_OBSERVATION_FIELDS = {"role", "path", "sha256", "byte_length"}
134
-
135
-
136
- def env_seconds(name: str, default: float) -> float:
137
- try:
138
- value = float(os.environ.get(name, default))
139
- except (TypeError, ValueError):
140
- return default
141
- return value if value > 0 else default
142
-
143
-
144
- DEFAULT_TIMEOUT = env_seconds("UBP_REVIEW_WAIT_TIMEOUT", 300.0)
145
- POLL_INTERVAL = env_seconds("UBP_REVIEW_WAIT_POLL", 2.0)
146
-
147
-
148
- def decode_json_bytes(data_bytes: bytes, label: str):
149
- try:
150
- data = json.loads(data_bytes.decode("utf-8"))
151
- except UnicodeDecodeError as error:
152
- return None, f"{label} is unreadable UTF-8 JSON: {error}"
153
- except json.JSONDecodeError as error:
154
- return None, f"{label} is unreadable JSON: {error}"
155
- if not isinstance(data, dict):
156
- return None, f"{label} top-level value must be an object"
157
- return data, None
158
-
159
-
160
- def _stat_identity(value):
161
- return (
162
- value.st_dev,
163
- value.st_ino,
164
- value.st_mode,
165
- value.st_size,
166
- value.st_mtime_ns,
167
- value.st_ctime_ns,
168
- )
169
-
170
-
171
- def _component_identity(value):
172
- return (value.st_dev, value.st_ino, value.st_mode)
173
-
174
-
175
- def _verify_fresh_repo_walk(
176
- root: Path,
177
- segments,
178
- directory_identities,
179
- file_identity,
180
- label: str,
181
- ):
182
- """Rewalk from the current root and reject any swapped path component."""
183
- directory_flags = os.O_RDONLY
184
- directory_flags |= getattr(os, "O_DIRECTORY", 0)
185
- directory_flags |= getattr(os, "O_NOFOLLOW", 0)
186
- descriptors = []
187
- try:
188
- root_path = os.stat(root, follow_symlinks=False)
189
- descriptor = os.open(root, directory_flags)
190
- descriptors.append(descriptor)
191
- root_fd = os.fstat(descriptor)
192
- if (
193
- _component_identity(root_path) != directory_identities[0]
194
- or _component_identity(root_fd) != directory_identities[0]
195
- ):
196
- return f"{label} repository root changed after its stable byte snapshot"
197
-
198
- for index, segment in enumerate(segments[:-1], start=1):
199
- component = os.stat(segment, dir_fd=descriptor, follow_symlinks=False)
200
- if stat.S_ISLNK(component.st_mode) or not stat.S_ISDIR(component.st_mode):
201
- return f"{label} path component changed after its stable byte snapshot"
202
- next_descriptor = os.open(segment, directory_flags, dir_fd=descriptor)
203
- descriptors.append(next_descriptor)
204
- opened = os.fstat(next_descriptor)
205
- if (
206
- _component_identity(component) != directory_identities[index]
207
- or _component_identity(opened) != directory_identities[index]
208
- ):
209
- return (
210
- f"{label} path component {'/'.join(segments[:index])} changed "
211
- "after its stable byte snapshot"
212
- )
213
- descriptor = next_descriptor
214
-
215
- current_file = os.stat(
216
- segments[-1],
217
- dir_fd=descriptor,
218
- follow_symlinks=False,
219
- )
220
- if (
221
- not stat.S_ISREG(current_file.st_mode)
222
- or _stat_identity(current_file) != file_identity
223
- ):
224
- return f"{label} changed after its stable byte snapshot"
225
- except (FileNotFoundError, NotADirectoryError):
226
- return f"{label} path changed after its stable byte snapshot"
227
- except OSError as error:
228
- return f"{label} fresh path identity cannot be verified: {error}"
229
- finally:
230
- for descriptor in reversed(descriptors):
231
- os.close(descriptor)
232
- return None
233
-
234
-
235
- def stable_read_repo_bytes(
236
- repo_root: Path,
237
- repo_path: str,
238
- label: str,
239
- max_bytes: int = MAX_JSON_BYTES,
240
- ):
241
- """Read one bounded file through a no-symlink repository descriptor walk."""
242
- normalized, path_error = validate_repo_path(repo_path, repo_root, label)
243
- if path_error:
244
- return None, path_error, False
245
- segments = normalized.split("/")
246
- directory_flags = os.O_RDONLY
247
- directory_flags |= getattr(os, "O_DIRECTORY", 0)
248
- directory_flags |= getattr(os, "O_NOFOLLOW", 0)
249
- file_flags = os.O_RDONLY
250
- file_flags |= getattr(os, "O_NONBLOCK", 0)
251
- file_flags |= getattr(os, "O_NOFOLLOW", 0)
252
- directory_descriptors = []
253
- descriptor = None
254
- directory_identities = []
255
- try:
256
- root = repo_root.resolve(strict=True)
257
- root_path = os.stat(root, follow_symlinks=False)
258
- directory_descriptor = os.open(root, directory_flags)
259
- directory_descriptors.append(directory_descriptor)
260
- root_fd = os.fstat(directory_descriptor)
261
- if _component_identity(root_path) != _component_identity(root_fd):
262
- return None, f"{label} repository root changed before its stable byte snapshot", False
263
- directory_identities.append(_component_identity(root_fd))
264
- for segment in segments[:-1]:
265
- component = os.stat(
266
- segment,
267
- dir_fd=directory_descriptor,
268
- follow_symlinks=False,
269
- )
270
- if stat.S_ISLNK(component.st_mode):
271
- return None, f"{label} path components must not be symlinks", False
272
- if not stat.S_ISDIR(component.st_mode):
273
- return None, f"{label} path component is not a directory", False
274
- next_descriptor = os.open(
275
- segment,
276
- directory_flags,
277
- dir_fd=directory_descriptor,
278
- )
279
- directory_descriptors.append(next_descriptor)
280
- opened_component = os.fstat(next_descriptor)
281
- if _component_identity(component) != _component_identity(opened_component):
282
- return None, (
283
- f"{label} path component changed before its stable byte snapshot"
284
- ), False
285
- directory_identities.append(_component_identity(opened_component))
286
- directory_descriptor = next_descriptor
287
-
288
- filename = segments[-1]
289
- before_path = os.stat(
290
- filename,
291
- dir_fd=directory_descriptor,
292
- follow_symlinks=False,
293
- )
294
- if stat.S_ISLNK(before_path.st_mode):
295
- return None, (
296
- f"{label} must be a stable repository-contained regular file, not a symlink"
297
- ), False
298
- if not stat.S_ISREG(before_path.st_mode):
299
- return None, f"{label} must be a stable repository-contained regular file", False
300
- if before_path.st_size > max_bytes:
301
- return None, f"{label} exceeds the {max_bytes}-byte artifact limit", False
302
-
303
- descriptor = os.open(filename, file_flags, dir_fd=directory_descriptor)
304
- before_fd = os.fstat(descriptor)
305
- if not stat.S_ISREG(before_fd.st_mode):
306
- return None, f"{label} must be a stable repository-contained regular file", False
307
- if _stat_identity(before_path) != _stat_identity(before_fd):
308
- return None, f"{label} changed before its stable byte snapshot could be read", False
309
-
310
- chunks = []
311
- observed = 0
312
- while True:
313
- chunk = os.read(descriptor, min(READ_CHUNK_BYTES, max_bytes + 1 - observed))
314
- if not chunk:
315
- break
316
- chunks.append(chunk)
317
- observed += len(chunk)
318
- if observed > max_bytes:
319
- return None, f"{label} exceeds the {max_bytes}-byte artifact limit", False
320
- after_fd = os.fstat(descriptor)
321
- after_path = os.stat(
322
- filename,
323
- dir_fd=directory_descriptor,
324
- follow_symlinks=False,
325
- )
326
- except FileNotFoundError:
327
- return None, f"{label} is missing", True
328
- except OSError as error:
329
- return None, f"{label} cannot be read as a stable regular file: {error}", False
330
- finally:
331
- if descriptor is not None:
332
- os.close(descriptor)
333
- for directory_descriptor in reversed(directory_descriptors):
334
- os.close(directory_descriptor)
335
-
336
- if (
337
- _stat_identity(before_fd) != _stat_identity(after_fd)
338
- or _stat_identity(after_fd) != _stat_identity(after_path)
339
- ):
340
- return None, f"{label} changed while its stable byte snapshot was read", False
341
-
342
- data_bytes = b"".join(chunks)
343
- if len(data_bytes) != after_fd.st_size:
344
- return None, f"{label} size changed while its stable byte snapshot was read", False
345
- rewalk_error = _verify_fresh_repo_walk(
346
- root,
347
- segments,
348
- directory_identities,
349
- _stat_identity(after_fd),
350
- label,
351
- )
352
- if rewalk_error:
353
- return None, rewalk_error, False
354
- return {
355
- "bytes": data_bytes,
356
- "sha256": hashlib.sha256(data_bytes).hexdigest(),
357
- "identity": _stat_identity(after_fd),
358
- "component_identities": tuple(directory_identities),
359
- "path": normalized,
360
- }, None, False
361
-
362
-
363
- def stable_read_repo_json(repo_root: Path, repo_path: str, label: str):
364
- snapshot, error, missing = stable_read_repo_bytes(repo_root, repo_path, label)
365
- if error:
366
- return None, snapshot, error, missing
367
- data, error = decode_json_bytes(snapshot["bytes"], label)
368
- return data, snapshot, error, False
369
-
370
-
371
- def nonempty_string(value) -> bool:
372
- return isinstance(value, str) and bool(value.strip())
373
-
374
-
375
- def digest_string(value) -> bool:
376
- return isinstance(value, str) and re.fullmatch(r"[0-9a-f]{64}", value) is not None
377
-
378
-
379
- def git_blob_digest_string(value) -> bool:
380
- return isinstance(value, str) and re.fullmatch(r"[0-9a-f]{40}", value) is not None
381
-
382
-
383
- def timestamp_string(value) -> bool:
384
- if not isinstance(value, str) or re.fullmatch(
385
- r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})",
386
- value,
387
- ) is None:
388
- return False
389
- try:
390
- parsed = datetime.fromisoformat(value[:-1] + "+00:00" if value.endswith("Z") else value)
391
- except ValueError:
392
- return False
393
- return parsed.tzinfo is not None
394
-
395
-
396
- def canonical_repo_root(session_path: Path):
397
- resolved_session = session_path.resolve()
398
- if (
399
- resolved_session.parent.name != "reviews"
400
- or resolved_session.parent.parent.name != ".ultra"
401
- ):
402
- return None, (
403
- "session path must resolve to the canonical repository location "
404
- "<repo>/.ultra/reviews/<session>"
405
- )
406
- return resolved_session.parent.parent.parent, None
407
-
408
-
409
- def validate_repo_path(value, repo_root: Path, field: str, allow_fragment=False):
410
- if not nonempty_string(value):
411
- return None, f"{field} must be a non-empty normalized repository-relative path"
412
-
413
- path_value = value
414
- if allow_fragment:
415
- if value.count("#") != 1:
416
- return None, f"{field} must contain one non-empty fragment"
417
- path_value, fragment = value.split("#", 1)
418
- if re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", fragment) is None:
419
- return None, f"{field} has an invalid fragment"
420
- elif "#" in value:
421
- return None, f"{field} must not contain a fragment"
422
-
423
- if (
424
- "\\" in path_value
425
- or "\x00" in path_value
426
- or path_value.startswith("/")
427
- or re.match(r"^[A-Za-z]:", path_value)
428
- ):
429
- return None, f"{field} must be a normalized repository-relative POSIX path"
430
- segments = path_value.split("/")
431
- if any(segment in {"", ".", ".."} for segment in segments):
432
- return None, f"{field} must not contain empty, dot, or parent path segments"
433
- if PurePosixPath(path_value).as_posix() != path_value:
434
- return None, f"{field} must be normalized"
435
-
436
- try:
437
- resolved_root = repo_root.resolve()
438
- resolved_target = (resolved_root / Path(*segments)).resolve(strict=False)
439
- except (OSError, RuntimeError) as error:
440
- return None, f"{field} cannot be resolved safely: {error}"
441
- try:
442
- resolved_target.relative_to(resolved_root)
443
- except ValueError:
444
- return None, f"{field} escapes the repository root"
445
-
446
- # Path.resolve(strict=False) stopped raising on symlink loops in Python
447
- # 3.13, so a looping or later-swapped component can survive resolution
448
- # while the stable-read descriptor walks would still refuse it. Refuse
449
- # statable symlink or non-directory components here too; components that
450
- # merely do not exist yet stay valid at validation time.
451
- probe = resolved_root
452
- for segment in segments[:-1]:
453
- probe = probe / segment
454
- try:
455
- component_stat = os.stat(probe, follow_symlinks=False)
456
- except FileNotFoundError:
457
- continue
458
- except NotADirectoryError:
459
- return None, f"{field} must traverse ordinary directories"
460
- if stat.S_ISLNK(component_stat.st_mode) or not stat.S_ISDIR(component_stat.st_mode):
461
- return None, f"{field} must traverse ordinary non-symlink directories"
462
- return path_value, None
463
-
464
-
465
- def validate_string_array(value, field: str, require_nonempty=False):
466
- if not isinstance(value, list) or not all(nonempty_string(item) for item in value):
467
- return f"{field} must be a string array"
468
- if require_nonempty and not value:
469
- return f"{field} must not be empty"
470
- if len(value) != len(set(value)):
471
- return f"{field} must not contain duplicates"
472
- return None
473
-
474
-
475
- def canonical_context_digest(context_files) -> str:
476
- if len(context_files) == 1:
477
- return context_files[0]["sha256"]
478
- canonical = sorted(
479
- (
480
- {"path": item["path"], "sha256": item["sha256"]}
481
- for item in context_files
482
- ),
483
- key=lambda item: item["path"].encode("utf-8"),
484
- )
485
- payload = json.dumps(
486
- canonical,
487
- ensure_ascii=False,
488
- separators=(",", ":"),
489
- sort_keys=True,
490
- ).encode("utf-8")
491
- return hashlib.sha256(payload).hexdigest()
492
-
493
-
494
- def _trace_arrays(value):
495
- if not isinstance(value, dict):
496
- return None
497
- result = {}
498
- patterns = {
499
- "first_principles": r"FP-[A-Za-z0-9][A-Za-z0-9._-]*",
500
- "serves": r"NS-[A-Za-z0-9][A-Za-z0-9._-]*",
501
- "touches": r"HC-[A-Za-z0-9][A-Za-z0-9._-]*",
502
- }
503
- for field, pattern in patterns.items():
504
- values = value.get(field)
505
- if not isinstance(values, list) or not all(
506
- isinstance(item, str) and re.fullmatch(pattern, item) for item in values
507
- ) or len(values) != len(set(values)):
508
- return None
509
- result[field] = values
510
- return result
511
-
512
-
513
- def _terminate_and_reap(process):
514
- if process.poll() is None:
515
- process.terminate()
516
- try:
517
- process.wait(timeout=0.25)
518
- except subprocess.TimeoutExpired:
519
- process.kill()
520
- process.wait()
521
- else:
522
- process.wait()
523
-
524
-
525
- def _bounded_process_output(argv, input_bytes: bytes, timeout: float, max_output_bytes: int):
526
- try:
527
- process = subprocess.Popen(
528
- argv,
529
- stdin=subprocess.PIPE,
530
- stdout=subprocess.PIPE,
531
- stderr=subprocess.PIPE,
532
- bufsize=0,
533
- )
534
- except OSError as error:
535
- return None, None, None, f"canonical North Star validator could not run: {error}"
536
-
537
- selector = selectors.DefaultSelector()
538
- buffers = {"stdout": bytearray(), "stderr": bytearray()}
539
- input_view = memoryview(input_bytes)
540
- input_offset = 0
541
- combined_output = 0
542
- failure = None
543
- returncode = None
544
-
545
- def stop_watching(stream):
546
- try:
547
- selector.unregister(stream)
548
- except (KeyError, ValueError):
549
- pass
550
- try:
551
- stream.close()
552
- except OSError:
553
- pass
554
-
555
- try:
556
- for stream, role, events in [
557
- (process.stdout, "stdout", selectors.EVENT_READ),
558
- (process.stderr, "stderr", selectors.EVENT_READ),
559
- (process.stdin, "stdin", selectors.EVENT_WRITE),
560
- ]:
561
- os.set_blocking(stream.fileno(), False)
562
- selector.register(stream, events, role)
563
- if not input_bytes:
564
- stop_watching(process.stdin)
565
-
566
- deadline = time.monotonic() + timeout
567
- while selector.get_map():
568
- remaining = deadline - time.monotonic()
569
- if remaining <= 0:
570
- failure = "canonical North Star validator timed out"
571
- break
572
- events = selector.select(min(0.05, remaining))
573
- for key, _mask in events:
574
- stream = key.fileobj
575
- role = key.data
576
- if role == "stdin":
577
- try:
578
- written = os.write(
579
- stream.fileno(),
580
- input_view[input_offset:input_offset + READ_CHUNK_BYTES],
581
- )
582
- except BlockingIOError:
583
- continue
584
- except BrokenPipeError:
585
- stop_watching(stream)
586
- continue
587
- input_offset += written
588
- if input_offset == len(input_view):
589
- stop_watching(stream)
590
- continue
591
-
592
- try:
593
- chunk = os.read(stream.fileno(), READ_CHUNK_BYTES)
594
- except BlockingIOError:
595
- continue
596
- if not chunk:
597
- stop_watching(stream)
598
- continue
599
- combined_output += len(chunk)
600
- if combined_output > max_output_bytes:
601
- failure = (
602
- "canonical North Star validator output exceeded its bounded "
603
- "combined report limit"
604
- )
605
- break
606
- buffers[role].extend(chunk)
607
- if failure:
608
- break
609
-
610
- if failure:
611
- _terminate_and_reap(process)
612
- else:
613
- remaining = max(0.0, deadline - time.monotonic())
614
- try:
615
- returncode = process.wait(timeout=remaining)
616
- except subprocess.TimeoutExpired:
617
- failure = "canonical North Star validator timed out"
618
- _terminate_and_reap(process)
619
- finally:
620
- selector.close()
621
- for stream in (process.stdin, process.stdout, process.stderr):
622
- if stream is not None and not stream.closed:
623
- try:
624
- stream.close()
625
- except OSError:
626
- pass
627
- if process.poll() is None:
628
- _terminate_and_reap(process)
629
-
630
- return returncode, bytes(buffers["stdout"]), bytes(buffers["stderr"]), failure
631
-
632
-
633
- def validate_north_star_snapshot(repo_root: Path, snapshot):
634
- node = shutil.which("node")
635
- if node is None:
636
- return None, "canonical North Star validation requires the installed node runtime"
637
- validator = (
638
- Path(__file__).resolve().parents[2]
639
- / "ultra-research"
640
- / "scripts"
641
- / "validate_north_star.cjs"
642
- )
643
- if not validator.is_file():
644
- return None, f"canonical North Star validator is missing: {validator}"
645
- canonical_path = repo_root.resolve() / ".ultra" / "north-star.md"
646
- returncode, stdout, stderr, process_error = _bounded_process_output(
647
- [node, str(validator), "--stdin", "--path", str(canonical_path)],
648
- snapshot["bytes"],
649
- VALIDATOR_TIMEOUT_SECONDS,
650
- MAX_VALIDATOR_REPORT_BYTES,
651
- )
652
- if process_error:
653
- return None, process_error
654
- report, report_error = decode_json_bytes(
655
- stdout,
656
- "canonical North Star validator report",
657
- )
658
- if report_error:
659
- detail = stderr.decode("utf-8", errors="replace").strip()
660
- return None, f"{report_error}{': ' + detail if detail else ''}"
661
- input_receipt = report.get("input")
662
- expected_input = {
663
- "path": str(canonical_path),
664
- "byte_length": len(snapshot["bytes"]),
665
- "sha256": snapshot["sha256"],
666
- }
667
- if input_receipt != expected_input:
668
- return None, (
669
- "canonical North Star validator report is not bound to the exact stdin "
670
- "snapshot bytes and canonical path"
671
- )
672
- binding = report.get("acceptance_binding")
673
- source_observations = report.get("source_observations")
674
- expected_sources = None
675
- if isinstance(binding, dict):
676
- source = binding.get("source")
677
- accepted_snapshot = binding.get("snapshot")
678
- if isinstance(source, str) and "#" in source and isinstance(accepted_snapshot, str):
679
- expected_sources = [
680
- ("decision", source.split("#", 1)[0]),
681
- ("snapshot", accepted_snapshot),
682
- ]
683
- if expected_sources is None or not isinstance(source_observations, list):
684
- return None, "canonical North Star validator report lacks exact source observations"
685
- observed_sources = []
686
- for index, observation in enumerate(source_observations):
687
- if (
688
- not isinstance(observation, dict)
689
- or set(observation) != NORTH_STAR_SOURCE_OBSERVATION_FIELDS
690
- or observation.get("role") not in {"decision", "snapshot"}
691
- or not digest_string(observation.get("sha256"))
692
- or not isinstance(observation.get("byte_length"), int)
693
- or isinstance(observation.get("byte_length"), bool)
694
- or observation["byte_length"] < 0
695
- ):
696
- return None, (
697
- "canonical North Star validator report has an invalid "
698
- f"source_observations[{index}] receipt"
699
- )
700
- normalized, path_error = validate_repo_path(
701
- observation.get("path"),
702
- repo_root,
703
- f"canonical North Star validator source_observations[{index}].path",
704
- )
705
- if path_error:
706
- return None, path_error
707
- observed_sources.append((observation["role"], normalized))
708
- if observed_sources != expected_sources:
709
- return None, (
710
- "canonical North Star validator report source observations do not exactly "
711
- "match its decision and accepted snapshot binding"
712
- )
713
- if returncode != 0 or report.get("valid") is not True:
714
- codes = [
715
- item.get("code")
716
- for item in report.get("diagnostics", [])
717
- if isinstance(item, dict) and nonempty_string(item.get("code"))
718
- ]
719
- return None, (
720
- "canonical North Star validator rejected the current snapshot"
721
- + (f": {', '.join(codes)}" if codes else "")
722
- )
723
- return {
724
- "data": report,
725
- "sha256": hashlib.sha256(stdout).hexdigest(),
726
- }, None
727
-
728
-
729
- def current_git_head(repo_root: Path):
730
- try:
731
- completed = subprocess.run(
732
- ["git", "-C", str(repo_root), "rev-parse", "--verify", "HEAD"],
733
- stdout=subprocess.PIPE,
734
- stderr=subprocess.PIPE,
735
- timeout=VALIDATOR_TIMEOUT_SECONDS,
736
- check=False,
737
- )
738
- except subprocess.TimeoutExpired:
739
- return None, "current Git HEAD observation timed out"
740
- except OSError as error:
741
- return None, f"current Git HEAD cannot be observed: {error}"
742
- if completed.returncode != 0:
743
- detail = completed.stderr.decode("utf-8", errors="replace").strip()
744
- return None, "current Git HEAD cannot be observed" + (f": {detail}" if detail else "")
745
- try:
746
- head = completed.stdout.decode("ascii").strip()
747
- except UnicodeDecodeError:
748
- return None, "current Git HEAD is not ASCII"
749
- if not git_blob_digest_string(head):
750
- return None, f"current Git HEAD is not a lowercase 40-hex commit digest: {head!r}"
751
- return head, None
752
-
753
-
754
- def acceptance_source_paths(packet):
755
- return sorted(
756
- {
757
- acceptance.partition(": ")[0].split("#", 1)[0]
758
- for acceptance in packet["acceptance"]
759
- },
760
- key=lambda value: value.encode("utf-8"),
761
- )
762
-
763
-
764
- def trace_source_path(packet):
765
- return packet["north_star_trace"]["path"].split("#", 1)[0]
766
-
767
-
768
- def validate_packet_subject_observations(packet, repo_root: Path):
769
- observations = packet.get("subject_observations")
770
- acceptance_paths = acceptance_source_paths(packet)
771
- expected_count = 1 + len(acceptance_paths) + 2
772
- if not isinstance(observations, list) or len(observations) != expected_count:
773
- return (
774
- "WORKER-PACKET.json subject_observations must be the exact ordered "
775
- "Change, acceptance-source, decision, and snapshot observation array"
776
- )
777
-
778
- expected_prefix = [
779
- ("change", trace_source_path(packet)),
780
- *(("acceptance_source", path) for path in acceptance_paths),
781
- ]
782
- expected_roles = [
783
- *(role for role, _path in expected_prefix),
784
- "decision",
785
- "snapshot",
786
- ]
787
- observed_roles_and_paths = []
788
- for index, observation in enumerate(observations):
789
- if (
790
- not isinstance(observation, dict)
791
- or set(observation) != PACKET_SUBJECT_OBSERVATION_FIELDS
792
- ):
793
- return (
794
- f"WORKER-PACKET.json subject_observations[{index}] must contain "
795
- "exactly role, path, sha256, and byte_length"
796
- )
797
- if observation.get("role") != expected_roles[index]:
798
- return (
799
- "WORKER-PACKET.json subject_observations must use exact ordered roles: "
800
- + ", ".join(expected_roles)
801
- )
802
- normalized, path_error = validate_repo_path(
803
- observation.get("path"),
804
- repo_root,
805
- f"WORKER-PACKET.json subject_observations[{index}].path",
806
- )
807
- if path_error:
808
- return path_error
809
- if not digest_string(observation.get("sha256")):
810
- return (
811
- f"WORKER-PACKET.json subject_observations[{index}].sha256 must be a "
812
- "lowercase SHA-256 digest"
813
- )
814
- byte_length = observation.get("byte_length")
815
- if (
816
- not isinstance(byte_length, int)
817
- or isinstance(byte_length, bool)
818
- or byte_length < 0
819
- ):
820
- return (
821
- f"WORKER-PACKET.json subject_observations[{index}].byte_length must "
822
- "be a non-negative integer"
823
- )
824
- observed_roles_and_paths.append((observation["role"], normalized))
825
-
826
- if observed_roles_and_paths[:len(expected_prefix)] != expected_prefix:
827
- return (
828
- "WORKER-PACKET.json subject_observations must bind the exact validated "
829
- "Change path and ordered acceptance sources"
830
- )
831
- return None
832
-
833
-
834
- def canonical_admission_subject(value):
835
- return {
836
- "version": value["version"],
837
- "session": value["session"],
838
- "packet_digest": value["packet_digest"],
839
- "head": value["head"],
840
- "observations": value["observations"],
841
- "north_star_report": value["north_star_report"],
842
- }
843
-
844
-
845
- def canonical_json_digest(value):
846
- payload = json.dumps(
847
- value,
848
- ensure_ascii=False,
849
- separators=(",", ":"),
850
- sort_keys=True,
851
- ).encode("utf-8")
852
- return hashlib.sha256(payload).hexdigest()
853
-
854
-
855
- def validate_packet_admission(packet, repo_root: Path, packet_digest: str):
856
- observed_head, error = current_git_head(repo_root)
857
- if error:
858
- return None, error
859
- if observed_head != packet["head"]:
860
- return None, (
861
- f"WORKER-PACKET.json head does not match current Git HEAD {observed_head}; "
862
- "create a new review session for the current subject"
863
- )
864
-
865
- snapshots = {}
866
-
867
- def subject_snapshot(repo_path, label):
868
- if repo_path not in snapshots:
869
- snapshot, snapshot_error, _missing = stable_read_repo_bytes(
870
- repo_root,
871
- repo_path,
872
- label,
873
- )
874
- if snapshot_error:
875
- return None, snapshot_error
876
- snapshots[repo_path] = snapshot
877
- return snapshots[repo_path], None
878
-
879
- observations = []
880
- for index, context_file in enumerate(packet["context_files"]):
881
- snapshot, error = subject_snapshot(
882
- context_file["path"],
883
- f"WORKER-PACKET.json context_files[{index}] subject",
884
- )
885
- if error:
886
- return None, error
887
- observed = snapshot["sha256"]
888
- if observed != context_file["sha256"]:
889
- return None, (
890
- f"WORKER-PACKET.json context_files[{index}] subject bytes do not match "
891
- f"recorded sha256 {context_file['sha256']}; observed {observed}"
892
- )
893
- observations.append({
894
- "role": "context",
895
- "path": context_file["path"],
896
- "sha256": observed,
897
- "byte_length": len(snapshot["bytes"]),
898
- })
899
-
900
- north_star_path = ".ultra/north-star.md"
901
- north_star, error = subject_snapshot(
902
- north_star_path,
903
- "WORKER-PACKET.json canonical North Star subject",
904
- )
905
- if error:
906
- return None, error
907
- validated_report, error = validate_north_star_snapshot(repo_root, north_star)
908
- if error:
909
- return None, error
910
- report = validated_report["data"]
911
- if (
912
- report.get("$schema") != "ultra-north-star-validation-v1"
913
- or report.get("kind") != "north-star-v2"
914
- or report.get("status") != "accepted"
915
- or report.get("classification") != "accepted"
916
- ):
917
- return None, "canonical North Star validator report must describe an accepted v2 authority"
918
-
919
- trace = packet["north_star_trace"]
920
- if report.get("revision") != trace["north_star_revision"]:
921
- return None, (
922
- "north_star_trace.north_star_revision does not match the canonical "
923
- f"validator report; observed {report.get('revision')!r}"
924
- )
925
- binding = report.get("acceptance_binding")
926
- if not isinstance(binding, dict):
927
- return None, "canonical North Star validator report lacks acceptance binding evidence"
928
- if binding.get("git_blob_digest") != trace["north_star_digest"]:
929
- return None, (
930
- "north_star_trace.north_star_digest does not match the canonical validator "
931
- f"report; observed {binding.get('git_blob_digest')!r}"
932
- )
933
- if binding.get("content_sha256") != north_star["sha256"]:
934
- return None, "canonical North Star validator report is not bound to the stable input bytes"
935
-
936
- ids = report.get("ids")
937
- if not isinstance(ids, dict) or set(ids) != {"FP", "NS", "HC"}:
938
- return None, "canonical North Star validator report has an invalid ID inventory"
939
- for field, kind in [
940
- ("first_principles", "FP"),
941
- ("serves", "NS"),
942
- ("touches", "HC"),
943
- ]:
944
- available = ids.get(kind)
945
- if not isinstance(available, list) or not all(nonempty_string(item) for item in available):
946
- return None, f"canonical North Star validator report has invalid {kind} IDs"
947
- unresolved = [item for item in trace[field] if item not in set(available)]
948
- if unresolved:
949
- return None, (
950
- f"north_star_trace.{field} contains IDs unresolved by the canonical "
951
- f"validator report: {', '.join(unresolved)}"
952
- )
953
-
954
- observations.append({
955
- "role": "north_star",
956
- "path": north_star_path,
957
- "sha256": north_star["sha256"],
958
- "byte_length": len(north_star["bytes"]),
959
- })
960
-
961
- packet_subjects = packet["subject_observations"]
962
- canonical_sources = packet_subjects[-2:]
963
- if report.get("source_observations") != canonical_sources:
964
- return None, (
965
- "canonical North Star validator decision and snapshot observations do not "
966
- "match WORKER-PACKET.json subject_observations"
967
- )
968
- for index, expected_observation in enumerate(packet_subjects):
969
- source, error = subject_snapshot(
970
- expected_observation["path"],
971
- f"WORKER-PACKET.json subject_observations[{index}] subject",
972
- )
973
- if error:
974
- return None, error
975
- observed_length = len(source["bytes"])
976
- if (
977
- source["sha256"] != expected_observation["sha256"]
978
- or observed_length != expected_observation["byte_length"]
979
- ):
980
- return None, (
981
- f"WORKER-PACKET.json subject_observations[{index}] bytes do not match "
982
- f"recorded byte_length and sha256 for {expected_observation['path']}"
983
- )
984
- if expected_observation["role"] in {"change", "acceptance_source"}:
985
- observations.append(dict(expected_observation))
986
-
987
- final_head, error = current_git_head(repo_root)
988
- if error:
989
- return None, error
990
- if final_head != observed_head:
991
- return None, "current Git HEAD changed during packet admission; retry with a new session"
992
- for repo_path, expected in snapshots.items():
993
- current, snapshot_error, _missing = stable_read_repo_bytes(
994
- repo_root,
995
- repo_path,
996
- f"WORKER-PACKET.json admitted subject {repo_path}",
997
- )
998
- if snapshot_error:
999
- return None, snapshot_error
1000
- if (
1001
- current["bytes"] != expected["bytes"]
1002
- or len(current["bytes"]) != len(expected["bytes"])
1003
- or current["sha256"] != expected["sha256"]
1004
- ):
1005
- return None, (
1006
- f"admitted subject {repo_path} bytes, byte length, or sha256 changed "
1007
- "during packet admission"
1008
- )
1009
-
1010
- report_receipt = {
1011
- "schema": report["$schema"],
1012
- "sha256": validated_report["sha256"],
1013
- "input_sha256": north_star["sha256"],
1014
- "input_byte_length": len(north_star["bytes"]),
1015
- "source_observations": report["source_observations"],
1016
- }
1017
- admission = {
1018
- "$schema": ADMISSION_SCHEMA_V2,
1019
- "version": 2,
1020
- "session": packet["session"],
1021
- "packet_digest": packet_digest,
1022
- "head": packet["head"],
1023
- "observations": observations,
1024
- "north_star_report": report_receipt,
1025
- }
1026
- admission["subject_digest"] = canonical_json_digest(
1027
- canonical_admission_subject(admission)
1028
- )
1029
- return admission, None
1030
-
1031
-
1032
- def cleanup_admission_temporary(
1033
- session_descriptor: int,
1034
- temporary: str,
1035
- publication_descriptor: int,
1036
- ):
1037
- """Remove only the temporary pathname that still names our open inode."""
1038
- filename = os.path.basename(os.fspath(temporary))
1039
- try:
1040
- current = os.lstat(filename, dir_fd=session_descriptor)
1041
- owned = os.fstat(publication_descriptor)
1042
- except FileNotFoundError:
1043
- return True, None
1044
- except OSError as error:
1045
- return False, error
1046
- if (
1047
- not stat.S_ISREG(current.st_mode)
1048
- or _stat_identity(current) != _stat_identity(owned)
1049
- ):
1050
- return False, (
1051
- "temporary pathname identity differs from the owned admission file; "
1052
- "the replacement was retained for manual recovery"
1053
- )
1054
- try:
1055
- os.unlink(filename, dir_fd=session_descriptor)
1056
- except OSError as error:
1057
- return False, error
1058
- return True, None
1059
-
1060
-
1061
- def atomic_write_admission(session_path: Path, admission):
1062
- target = session_path / "ADMISSION.json"
1063
- payload = (json.dumps(admission, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
1064
- payload_digest = hashlib.sha256(payload).hexdigest()
1065
- descriptor = None
1066
- session_descriptor = None
1067
- publication_descriptor = None
1068
- temporary = None
1069
- published = False
1070
- publication = "created"
1071
- result_digest = payload_digest
1072
- durability_warnings = []
1073
- temporary_cleanup_error = None
1074
- try:
1075
- session_flags = os.O_RDONLY
1076
- session_flags |= getattr(os, "O_DIRECTORY", 0)
1077
- session_flags |= getattr(os, "O_NOFOLLOW", 0)
1078
- session_path_identity = os.stat(session_path, follow_symlinks=False)
1079
- session_descriptor = os.open(session_path, session_flags)
1080
- session_fd_identity = os.fstat(session_descriptor)
1081
- if (
1082
- not stat.S_ISDIR(session_path_identity.st_mode)
1083
- or _component_identity(session_path_identity)
1084
- != _component_identity(session_fd_identity)
1085
- ):
1086
- return None, {
1087
- "code": "admission_publish_failed",
1088
- "message": "review session changed before ADMISSION.json publication",
1089
- }
1090
- descriptor, temporary = tempfile.mkstemp(prefix=".ADMISSION.", dir=session_path)
1091
- with os.fdopen(descriptor, "wb", closefd=True) as stream:
1092
- descriptor = None
1093
- stream.write(payload)
1094
- stream.flush()
1095
- os.fsync(stream.fileno())
1096
- publication_flags = os.O_RDONLY
1097
- publication_flags |= getattr(os, "O_NONBLOCK", 0)
1098
- publication_flags |= getattr(os, "O_NOFOLLOW", 0)
1099
- publication_descriptor = os.open(temporary, publication_flags)
1100
- try:
1101
- # A same-directory hard link is an atomic create-if-absent publication.
1102
- # Unlike replace/rename, it can never overwrite an admitted session.
1103
- os.link(temporary, target, follow_symlinks=False)
1104
- published = True
1105
- temporary_identity = _stat_identity(os.fstat(publication_descriptor))
1106
- repo_root, root_error = canonical_repo_root(session_path)
1107
- if root_error:
1108
- return None, {
1109
- "code": "admission_publication_changed",
1110
- "message": (
1111
- "ADMISSION.json publication cannot be verified from the canonical "
1112
- f"session path: {root_error}"
1113
- ),
1114
- }
1115
- published_snapshot, published_error, _missing = stable_read_repo_bytes(
1116
- repo_root,
1117
- f".ultra/reviews/{session_path.resolve().name}/ADMISSION.json",
1118
- "published ADMISSION.json",
1119
- )
1120
- if (
1121
- published_error
1122
- or published_snapshot["identity"] != temporary_identity
1123
- or published_snapshot["bytes"] != payload
1124
- ):
1125
- detail = published_error or (
1126
- "the canonical receipt identity or bytes differ from the newly "
1127
- "published file"
1128
- )
1129
- return None, {
1130
- "code": "admission_publication_changed",
1131
- "message": (
1132
- "ADMISSION.json canonical session or receipt changed during its "
1133
- f"post-publication fresh rewalk: {detail}"
1134
- ),
1135
- }
1136
- except FileExistsError:
1137
- repo_root, root_error = canonical_repo_root(session_path)
1138
- if root_error:
1139
- return None, {
1140
- "code": "admission_conflict",
1141
- "message": f"existing ADMISSION.json cannot be verified: {root_error}",
1142
- }
1143
- existing, existing_error, _missing = stable_read_repo_bytes(
1144
- repo_root,
1145
- f".ultra/reviews/{session_path.resolve().name}/ADMISSION.json",
1146
- "existing ADMISSION.json",
1147
- )
1148
- if existing_error or existing["bytes"] != payload:
1149
- detail = existing_error or "its bytes differ from the newly observed receipt"
1150
- return None, {
1151
- "code": "admission_conflict",
1152
- "message": (
1153
- "ADMISSION.json is immutable once published and the existing "
1154
- f"receipt cannot be reused because {detail}; start a fresh review session"
1155
- ),
1156
- }
1157
- published = True
1158
- publication = "existing"
1159
- result_digest = existing["sha256"]
1160
- except OSError as error:
1161
- return None, {
1162
- "code": "admission_publish_failed",
1163
- "message": f"ADMISSION.json could not be published atomically: {error}",
1164
- }
1165
-
1166
- removed, cleanup_error = cleanup_admission_temporary(
1167
- session_descriptor,
1168
- temporary,
1169
- publication_descriptor,
1170
- )
1171
- if removed:
1172
- temporary = None
1173
- else:
1174
- temporary_cleanup_error = cleanup_error
1175
-
1176
- try:
1177
- os.fsync(session_descriptor)
1178
- except OSError as error:
1179
- durability_warnings.append({
1180
- "code": "admission_directory_fsync_failed",
1181
- "message": (
1182
- "ADMISSION.json is published and usable, but directory durability "
1183
- f"could not be confirmed: {error}"
1184
- ),
1185
- })
1186
- except OSError as error:
1187
- if published:
1188
- if not any(
1189
- warning["code"] == "admission_directory_fsync_failed"
1190
- for warning in durability_warnings
1191
- ):
1192
- durability_warnings.append({
1193
- "code": "admission_directory_fsync_failed",
1194
- "message": (
1195
- "ADMISSION.json is published and usable, but directory durability "
1196
- f"could not be confirmed: {error}"
1197
- ),
1198
- })
1199
- else:
1200
- return None, {
1201
- "code": "admission_publish_failed",
1202
- "message": f"ADMISSION.json could not be published atomically: {error}",
1203
- }
1204
- finally:
1205
- if descriptor is not None:
1206
- os.close(descriptor)
1207
- if (
1208
- temporary is not None
1209
- and session_descriptor is not None
1210
- and publication_descriptor is not None
1211
- ):
1212
- removed, cleanup_error = cleanup_admission_temporary(
1213
- session_descriptor,
1214
- temporary,
1215
- publication_descriptor,
1216
- )
1217
- if removed:
1218
- temporary = None
1219
- temporary_cleanup_error = None
1220
- else:
1221
- temporary_cleanup_error = cleanup_error
1222
- if publication_descriptor is not None:
1223
- os.close(publication_descriptor)
1224
- if session_descriptor is not None:
1225
- os.close(session_descriptor)
1226
- if temporary is not None and temporary_cleanup_error is not None:
1227
- durability_warnings.append({
1228
- "code": "admission_temp_cleanup_failed",
1229
- "message": (
1230
- "ADMISSION.json is published and usable, but its temporary pathname "
1231
- f"could not be removed: {temporary_cleanup_error}"
1232
- ),
1233
- })
1234
- result = {
1235
- "digest": result_digest,
1236
- "publication": publication,
1237
- }
1238
- if len(durability_warnings) == 1:
1239
- result["durability_warning"] = durability_warnings[0]
1240
- elif durability_warnings:
1241
- result["durability_warning"] = {
1242
- "code": "admission_multiple_durability_warnings",
1243
- "message": (
1244
- "ADMISSION.json is published and usable, but multiple durability "
1245
- "operations remain unresolved"
1246
- ),
1247
- "warnings": durability_warnings,
1248
- }
1249
- return result, None
1250
-
1251
-
1252
- def validate_admission_receipt(receipt, packet):
1253
- if not isinstance(receipt, dict) or set(receipt) != ADMISSION_FIELDS:
1254
- return "ADMISSION.json must contain exactly the strict admission receipt fields"
1255
- contract = packet["data"].get("admission_contract")
1256
- if contract == ADMISSION_CONTRACT_V1:
1257
- expected_schema = ADMISSION_SCHEMA_V1
1258
- expected_version = 1
1259
- observation_fields = ADMISSION_OBSERVATION_FIELDS_V1
1260
- elif contract == ADMISSION_CONTRACT_V2:
1261
- expected_schema = ADMISSION_SCHEMA_V2
1262
- expected_version = 2
1263
- observation_fields = ADMISSION_OBSERVATION_FIELDS_V2
1264
- else:
1265
- return "ADMISSION.json requires a recognized strict packet admission_contract"
1266
- if (
1267
- receipt.get("$schema") != expected_schema
1268
- or receipt.get("version") != expected_version
1269
- ):
1270
- return (
1271
- f"ADMISSION.json must use {expected_schema} version {expected_version} "
1272
- "for its packet admission_contract"
1273
- )
1274
- if receipt.get("session") != packet["data"]["session"]:
1275
- return "ADMISSION.json session does not match WORKER-PACKET.json"
1276
- if receipt.get("packet_digest") != packet["digest"]:
1277
- return "ADMISSION.json packet_digest does not match WORKER-PACKET.json"
1278
- if receipt.get("head") != packet["data"]["head"]:
1279
- return "ADMISSION.json head does not match WORKER-PACKET.json"
1280
- if not digest_string(receipt.get("subject_digest")):
1281
- return "ADMISSION.json subject_digest must be a lowercase SHA-256 digest"
1282
-
1283
- observations = receipt.get("observations")
1284
- if not isinstance(observations, list):
1285
- return "ADMISSION.json observations must be an array"
1286
- for index, observation in enumerate(observations):
1287
- if not isinstance(observation, dict) or set(observation) != observation_fields:
1288
- return f"ADMISSION.json observations[{index}] has invalid fields"
1289
- if observation.get("role") not in {
1290
- "context", "north_star", "change", "acceptance_source",
1291
- }:
1292
- return f"ADMISSION.json observations[{index}] has an invalid role"
1293
- _, path_error = validate_repo_path(
1294
- observation.get("path"),
1295
- packet["repo_root"],
1296
- f"ADMISSION.json observations[{index}].path",
1297
- )
1298
- if path_error:
1299
- return path_error
1300
- if not digest_string(observation.get("sha256")):
1301
- return f"ADMISSION.json observations[{index}].sha256 is invalid"
1302
- if expected_version == 2:
1303
- byte_length = observation.get("byte_length")
1304
- if (
1305
- not isinstance(byte_length, int)
1306
- or isinstance(byte_length, bool)
1307
- or byte_length < 0
1308
- ):
1309
- return f"ADMISSION.json observations[{index}].byte_length is invalid"
1310
-
1311
- subject_projection = (
1312
- packet["data"]["subject_observations"][:-2]
1313
- if expected_version == 2
1314
- else [
1315
- {
1316
- "role": "change",
1317
- "path": f".ultra/changes/active/{packet['data']['change_id']}/intent.md",
1318
- },
1319
- *(
1320
- {"role": "acceptance_source", "path": path}
1321
- for path in acceptance_source_paths(packet["data"])
1322
- ),
1323
- ]
1324
- )
1325
- expected_roles_and_paths = [
1326
- *(('context', item["path"]) for item in packet["data"]["context_files"]),
1327
- ("north_star", ".ultra/north-star.md"),
1328
- *((item["role"], item["path"]) for item in subject_projection),
1329
- ]
1330
- observed_roles_and_paths = [
1331
- (item["role"], item["path"]) for item in observations
1332
- ]
1333
- if observed_roles_and_paths != expected_roles_and_paths:
1334
- return "ADMISSION.json observations are not the exact packet-derived subject projection"
1335
- for index, context_file in enumerate(packet["data"]["context_files"]):
1336
- if observations[index]["sha256"] != context_file["sha256"]:
1337
- return "ADMISSION.json context observation does not match WORKER-PACKET.json"
1338
- if expected_version == 2:
1339
- subject_offset = len(packet["data"]["context_files"]) + 1
1340
- if observations[subject_offset:] != subject_projection:
1341
- return (
1342
- "ADMISSION.json Change and acceptance observations do not exactly "
1343
- "match WORKER-PACKET.json subject_observations"
1344
- )
1345
-
1346
- report_receipt = receipt.get("north_star_report")
1347
- if (
1348
- not isinstance(report_receipt, dict)
1349
- or set(report_receipt) != NORTH_STAR_REPORT_RECEIPT_FIELDS
1350
- or report_receipt.get("schema") != "ultra-north-star-validation-v1"
1351
- or not digest_string(report_receipt.get("sha256"))
1352
- or not digest_string(report_receipt.get("input_sha256"))
1353
- or not isinstance(report_receipt.get("input_byte_length"), int)
1354
- or isinstance(report_receipt.get("input_byte_length"), bool)
1355
- or report_receipt["input_byte_length"] < 0
1356
- ):
1357
- return "ADMISSION.json north_star_report receipt is invalid"
1358
- source_observations = report_receipt.get("source_observations")
1359
- if not isinstance(source_observations, list) or len(source_observations) != 2:
1360
- return "ADMISSION.json north_star_report source observations are invalid"
1361
- observed_source_roles = []
1362
- for index, observation in enumerate(source_observations):
1363
- if (
1364
- not isinstance(observation, dict)
1365
- or set(observation) != NORTH_STAR_SOURCE_OBSERVATION_FIELDS
1366
- or observation.get("role") not in {"decision", "snapshot"}
1367
- or not digest_string(observation.get("sha256"))
1368
- or not isinstance(observation.get("byte_length"), int)
1369
- or isinstance(observation.get("byte_length"), bool)
1370
- or observation["byte_length"] < 0
1371
- ):
1372
- return (
1373
- "ADMISSION.json north_star_report "
1374
- f"source_observations[{index}] is invalid"
1375
- )
1376
- _, path_error = validate_repo_path(
1377
- observation.get("path"),
1378
- packet["repo_root"],
1379
- f"ADMISSION.json north_star_report source_observations[{index}].path",
1380
- )
1381
- if path_error:
1382
- return path_error
1383
- observed_source_roles.append(observation["role"])
1384
- if observed_source_roles != ["decision", "snapshot"]:
1385
- return (
1386
- "ADMISSION.json north_star_report source observations must be the exact "
1387
- "decision then snapshot pair"
1388
- )
1389
- if (
1390
- expected_version == 2
1391
- and source_observations != packet["data"]["subject_observations"][-2:]
1392
- ):
1393
- return (
1394
- "ADMISSION.json canonical report decision and snapshot observations do not "
1395
- "exactly match WORKER-PACKET.json subject_observations"
1396
- )
1397
- north_star_observation = next(
1398
- item for item in observations if item["role"] == "north_star"
1399
- )
1400
- if report_receipt["input_sha256"] != north_star_observation["sha256"]:
1401
- return "ADMISSION.json canonical report input does not match its North Star observation"
1402
- expected_subject_digest = canonical_json_digest(canonical_admission_subject(receipt))
1403
- if receipt["subject_digest"] != expected_subject_digest:
1404
- return "ADMISSION.json subject_digest does not match its exact admission observations"
1405
- return None
1406
-
1407
-
1408
- def load_admission_receipt(session_path: Path, packet):
1409
- admission_repo_path = (
1410
- f".ultra/reviews/{packet['data']['session']}/ADMISSION.json"
1411
- )
1412
- receipt, snapshot, error, _missing = stable_read_repo_json(
1413
- packet["repo_root"],
1414
- admission_repo_path,
1415
- "ADMISSION.json",
1416
- )
1417
- if error:
1418
- return None, error
1419
- error = validate_admission_receipt(receipt, packet)
1420
- if error:
1421
- return None, error
1422
- return {"data": receipt, "digest": snapshot["sha256"]}, None
1423
-
1424
-
1425
- def _foreign_review_references(value, own_session: str):
1426
- """Collect every foreign review-session path carried anywhere in a value."""
1427
- found = []
1428
- if isinstance(value, str):
1429
- for match in FOREIGN_REVIEW_SESSION.finditer(value):
1430
- if match.group(1) != own_session:
1431
- found.append(match.group(0))
1432
- elif isinstance(value, dict):
1433
- for key, item in value.items():
1434
- found.extend(_foreign_review_references(key, own_session))
1435
- found.extend(_foreign_review_references(item, own_session))
1436
- elif isinstance(value, list):
1437
- for item in value:
1438
- found.extend(_foreign_review_references(item, own_session))
1439
- return found
1440
-
1441
-
1442
- def validate_packet_review_history(packet, repo_root: Path) -> str | None:
1443
- """Bound packet review history to one fully validated direct parent.
1444
-
1445
- The only foreign review path allowed anywhere in a task delta packet is the
1446
- exact `review_history.parent_summary_ref` field; an initial packet carries
1447
- no foreign review reference at all. Admission then observes the parent
1448
- SUMMARY as a bounded stable repository regular file, requires its exact
1449
- recorded digest, requires the parent to bind the same mode, change, and
1450
- tasks as the current packet, and accepts only still-unresolved current
1451
- P0/P1 blocker ids that exist in that parent. Transitive summary chains and
1452
- full-history replays stay out of new packets; the archive under
1453
- `.ultra/reviews/**` is read only for a dedicated incident or owner audit.
1454
- """
1455
- own_session = packet.get("session")
1456
- history = packet.get("review_history")
1457
- if history is not None and (
1458
- not isinstance(history, dict) or set(history) != REVIEW_HISTORY_FIELDS
1459
- ):
1460
- return (
1461
- "WORKER-PACKET.json review_history must be exactly one direct parent "
1462
- "{parent_summary_ref, parent_summary_digest, unresolved_blocking_ids}"
1463
- )
1464
- references: list[str] = []
1465
- if isinstance(packet, dict):
1466
- for key, item in packet.items():
1467
- if key == "review_history":
1468
- continue
1469
- references.extend(_foreign_review_references(key, own_session))
1470
- references.extend(_foreign_review_references(item, own_session))
1471
- if history is not None:
1472
- reference = history.get("parent_summary_ref")
1473
- if (
1474
- not isinstance(reference, str)
1475
- or not re.fullmatch(
1476
- r"\.ultra/reviews/[A-Za-z0-9][A-Za-z0-9._-]*/SUMMARY\.json",
1477
- reference,
1478
- )
1479
- or reference.split("/")[2] == own_session
1480
- ):
1481
- return (
1482
- "WORKER-PACKET.json review_history.parent_summary_ref must name "
1483
- "exactly one prior session's SUMMARY.json"
1484
- )
1485
- if not digest_string(history.get("parent_summary_digest")):
1486
- return (
1487
- "WORKER-PACKET.json review_history.parent_summary_digest must be a "
1488
- "lowercase SHA-256 digest"
1489
- )
1490
- blocking_ids = history.get("unresolved_blocking_ids")
1491
- if (
1492
- not isinstance(blocking_ids, list)
1493
- or not all(nonempty_string(item) for item in blocking_ids)
1494
- ):
1495
- return (
1496
- "WORKER-PACKET.json review_history.unresolved_blocking_ids must be "
1497
- "an array of finding-id strings"
1498
- )
1499
- if references:
1500
- return (
1501
- "WORKER-PACKET.json may reference a foreign review session only through "
1502
- "review_history.parent_summary_ref; observed outside that field: "
1503
- + ", ".join(sorted(set(references)))
1504
- )
1505
- if history is None:
1506
- return None
1507
-
1508
- parent_snapshot, parent_error, _missing = stable_read_repo_bytes(
1509
- repo_root,
1510
- history["parent_summary_ref"],
1511
- "review_history parent SUMMARY",
1512
- )
1513
- if parent_error:
1514
- return (
1515
- "WORKER-PACKET.json review_history parent summary could not be observed "
1516
- f"as a stable repository regular file: {parent_error}"
1517
- )
1518
- if parent_snapshot["sha256"] != history["parent_summary_digest"]:
1519
- return (
1520
- "WORKER-PACKET.json review_history.parent_summary_digest does not match "
1521
- "the observed parent SUMMARY bytes"
1522
- )
1523
- try:
1524
- parent = json.loads(parent_snapshot["bytes"].decode("utf-8"))
1525
- except (UnicodeDecodeError, json.JSONDecodeError) as error:
1526
- return (
1527
- "WORKER-PACKET.json review_history parent SUMMARY is invalid UTF-8 "
1528
- f"JSON: {error}"
1529
- )
1530
- if not isinstance(parent, dict) or parent.get("$schema") != "ultra-review-summary-v4":
1531
- return (
1532
- "WORKER-PACKET.json review_history parent SUMMARY must be an "
1533
- "ultra-review-summary-v4 object"
1534
- )
1535
- for field, current in (
1536
- ("mode", packet.get("mode")),
1537
- ("change_id", packet.get("change_id")),
1538
- ("task_ids", packet.get("task_ids")),
1539
- ):
1540
- if parent.get(field) != current:
1541
- return (
1542
- "WORKER-PACKET.json review_history parent SUMMARY "
1543
- f"{field} does not match the current packet"
1544
- )
1545
- findings = parent.get("findings")
1546
- if not isinstance(findings, list):
1547
- return (
1548
- "WORKER-PACKET.json review_history parent SUMMARY carries no findings "
1549
- "array to resolve blocking ids against"
1550
- )
1551
- severity_by_id: dict[str, str] = {}
1552
- for finding in findings:
1553
- if isinstance(finding, dict) and nonempty_string(finding.get("id")):
1554
- severity_by_id[finding["id"]] = finding.get("severity")
1555
- blocking_ids = history["unresolved_blocking_ids"]
1556
- if len(blocking_ids) != len(set(blocking_ids)):
1557
- return (
1558
- "WORKER-PACKET.json review_history.unresolved_blocking_ids must not "
1559
- "repeat a finding id"
1560
- )
1561
- for blocking_id in blocking_ids:
1562
- severity = severity_by_id.get(blocking_id)
1563
- if severity is None:
1564
- return (
1565
- "WORKER-PACKET.json review_history.unresolved_blocking_ids names "
1566
- f"`{blocking_id}`, which is not a finding of the parent SUMMARY"
1567
- )
1568
- if severity not in {"P0", "P1"}:
1569
- return (
1570
- "WORKER-PACKET.json review_history.unresolved_blocking_ids names "
1571
- f"`{blocking_id}`, whose parent severity `{severity}` is not a "
1572
- "current P0/P1 blocker"
1573
- )
1574
- return None
1575
-
1576
-
1577
- def load_worker_packet(
1578
- session_path: Path,
1579
- expected_packet_digest: str,
1580
- admit_subjects=False,
1581
- require_admission=False,
1582
- legacy_v4=False,
1583
- ):
1584
- repo_root, root_error = canonical_repo_root(session_path)
1585
- if root_error:
1586
- return None, f"WORKER-PACKET.json {root_error}"
1587
- session_name = session_path.resolve().name
1588
- packet_repo_path = f".ultra/reviews/{session_name}/WORKER-PACKET.json"
1589
- packet_snapshot, packet_error, _missing = stable_read_repo_bytes(
1590
- repo_root,
1591
- packet_repo_path,
1592
- "WORKER-PACKET.json",
1593
- )
1594
- if packet_error:
1595
- return None, packet_error
1596
- packet_bytes = packet_snapshot["bytes"]
1597
- actual_digest = packet_snapshot["sha256"]
1598
- if actual_digest != expected_packet_digest:
1599
- return None, (
1600
- "WORKER-PACKET.json bytes do not match expected immutable packet digest "
1601
- f"{expected_packet_digest}; observed {actual_digest}"
1602
- )
1603
- try:
1604
- packet = json.loads(packet_bytes.decode("utf-8"))
1605
- except (UnicodeDecodeError, json.JSONDecodeError) as error:
1606
- return None, f"WORKER-PACKET.json is invalid UTF-8 JSON: {error}"
1607
- if not isinstance(packet, dict):
1608
- return None, "WORKER-PACKET.json top-level value must be an object"
1609
- missing_fields = sorted(PACKET_REQUIRED_FIELDS - set(packet))
1610
- if missing_fields:
1611
- return None, (
1612
- "WORKER-PACKET.json missing required fields: " + ", ".join(missing_fields)
1613
- )
1614
- if packet.get("$schema") != "ultra-review-worker-packet-v1":
1615
- return None, "WORKER-PACKET.json must use ultra-review-worker-packet-v1"
1616
- admission_contract = packet.get("admission_contract")
1617
- if admit_subjects:
1618
- if admission_contract != ADMISSION_CONTRACT_V2:
1619
- return None, (
1620
- "WORKER-PACKET.json admission_contract must be "
1621
- f"{ADMISSION_CONTRACT_V2} for new strict v4 admission"
1622
- )
1623
- elif require_admission and admission_contract not in STRICT_ADMISSION_CONTRACTS:
1624
- return None, (
1625
- "WORKER-PACKET.json admission_contract must declare retained strict v1 "
1626
- "or current strict v2 for strict v4 consumption"
1627
- )
1628
- elif legacy_v4 and admission_contract in STRICT_ADMISSION_CONTRACTS:
1629
- return None, (
1630
- "WORKER-PACKET.json declares a strict admission_contract and "
1631
- "cannot be consumed with --legacy-v4"
1632
- )
1633
- for field in ["session", "head", "range", "change_id", "output_directory"]:
1634
- if not nonempty_string(packet.get(field)):
1635
- return None, f"WORKER-PACKET.json {field} must be a non-empty string"
1636
- if (
1637
- not ARTIFACT_STEM.fullmatch(packet["session"])
1638
- or packet["session"] != session_path.resolve().name
1639
- ):
1640
- return None, (
1641
- "WORKER-PACKET.json session must be a safe stem matching the canonical "
1642
- "session directory"
1643
- )
1644
- if not timestamp_string(packet.get("created_at")):
1645
- return None, "WORKER-PACKET.json created_at must be an RFC 3339 timestamp"
1646
- if not git_blob_digest_string(packet.get("head")):
1647
- return None, "WORKER-PACKET.json head must be a lowercase 40-hex Git commit digest"
1648
- if packet.get("mode") not in REVIEW_MODES:
1649
- return None, "WORKER-PACKET.json mode must be task, change, or plan"
1650
- for field, require_nonempty in [
1651
- ("task_ids", packet["mode"] == "task"),
1652
- ("acceptance", True),
1653
- ("public_seams", True),
1654
- ]:
1655
- array_error = validate_string_array(
1656
- packet.get(field),
1657
- f"WORKER-PACKET.json {field}",
1658
- require_nonempty,
1659
- )
1660
- if array_error:
1661
- return None, array_error
1662
- for index, acceptance in enumerate(packet["acceptance"]):
1663
- source, separator, literal = acceptance.partition(": ")
1664
- if not separator or not literal:
1665
- return None, (
1666
- f"WORKER-PACKET.json acceptance[{index}] must be "
1667
- "<repository-path>#<heading>: <captured owner-readable claim>"
1668
- )
1669
- _, path_error = validate_repo_path(
1670
- source,
1671
- repo_root,
1672
- f"WORKER-PACKET.json acceptance[{index}] source",
1673
- allow_fragment=True,
1674
- )
1675
- if path_error:
1676
- return None, path_error
1677
-
1678
- output_directory, path_error = validate_repo_path(
1679
- packet["output_directory"],
1680
- repo_root,
1681
- "WORKER-PACKET.json output_directory",
1682
- )
1683
- if path_error:
1684
- return None, path_error
1685
- canonical_output = f".ultra/reviews/{packet['session']}"
1686
- if output_directory != canonical_output:
1687
- return None, (
1688
- "WORKER-PACKET.json output_directory must equal the canonical session path "
1689
- f"{canonical_output}"
1690
- )
1691
-
1692
- trace_data = packet.get("north_star_trace")
1693
- if not isinstance(trace_data, dict) or set(trace_data) != TRACE_FIELDS:
1694
- return None, (
1695
- "WORKER-PACKET.json north_star_trace must contain exactly path, "
1696
- "first_principles, serves, touches, north_star_revision, and "
1697
- "north_star_digest"
1698
- )
1699
- trace = _trace_arrays(packet.get("north_star_trace"))
1700
- if trace is None:
1701
- return None, "WORKER-PACKET.json north_star_trace arrays are invalid"
1702
- _, path_error = validate_repo_path(
1703
- trace_data.get("path"),
1704
- repo_root,
1705
- "WORKER-PACKET.json north_star_trace.path",
1706
- allow_fragment=True,
1707
- )
1708
- if path_error:
1709
- return None, path_error
1710
- trace_source, fragment = trace_data["path"].split("#", 1)
1711
- parts = trace_source.split("/")
1712
- if (
1713
- fragment != "north-star-trace"
1714
- or len(parts) != 5
1715
- or parts[:2] != [".ultra", "changes"]
1716
- or parts[2] not in {"active", "archive", "abandoned"}
1717
- or parts[3] != packet["change_id"]
1718
- or parts[4] != "intent.md"
1719
- ):
1720
- return None, (
1721
- "north_star_trace.path must bind change_id to "
1722
- ".ultra/changes/<active|archive|abandoned>/<change_id>/intent.md"
1723
- "#north-star-trace"
1724
- )
1725
- if not nonempty_string(trace_data.get("north_star_revision")):
1726
- return None, (
1727
- "WORKER-PACKET.json north_star_trace.north_star_revision must be a "
1728
- "non-empty string"
1729
- )
1730
- if not git_blob_digest_string(trace_data.get("north_star_digest")):
1731
- return None, (
1732
- "WORKER-PACKET.json north_star_trace.north_star_digest must be a "
1733
- "lowercase 40-hex Git blob digest"
1734
- )
1735
- if admission_contract == ADMISSION_CONTRACT_V2:
1736
- subject_observation_error = validate_packet_subject_observations(packet, repo_root)
1737
- if subject_observation_error:
1738
- return None, subject_observation_error
1739
-
1740
- context_files = packet.get("context_files")
1741
- if not isinstance(context_files, list):
1742
- return None, "WORKER-PACKET.json context_files must be an array"
1743
- seen_context_paths = set()
1744
- for index, context_file in enumerate(context_files):
1745
- if not isinstance(context_file, dict) or set(context_file) != CONTEXT_FIELDS:
1746
- return None, (
1747
- f"WORKER-PACKET.json context_files[{index}] must contain exactly "
1748
- "path and sha256"
1749
- )
1750
- context_path, path_error = validate_repo_path(
1751
- context_file.get("path"),
1752
- repo_root,
1753
- f"WORKER-PACKET.json context_files[{index}].path",
1754
- )
1755
- if path_error:
1756
- return None, path_error
1757
- if context_path in seen_context_paths:
1758
- return None, "WORKER-PACKET.json context_files paths must be unique"
1759
- seen_context_paths.add(context_path)
1760
- if not digest_string(context_file.get("sha256")):
1761
- return None, (
1762
- f"WORKER-PACKET.json context_files[{index}].sha256 must be a "
1763
- "lowercase SHA-256 digest"
1764
- )
1765
- if packet["mode"] == "task":
1766
- expected_context_paths = [
1767
- f".ultra/contexts/task-{task_id}.md" for task_id in packet["task_ids"]
1768
- ]
1769
- observed_context_paths = [item["path"] for item in context_files]
1770
- if observed_context_paths != expected_context_paths:
1771
- return None, (
1772
- "task-mode context_files paths must exactly follow ordered task_ids: "
1773
- + ", ".join(expected_context_paths)
1774
- )
1775
-
1776
- diff_files = packet.get("diff_files")
1777
- if not isinstance(diff_files, list) or not diff_files:
1778
- return None, "WORKER-PACKET.json diff_files must be a non-empty path array"
1779
- normalized_diff_files = []
1780
- for index, diff_file in enumerate(diff_files):
1781
- normalized_path, path_error = validate_repo_path(
1782
- diff_file,
1783
- repo_root,
1784
- f"WORKER-PACKET.json diff_files[{index}]",
1785
- )
1786
- if path_error:
1787
- return None, path_error
1788
- normalized_diff_files.append(normalized_path)
1789
- if len(normalized_diff_files) != len(set(normalized_diff_files)):
1790
- return None, "WORKER-PACKET.json diff_files must not contain duplicates"
1791
-
1792
- history_error = validate_packet_review_history(packet, repo_root)
1793
- if history_error:
1794
- return None, history_error
1795
-
1796
- workers = packet.get("workers")
1797
- if not isinstance(workers, list) or not workers:
1798
- return None, "WORKER-PACKET.json workers must be a non-empty array"
1799
- by_stem = {}
1800
- by_agent = {}
1801
- ordered = []
1802
- previous_order = -1
1803
- for index, worker in enumerate(workers):
1804
- if not isinstance(worker, dict) or set(worker) != WORKER_FIELDS:
1805
- return None, (
1806
- f"WORKER-PACKET.json workers[{index}] must contain exactly agent, "
1807
- "axis, lens, and output"
1808
- )
1809
- agent = worker.get("agent")
1810
- axis = worker.get("axis")
1811
- lens = worker.get("lens")
1812
- output = worker.get("output")
1813
- if agent not in REVIEW_WORKERS or agent in by_agent:
1814
- return None, f"WORKER-PACKET.json has invalid or duplicate worker agent: {agent}"
1815
- canonical = CANONICAL_WORKER_BY_AGENT[agent]
1816
- if axis != canonical["axis"]:
1817
- return None, (
1818
- f"WORKER-PACKET.json worker {agent} axis must use canonical value "
1819
- f"{canonical['axis']}"
1820
- )
1821
- if lens != canonical["lens"]:
1822
- return None, (
1823
- f"WORKER-PACKET.json worker {agent} lens must use canonical path "
1824
- f"{canonical['lens']}"
1825
- )
1826
- _, path_error = validate_repo_path(
1827
- lens,
1828
- repo_root,
1829
- f"WORKER-PACKET.json worker {agent} lens",
1830
- )
1831
- if path_error:
1832
- return None, path_error
1833
- output_path, path_error = validate_repo_path(
1834
- output,
1835
- repo_root,
1836
- f"WORKER-PACKET.json worker {agent} output",
1837
- )
1838
- if path_error:
1839
- return None, path_error
1840
- canonical_worker_output = f"{output_directory}/{agent}.json"
1841
- if output_path != canonical_worker_output:
1842
- return None, (
1843
- f"WORKER-PACKET.json worker {agent} output must equal canonical path "
1844
- f"{canonical_worker_output}"
1845
- )
1846
- if canonical["order"] <= previous_order:
1847
- return None, "WORKER-PACKET.json workers must use canonical roster order"
1848
- previous_order = canonical["order"]
1849
- output_name = PurePosixPath(output_path).name
1850
- output_stem = PurePosixPath(output_path).stem
1851
- if output_stem in by_stem:
1852
- return None, f"WORKER-PACKET.json repeats output stem: {output_stem}"
1853
- normalized = {
1854
- "agent": agent,
1855
- "axis": axis,
1856
- "lens": lens,
1857
- "output": output_path,
1858
- "stem": output_stem,
1859
- "filename": output_name,
1860
- }
1861
- by_stem[output_stem] = normalized
1862
- by_agent[agent] = normalized
1863
- ordered.append(normalized)
1864
- if "review-spec" not in by_agent:
1865
- return None, (
1866
- "WORKER-PACKET.json workers must include canonical review-spec for the "
1867
- "permanent specification axis"
1868
- )
1869
- admission = None
1870
- if admit_subjects:
1871
- admission, subject_error = validate_packet_admission(
1872
- packet,
1873
- repo_root,
1874
- actual_digest,
1875
- )
1876
- if subject_error:
1877
- return None, f"WORKER-PACKET.json subject validation failed: {subject_error}"
1878
- loaded = {
1879
- "data": packet,
1880
- "digest": actual_digest,
1881
- "trace": {field: set(values) for field, values in trace.items()},
1882
- "repo_root": repo_root,
1883
- "diff_scope": set(normalized_diff_files),
1884
- "context_digest": canonical_context_digest(context_files),
1885
- "admission": admission,
1886
- "workers": ordered,
1887
- "by_stem": by_stem,
1888
- "by_agent": by_agent,
1889
- }
1890
- if require_admission:
1891
- receipt, receipt_error = load_admission_receipt(session_path, loaded)
1892
- if receipt_error:
1893
- return None, (
1894
- "strict v4 packet consumption requires a valid ADMISSION.json receipt "
1895
- f"from packet mode: {receipt_error}"
1896
- )
1897
- loaded["admission_receipt"] = receipt
1898
- return loaded, None
1899
-
1900
-
1901
- def reload_worker_packet(
1902
- session_path: Path,
1903
- expected_packet_digest: str,
1904
- admit_subjects=False,
1905
- require_admission=False,
1906
- legacy_v4=False,
1907
- ):
1908
- return load_worker_packet(
1909
- session_path,
1910
- expected_packet_digest,
1911
- admit_subjects,
1912
- require_admission,
1913
- legacy_v4,
1914
- )
1915
-
1916
-
1917
- def packet_admission_binding(packet):
1918
- receipt = packet.get("admission_receipt") if packet is not None else None
1919
- if receipt is None:
1920
- return None
1921
- return {
1922
- "admission_digest": receipt["digest"],
1923
- "subject_digest": receipt["data"]["subject_digest"],
1924
- }
1925
-
1926
-
1927
- def validate_artifact_admission_binding(data, packet, label: str):
1928
- expected = packet_admission_binding(packet)
1929
- if expected is None:
1930
- return None
1931
- for field in ("admission_digest", "subject_digest"):
1932
- if not digest_string(data.get(field)):
1933
- return f"{label} {field} must be the lowercase SHA-256 from ADMISSION.json"
1934
- if data[field] != expected[field]:
1935
- return f"{label} {field} must exactly match ADMISSION.json"
1936
- return None
1937
-
1938
-
1939
- def validate_pinned_admission(packet, expected):
1940
- if expected is None:
1941
- return None
1942
- if packet_admission_binding(packet) != expected:
1943
- return (
1944
- "ADMISSION.json receipt changed during polling; the strict review session "
1945
- "is INCOMPLETE and must be restarted as a fresh session"
1946
- )
1947
- return None
1948
-
1949
-
1950
- def validate_north_star_trace(value, allowed=None):
1951
- if not isinstance(value, dict) or set(value) != {"first_principles", "serves", "touches"}:
1952
- return "finding north_star_trace must contain exactly first_principles, serves, and touches"
1953
- patterns = {
1954
- "first_principles": r"FP-[A-Za-z0-9][A-Za-z0-9._-]*",
1955
- "serves": r"NS-[A-Za-z0-9][A-Za-z0-9._-]*",
1956
- "touches": r"HC-[A-Za-z0-9][A-Za-z0-9._-]*",
1957
- }
1958
- for field, pattern in patterns.items():
1959
- values = value.get(field)
1960
- if not isinstance(values, list) or not all(
1961
- isinstance(item, str) and re.fullmatch(pattern, item) for item in values
1962
- ):
1963
- return f"finding north_star_trace.{field} must be an array of resolving {pattern[:2]} IDs"
1964
- if len(values) != len(set(values)):
1965
- return f"finding north_star_trace.{field} must not contain duplicates"
1966
- if allowed is not None:
1967
- outside = [item for item in values if item not in allowed[field]]
1968
- if outside:
1969
- return (
1970
- f"finding north_star_trace.{field} contains IDs outside the immutable "
1971
- f"Worker Packet: {', '.join(outside)}"
1972
- )
1973
- return None
1974
-
1975
-
1976
- def validate_finding(
1977
- finding,
1978
- artifact_axis: str,
1979
- seen_ids,
1980
- findings_schema: str,
1981
- allowed_trace=None,
1982
- allowed_files=None,
1983
- repo_root=None,
1984
- ):
1985
- if not isinstance(finding, dict):
1986
- return "finding must be an object"
1987
- missing = sorted(FINDING_FIELDS - set(finding))
1988
- if missing:
1989
- return f"finding missing fields: {', '.join(missing)}"
1990
- if not nonempty_string(finding.get("id")):
1991
- return "finding id must be a non-empty string"
1992
- if finding["id"] in seen_ids:
1993
- return f"duplicate finding id: {finding['id']}"
1994
- seen_ids.add(finding["id"])
1995
- if finding.get("axis") != artifact_axis:
1996
- return "finding axis must match artifact axis"
1997
- if finding.get("severity") not in SEVERITIES:
1998
- return "finding severity must be P0, P1, P2, or P3"
1999
- for field in ["category", "title", "file", "trigger", "impact", "evidence", "suggestion"]:
2000
- if not nonempty_string(finding.get(field)):
2001
- return f"finding {field} must be a non-empty string"
2002
- if repo_root is not None:
2003
- finding_file, path_error = validate_repo_path(
2004
- finding["file"],
2005
- repo_root,
2006
- "finding file",
2007
- )
2008
- if path_error:
2009
- return path_error
2010
- if finding_file not in allowed_files:
2011
- return "finding file must be within the immutable Worker Packet diff scope"
2012
- if not isinstance(finding.get("line"), int) or finding["line"] < 1:
2013
- return "finding line must be a positive integer"
2014
- line_end = finding.get("line_end")
2015
- if line_end is not None and (not isinstance(line_end, int) or line_end < finding["line"]):
2016
- return "finding line_end must be at or after line"
2017
- if findings_schema == FINDINGS_SCHEMA:
2018
- if "north_star_trace" not in finding:
2019
- return "finding missing fields: north_star_trace"
2020
- trace_error = validate_north_star_trace(
2021
- finding.get("north_star_trace"),
2022
- allowed_trace,
2023
- )
2024
- if trace_error:
2025
- return trace_error
2026
- return None
2027
-
2028
-
2029
- def validate_specialist(
2030
- data,
2031
- expected_packet_digest: str,
2032
- findings_schema: str,
2033
- expected_worker=None,
2034
- packet=None,
2035
- ):
2036
- if data.get("$schema") != findings_schema:
2037
- return f"$schema must be {findings_schema}"
2038
- if not nonempty_string(data.get("agent")):
2039
- return "agent must be a non-empty string"
2040
- if expected_worker is not None and data.get("agent") != expected_worker["agent"]:
2041
- return f"agent must match packet roster worker {expected_worker['agent']}"
2042
- if not digest_string(data.get("packet_digest")):
2043
- return "packet_digest must be a lowercase SHA-256 digest"
2044
- if data["packet_digest"] != expected_packet_digest:
2045
- return f"packet_digest does not match expected immutable packet {expected_packet_digest}"
2046
- admission_error = validate_artifact_admission_binding(
2047
- data,
2048
- packet,
2049
- "specialist artifact",
2050
- )
2051
- if admission_error:
2052
- return admission_error
2053
- axis = data.get("axis")
2054
- if axis not in AXES:
2055
- return "axis must be spec_fidelity or engineering_standards"
2056
- if expected_worker is not None and axis != expected_worker["axis"]:
2057
- return f"axis must match packet roster axis {expected_worker['axis']}"
2058
- if not nonempty_string(data.get("session")):
2059
- return "session must be a non-empty string"
2060
- if findings_schema == FINDINGS_SCHEMA:
2061
- if not timestamp_string(data.get("timestamp")):
2062
- return "timestamp must be an RFC 3339 timestamp"
2063
- elif not nonempty_string(data.get("timestamp")):
2064
- return "timestamp must be a non-empty string"
2065
- if packet is not None and data.get("session") != packet["data"]["session"]:
2066
- return "session must match WORKER-PACKET.json"
2067
- scope = data.get("scope")
2068
- if not isinstance(scope, dict):
2069
- return "scope must be an object"
2070
- for field in ["head", "range"]:
2071
- if not nonempty_string(scope.get(field)):
2072
- return f"scope.{field} must be a non-empty string"
2073
- if packet is not None and scope.get(field) != packet["data"][field]:
2074
- return f"scope.{field} must match WORKER-PACKET.json"
2075
- if not isinstance(scope.get("files_analyzed"), list) or not all(
2076
- nonempty_string(item) for item in scope["files_analyzed"]
2077
- ):
2078
- return "scope.files_analyzed must be a string array"
2079
- if packet is not None:
2080
- normalized_analyzed = []
2081
- for index, analyzed_file in enumerate(scope["files_analyzed"]):
2082
- normalized_path, path_error = validate_repo_path(
2083
- analyzed_file,
2084
- packet["repo_root"],
2085
- f"scope.files_analyzed[{index}]",
2086
- )
2087
- if path_error:
2088
- return path_error
2089
- normalized_analyzed.append(normalized_path)
2090
- if len(normalized_analyzed) != len(set(normalized_analyzed)):
2091
- return "scope.files_analyzed must not contain duplicates"
2092
- outside_scope = [
2093
- item for item in normalized_analyzed if item not in packet["diff_scope"]
2094
- ]
2095
- if outside_scope:
2096
- return (
2097
- "scope.files_analyzed must stay within the immutable Worker Packet "
2098
- "diff scope: " + ", ".join(outside_scope)
2099
- )
2100
- if scope.get("diff_only") is not True:
2101
- return "scope.diff_only must be true for packet-bound v4 artifacts"
2102
- elif not isinstance(scope.get("diff_only"), bool):
2103
- return "scope.diff_only must be boolean"
2104
- if data.get("status") != "complete":
2105
- return "status must be complete"
2106
- findings = data.get("findings")
2107
- if not isinstance(findings, list):
2108
- return "findings must be an array"
2109
- seen_ids = set()
2110
- for finding in findings:
2111
- error = validate_finding(
2112
- finding,
2113
- axis,
2114
- seen_ids,
2115
- findings_schema,
2116
- packet["trace"] if packet is not None else None,
2117
- packet["diff_scope"] if packet is not None else None,
2118
- packet["repo_root"] if packet is not None else None,
2119
- )
2120
- if error:
2121
- return error
2122
- for field in ["positive_observations", "limitations"]:
2123
- if not isinstance(data.get(field), list):
2124
- return f"{field} must be an array"
2125
- coverage_refs = data.get("coverage_refs")
2126
- if not isinstance(coverage_refs, list) or not all(
2127
- nonempty_string(item) for item in coverage_refs
2128
- ):
2129
- return "coverage_refs must be a string array"
2130
- if not findings and not coverage_refs and not data["limitations"]:
2131
- return "zero findings require coverage_refs or an explicit limitation"
2132
- return None
2133
-
2134
-
2135
- def evaluate_artifacts(
2136
- session_path: Path,
2137
- expected,
2138
- expected_packet_digest: str,
2139
- findings_schema: str,
2140
- packet=None,
2141
- ):
2142
- done = []
2143
- missing = []
2144
- invalid = []
2145
- errors = {}
2146
- digests = {}
2147
- if packet is None:
2148
- repo_root, root_error = canonical_repo_root(session_path)
2149
- if root_error:
2150
- return [], [], list(expected), {"session": root_error}, {}
2151
- else:
2152
- repo_root = packet["repo_root"]
2153
- for stem in expected:
2154
- expected_worker = packet["by_stem"].get(stem) if packet is not None else None
2155
- if packet is not None and expected_worker is None:
2156
- invalid.append(stem)
2157
- errors[stem] = "requested artifact stem is absent from WORKER-PACKET.json roster"
2158
- continue
2159
- repo_path = (
2160
- expected_worker["output"]
2161
- if expected_worker is not None
2162
- else f".ultra/reviews/{session_path.resolve().name}/{stem}.json"
2163
- )
2164
- data, snapshot, error, is_missing = stable_read_repo_json(
2165
- repo_root,
2166
- repo_path,
2167
- f"specialist artifact {stem}",
2168
- )
2169
- if is_missing:
2170
- missing.append(stem)
2171
- continue
2172
- if error is None:
2173
- error = validate_specialist(
2174
- data,
2175
- expected_packet_digest,
2176
- findings_schema,
2177
- expected_worker,
2178
- packet,
2179
- )
2180
- if error:
2181
- invalid.append(stem)
2182
- errors[stem] = error
2183
- else:
2184
- done.append(stem)
2185
- digests[stem] = snapshot["sha256"]
2186
- return done, missing, invalid, errors, digests
2187
-
2188
-
2189
- def wait_for_agents(
2190
- session_path: Path,
2191
- expected,
2192
- expected_packet_digest: str,
2193
- findings_schema: str,
2194
- timeout: float,
2195
- require_admission: bool,
2196
- legacy_v4: bool = False,
2197
- ) -> bool:
2198
- packet = None
2199
- if findings_schema == FINDINGS_SCHEMA:
2200
- packet, packet_error = reload_worker_packet(
2201
- session_path,
2202
- expected_packet_digest,
2203
- require_admission=require_admission,
2204
- legacy_v4=legacy_v4,
2205
- )
2206
- if packet_error:
2207
- print(json.dumps({
2208
- "status": "incomplete",
2209
- "artifacts_done": [],
2210
- "artifacts_missing": [],
2211
- "artifacts_invalid": list(expected),
2212
- "errors": {"WORKER-PACKET": packet_error},
2213
- "count": 0,
2214
- }, sort_keys=True))
2215
- return False
2216
- pinned_admission = packet_admission_binding(packet)
2217
- deadline = time.monotonic() + timeout
2218
- while True:
2219
- current_packet = packet
2220
- if packet is not None:
2221
- current_packet, packet_error = reload_worker_packet(
2222
- session_path,
2223
- expected_packet_digest,
2224
- require_admission=require_admission,
2225
- legacy_v4=legacy_v4,
2226
- )
2227
- if packet_error:
2228
- print(json.dumps({
2229
- "status": "incomplete",
2230
- "artifacts_done": [],
2231
- "artifacts_missing": [],
2232
- "artifacts_invalid": list(expected),
2233
- "errors": {"WORKER-PACKET": packet_error},
2234
- "count": 0,
2235
- }, sort_keys=True))
2236
- return False
2237
- admission_error = validate_pinned_admission(
2238
- current_packet,
2239
- pinned_admission,
2240
- )
2241
- if admission_error:
2242
- print(json.dumps({
2243
- "status": "incomplete",
2244
- "artifacts_done": [],
2245
- "artifacts_missing": [],
2246
- "artifacts_invalid": list(expected),
2247
- "errors": {"ADMISSION": admission_error},
2248
- "count": 0,
2249
- }, sort_keys=True))
2250
- return False
2251
- done, missing, invalid, errors, digests = evaluate_artifacts(
2252
- session_path,
2253
- expected,
2254
- expected_packet_digest,
2255
- findings_schema,
2256
- current_packet,
2257
- )
2258
- if len(done) == len(expected):
2259
- if packet is not None:
2260
- final_packet, packet_error = reload_worker_packet(
2261
- session_path,
2262
- expected_packet_digest,
2263
- require_admission=require_admission,
2264
- legacy_v4=legacy_v4,
2265
- )
2266
- if packet_error:
2267
- print(json.dumps({
2268
- "status": "incomplete",
2269
- "artifacts_done": [],
2270
- "artifacts_missing": [],
2271
- "artifacts_invalid": list(expected),
2272
- "errors": {"WORKER-PACKET": packet_error},
2273
- "count": 0,
2274
- }, sort_keys=True))
2275
- return False
2276
- admission_error = validate_pinned_admission(
2277
- final_packet,
2278
- pinned_admission,
2279
- )
2280
- if admission_error:
2281
- print(json.dumps({
2282
- "status": "incomplete",
2283
- "artifacts_done": [],
2284
- "artifacts_missing": [],
2285
- "artifacts_invalid": list(expected),
2286
- "errors": {"ADMISSION": admission_error},
2287
- "count": 0,
2288
- }, sort_keys=True))
2289
- return False
2290
- output = {
2291
- "status": "complete",
2292
- "artifacts_done": done,
2293
- "artifact_digests": digests,
2294
- "artifacts_missing": [],
2295
- "artifacts_invalid": [],
2296
- "errors": {},
2297
- "count": len(done),
2298
- }
2299
- if pinned_admission is not None:
2300
- output.update(pinned_admission)
2301
- print(json.dumps(output, sort_keys=True))
2302
- return True
2303
- if time.monotonic() >= deadline:
2304
- print(json.dumps({
2305
- "status": "incomplete",
2306
- "artifacts_done": done,
2307
- "artifacts_missing": missing,
2308
- "artifacts_invalid": invalid,
2309
- "errors": errors,
2310
- "count": len(done),
2311
- }, sort_keys=True))
2312
- return False
2313
- time.sleep(min(POLL_INTERVAL, max(0.0, deadline - time.monotonic())))
2314
-
2315
-
2316
- def expected_overall_verdict(data) -> str:
2317
- verdicts = {data["axes"][axis]["verdict"] for axis in AXES}
2318
- severities = {finding["severity"] for finding in data["findings"]}
2319
- if "INCOMPLETE" in verdicts:
2320
- return "INCOMPLETE"
2321
- if "FAIL" in verdicts or severities.intersection({"P0", "P1"}):
2322
- return "REQUEST_CHANGES"
2323
- return "APPROVE"
2324
-
2325
-
2326
- def load_completed_specialists(
2327
- session_path: Path,
2328
- packet,
2329
- completed,
2330
- expected_packet_digest: str,
2331
- ):
2332
- union = []
2333
- refs_by_axis = {axis: [] for axis in AXES}
2334
- for worker in packet["workers"]:
2335
- if worker["agent"] not in completed:
2336
- continue
2337
- data, _snapshot, error, is_missing = stable_read_repo_json(
2338
- packet["repo_root"],
2339
- worker["output"],
2340
- f"completed specialist {worker['agent']}",
2341
- )
2342
- if is_missing:
2343
- error = "artifact is missing"
2344
- if error is None:
2345
- error = validate_specialist(
2346
- data,
2347
- expected_packet_digest,
2348
- FINDINGS_SCHEMA,
2349
- worker,
2350
- packet,
2351
- )
2352
- if error:
2353
- return None, None, (
2354
- f"completed specialist {worker['agent']} is invalid: {error}"
2355
- )
2356
- refs_by_axis[worker["axis"]].append(worker["filename"])
2357
- union.extend(data["findings"])
2358
- return union, refs_by_axis, None
2359
-
2360
-
2361
- def expected_axis_verdict(axis, union, packet, failed) -> str:
2362
- selected = [worker for worker in packet["workers"] if worker["axis"] == axis]
2363
- if not selected or any(worker["agent"] in failed for worker in selected):
2364
- return "INCOMPLETE"
2365
- severities = {
2366
- finding["severity"] for finding in union if finding.get("axis") == axis
2367
- }
2368
- return "FAIL" if severities.intersection({"P0", "P1"}) else "PASS"
2369
-
2370
-
2371
- def validate_summary(
2372
- data,
2373
- expected_packet_digest: str,
2374
- summary_schema: str,
2375
- findings_schema: str,
2376
- session_path=None,
2377
- packet=None,
2378
- ):
2379
- if data.get("$schema") != summary_schema:
2380
- return f"$schema must be {summary_schema}"
2381
- if data.get("mode") not in REVIEW_MODES:
2382
- return "mode must be task, change, or plan"
2383
- if data.get("execution_mode") not in EXECUTION_MODES:
2384
- return "execution_mode must be isolated or sequential-shared-context"
2385
- for field in ["session", "change_id", "head"]:
2386
- if not nonempty_string(data.get(field)):
2387
- return f"{field} must be a non-empty string"
2388
- if not digest_string(data.get("packet_digest")):
2389
- return "packet_digest must be a lowercase SHA-256 digest"
2390
- if data["packet_digest"] != expected_packet_digest:
2391
- return f"packet_digest does not match expected immutable packet {expected_packet_digest}"
2392
- admission_error = validate_artifact_admission_binding(data, packet, "SUMMARY.json")
2393
- if admission_error:
2394
- return admission_error
2395
- if packet is not None:
2396
- packet_data = packet["data"]
2397
- exact_fields = ["mode", "session", "change_id", "task_ids", "head"]
2398
- for field in exact_fields:
2399
- if data.get(field) != packet_data.get(field):
2400
- return f"summary {field} must match WORKER-PACKET.json"
2401
- if summary_schema == SUMMARY_SCHEMA:
2402
- if "worktree_digest" not in data or data.get("worktree_digest") is not None:
2403
- return (
2404
- "worktree_digest must be present and null until an exact worktree "
2405
- "observation is bound by the immutable Worker Packet"
2406
- )
2407
- if not digest_string(data.get("context_digest")):
2408
- return "context_digest must be a lowercase SHA-256 digest"
2409
- if packet is not None and data["context_digest"] != packet["context_digest"]:
2410
- return (
2411
- "context_digest must equal the immutable Worker Packet context digest "
2412
- f"{packet['context_digest']}"
2413
- )
2414
- else:
2415
- if not nonempty_string(data.get("context_digest")):
2416
- return "context_digest must be a non-empty string"
2417
- if data.get("worktree_digest") is not None and not nonempty_string(
2418
- data.get("worktree_digest")
2419
- ):
2420
- return "worktree_digest must be null or a non-empty string"
2421
- task_ids = data.get("task_ids")
2422
- if not isinstance(task_ids, list) or not all(nonempty_string(item) for item in task_ids):
2423
- return "task_ids must be a string array"
2424
- if data.get("status") != "complete":
2425
- return "status must be complete"
2426
- if data.get("verdict") not in OVERALL_VERDICTS:
2427
- return "invalid overall verdict"
2428
- axes = data.get("axes")
2429
- if not isinstance(axes, dict) or set(axes) != AXES:
2430
- return "axes must contain exactly spec_fidelity and engineering_standards"
2431
- for axis in AXES:
2432
- item = axes[axis]
2433
- if not isinstance(item, dict) or item.get("verdict") not in AXIS_VERDICTS:
2434
- return f"invalid {axis} verdict"
2435
- refs = item.get("evidence_refs")
2436
- if not isinstance(refs, list) or not all(nonempty_string(ref) for ref in refs):
2437
- return f"{axis}.evidence_refs must be a string array"
2438
- if packet is None and not refs:
2439
- return f"{axis}.evidence_refs must be non-empty for legacy summaries"
2440
- workers = data.get("workers")
2441
- if not isinstance(workers, dict):
2442
- return "workers must be an object"
2443
- for field in ["completed", "failed", "skipped"]:
2444
- values = workers.get(field)
2445
- if not isinstance(values, list) or not all(nonempty_string(item) for item in values):
2446
- return f"workers.{field} must be a string array"
2447
- if len(values) != len(set(values)):
2448
- return f"workers.{field} must not contain duplicates"
2449
- completed_set = set(workers["completed"])
2450
- failed_set = set(workers["failed"])
2451
- skipped_set = set(workers["skipped"])
2452
- if completed_set & failed_set or completed_set & skipped_set or failed_set & skipped_set:
2453
- return "workers completed, failed, and skipped sets must be disjoint"
2454
- worker_selection = data.get("worker_selection")
2455
- if not isinstance(worker_selection, list) or not worker_selection:
2456
- return "worker_selection must be a non-empty array"
2457
- selected = set()
2458
- skipped = set()
2459
- seen_workers = set()
2460
- for item in worker_selection:
2461
- if not isinstance(item, dict):
2462
- return "worker_selection items must be objects"
2463
- worker = item.get("worker")
2464
- status = item.get("status")
2465
- if not nonempty_string(worker) or status not in SELECTION_STATUSES:
2466
- return "worker_selection requires worker and selected or skipped status"
2467
- if worker in seen_workers:
2468
- return f"duplicate worker selection: {worker}"
2469
- if not nonempty_string(item.get("rationale")):
2470
- return f"worker selection rationale is required: {worker}"
2471
- seen_workers.add(worker)
2472
- (selected if status == "selected" else skipped).add(worker)
2473
- if selected != completed_set | failed_set:
2474
- return "selected workers must equal completed and failed workers"
2475
- if skipped != skipped_set:
2476
- return "skipped worker selection must match workers.skipped"
2477
- if seen_workers != REVIEW_WORKERS:
2478
- missing = sorted(REVIEW_WORKERS - seen_workers)
2479
- extra = sorted(seen_workers - REVIEW_WORKERS)
2480
- return f"worker_selection must disposition the complete roster; missing={missing}, extra={extra}"
2481
- if "review-spec" not in selected:
2482
- return "review-spec must be selected"
2483
- if packet is not None:
2484
- packet_selected = {worker["agent"] for worker in packet["workers"]}
2485
- if selected != packet_selected:
2486
- return "selected worker roster must equal WORKER-PACKET.json workers"
2487
- if skipped != REVIEW_WORKERS - packet_selected:
2488
- return "skipped worker roster must equal workers absent from WORKER-PACKET.json"
2489
- findings = data.get("findings")
2490
- if not isinstance(findings, list):
2491
- return "findings must be an array"
2492
- seen_ids = set()
2493
- for finding in findings:
2494
- axis = finding.get("axis") if isinstance(finding, dict) else None
2495
- if axis not in AXES:
2496
- return "summary finding has invalid axis"
2497
- error = validate_finding(
2498
- finding,
2499
- axis,
2500
- seen_ids,
2501
- findings_schema,
2502
- packet["trace"] if packet is not None else None,
2503
- packet["diff_scope"] if packet is not None else None,
2504
- packet["repo_root"] if packet is not None else None,
2505
- )
2506
- if error:
2507
- return error
2508
- for field in ["positive_observations", "limitations"]:
2509
- if not isinstance(data.get(field), list):
2510
- return f"{field} must be an array"
2511
- coverage_refs = data.get("coverage_refs")
2512
- if not isinstance(coverage_refs, list) or not all(
2513
- nonempty_string(item) for item in coverage_refs
2514
- ):
2515
- return "coverage_refs must be a string array"
2516
- if not findings and not coverage_refs and not data["limitations"]:
2517
- return "zero findings require coverage_refs or an explicit limitation"
2518
- if packet is not None:
2519
- if session_path is None:
2520
- return "v4 summary validation requires the session path"
2521
- union, refs_by_axis, specialist_error = load_completed_specialists(
2522
- session_path,
2523
- packet,
2524
- set(workers["completed"]),
2525
- expected_packet_digest,
2526
- )
2527
- if specialist_error:
2528
- return specialist_error
2529
- if findings != union:
2530
- return (
2531
- "SUMMARY findings must equal the complete ordered specialist finding "
2532
- "union unchanged"
2533
- )
2534
- failed = set(workers["failed"])
2535
- for axis in AXES:
2536
- if axes[axis]["evidence_refs"] != refs_by_axis[axis]:
2537
- return f"{axis}.evidence_refs must equal packet-ordered completed specialist outputs"
2538
- axis_expected = expected_axis_verdict(axis, union, packet, failed)
2539
- if axes[axis]["verdict"] != axis_expected:
2540
- return (
2541
- f"{axis} verdict {axes[axis]['verdict']} conflicts with completed "
2542
- f"specialist evidence; expected {axis_expected}"
2543
- )
2544
- expected = expected_overall_verdict(data)
2545
- if data["verdict"] != expected:
2546
- return f"verdict {data['verdict']} conflicts with evidence; expected {expected}"
2547
- return None
2548
-
2549
-
2550
- def wait_for_summary(
2551
- session_path: Path,
2552
- expected_packet_digest: str,
2553
- summary_schema: str,
2554
- findings_schema: str,
2555
- timeout: float,
2556
- summary_snapshot_bytes=None,
2557
- expected_summary_digest=None,
2558
- require_admission=True,
2559
- legacy_v4=False,
2560
- ) -> bool:
2561
- packet = None
2562
- if summary_schema == SUMMARY_SCHEMA:
2563
- packet, packet_error = reload_worker_packet(
2564
- session_path,
2565
- expected_packet_digest,
2566
- require_admission=require_admission,
2567
- legacy_v4=legacy_v4,
2568
- )
2569
- if packet_error:
2570
- print(json.dumps({"status": "incomplete", "error": packet_error}, sort_keys=True))
2571
- return False
2572
- pinned_admission = packet_admission_binding(packet)
2573
- repo_root = packet["repo_root"] if packet is not None else canonical_repo_root(session_path)[0]
2574
- summary_repo_path = f".ultra/reviews/{session_path.resolve().name}/SUMMARY.json"
2575
- deadline = time.monotonic() + timeout
2576
- last_error = "SUMMARY.json is missing"
2577
- while True:
2578
- current_packet = packet
2579
- if packet is not None:
2580
- current_packet, packet_error = reload_worker_packet(
2581
- session_path,
2582
- expected_packet_digest,
2583
- require_admission=require_admission,
2584
- legacy_v4=legacy_v4,
2585
- )
2586
- if packet_error:
2587
- print(json.dumps({"status": "incomplete", "error": packet_error}, sort_keys=True))
2588
- return False
2589
- admission_error = validate_pinned_admission(
2590
- current_packet,
2591
- pinned_admission,
2592
- )
2593
- if admission_error:
2594
- print(json.dumps({"status": "incomplete", "error": admission_error}, sort_keys=True))
2595
- return False
2596
-
2597
- if summary_snapshot_bytes is not None:
2598
- summary_digest = hashlib.sha256(summary_snapshot_bytes).hexdigest()
2599
- if summary_digest != expected_summary_digest:
2600
- error = (
2601
- "caller-owned SUMMARY snapshot bytes do not match "
2602
- f"--summary-snapshot-digest {expected_summary_digest}; observed "
2603
- f"{summary_digest}"
2604
- )
2605
- data = None
2606
- else:
2607
- data, error = decode_json_bytes(
2608
- summary_snapshot_bytes,
2609
- "caller-owned SUMMARY snapshot",
2610
- )
2611
- is_missing = False
2612
- else:
2613
- data, snapshot, error, is_missing = stable_read_repo_json(
2614
- repo_root,
2615
- summary_repo_path,
2616
- "SUMMARY.json",
2617
- )
2618
- summary_digest = snapshot["sha256"] if snapshot is not None else None
2619
-
2620
- if error is None:
2621
- error = validate_summary(
2622
- data,
2623
- expected_packet_digest,
2624
- summary_schema,
2625
- findings_schema,
2626
- session_path,
2627
- current_packet,
2628
- )
2629
- if error is None:
2630
- if packet is not None:
2631
- final_packet, packet_error = reload_worker_packet(
2632
- session_path,
2633
- expected_packet_digest,
2634
- require_admission=require_admission,
2635
- legacy_v4=legacy_v4,
2636
- )
2637
- if packet_error:
2638
- print(json.dumps({"status": "incomplete", "error": packet_error}, sort_keys=True))
2639
- return False
2640
- admission_error = validate_pinned_admission(
2641
- final_packet,
2642
- pinned_admission,
2643
- )
2644
- if admission_error:
2645
- print(json.dumps({"status": "incomplete", "error": admission_error}, sort_keys=True))
2646
- return False
2647
- counts = {severity: 0 for severity in sorted(SEVERITIES)}
2648
- for finding in data["findings"]:
2649
- counts[finding["severity"]] += 1
2650
- output = {
2651
- "status": "complete",
2652
- "verdict": data["verdict"],
2653
- "message": (
2654
- f"Review complete: {data['verdict']} "
2655
- f"(P0:{counts['P0']} P1:{counts['P1']} P2:{counts['P2']} "
2656
- f"P3:{counts['P3']} total:{len(data['findings'])})"
2657
- ),
2658
- "counts": {
2659
- **counts,
2660
- "total": len(data["findings"]),
2661
- },
2662
- "summary_digest": summary_digest,
2663
- }
2664
- if pinned_admission is not None:
2665
- output.update(pinned_admission)
2666
- print(json.dumps(output, sort_keys=True))
2667
- return True
2668
- if not is_missing:
2669
- last_error = error
2670
- if summary_snapshot_bytes is not None:
2671
- print(json.dumps({"status": "incomplete", "error": last_error}, sort_keys=True))
2672
- return False
2673
- if time.monotonic() >= deadline:
2674
- print(json.dumps({"status": "incomplete", "error": last_error}, sort_keys=True))
2675
- return False
2676
- time.sleep(min(POLL_INTERVAL, max(0.0, deadline - time.monotonic())))
2677
-
2678
-
2679
- def validate_packet_preflight(session_path: Path, expected_packet_digest: str) -> bool:
2680
- packet, error = reload_worker_packet(
2681
- session_path,
2682
- expected_packet_digest,
2683
- )
2684
- if error:
2685
- print(json.dumps({"status": "incomplete", "error": error}, sort_keys=True))
2686
- return False
2687
- admission_contract = packet["data"].get("admission_contract")
2688
- if admission_contract == ADMISSION_CONTRACT_V1:
2689
- retained, receipt_error = reload_worker_packet(
2690
- session_path,
2691
- expected_packet_digest,
2692
- require_admission=True,
2693
- )
2694
- if receipt_error:
2695
- print(json.dumps({
2696
- "status": "incomplete",
2697
- "error_code": "admission_v1_receipt_required",
2698
- "error": (
2699
- "retained strict v1 is read-only and requires its exact existing "
2700
- "ADMISSION.json receipt; start a fresh strict v2 review session "
2701
- f"instead of creating or recreating it: {receipt_error}"
2702
- ),
2703
- }, sort_keys=True))
2704
- return False
2705
- binding = packet_admission_binding(retained)
2706
- print(json.dumps({
2707
- "status": "complete",
2708
- "packet_digest": retained["digest"],
2709
- "context_digest": retained["context_digest"],
2710
- "subject_digest": binding["subject_digest"],
2711
- "admission_digest": binding["admission_digest"],
2712
- "publication": "existing",
2713
- }, sort_keys=True))
2714
- return True
2715
- if admission_contract != ADMISSION_CONTRACT_V2:
2716
- print(json.dumps({
2717
- "status": "incomplete",
2718
- "error_code": "admission_contract_required",
2719
- "error": (
2720
- "WORKER-PACKET.json admission_contract must be "
2721
- f"{ADMISSION_CONTRACT_V2} for new strict v4 admission"
2722
- ),
2723
- }, sort_keys=True))
2724
- return False
2725
-
2726
- admission_path = session_path / "ADMISSION.json"
2727
- try:
2728
- os.lstat(admission_path)
2729
- admission_missing = False
2730
- except FileNotFoundError:
2731
- admission_missing = True
2732
- except OSError as observation_error:
2733
- print(json.dumps({
2734
- "status": "incomplete",
2735
- "error_code": "admission_receipt_observation_failed",
2736
- "error": f"ADMISSION.json presence could not be observed safely: {observation_error}",
2737
- }, sort_keys=True))
2738
- return False
2739
- if admission_missing:
2740
- existing_outputs = []
2741
- output_paths = [
2742
- *(worker["output"] for worker in packet["workers"]),
2743
- f".ultra/reviews/{packet['data']['session']}/SUMMARY.json",
2744
- ]
2745
- for output_path in output_paths:
2746
- try:
2747
- os.lstat(packet["repo_root"] / output_path)
2748
- existing_outputs.append(output_path)
2749
- except FileNotFoundError:
2750
- continue
2751
- except OSError as observation_error:
2752
- print(json.dumps({
2753
- "status": "incomplete",
2754
- "error_code": "admission_output_observation_failed",
2755
- "error": (
2756
- f"review output presence could not be observed safely for "
2757
- f"{output_path}: {observation_error}"
2758
- ),
2759
- }, sort_keys=True))
2760
- return False
2761
- if existing_outputs:
2762
- print(json.dumps({
2763
- "status": "incomplete",
2764
- "error_code": "admission_receipt_missing_after_outputs",
2765
- "error": (
2766
- "ADMISSION.json is missing after review output exists; the current "
2767
- "strict session is INCOMPLETE and requires a fresh review session: "
2768
- + ", ".join(existing_outputs)
2769
- ),
2770
- }, sort_keys=True))
2771
- return False
2772
-
2773
- packet, error = reload_worker_packet(
2774
- session_path,
2775
- expected_packet_digest,
2776
- admit_subjects=True,
2777
- )
2778
- if error:
2779
- print(json.dumps({"status": "incomplete", "error": error}, sort_keys=True))
2780
- return False
2781
- publication, error = atomic_write_admission(
2782
- session_path,
2783
- packet["admission"],
2784
- )
2785
- if error:
2786
- print(json.dumps({
2787
- "status": "incomplete",
2788
- "error_code": error["code"],
2789
- "error": error["message"],
2790
- }, sort_keys=True))
2791
- return False
2792
- output = {
2793
- "status": "complete",
2794
- "packet_digest": packet["digest"],
2795
- "context_digest": packet["context_digest"],
2796
- "subject_digest": packet["admission"]["subject_digest"],
2797
- "admission_digest": publication["digest"],
2798
- "publication": publication["publication"],
2799
- }
2800
- if "durability_warning" in publication:
2801
- output["durability_warning"] = publication["durability_warning"]
2802
- print(json.dumps(output, sort_keys=True))
2803
- return True
2804
-
2805
-
2806
- def validate_legacy_packet_boundary(
2807
- session_path: Path,
2808
- expected_packet_digest: str,
2809
- legacy_flag: str,
2810
- ):
2811
- repo_root, root_error = canonical_repo_root(session_path)
2812
- if root_error:
2813
- return f"WORKER-PACKET.json {root_error}"
2814
- packet_repo_path = (
2815
- f".ultra/reviews/{session_path.resolve().name}/WORKER-PACKET.json"
2816
- )
2817
- snapshot, error, missing = stable_read_repo_bytes(
2818
- repo_root,
2819
- packet_repo_path,
2820
- "WORKER-PACKET.json",
2821
- )
2822
- if missing:
2823
- return None
2824
- if error:
2825
- return error
2826
- if snapshot["sha256"] != expected_packet_digest:
2827
- return (
2828
- "WORKER-PACKET.json bytes do not match expected immutable packet digest "
2829
- f"{expected_packet_digest}; observed {snapshot['sha256']}"
2830
- )
2831
- packet, decode_error = decode_json_bytes(snapshot["bytes"], "WORKER-PACKET.json")
2832
- if decode_error:
2833
- return decode_error
2834
- if "admission_contract" in packet:
2835
- return (
2836
- "WORKER-PACKET.json declares a strict admission_contract and cannot be "
2837
- f"consumed with {legacy_flag}; retain and use its exact ADMISSION.json receipt"
2838
- )
2839
- return None
2840
-
2841
-
2842
- def main():
2843
- if len(sys.argv) < 3:
2844
- print(__doc__, file=sys.stderr)
2845
- sys.exit(2)
2846
-
2847
- session_path = Path(sys.argv[1])
2848
- mode = sys.argv[2]
2849
- if not session_path.is_dir():
2850
- print(f"Error: session directory not found: {session_path}", file=sys.stderr)
2851
- sys.exit(2)
2852
-
2853
- if len(sys.argv) < 5 or sys.argv[3] != "--packet-digest" or not digest_string(sys.argv[4]):
2854
- print("Error: --packet-digest requires the expected lowercase SHA-256 digest", file=sys.stderr)
2855
- sys.exit(2)
2856
- expected_packet_digest = sys.argv[4]
2857
- arguments = sys.argv[5:]
2858
- legacy_v3 = bool(arguments and arguments[0] == "--legacy-v3")
2859
- legacy_v4 = bool(arguments and arguments[0] == "--legacy-v4")
2860
- if legacy_v3 or legacy_v4:
2861
- arguments = arguments[1:]
2862
- findings_schema = LEGACY_FINDINGS_SCHEMA if legacy_v3 else FINDINGS_SCHEMA
2863
- summary_schema = LEGACY_SUMMARY_SCHEMA if legacy_v3 else SUMMARY_SCHEMA
2864
- require_admission = not legacy_v3 and not legacy_v4
2865
-
2866
- if mode in {"agents", "summary"} and (legacy_v3 or legacy_v4):
2867
- legacy_flag = "--legacy-v3" if legacy_v3 else "--legacy-v4"
2868
- legacy_error = validate_legacy_packet_boundary(
2869
- session_path,
2870
- expected_packet_digest,
2871
- legacy_flag,
2872
- )
2873
- if legacy_error:
2874
- print(json.dumps({"status": "incomplete", "error": legacy_error}, sort_keys=True))
2875
- sys.exit(1)
2876
-
2877
- if mode == "packet":
2878
- if legacy_v3 or legacy_v4 or arguments:
2879
- print("Error: packet mode accepts only --packet-digest", file=sys.stderr)
2880
- sys.exit(2)
2881
- ok = validate_packet_preflight(session_path, expected_packet_digest)
2882
- elif mode == "agents":
2883
- expected = arguments
2884
- if not expected or len(expected) != len(set(expected)) or not all(
2885
- ARTIFACT_STEM.fullmatch(stem) for stem in expected
2886
- ):
2887
- print("Error: agents mode requires unique safe artifact stems", file=sys.stderr)
2888
- sys.exit(2)
2889
- ok = wait_for_agents(
2890
- session_path,
2891
- expected,
2892
- expected_packet_digest,
2893
- findings_schema,
2894
- DEFAULT_TIMEOUT,
2895
- require_admission,
2896
- legacy_v4,
2897
- )
2898
- elif mode == "summary":
2899
- summary_snapshot_bytes = None
2900
- expected_summary_digest = None
2901
- if arguments:
2902
- if (
2903
- legacy_v3
2904
- or len(arguments) != 2
2905
- or arguments[0] != "--summary-snapshot-digest"
2906
- or not digest_string(arguments[1])
2907
- ):
2908
- print(
2909
- "Error: summary mode accepts optional --legacy-v3, --legacy-v4, or "
2910
- "--summary-snapshot-digest <sha256>",
2911
- file=sys.stderr,
2912
- )
2913
- sys.exit(2)
2914
- expected_summary_digest = arguments[1]
2915
- summary_snapshot_bytes = sys.stdin.buffer.read(MAX_JSON_BYTES + 1)
2916
- if len(summary_snapshot_bytes) > MAX_JSON_BYTES:
2917
- print(json.dumps({
2918
- "status": "incomplete",
2919
- "error": (
2920
- "caller-owned SUMMARY snapshot exceeds the "
2921
- f"{MAX_JSON_BYTES}-byte artifact limit"
2922
- ),
2923
- }, sort_keys=True))
2924
- sys.exit(1)
2925
- ok = wait_for_summary(
2926
- session_path,
2927
- expected_packet_digest,
2928
- summary_schema,
2929
- findings_schema,
2930
- DEFAULT_TIMEOUT,
2931
- summary_snapshot_bytes,
2932
- expected_summary_digest,
2933
- require_admission,
2934
- legacy_v4,
2935
- )
2936
- else:
2937
- print(f"Error: unknown mode '{mode}'. Use 'packet', 'agents', or 'summary'.", file=sys.stderr)
2938
- sys.exit(2)
2939
- sys.exit(0 if ok else 1)
2940
-
2941
-
2942
- if __name__ == "__main__":
2943
- main()