@junghanacs/entwurf 0.25.0 → 0.30.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 (88) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +1 -1
  3. package/CHANGELOG.md +182 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/DELIVERY.md +1 -1
  6. package/README.md +178 -674
  7. package/VERIFY.md +17 -16
  8. package/demo/README.md +1 -1
  9. package/docs/acp-backend-rail.md +6 -2
  10. package/docs/external-mcp-host.md +1 -1
  11. package/docs/setup-clean-host.md +346 -10
  12. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +27 -4
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +12 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +1 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +2 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +2 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +2 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -5
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +3 -3
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +13 -6
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +3 -3
  23. package/mcp/entwurf-bridge/src/index.ts +30 -3
  24. package/package.json +11 -11
  25. package/pi-extensions/entwurf-control.ts +50 -637
  26. package/pi-extensions/lib/acp/backend-adapter.ts +13 -1
  27. package/pi-extensions/lib/entwurf-fact-provider.ts +2 -2
  28. package/pi-extensions/lib/entwurf-mcp-server.ts +74 -0
  29. package/pi-extensions/lib/entwurf-peers-render.ts +1 -1
  30. package/pi-extensions/lib/entwurf-v2-decider.ts +2 -2
  31. package/pi-extensions/lib/entwurf-v2-mailbox.ts +2 -2
  32. package/pi-extensions/lib/entwurf-v2-production.ts +3 -3
  33. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -5
  34. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +3 -3
  35. package/pi-extensions/lib/fresh-call-composition.ts +13 -6
  36. package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
  37. package/pi-extensions/lib/mux-fresh-call.ts +3 -3
  38. package/run.sh +168 -54
  39. package/scripts/check-entwurf-peers-surface.ts +8 -15
  40. package/scripts/check-entwurf-self-address.ts +7 -11
  41. package/scripts/check-entwurf-v2-surface.ts +20 -112
  42. package/scripts/check-fresh-call-dispatch.ts +12 -13
  43. package/scripts/check-gate-qualification.ts +2741 -157
  44. package/scripts/check-install-surface.ts +3 -2
  45. package/scripts/check-mux-resume-call.ts +24 -35
  46. package/scripts/check-pi-bridge-sender.ts +130 -0
  47. package/scripts/check-pi-mcp-bridge.ts +251 -0
  48. package/scripts/lib/codex-fresh-live-protocol.ts +17 -5
  49. package/scripts/lib/codex-fresh-source-receipts.ts +21 -2
  50. package/scripts/lib/mutation-qualify.ts +269 -44
  51. package/scripts/lib/pi-mcp-bridge-probe.mjs +170 -0
  52. package/scripts/lib/qualification-acceptance.ts +146 -0
  53. package/scripts/lib/qualification-artifact.ts +359 -0
  54. package/scripts/lib/qualification-collection.ts +384 -0
  55. package/scripts/lib/qualification-gh.ts +231 -0
  56. package/scripts/lib/qualification-ledger.ts +669 -0
  57. package/scripts/lib/qualification-oracle-core.ts +279 -0
  58. package/scripts/lib/qualification-receipt.ts +561 -0
  59. package/scripts/lib/qualification-release.ts +271 -0
  60. package/scripts/meta-bridge-doctor.sh +16 -5
  61. package/scripts/mutants/acp-foreground-only.json +17 -0
  62. package/scripts/mutants/codex-native.json +11 -11
  63. package/scripts/mutants/control-send-receipt.json +6 -4
  64. package/scripts/mutants/copilot-birth.json +2 -1
  65. package/scripts/mutants/fresh-call-dispatch.json +3 -14
  66. package/scripts/mutants/gate-qualification.json +59 -0
  67. package/scripts/mutants/mux-fresh-call.json +43 -45
  68. package/scripts/mutants/mux-resume-call.json +11 -35
  69. package/scripts/mutants/pack-install.json +15 -4
  70. package/scripts/mutants/pi-bridge-sender.json +54 -0
  71. package/scripts/mutants/pi-floor.json +6 -2
  72. package/scripts/mutants/pi-mcp-bridge.json +36 -0
  73. package/scripts/mutants/pi-mcp-register.json +51 -0
  74. package/scripts/mutants/qualification-ledger.json +489 -0
  75. package/scripts/mutants/v2-surface.json +6 -6
  76. package/scripts/qualification-oracle.ts +124 -0
  77. package/scripts/resident-rpc-drive.ts +5 -1
  78. package/scripts/smoke-acp-bundled-mcp-live.ts +3 -1
  79. package/scripts/smoke-acp-cortex-live.ts +3 -1
  80. package/scripts/smoke-acp-session-reuse-live.ts +29 -12
  81. package/scripts/smoke-acp-socket-citizen-live.ts +3 -1
  82. package/scripts/smoke-acp-v2-send-live.ts +3 -1
  83. package/scripts/smoke-codex-fresh-live.ts +20 -1
  84. package/scripts/smoke-entwurf-chain-live.ts +6 -2
  85. package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -1
  86. package/scripts/smoke-herdr-raw-install-live.sh +8 -3
  87. package/scripts/smoke-mux-lifecycle-live.ts +1 -1
  88. package/scripts/smoke-resident-garden-guard.sh +3 -1
@@ -0,0 +1,489 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "qualification-ledger",
4
+ "mutants": [
5
+ {
6
+ "claim": "LEDGER-DIGEST-CHECKED",
7
+ "title": "the composer stops re-deriving receiptSha256 — a receipt whose verdict was edited after it was written would count as the measurement it no longer is (#124 P2)",
8
+ "subject": "scripts/lib/qualification-ledger.ts",
9
+ "find": ["\tif (sha256(canonicalJson(body)) !== receiptSha256) {"],
10
+ "replace": ["\tif (false) {"],
11
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
12
+ "timeoutSeconds": 120,
13
+ "signature": "[QK:LEDGER-DIGEST-CHECKED]",
14
+ "signatureSource": "scripts/check-gate-qualification.ts"
15
+ },
16
+ {
17
+ "claim": "LEDGER-FLAG-BLOCKS-GREEN",
18
+ "title": "the verdict ignores group flags — content-identical provenance, a red-then-green on identical bytes or observed cache drift would read COMPOSITE-GREEN instead of RISK-PENDING (#124 P2)",
19
+ "subject": "scripts/lib/qualification-ledger.ts",
20
+ "find": ["\t\t\t\t: gCounts.MEASURED === gCounts.total && gCounts.total > 0 && flagged.length === 0"],
21
+ "replace": ["\t\t\t\t: gCounts.MEASURED === gCounts.total && gCounts.total > 0"],
22
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
23
+ "timeoutSeconds": 120,
24
+ "signature": "[QK:LEDGER-FLAG-BLOCKS-GREEN]",
25
+ "signatureSource": "scripts/check-gate-qualification.ts"
26
+ },
27
+ {
28
+ "claim": "LEDGER-SEAL-IN-PASS-RECOMPUTE",
29
+ "title": "the composer's pass recompute forgets the seal — a receipt whose candidate moved during the run could still claim passed and count as MEASURED (#124 P2)",
30
+ "subject": "scripts/lib/qualification-ledger.ts",
31
+ "find": ["\t\tr.result.porcelainClean &&", "\t\tr.seal.stable;"],
32
+ "replace": ["\t\tr.result.porcelainClean;"],
33
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
34
+ "timeoutSeconds": 120,
35
+ "signature": "[QK:LEDGER-SEAL-IN-PASS-RECOMPUTE]",
36
+ "signatureSource": "scripts/check-gate-qualification.ts"
37
+ },
38
+ {
39
+ "claim": "LEDGER-INVALID-BLOCKS-GREEN",
40
+ "title": "the verdict stops failing closed on invalid receipts — a broken or duplicated receipt beside all-MEASURED groups would read COMPOSITE-GREEN instead of RED (#124 P2 review D2)",
41
+ "subject": "scripts/lib/qualification-ledger.ts",
42
+ "find": ["\t\tgCounts.FAIL > 0 || invalid.length > 0 || inputErrors.length > 0"],
43
+ "replace": ["\t\tgCounts.FAIL > 0 || inputErrors.length > 0"],
44
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
45
+ "timeoutSeconds": 120,
46
+ "signature": "[QK:LEDGER-INVALID-BLOCKS-GREEN]",
47
+ "signatureSource": "scripts/check-gate-qualification.ts"
48
+ },
49
+ {
50
+ "claim": "RECEIPT-SEAL-MOVED-IS-RED",
51
+ "title": "the group receipt producer drops the seal from `passed` — a run during which the origin candidate changed would be written green (#124 P1)",
52
+ "subject": "scripts/lib/qualification-receipt.ts",
53
+ "find": ["\tconst passed = reportPassed(report) && sealStable;"],
54
+ "replace": ["\tconst passed = reportPassed(report);"],
55
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
56
+ "timeoutSeconds": 120,
57
+ "signature": "[QK:RECEIPT-SEAL-MOVED-IS-RED]",
58
+ "signatureSource": "scripts/check-gate-qualification.ts"
59
+ },
60
+ {
61
+ "claim": "RECEIPT-PARENT-REALPATH-CONTAINED",
62
+ "title": "fingerprints lose the parent-realpath containment — a declared input reached through an intermediate directory symlink would hash bytes outside the repo as if they were the candidate's (#124 P1 review)",
63
+ "subject": "scripts/lib/qualification-receipt.ts",
64
+ "find": [
65
+ "\t\tif (parentReal !== repoReal && !parentReal.startsWith(repoReal + path.sep)) {",
66
+ "\t\t\tout[rel] = \"outside-repo\";",
67
+ "\t\t\tcontinue;",
68
+ "\t\t}"
69
+ ],
70
+ "replace": ["\t\t// containment removed"],
71
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
72
+ "timeoutSeconds": 120,
73
+ "signature": "[QK:RECEIPT-PARENT-REALPATH-CONTAINED]",
74
+ "signatureSource": "scripts/check-gate-qualification.ts"
75
+ },
76
+ {
77
+ "claim": "COLLECT-RED-DOES-NOT-STOP",
78
+ "title": "the collector stops at the first red group — the tail after a late failure is never measured, which is exactly the serial-body gap #124 exists to close",
79
+ "subject": "scripts/lib/qualification-collection.ts",
80
+ "find": ["\t\t\tif (!r.sealStable) c.stoppedBy = \"seal-moved\";"],
81
+ "replace": ["\t\t\tif (!r.sealStable || !r.passed) c.stoppedBy = \"seal-moved\";"],
82
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
83
+ "timeoutSeconds": 120,
84
+ "signature": "[QK:COLLECT-RED-DOES-NOT-STOP]",
85
+ "signatureSource": "scripts/check-gate-qualification.ts"
86
+ },
87
+ {
88
+ "claim": "COLLECT-SEAL-MOVED-STOPS",
89
+ "title": "the collector ignores a moved origin seal — groups after a candidate change would still be collected as if they measured the same bytes",
90
+ "subject": "scripts/lib/qualification-collection.ts",
91
+ "find": ["\t\t\tif (!r.sealStable) c.stoppedBy = \"seal-moved\";"],
92
+ "replace": ["\t\t\tif (false) c.stoppedBy = \"seal-moved\";"],
93
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
94
+ "timeoutSeconds": 120,
95
+ "signature": "[QK:COLLECT-SEAL-MOVED-STOPS]",
96
+ "signatureSource": "scripts/check-gate-qualification.ts"
97
+ },
98
+ {
99
+ "claim": "COLLECT-UNDECLARED-FILE-NAMED",
100
+ "title": "checkCollection stops listing undeclared files — a stray receipt dropped into a collection would ride along unnamed",
101
+ "subject": "scripts/lib/qualification-collection.ts",
102
+ "find": ["\tfor (const f of names) if (!declared.has(f)) errors.push(`collection-extra: ${f}`);"],
103
+ "replace": ["\tvoid names;"],
104
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
105
+ "timeoutSeconds": 120,
106
+ "signature": "[QK:COLLECT-UNDECLARED-FILE-NAMED]",
107
+ "signatureSource": "scripts/check-gate-qualification.ts"
108
+ },
109
+ {
110
+ "claim": "COLLECT-CANDIDATE-DRIFT-STOPS",
111
+ "title": "the collector does not compare each receipt with the candidate it started on — bytes moved BETWEEN seal-stable groups would yield a COMPLETE collection of two candidates (#124 step-1 review D1)",
112
+ "subject": "scripts/lib/qualification-collection.ts",
113
+ "find": ["\t\t\t\tif (drift.length > 0) {"],
114
+ "replace": ["\t\t\t\tif (false) {"],
115
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
116
+ "timeoutSeconds": 120,
117
+ "signature": "[QK:COLLECT-CANDIDATE-DRIFT-STOPS]",
118
+ "signatureSource": "scripts/check-gate-qualification.ts"
119
+ },
120
+ {
121
+ "claim": "COLLECT-FILE-NAME-CONFINED",
122
+ "title": "checkCollection trusts the file name collection.json declares — a `../` or absolute name would steer the read outside the collection (#124 step-1 review D2)",
123
+ "subject": "scripts/lib/qualification-collection.ts",
124
+ "find": ["\t\tif (a.file !== receiptFileName(i)) {"],
125
+ "replace": ["\t\tif (false) {"],
126
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
127
+ "timeoutSeconds": 120,
128
+ "signature": "[QK:COLLECT-FILE-NAME-CONFINED]",
129
+ "signatureSource": "scripts/check-gate-qualification.ts"
130
+ },
131
+ {
132
+ "claim": "RELEASE-RED-REMEASURED-CLEARS",
133
+ "title": "the release composer forgets which groups were red on the ancestor chain — the section that broke would no longer be required on the final SHA, so an unre-measured late failure could hide behind older greens (#124 step 2)",
134
+ "subject": "scripts/lib/qualification-release.ts",
135
+ "find": ["\tfor (const [k, passed] of newestPassed) if (!passed) requiredKeys.add(k);"],
136
+ "replace": ["\tvoid newestPassed;"],
137
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
138
+ "timeoutSeconds": 120,
139
+ "signature": "[QK:RELEASE-RED-REMEASURED-CLEARS]",
140
+ "signatureSource": "scripts/check-gate-qualification.ts"
141
+ },
142
+ {
143
+ "claim": "RELEASE-UNRUN-BLOCKS",
144
+ "title": "release mode stops blocking UNRUN — a group with no pass receipt anywhere in the chain could ride into a RISK proposal as if it had ever been measured (#124 step 2)",
145
+ "subject": "scripts/lib/qualification-ledger.ts",
146
+ "find": ["\t\tif (opts.mode === \"release\" && g.state === \"UNRUN\") {"],
147
+ "replace": ["\t\tif (false) {"],
148
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
149
+ "timeoutSeconds": 120,
150
+ "signature": "[QK:RELEASE-UNRUN-BLOCKS]",
151
+ "signatureSource": "scripts/check-gate-qualification.ts"
152
+ },
153
+ {
154
+ "claim": "RELEASE-CANDIDATE-CROSS-CHECKED",
155
+ "title": "the release composer ignores a CI-claimed candidate the final tree contradicts — receipts measured on other bytes would be composed as the final SHA's (#124 step 2)",
156
+ "subject": "scripts/lib/qualification-release.ts",
157
+ "find": ["\tconst red = contractErrors.length > 0 || crossCheck.length > 0;"],
158
+ "replace": ["\tconst red = contractErrors.length > 0;"],
159
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
160
+ "timeoutSeconds": 120,
161
+ "signature": "[QK:RELEASE-CANDIDATE-CROSS-CHECKED]",
162
+ "signatureSource": "scripts/check-gate-qualification.ts"
163
+ },
164
+ {
165
+ "claim": "RELEASE-NEWEST-ATTEMPT-WINS",
166
+ "title": "the per-SHA run choice keeps whichever attempt came first in the input — an older, more favorable attempt could be selected over the newest one (#124 step 2)",
167
+ "subject": "scripts/lib/qualification-release.ts",
168
+ "find": ["\t\tif (!cur || newer(a.run, cur.run)) {"],
169
+ "replace": ["\t\tif (!cur) {"],
170
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
171
+ "timeoutSeconds": 120,
172
+ "signature": "[QK:RELEASE-NEWEST-ATTEMPT-WINS]",
173
+ "signatureSource": "scripts/check-gate-qualification.ts"
174
+ },
175
+ {
176
+ "claim": "RELEASE-INPUT-CONTRACT",
177
+ "title": "the release composer admits a run whose headSha is off the first-parent chain — a side branch's or an unrelated SHA's receipts could count toward this release (#124 step 2)",
178
+ "subject": "scripts/lib/qualification-release.ts",
179
+ "find": ["\t\tif (!chainPos.has(run.headSha)) {"],
180
+ "replace": ["\t\tif (false) {"],
181
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
182
+ "timeoutSeconds": 120,
183
+ "signature": "[QK:RELEASE-INPUT-CONTRACT]",
184
+ "signatureSource": "scripts/check-gate-qualification.ts"
185
+ },
186
+ {
187
+ "claim": "RELEASE-FINAL-COLLECTION-REQUIRED",
188
+ "title": "the release composer accepts an INCOMPLETE final-SHA collection — a final run that crashed or stopped midway would be composed as if every requested group had been measured (#124 step 2)",
189
+ "subject": "scripts/lib/qualification-release.ts",
190
+ "find": [
191
+ "\telse if (!finalEntry.complete)",
192
+ "\t\tcontractErrors.push(`final-SHA collection incomplete: ${finalEntry.errors.join(\"; \")}`);"
193
+ ],
194
+ "replace": [
195
+ "\telse if (false)",
196
+ "\t\tcontractErrors.push(`final-SHA collection incomplete: ${finalEntry.errors.join(\"; \")}`);"
197
+ ],
198
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
199
+ "timeoutSeconds": 120,
200
+ "signature": "[QK:RELEASE-FINAL-COLLECTION-REQUIRED]",
201
+ "signatureSource": "scripts/check-gate-qualification.ts"
202
+ },
203
+ {
204
+ "claim": "ARTIFACT-SELECT-EXACTLY-ONE",
205
+ "title": "artifact selection only refuses zero hits — with two artifacts of the run's name the first would be taken silently (#124 stage 3a)",
206
+ "subject": "scripts/lib/qualification-artifact.ts",
207
+ "find": ["\tif (hits.length !== 1)"],
208
+ "replace": ["\tif (hits.length === 0)"],
209
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
210
+ "timeoutSeconds": 120,
211
+ "signature": "[QK:ARTIFACT-SELECT-EXACTLY-ONE]",
212
+ "signatureSource": "scripts/check-gate-qualification.ts"
213
+ },
214
+ {
215
+ "claim": "ARTIFACT-DIGEST-CHECKED",
216
+ "title": "artifact admission stops comparing GitHub's recorded digest with the zip bytes' own sha256 — any bytes could be admitted as that artifact (#124 stage 3a)",
217
+ "subject": "scripts/lib/qualification-artifact.ts",
218
+ "find": ["\tif (a.digest !== `sha256:${zipSha}`)"],
219
+ "replace": ["\tif (false)"],
220
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
221
+ "timeoutSeconds": 120,
222
+ "signature": "[QK:ARTIFACT-DIGEST-CHECKED]",
223
+ "signatureSource": "scripts/check-gate-qualification.ts"
224
+ },
225
+ {
226
+ "claim": "ARTIFACT-ZIP-CONFINED",
227
+ "title": "the zip reader stops refusing symlink entries — a link named like a receipt would be read as one (#124 stage 3a)",
228
+ "subject": "scripts/lib/qualification-artifact.ts",
229
+ "find": ["\t\tif ((unixMode & 0o170000) === 0o120000) {"],
230
+ "replace": ["\t\tif (false) {"],
231
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
232
+ "timeoutSeconds": 120,
233
+ "signature": "[QK:ARTIFACT-ZIP-CONFINED]",
234
+ "signatureSource": "scripts/check-gate-qualification.ts"
235
+ },
236
+ {
237
+ "claim": "ARTIFACT-ZIP-STRUCTURE",
238
+ "title": "the zip reader stops requiring its entry count to consume the whole central directory — a lowered EOCD count hides a listed entry (#124 stage 3a review)",
239
+ "subject": "scripts/lib/qualification-artifact.ts",
240
+ "find": ["\tif (p !== cdOff + cdSize) {"],
241
+ "replace": ["\tif (false) {"],
242
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
243
+ "timeoutSeconds": 120,
244
+ "signature": "[QK:ARTIFACT-ZIP-STRUCTURE]",
245
+ "signatureSource": "scripts/check-gate-qualification.ts"
246
+ },
247
+ {
248
+ "claim": "ARTIFACT-CORE-RECOMPUTED",
249
+ "title": "admission stops recomputing core.sha256 from the receipt's own parts — a C0 witness could be edited while keeping its hash (#124 stage 3a)",
250
+ "subject": "scripts/lib/qualification-artifact.ts",
251
+ "find": ["\t\tif (recomputed !== core.sha256)"],
252
+ "replace": ["\t\tif (false)"],
253
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
254
+ "timeoutSeconds": 120,
255
+ "signature": "[QK:ARTIFACT-CORE-RECOMPUTED]",
256
+ "signatureSource": "scripts/check-gate-qualification.ts"
257
+ },
258
+ {
259
+ "claim": "ARTIFACT-CORE-SHAPE",
260
+ "title": "admission stops checking the core's fields — a receipt without node identity could be labelled a CI witness (#124 stage 3a review)",
261
+ "subject": "scripts/lib/qualification-artifact.ts",
262
+ "find": ["\t\tconst shapeErr = coreShapeError(core);"],
263
+ "replace": ["\t\tconst shapeErr = null as string | null;"],
264
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
265
+ "timeoutSeconds": 120,
266
+ "signature": "[QK:ARTIFACT-CORE-SHAPE]",
267
+ "signatureSource": "scripts/check-gate-qualification.ts"
268
+ },
269
+ {
270
+ "claim": "GH-PROVENANCE-BOUNDARY",
271
+ "title": "the GitHub boundary stops checking a run's head repository — a fork's run could supply this repository's release evidence (#124 stage 3b)",
272
+ "subject": "scripts/lib/qualification-gh.ts",
273
+ "find": ["\tif (run.head_repository?.full_name !== repo) {"],
274
+ "replace": ["\tif (false) {"],
275
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
276
+ "timeoutSeconds": 120,
277
+ "signature": "[QK:GH-PROVENANCE-BOUNDARY]",
278
+ "signatureSource": "scripts/check-gate-qualification.ts"
279
+ },
280
+ {
281
+ "claim": "GH-NEWEST-RUN-ONLY",
282
+ "title": "the GitHub boundary judges the OLDEST ci.yml run at the SHA — an older, friendlier collection would be chosen over the newest run (#124 stage 3b)",
283
+ "subject": "scripts/lib/qualification-gh.ts",
284
+ "find": ["\tconst run = runs.reduce((a, b) => (b.id > a.id ? b : a));"],
285
+ "replace": ["\tconst run = runs.reduce((a, b) => (b.id < a.id ? b : a));"],
286
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
287
+ "timeoutSeconds": 120,
288
+ "signature": "[QK:GH-NEWEST-RUN-ONLY]",
289
+ "signatureSource": "scripts/check-gate-qualification.ts"
290
+ },
291
+ {
292
+ "claim": "GH-NEWER-FAILED-COLLECTION-NOT-PAPERED",
293
+ "title": "an ancestor run is admitted although a newer run at that SHA attempted a collection and failed — the new failure would be papered over by the old green (#124 stage 3b review)",
294
+ "subject": "scripts/lib/qualification-gh.ts",
295
+ "find": ["\t\tif (compositeSucceeded(jobs).length > 0 || r.conclusion !== \"success\") {"],
296
+ "replace": ["\t\tif (false) {"],
297
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
298
+ "timeoutSeconds": 120,
299
+ "signature": "[QK:GH-NEWER-FAILED-COLLECTION-NOT-PAPERED]",
300
+ "signatureSource": "scripts/check-gate-qualification.ts"
301
+ },
302
+ {
303
+ "claim": "GH-COLLECT-STEPS-REQUIRED",
304
+ "title": "the GitHub boundary stops requiring the collect and upload steps to have succeeded — a run whose collection failed could still be read (#124 stage 3b)",
305
+ "subject": "scripts/lib/qualification-gh.ts",
306
+ "find": ["\tfor (const name of [COMPOSITE_STEP, COMPOSITE_UPLOAD_STEP]) {"],
307
+ "replace": ["\tfor (const name of [] as string[]) {"],
308
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
309
+ "timeoutSeconds": 120,
310
+ "signature": "[QK:GH-COLLECT-STEPS-REQUIRED]",
311
+ "signatureSource": "scripts/check-gate-qualification.ts"
312
+ },
313
+ {
314
+ "claim": "RELEASE-DIRTY-NOT-INVENTED",
315
+ "title": "the release composer asserts a clean final candidate instead of keeping the independent one's porcelain — a dirty tree could compose to COMPOSITE-GREEN (#124 Grok review F1)",
316
+ "subject": "scripts/lib/qualification-release.ts",
317
+ "find": ["\t\thead: input.finalSha,"],
318
+ "replace": ["\t\thead: input.finalSha,", "\t\tporcelainClean: true,"],
319
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
320
+ "timeoutSeconds": 120,
321
+ "signature": "[QK:RELEASE-DIRTY-NOT-INVENTED]",
322
+ "signatureSource": "scripts/check-gate-qualification.ts"
323
+ },
324
+ {
325
+ "claim": "ORACLE-SELF-CHECK",
326
+ "title": "the oracle core's self-check stops refusing a dirty checkout — run in place, an operator's uncommitted bytes could be judged as the SHA they are not (#124 P1)",
327
+ "subject": "scripts/lib/qualification-oracle-core.ts",
328
+ "find": ["\tif (st.status !== 0 || st.stdout !== \"\") errors.push(\"self-check: this checkout is not clean\");"],
329
+ "replace": ["\tif (st.status !== 0) errors.push(\"self-check: this checkout is not clean\");"],
330
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
331
+ "timeoutSeconds": 120,
332
+ "signature": "[QK:ORACLE-SELF-CHECK]",
333
+ "signatureSource": "scripts/check-gate-qualification.ts"
334
+ },
335
+ {
336
+ "claim": "ORACLE-FINAL-RUN-PINNED",
337
+ "title": "the oracle core stops comparing the newest run at the SHA with the pinned final run — evidence from a run the exact-CI oracle did not choose would be composed (#124 P1)",
338
+ "subject": "scripts/lib/qualification-oracle-core.ts",
339
+ "find": ["\tif (final.runId !== null && final.runId !== input.finalRunId) {"],
340
+ "replace": ["\tif (false) {"],
341
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
342
+ "timeoutSeconds": 120,
343
+ "signature": "[QK:ORACLE-FINAL-RUN-PINNED]",
344
+ "signatureSource": "scripts/check-gate-qualification.ts"
345
+ },
346
+ {
347
+ "claim": "ORACLE-DIGEST-MACHINE-INDEPENDENT",
348
+ "title": "a non-deterministic input leaks into the decision digest — two runs over identical inputs would name different ledgers; this negative covers the REPEAT axis of the claim only, not the clone-location axis (#124 P1)",
349
+ "subject": "scripts/lib/qualification-oracle-core.ts",
350
+ "find": ["\treturn sha256(canonicalJson(ledger));"],
351
+ "replace": ["\treturn sha256(canonicalJson(ledger) + process.hrtime.bigint());"],
352
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
353
+ "timeoutSeconds": 120,
354
+ "signature": "[QK:ORACLE-DIGEST-MACHINE-INDEPENDENT]",
355
+ "signatureSource": "scripts/check-gate-qualification.ts"
356
+ },
357
+ {
358
+ "claim": "ORACLE-SOURCE-MISSING",
359
+ "title": "the launcher stops refusing a SHA that does not carry the oracle core — the 8d508d3 shape would fail as a node error instead of ABORT oracle-source-missing (#124 P1)",
360
+ "subject": "scripts/qualification-oracle.ts",
361
+ "find": ["\t\tif (!fs.existsSync(path.join(clone, ORACLE_CORE_PATH))) {"],
362
+ "replace": ["\t\tif (false) {"],
363
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
364
+ "timeoutSeconds": 120,
365
+ "signature": "[QK:ORACLE-SOURCE-MISSING]",
366
+ "signatureSource": "scripts/check-gate-qualification.ts"
367
+ },
368
+ {
369
+ "claim": "ORACLE-RUNS-TARGET-BYTES",
370
+ "title": "the launcher runs the ORIGIN checkout's oracle core instead of the target SHA's — the operator's working-tree bytes would judge a SHA (#124 P1)",
371
+ "subject": "scripts/qualification-oracle.ts",
372
+ "find": [
373
+ "\t\tconst child = spawnSync(process.execPath, [\"--experimental-strip-types\", ORACLE_CORE_PATH, ...passArgs], {"
374
+ ],
375
+ "replace": [
376
+ "\t\tconst child = spawnSync(process.execPath, [\"--experimental-strip-types\", path.join(origin, ORACLE_CORE_PATH), ...passArgs], {"
377
+ ],
378
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
379
+ "timeoutSeconds": 120,
380
+ "signature": "[QK:ORACLE-RUNS-TARGET-BYTES]",
381
+ "signatureSource": "scripts/check-gate-qualification.ts"
382
+ },
383
+ {
384
+ "claim": "ORACLE-ORIGIN-TRIPWIRE",
385
+ "title": "the launcher stops re-reading the origin tripwire — a core that wrote the operator's checkout while it ran would keep its own exit 0 (#124 P1)",
386
+ "subject": "scripts/qualification-oracle.ts",
387
+ "find": ["\t\tif (after !== before) {"],
388
+ "replace": ["\t\tif (false) {"],
389
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
390
+ "timeoutSeconds": 120,
391
+ "signature": "[QK:ORACLE-ORIGIN-TRIPWIRE]",
392
+ "signatureSource": "scripts/check-gate-qualification.ts"
393
+ },
394
+ {
395
+ "claim": "ACCEPT-ONLY-RISK-PENDING",
396
+ "title": "the acceptance-record check stops requiring a RISK-PENDING ledger — a record against a NOT-QUALIFIED (or RED/GREEN) ledger would read RECORD-CONSISTENT (#124 P2)",
397
+ "subject": "scripts/lib/qualification-acceptance.ts",
398
+ "find": [
399
+ "\tif (ledger.verdict !== \"RISK-PENDING\") errors.push(`ledger: verdict is ${ledger.verdict}, not RISK-PENDING`);"
400
+ ],
401
+ "replace": ["\tif (false) errors.push(`ledger: verdict is ${ledger.verdict}, not RISK-PENDING`);"],
402
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
403
+ "timeoutSeconds": 120,
404
+ "signature": "[QK:ACCEPT-ONLY-RISK-PENDING]",
405
+ "signatureSource": "scripts/check-gate-qualification.ts"
406
+ },
407
+ {
408
+ "claim": "ACCEPT-DIGEST-RECOMPUTED",
409
+ "title": "the acceptance-record check stops recomputing the ledger digest — a ledger edited after the record named it would still read RECORD-CONSISTENT (#124 P2)",
410
+ "subject": "scripts/lib/qualification-acceptance.ts",
411
+ "find": ["\tif (sha256(canonicalJson(ledger)) !== rec.ledgerSha256)"],
412
+ "replace": ["\tif (false)"],
413
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
414
+ "timeoutSeconds": 120,
415
+ "signature": "[QK:ACCEPT-DIGEST-RECOMPUTED]",
416
+ "signatureSource": "scripts/check-gate-qualification.ts"
417
+ },
418
+ {
419
+ "claim": "ACCEPT-RISKS-EXACT",
420
+ "title": "the acceptance-record check compares risks as a multiset — a record listing the risks in another order would read RECORD-CONSISTENT (#124 P2)",
421
+ "subject": "scripts/lib/qualification-acceptance.ts",
422
+ "find": ["\tif (canonicalJson(rec.risks) !== canonicalJson(ledger.risks))"],
423
+ "replace": ["\tif (multiset(rec.risks) !== multiset(ledger.risks))"],
424
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
425
+ "timeoutSeconds": 120,
426
+ "signature": "[QK:ACCEPT-RISKS-EXACT]",
427
+ "signatureSource": "scripts/check-gate-qualification.ts"
428
+ },
429
+ {
430
+ "claim": "ACCEPT-RISKS-REDERIVED",
431
+ "title": "the acceptance-record check trusts the ledger's own risk list — a composer that dropped a risk its groups imply would go unnoticed behind a consistent digest (#124 P2)",
432
+ "subject": "scripts/lib/qualification-acceptance.ts",
433
+ "find": ["\tif (multiset(rederiveRisks(ledger.groups)) !== multiset(ledger.risks))"],
434
+ "replace": ["\tif (false)"],
435
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
436
+ "timeoutSeconds": 120,
437
+ "signature": "[QK:ACCEPT-RISKS-REDERIVED]",
438
+ "signatureSource": "scripts/check-gate-qualification.ts"
439
+ },
440
+ {
441
+ "claim": "ACCEPT-DATE-REAL",
442
+ "title": "the acceptance-record check accepts any date-shaped acceptedOn — 2026-02-30 would read RECORD-CONSISTENT (#124 P2)",
443
+ "subject": "scripts/lib/qualification-acceptance.ts",
444
+ "find": ["\t} else if (!isRealDate(r.acceptedOn)) {"],
445
+ "replace": ["\t} else if (false) {"],
446
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
447
+ "timeoutSeconds": 120,
448
+ "signature": "[QK:ACCEPT-DATE-REAL]",
449
+ "signatureSource": "scripts/check-gate-qualification.ts"
450
+ },
451
+ {
452
+ "claim": "ORACLE-DECLARED-REQUIRED",
453
+ "title": "the oracle core drops --require-group before the release composer — a group GLG required on the final SHA would never be required (#124 P2)",
454
+ "subject": "scripts/lib/qualification-oracle-core.ts",
455
+ "find": ["\t\toperatorDeclared: input.operatorDeclared,"],
456
+ "replace": ["\t\toperatorDeclared: [],"],
457
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
458
+ "timeoutSeconds": 120,
459
+ "signature": "[QK:ORACLE-DECLARED-REQUIRED]",
460
+ "signatureSource": "scripts/check-gate-qualification.ts"
461
+ },
462
+ {
463
+ "claim": "CORE-FILES-COVER-QUALIFICATION",
464
+ "title": "CORE_FILES forgets a qualification source — acceptance.ts bytes could change without moving the receipt core seal, the c-core decision or the CI core witness (#124 D1)",
465
+ "subject": "scripts/lib/qualification-receipt.ts",
466
+ "find": ["\t\"scripts/lib/qualification-acceptance.ts\","],
467
+ "replace": ["\t// (qualification-acceptance.ts not listed)"],
468
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
469
+ "timeoutSeconds": 120,
470
+ "signature": "[QK:CORE-FILES-COVER-QUALIFICATION]",
471
+ "signatureSource": "scripts/check-gate-qualification.ts"
472
+ },
473
+ {
474
+ "claim": "ACCEPT-REFUSAL-REDERIVED",
475
+ "title": "the acceptance-record check trusts the ledger's own blockers — a RISK-PENDING ledger with an UNRUN group or a dirty candidate and its blockers stripped would read RECORD-CONSISTENT (#124 D2)",
476
+ "subject": "scripts/lib/qualification-acceptance.ts",
477
+ "find": [
478
+ "\tif (refusals.length > 0) errors.push(`ledger: refusal state in its own groups/candidate: ${refusals.join(\"; \")}`);"
479
+ ],
480
+ "replace": [
481
+ "\tif (false) errors.push(`ledger: refusal state in its own groups/candidate: ${refusals.join(\"; \")}`);"
482
+ ],
483
+ "gate": ["bash", "run.sh", "check-gate-manifests"],
484
+ "timeoutSeconds": 120,
485
+ "signature": "[QK:ACCEPT-REFUSAL-REDERIVED]",
486
+ "signatureSource": "scripts/check-gate-qualification.ts"
487
+ }
488
+ ]
489
+ }
@@ -39,20 +39,20 @@
39
39
  "signatureSource": "scripts/check-entwurf-v2-production.ts"
40
40
  },
41
41
  {
42
- "claim": "V2SURF-PI-MODE-SCOPE",
43
- "title": "pi-native mode param reverts to \"Delivery mode for a live send\" (false for native-push) (#120 P2: the description was rewritten to stop claiming an interrupt, so the mutation now strips the rail scope from the corrected sentence)",
44
- "subject": "pi-extensions/entwurf-control.ts",
42
+ "claim": "V2SURF-MODE-SCOPE",
43
+ "title": "the bridge mode param (the one every caller reads, pi included since #125) reverts to \"Delivery mode for a live send\" (false for native-push) (#120 P2: the description was rewritten to stop claiming an interrupt, so the mutation now strips the rail scope from the corrected sentence)",
44
+ "subject": "mcp/entwurf-bridge/src/index.ts",
45
45
  "find": [
46
- "\t\t\t\t\t\"Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. \" +",
46
+ "\t\t\t\t\"Injection style for a CONTROL-SOCKET send only: steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. \" +",
47
47
  "\t\t\t\t\t\"The mailbox and native-push plans carry no mode, so it has no effect on those rails.\","
48
48
  ],
49
49
  "replace": [
50
- "\t\t\t\t\t\"Delivery mode for a live send: steer (ask the steering queue) or follow_up (ask the follow-up queue). \" +",
50
+ "\t\t\t\t\"Delivery mode for a live send: steer (ask the steering queue) or follow_up (ask the follow-up queue). \" +",
51
51
  "\t\t\t\t\t\"Both queues are volatile process memory.\","
52
52
  ],
53
53
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
54
54
  "timeoutSeconds": 60,
55
- "signature": "[QK:V2SURF-PI-MODE-SCOPE]",
55
+ "signature": "[QK:V2SURF-MODE-SCOPE]",
56
56
  "signatureSource": "scripts/check-entwurf-v2-surface.ts"
57
57
  },
58
58
  {
@@ -0,0 +1,124 @@
1
+ /**
2
+ * qualification-oracle — launcher for the exact-SHA composite release oracle (#124 P1).
3
+ *
4
+ * It JUDGES NOTHING. It clones the target SHA cleanly, then runs THAT SHA's own
5
+ * `scripts/lib/qualification-oracle-core.ts` inside the clone and relays its output and
6
+ * exit code. The operator's checkout — possibly dirty or stale — never evaluates a SHA
7
+ * with its own bytes.
8
+ *
9
+ * 1. tripwire the origin checkout: HEAD, porcelain, work-surface content hash;
10
+ * 2. `git clone --no-hardlinks --no-checkout <origin> <tmp>`; `checkout --detach <sha>`;
11
+ * the clone must be exactly <sha> and clean, its surface stable across two reads;
12
+ * 3. no core source at <sha> → ABORT oracle-source-missing (every SHA before the core
13
+ * landed, 8d508d3 included, is judged this way — by design, not by accident);
14
+ * 4. run `node --experimental-strip-types <clone>/scripts/lib/qualification-oracle-core.ts`
15
+ * with cwd = the clone;
16
+ * 5. ALWAYS (child failure, child signal, our own SIGINT/SIGTERM): remove the clone, then
17
+ * re-read the origin tripwire. A moved origin overrides any child result with ABORT.
18
+ *
19
+ * Nothing here writes the origin's .git, worktree or index (no `worktree add`).
20
+ */
21
+
22
+ import { spawnSync } from "node:child_process";
23
+ import * as fs from "node:fs";
24
+ import * as os from "node:os";
25
+ import * as path from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+ import { originHead, originWorkSurfaceSha } from "./lib/mutation-qualify.ts";
28
+ import { ORACLE_CORE_PATH, parseOracleArgs } from "./lib/qualification-oracle-core.ts";
29
+ import { checkReceiptPath } from "./lib/qualification-receipt.ts";
30
+
31
+ const SELF_REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
32
+
33
+ function tripwire(dir: string): string {
34
+ const porcelain = spawnSync("git", ["-C", dir, "status", "--porcelain"], { encoding: "utf8" }).stdout;
35
+ return JSON.stringify({ head: originHead(dir), porcelain, surface: originWorkSurfaceSha(dir) });
36
+ }
37
+
38
+ export function launch(argv: string[], log = console.log, logError = console.error): number {
39
+ let origin: string;
40
+ let sha: string;
41
+ let ledgerOut: string | undefined;
42
+ try {
43
+ const args = parseOracleArgs(argv, true);
44
+ origin = path.resolve(args.origin ?? SELF_REPO);
45
+ sha = args.sha;
46
+ ledgerOut = args.ledgerOut;
47
+ if (ledgerOut) checkReceiptPath(ledgerOut, origin);
48
+ } catch (err) {
49
+ logError(`[qualification-oracle] ABORT: ${err instanceof Error ? err.message : String(err)}`);
50
+ return 2;
51
+ }
52
+ const passArgs = argv.filter((_, i) => argv[i] !== "--origin" && argv[i - 1] !== "--origin");
53
+
54
+ let interrupted: NodeJS.Signals | null = null;
55
+ const onSignal = (sig: NodeJS.Signals) => {
56
+ interrupted = sig;
57
+ };
58
+ process.on("SIGINT", onSignal);
59
+ process.on("SIGTERM", onSignal);
60
+ const before = tripwire(origin);
61
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-oracle-clone-"));
62
+ const clone = path.join(tmp, "repo");
63
+ let code = 2;
64
+ try {
65
+ const git = (...a: string[]) => spawnSync("git", a, { encoding: "utf8" });
66
+ if (git("clone", "-q", "--no-hardlinks", "--no-checkout", origin, clone).status !== 0) {
67
+ logError("[qualification-oracle] ABORT clone-failed");
68
+ return 2;
69
+ }
70
+ if (git("-C", clone, "-c", "advice.detachedHead=false", "checkout", "-q", "--detach", sha).status !== 0) {
71
+ logError(`[qualification-oracle] ABORT sha-unknown: ${sha} is not in ${origin}`);
72
+ return 2;
73
+ }
74
+ const head = git("-C", clone, "rev-parse", "HEAD").stdout.trim();
75
+ const clean = git("-C", clone, "status", "--porcelain").stdout === "";
76
+ const s1 = originWorkSurfaceSha(clone);
77
+ const s2 = originWorkSurfaceSha(clone);
78
+ if (head !== sha || !clean || s1 !== s2) {
79
+ logError(
80
+ `[qualification-oracle] ABORT clone-not-exact: head ${head}, clean ${clean}, surface stable ${s1 === s2}`,
81
+ );
82
+ return 2;
83
+ }
84
+ if (!fs.existsSync(path.join(clone, ORACLE_CORE_PATH))) {
85
+ logError(`[qualification-oracle] ABORT oracle-source-missing: ${sha} does not carry ${ORACLE_CORE_PATH}`);
86
+ return 2;
87
+ }
88
+ const child = spawnSync(process.execPath, ["--experimental-strip-types", ORACLE_CORE_PATH, ...passArgs], {
89
+ cwd: clone,
90
+ encoding: "utf8",
91
+ maxBuffer: 64 * 1024 * 1024,
92
+ });
93
+ if (child.stdout) log(child.stdout.replace(/\n$/, ""));
94
+ if (child.stderr) logError(child.stderr.replace(/\n$/, ""));
95
+ code = child.status === null ? 2 : child.status;
96
+ if (child.signal) {
97
+ logError(`[qualification-oracle] ABORT the oracle core ended by ${child.signal}`);
98
+ code = 2;
99
+ }
100
+ } finally {
101
+ fs.rmSync(tmp, { recursive: true, force: true });
102
+ process.off("SIGINT", onSignal);
103
+ process.off("SIGTERM", onSignal);
104
+ let after = "";
105
+ try {
106
+ after = tripwire(origin);
107
+ } catch (err) {
108
+ after = `unreadable: ${err instanceof Error ? err.message : String(err)}`;
109
+ }
110
+ if (after !== before) {
111
+ logError("[qualification-oracle] ABORT origin-moved: the origin checkout changed while the oracle ran");
112
+ code = 2;
113
+ }
114
+ if (interrupted) {
115
+ logError(`[qualification-oracle] ABORT interrupted by ${interrupted}`);
116
+ code = 2;
117
+ }
118
+ }
119
+ return code;
120
+ }
121
+
122
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
123
+ process.exit(launch(process.argv.slice(2)));
124
+ }