@unrulysystems/native-motion-conformance 0.1.0-alpha.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 (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +66 -0
  4. package/package.json +33 -0
  5. package/src/adapter.ts +42 -0
  6. package/src/adapters/motion-dom.ts +96 -0
  7. package/src/adapters/native.ts +95 -0
  8. package/src/authoring.ts +78 -0
  9. package/src/comparator.ts +129 -0
  10. package/src/config.ts +22 -0
  11. package/src/declarations.ts +21 -0
  12. package/src/index.ts +144 -0
  13. package/src/oracle/attestation.ts +100 -0
  14. package/src/oracle/constants.ts +24 -0
  15. package/src/oracle/controls.ts +202 -0
  16. package/src/oracle/errors.ts +12 -0
  17. package/src/oracle/exportTrace.ts +134 -0
  18. package/src/oracle/index.ts +133 -0
  19. package/src/oracle/judge.ts +1374 -0
  20. package/src/oracle/presenter.ts +372 -0
  21. package/src/oracle/runRecord.ts +307 -0
  22. package/src/oracle/scenarios.ts +115 -0
  23. package/src/oracle/scripts/gesture.ts +218 -0
  24. package/src/oracle/serialize.ts +91 -0
  25. package/src/oracle/sweep.ts +155 -0
  26. package/src/oracle/types.ts +76 -0
  27. package/src/oracle/velocity.ts +44 -0
  28. package/src/parity.ts +136 -0
  29. package/src/runner.ts +168 -0
  30. package/src/scenario.ts +179 -0
  31. package/src/scenarios/appstore-choreography.ts +105 -0
  32. package/src/scenarios/component.ts +516 -0
  33. package/src/scenarios/driver.ts +322 -0
  34. package/src/scenarios/gesture.ts +363 -0
  35. package/src/scenarios/layout-identity.ts +264 -0
  36. package/src/scenarios/layout.ts +258 -0
  37. package/src/scenarios/presence.ts +302 -0
  38. package/src/scenarios/spring.ts +180 -0
  39. package/src/scenarios/value-types.ts +107 -0
  40. package/src/suite.ts +44 -0
@@ -0,0 +1,1374 @@
1
+ // The judge boundary accepts only the provenance-free serialized bundle. Unblinding is intentionally
2
+ // absent from this module's dispatch API; only `decideOracleGuard` receives it after judgment.
3
+
4
+ import { oracleContentHash } from './attestation'
5
+ import { isOracleControlKind, type OracleControlKind } from './controls'
6
+ import {
7
+ serializeJudgeBundle,
8
+ type OracleJudgeBundle,
9
+ type OracleUnblindingRecord,
10
+ } from './presenter'
11
+ import type { OracleTraceSpan } from './scenarios'
12
+ import type { TraceEvent } from './types'
13
+
14
+ interface BunJudgeProcess {
15
+ readonly exited: Promise<number>
16
+ readonly stdout: ReadableStream<Uint8Array>
17
+ readonly stderr: ReadableStream<Uint8Array>
18
+ kill(signal?: number): void
19
+ }
20
+
21
+ interface BunSyncProcess {
22
+ readonly exitCode: number
23
+ readonly stdout: Uint8Array
24
+ readonly stderr: Uint8Array
25
+ }
26
+
27
+ // Bun is the repository's runtime, but its ambient types are intentionally not in the conformance
28
+ // tsconfig. Declare only this local subprocess capability rather than widening the package types.
29
+ declare const Bun: {
30
+ readonly env: Readonly<Record<string, string | undefined>>
31
+ which(binary: string): string | null
32
+ spawn(
33
+ command: readonly string[],
34
+ options: {
35
+ readonly stdin: Blob
36
+ readonly stdout: 'pipe'
37
+ readonly stderr: 'pipe'
38
+ readonly cwd: string
39
+ readonly env: Readonly<Record<string, string>>
40
+ },
41
+ ): BunJudgeProcess
42
+ spawnSync(
43
+ command: readonly string[],
44
+ options: {
45
+ readonly stdout: 'pipe' | 'ignore'
46
+ readonly stderr: 'pipe' | 'ignore'
47
+ readonly cwd?: string
48
+ },
49
+ ): BunSyncProcess
50
+ write(path: string, content: string): Promise<number>
51
+ }
52
+
53
+ export const ORACLE_JUDGE_DIMENSIONS = ['continuity', 'physicalPlausibility'] as const
54
+ export type OracleJudgeDimension = (typeof ORACLE_JUDGE_DIMENSIONS)[number]
55
+
56
+ export const ORACLE_JUDGE_VALUES = ['pass', 'borderline', 'fail'] as const
57
+ export type OracleJudgeValue = (typeof ORACLE_JUDGE_VALUES)[number]
58
+
59
+ export interface OracleJudgeIssue {
60
+ readonly timestamps: readonly number[]
61
+ readonly description: string
62
+ }
63
+
64
+ export interface OracleJudgeDimensionVerdict {
65
+ readonly verdict: OracleJudgeValue
66
+ readonly issues: readonly OracleJudgeIssue[]
67
+ }
68
+
69
+ export interface OracleJudgeVerdict {
70
+ readonly rankings: Readonly<Record<OracleJudgeDimension, readonly string[]>>
71
+ readonly traces: Readonly<
72
+ Record<string, Readonly<Record<OracleJudgeDimension, OracleJudgeDimensionVerdict>>>
73
+ >
74
+ }
75
+
76
+ /** The narrow production/test seam; it can receive no repository, session, or unblinding context. */
77
+ export interface JudgeProvider {
78
+ /** The configuration ACTUALLY used (binary resolved absolute); carried onto verdict outcomes. */
79
+ readonly config: Pick<JudgeProviderConfig, 'binary' | 'args' | 'timeoutMs'>
80
+ /**
81
+ * Declared by a provider that enforces config.timeoutMs itself (kills its child, removes its
82
+ * sandbox, and carries cleanup evidence on the thrown error). Exactly ONE owner per deadline:
83
+ * when set, dispatchJudge must not race its own timer — two timers at the same deadline let
84
+ * the outer one win and strand the provider's kill/cleanup evidence (review lqzkrr).
85
+ */
86
+ readonly enforcesTimeout?: true
87
+ /**
88
+ * `outputSchema` (serialized JSON Schema, derived from the bundle in the TRUSTED dispatch
89
+ * layer) is dispatch mechanics: providers that can enforce an output shape apply it; the
90
+ * strict verdict parse remains the gate either way. Providers never parse bundle bytes.
91
+ */
92
+ judge(
93
+ serializedBundle: string,
94
+ outputSchema?: string,
95
+ ): Promise<string | OracleJudgeProviderResult>
96
+ }
97
+
98
+ /**
99
+ * The per-run blindness proof (ruling 2026-07-14): the judge's transcript showed a pure
100
+ * prompt→reasoning→message turn — zero tool use — so the verdict was formed from the blinded
101
+ * bundle alone. REQ-ORACLE-002's "void" semantics, made checkable.
102
+ */
103
+ export interface OracleBlindnessAudit {
104
+ /** Content hash of the raw --json event stream the audit ran over. */
105
+ readonly transcriptHash: string
106
+ readonly eventCount: number
107
+ /** The provider session/thread id, so the full rollout transcript can be inspected later. */
108
+ readonly threadId: string
109
+ }
110
+
111
+ /**
112
+ * Providers return the RAW --json transcript; they never audit. The blindness proof is
113
+ * computed inside `dispatchJudge` (the trusted boundary), so no provider — real or injected —
114
+ * can mint or forge one (review v3xau5 m5c76852b). CLI results additionally carry the hash of
115
+ * the exact bundle bytes they executed against plus the config that ACTUALLY ran, and are
116
+ * frozen — a captured result can be neither doctored nor replayed for another bundle
117
+ * (review hscuvh m4dc9de18/ma799e14e).
118
+ */
119
+ export interface OracleJudgeProviderResult {
120
+ readonly transcript: string
121
+ /** Set by the CLI provider: hash of the serialized bundle bytes this execution received. */
122
+ readonly bundleHash?: string
123
+ /** Set by the CLI provider: the configuration the child ACTUALLY executed with. */
124
+ readonly executedConfig?: Pick<JudgeProviderConfig, 'binary' | 'args' | 'timeoutMs'>
125
+ readonly cleanupFailed?: OracleJudgeCleanupError
126
+ }
127
+
128
+ export interface JudgeProviderConfig {
129
+ readonly binary: string
130
+ readonly args: readonly string[]
131
+ readonly timeoutMs: number
132
+ }
133
+
134
+ /** The complete, inspectable child-process boundary. No parent environment is ever spread in. */
135
+ export interface IsolatedJudgeSpawnOptions {
136
+ readonly command: readonly string[]
137
+ readonly stdin: Blob
138
+ readonly stdout: 'pipe'
139
+ readonly stderr: 'pipe'
140
+ readonly cwd: string
141
+ readonly env: Readonly<{ PATH: string; HOME: string }>
142
+ }
143
+
144
+ /**
145
+ * `codex exec` is a non-interactive default, but the provider stays a driver-configurable seam.
146
+ * `--skip-git-repo-check` is required because the child deliberately runs in an empty non-git
147
+ * temp directory (review seuljp m69ea2634).
148
+ */
149
+ export const DEFAULT_JUDGE_PROVIDER_CONFIG: JudgeProviderConfig = {
150
+ binary: 'codex',
151
+ args: ['exec', '--skip-git-repo-check'],
152
+ timeoutMs: 60_000,
153
+ }
154
+
155
+ /**
156
+ * The child PATH is this FIXED system allowlist, never the parent PATH: the parent PATH carries
157
+ * the repository's bin/, node_modules/.bin, and .direnv/bin — all provenance a path-blind judge
158
+ * must not see (review seuljp m2c0dce9e). The provider binary is therefore resolved to an
159
+ * ABSOLUTE executable in the parent environment before dispatch; the child never searches.
160
+ */
161
+ export const SYSTEM_JUDGE_PATH = '/usr/bin:/bin:/usr/sbin:/sbin'
162
+
163
+ export interface JudgeBinaryResolvers {
164
+ /** Parent-env executable lookup (production: `Bun.which`). */
165
+ which(binary: string): string | undefined
166
+ /** Real-executable lookup behind a version-manager shim (production: `mise which`). */
167
+ miseWhich(binary: string): string | undefined
168
+ /**
169
+ * Filesystem canonicalization of the final executable (production: `readlink -f` via the
170
+ * parent PATH — review 6018lg). Containment runs over THIS result — a lexical path would
171
+ * let an external symlink execute repository-local code (review 2noxkl m3b1becef70d3).
172
+ * Deeper environment compromise (hard links, PATH shadowing) is wontfix by the ratified
173
+ * environment-trust cap (packet Decision 2026-07-14).
174
+ */
175
+ realpath(path: string): string | undefined
176
+ /**
177
+ * The symlink-resolved repository TOPLEVEL (production: git, fail-closed — review x4why2
178
+ * db4609a4dbd1 killed the cwd fallback); a binary resolved inside it is provenance and is
179
+ * refused.
180
+ */
181
+ readonly repoRoot: string
182
+ }
183
+
184
+ /**
185
+ * Resolves the configured binary to the real absolute executable, in the PARENT environment,
186
+ * at provider construction (fail-loud early, not at dispatch). A mise shim re-resolves to the
187
+ * shimmed executable because shims exit 1 under an isolated HOME (review seuljp m69ea2634).
188
+ */
189
+ export function resolveJudgeBinary(binary: string, resolvers: JudgeBinaryResolvers): string {
190
+ const direct = resolvers.which(binary)
191
+ if (direct === undefined || direct === '') {
192
+ throw new OracleJudgeError(`provider binary ${binary} is not resolvable on the parent PATH`)
193
+ }
194
+ const resolved = direct.includes('/mise/shims/') ? resolvers.miseWhich(binary) : direct
195
+ if (resolved === undefined || resolved === '') {
196
+ throw new OracleJudgeError(
197
+ `provider binary ${binary} resolves to a mise shim and mise cannot name the real executable`,
198
+ )
199
+ }
200
+ // Canonicalize BEFORE containment (review 2noxkl m3b1becef70d3): an external PATH entry that
201
+ // is a symlink into the repository passes a lexical check while executing repository-local
202
+ // code. The canonical path is also what executes and what records carry.
203
+ const canonical = resolvers.realpath(resolved)
204
+ if (canonical === undefined || canonical === '' || !canonical.startsWith('/')) {
205
+ throw new OracleJudgeError(
206
+ `provider binary ${binary} could not be canonicalized to an absolute executable path (resolved ${resolved})`,
207
+ )
208
+ }
209
+ if (!resolvers.repoRoot.startsWith('/')) {
210
+ throw new OracleJudgeError(
211
+ `binary containment needs an absolute repository root, received ${resolvers.repoRoot === '' ? 'an empty path' : `"${resolvers.repoRoot}"`}`,
212
+ )
213
+ }
214
+ // Path-segment exact (review x4why2/zo9ag1 boundary class): a loose prefix both accepted
215
+ // in-repo binaries under a narrowed boundary and rejected innocent repository siblings.
216
+ if (canonical === resolvers.repoRoot || canonical.startsWith(`${resolvers.repoRoot}/`)) {
217
+ throw new OracleJudgeError(
218
+ `provider binary ${binary} resolves inside the repository (${canonical}) — repository-local executables are provenance; configure an external binary`,
219
+ )
220
+ }
221
+ return canonical
222
+ }
223
+
224
+ // The judge transcript's ALLOWED vocabulary: a blind judgment is exactly session bookkeeping,
225
+ // private reasoning, and the verdict message. Everything else — tool calls, shell executions,
226
+ // file access, web search — is context acquisition and voids the run. Unknown types fail
227
+ // closed: a codex upgrade that adds event kinds must be re-vetted here, loudly.
228
+ const ALLOWED_JUDGE_EVENT_TYPES = new Set([
229
+ 'thread.started',
230
+ 'turn.started',
231
+ 'turn.completed',
232
+ 'item.started',
233
+ 'item.updated',
234
+ 'item.completed',
235
+ ])
236
+ const ALLOWED_JUDGE_ITEM_TYPES = new Set(['agent_message', 'reasoning'])
237
+
238
+ /**
239
+ * Parses a `codex exec --json` event stream into the verdict text plus the blindness proof.
240
+ * Fail-closed per REQ-ORACLE-002: any tool-use item, unknown event/item type, unparseable
241
+ * line, or missing agent message throws (the dispatch becomes `blocked`, the verdict void).
242
+ */
243
+ export function parseJudgeEventStream(stdout: string): {
244
+ rawText: string
245
+ blindness: OracleBlindnessAudit
246
+ } {
247
+ const lines = stdout
248
+ .split('\n')
249
+ .map((line) => line.trim())
250
+ .filter((line) => line !== '')
251
+ if (lines.length === 0) throw new OracleJudgeError('judge transcript is empty')
252
+
253
+ // Fail-closed lifecycle state machine (review v3xau5 m96f0ce16): exactly ONE fresh completed
254
+ // turn — thread.started first, then turn.started, then judgment items, then turn.completed
255
+ // last. Missing, duplicated, reordered, or trailing events void the verdict.
256
+ let state: 'expect-thread' | 'expect-turn-start' | 'in-turn' | 'done' = 'expect-thread'
257
+ let threadId: string | undefined
258
+ let lastAgentMessage: string | undefined
259
+ const voidVerdict = (why: string): never => {
260
+ throw new OracleJudgeError(`judge transcript ${why} — verdict void`)
261
+ }
262
+ for (const line of lines) {
263
+ let event: { type?: string; thread_id?: string; item?: { type?: string; text?: string } }
264
+ try {
265
+ event = JSON.parse(line) as typeof event
266
+ } catch {
267
+ return voidVerdict('line is not valid JSON')
268
+ }
269
+ // Valid JSON that is not an object (null, arrays, scalars) must hit the typed boundary
270
+ // here — a property read below would leak a raw TypeError (review i7n8qn 59d8eac0f713).
271
+ if (typeof event !== 'object' || event === null || Array.isArray(event)) {
272
+ return voidVerdict('line is not a JSON event object')
273
+ }
274
+ if (typeof event.type !== 'string' || !ALLOWED_JUDGE_EVENT_TYPES.has(event.type)) {
275
+ return voidVerdict(
276
+ `contains a disallowed event type ${String(event.type)} — blindness cannot be verified`,
277
+ )
278
+ }
279
+ switch (event.type) {
280
+ case 'thread.started': {
281
+ if (state !== 'expect-thread') return voidVerdict('has an out-of-order thread.started')
282
+ if (typeof event.thread_id !== 'string' || event.thread_id === '') {
283
+ return voidVerdict('carries no thread id')
284
+ }
285
+ threadId = event.thread_id
286
+ state = 'expect-turn-start'
287
+ break
288
+ }
289
+ case 'turn.started': {
290
+ if (state !== 'expect-turn-start') return voidVerdict('has an out-of-order turn.started')
291
+ state = 'in-turn'
292
+ break
293
+ }
294
+ case 'turn.completed': {
295
+ if (state !== 'in-turn') return voidVerdict('has an out-of-order turn.completed')
296
+ state = 'done'
297
+ break
298
+ }
299
+ default: {
300
+ // item.started / item.updated / item.completed
301
+ if (state !== 'in-turn') return voidVerdict('has a judgment item outside the turn')
302
+ const itemType = event.item?.type
303
+ if (typeof itemType !== 'string' || !ALLOWED_JUDGE_ITEM_TYPES.has(itemType)) {
304
+ return voidVerdict(
305
+ `contains a tool-use or unknown item type ${String(itemType)} — the judge acquired context beyond the blinded bundle`,
306
+ )
307
+ }
308
+ if (event.type === 'item.completed' && itemType === 'agent_message') {
309
+ const text = event.item?.text
310
+ if (typeof text !== 'string') {
311
+ return voidVerdict('carries a non-string agent message')
312
+ }
313
+ // The prompt requires exactly ONE JSON response; a second completed message means
314
+ // extra prose or a multi-answer turn — parsing "the last one" would let arbitrary
315
+ // unaudited text ride ahead of the verdict (review i7n8qn bf3c4791a24e).
316
+ if (lastAgentMessage !== undefined) {
317
+ return voidVerdict('contains more than one agent message')
318
+ }
319
+ lastAgentMessage = text
320
+ }
321
+ break
322
+ }
323
+ }
324
+ }
325
+ if (state !== 'done') return voidVerdict('does not contain one completed turn')
326
+ if (threadId === undefined) return voidVerdict('carries no thread id')
327
+ if (lastAgentMessage === undefined || lastAgentMessage.trim() === '') {
328
+ return voidVerdict('contains no agent message')
329
+ }
330
+ return {
331
+ rawText: lastAgentMessage,
332
+ blindness: {
333
+ transcriptHash: oracleContentHash(stdout),
334
+ eventCount: lines.length,
335
+ threadId,
336
+ },
337
+ }
338
+ }
339
+
340
+ function productionJudgeBinaryResolvers(): JudgeBinaryResolvers {
341
+ return {
342
+ which: (binary) => Bun.which(binary) ?? undefined,
343
+ miseWhich: (binary) => {
344
+ const result = Bun.spawnSync(['mise', 'which', binary], { stdout: 'pipe', stderr: 'ignore' })
345
+ if (result.exitCode !== 0) return undefined
346
+ const path = new TextDecoder().decode(result.stdout).trim()
347
+ return path === '' ? undefined : path
348
+ },
349
+ // Plain name via the parent PATH like the sibling spawns (git/mktemp/rm): a hard-coded
350
+ // /usr/bin/realpath does not exist on this macOS host and killed provider construction
351
+ // with ENOENT (review 6018lg mcd86451d3b6c, grounded live). `readlink -f` resolves on
352
+ // both BSD (macOS >= 12.3) and coreutils PATHs; failure stays fail-loud at construction.
353
+ realpath: (path) => {
354
+ const result = Bun.spawnSync(['readlink', '-f', path], {
355
+ stdout: 'pipe',
356
+ stderr: 'pipe',
357
+ })
358
+ if (result.exitCode !== 0) return undefined
359
+ const resolved = new TextDecoder().decode(result.stdout).trim()
360
+ return resolved === '' ? undefined : resolved
361
+ },
362
+ // The provenance boundary is the symlink-resolved git TOPLEVEL, never process.cwd() — a
363
+ // nested package cwd narrows the boundary and accepts in-repo binaries elsewhere in the
364
+ // checkout (review zo9ag1 237efe935de5). Fail-closed when git cannot name it.
365
+ repoRoot: repositoryRealPathBoundary(),
366
+ }
367
+ }
368
+
369
+ export class OracleJudgeError extends Error {
370
+ /**
371
+ * Structured sandbox-cleanup evidence riding the error itself — the message-string append
372
+ * alone lets a cleanup failure vanish behind a successful retry (terminal gate r3 major).
373
+ */
374
+ readonly cleanupFailed?: OracleJudgeCleanupError
375
+ constructor(message: string, cleanupFailed?: OracleJudgeCleanupError) {
376
+ super(`oracle judge: ${message}`)
377
+ this.name = 'OracleJudgeError'
378
+ if (cleanupFailed !== undefined) this.cleanupFailed = cleanupFailed
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Whether the transcript came from a process the REAL CLI provider spawned (`cli`) or from an
384
+ * injected provider (`unverified`, test-only). The run record admits only `cli` outcomes.
385
+ */
386
+ export type JudgeExecutionProvenance = 'cli' | 'unverified'
387
+
388
+ export type JudgeDispatchOutcome =
389
+ | {
390
+ readonly status: 'complete'
391
+ readonly rawText: string
392
+ /** The full raw --json event stream, retained for independent re-audit. */
393
+ readonly transcript: string
394
+ readonly blindness?: OracleBlindnessAudit
395
+ readonly executionProvenance: JudgeExecutionProvenance
396
+ /** For CLI executions: the config that ACTUALLY ran, carried on the branded result. */
397
+ readonly executedConfig?: Pick<JudgeProviderConfig, 'binary' | 'args' | 'timeoutMs'>
398
+ readonly cleanupFailed?: OracleJudgeCleanupError
399
+ }
400
+ | {
401
+ readonly status: 'blocked'
402
+ readonly cause: string
403
+ /** Structured sandbox-cleanup evidence — message-string-only evidence lets a cleanup
404
+ * failure vanish behind a successful retry (terminal gate r3 major). */
405
+ readonly cleanupFailed?: OracleJudgeCleanupError
406
+ }
407
+
408
+ export type JudgeVerdictOutcome =
409
+ | {
410
+ readonly status: 'complete'
411
+ readonly rawText: string
412
+ /** The full raw --json event stream, retained for independent re-audit. */
413
+ readonly transcript: string
414
+ readonly verdict: OracleJudgeVerdict
415
+ /** Content hash of the EXACT serialized bundle this outcome judged — binds outcome↔bundle. */
416
+ readonly bundleHash: string
417
+ /** The provider configuration ACTUALLY used, carried from the provider — never claimed. */
418
+ readonly provider: Pick<JudgeProviderConfig, 'binary' | 'args' | 'timeoutMs'>
419
+ /** The per-run transcript audit; REQUIRED by the run record (ruling 2026-07-14). */
420
+ readonly blindness?: OracleBlindnessAudit
421
+ readonly executionProvenance: JudgeExecutionProvenance
422
+ readonly cleanupFailed?: OracleJudgeCleanupError
423
+ }
424
+ | {
425
+ readonly status: 'blocked'
426
+ readonly cause: string
427
+ /**
428
+ * The agent-message text a verdict parse REJECTED, carried for operational diagnosis —
429
+ * without it a live integrity block is undiagnosable (the run discards the transcript).
430
+ * Blinded content only; absent when the dispatch itself failed before any message.
431
+ */
432
+ readonly rawText?: string
433
+ /**
434
+ * Sandbox-cleanup evidence from a dispatch that completed before the verdict parse
435
+ * blocked — dropping it here would let a leftover sandbox vanish behind a retry
436
+ * (terminal gate r2 major 3).
437
+ */
438
+ readonly cleanupFailed?: OracleJudgeCleanupError
439
+ }
440
+
441
+ function causeOf(error: unknown): string {
442
+ return error instanceof Error ? error.message : String(error)
443
+ }
444
+
445
+ function positiveTimeout(timeoutMs: number): number {
446
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
447
+ throw new OracleJudgeError(`timeout must be a positive finite number, received ${timeoutMs}`)
448
+ }
449
+ return timeoutMs
450
+ }
451
+
452
+ const JUDGE_TEMP_DIRECTORY_PREFIX = 'native-motion-oracle-judge-'
453
+
454
+ /**
455
+ * Proves the judge child's cwd is OUTSIDE the repository. Codex discovers ambient context
456
+ * (AGENTS.md, git metadata) by walking UP from its cwd, so a sandbox created inside the worktree
457
+ * leaks repository provenance while the event stream still audits clean (review edscpo
458
+ * e7b02aec0f21). Both inputs must be absolute, symlink-resolved paths; containment is
459
+ * path-segment exact so a sibling like `/repo-x` is never mistaken for `/repo`. Fail-closed:
460
+ * an unprovable input throws rather than passing.
461
+ */
462
+ export function assertJudgeCwdOutsideRepository(
463
+ cwdRealPath: string,
464
+ repositoryRealPath: string,
465
+ ): void {
466
+ if (!cwdRealPath.startsWith('/')) {
467
+ throw new OracleJudgeError(
468
+ `cwd containment needs an absolute resolved judge cwd, received ${cwdRealPath === '' ? 'an empty path' : `"${cwdRealPath}"`}`,
469
+ )
470
+ }
471
+ if (!repositoryRealPath.startsWith('/')) {
472
+ throw new OracleJudgeError(
473
+ `cwd containment needs an absolute resolved repository root, received ${repositoryRealPath === '' ? 'an empty path' : `"${repositoryRealPath}"`}`,
474
+ )
475
+ }
476
+ if (cwdRealPath === repositoryRealPath || cwdRealPath.startsWith(`${repositoryRealPath}/`)) {
477
+ throw new OracleJudgeError(
478
+ `judge cwd ${cwdRealPath} is inside the repository ${repositoryRealPath} — the child could acquire repository context; point TMPDIR outside the worktree`,
479
+ )
480
+ }
481
+ }
482
+
483
+ /** Symlink-resolved absolute path via `/bin/pwd -P` (conformance deliberately has no fs types). */
484
+ function judgeRealPathOf(path: string): string {
485
+ const result = Bun.spawnSync(['/bin/pwd', '-P'], { stdout: 'pipe', stderr: 'pipe', cwd: path })
486
+ const resolved = new TextDecoder().decode(result.stdout).trim()
487
+ if (result.exitCode !== 0 || resolved === '') {
488
+ const stderr = new TextDecoder().decode(result.stderr).trim()
489
+ throw new OracleJudgeError(`could not resolve real path of ${path}: ${stderr || 'pwd failed'}`)
490
+ }
491
+ return resolved
492
+ }
493
+
494
+ /**
495
+ * The fail-closed boundary policy: the containment proof needs the repository TOPLEVEL. A
496
+ * process-cwd fallback would narrow the boundary from a nested package cwd and let a TMPDIR
497
+ * elsewhere inside the repository pass containment (review x4why2 db4609a4dbd1) — when git
498
+ * cannot name the toplevel, dispatch must not proceed.
499
+ */
500
+ export function resolveJudgeRepositoryBoundary(gitToplevel: string | undefined): string {
501
+ if (gitToplevel === undefined || gitToplevel === '') {
502
+ throw new OracleJudgeError(
503
+ 'git could not name the repository toplevel — the cwd containment boundary is unprovable, refusing dispatch',
504
+ )
505
+ }
506
+ return gitToplevel
507
+ }
508
+
509
+ /** The repository toplevel per git, symlink-resolved; throws when git cannot name one. */
510
+ function repositoryRealPathBoundary(): string {
511
+ const result = Bun.spawnSync(['git', 'rev-parse', '--show-toplevel'], {
512
+ stdout: 'pipe',
513
+ stderr: 'pipe',
514
+ })
515
+ const toplevel = new TextDecoder().decode(result.stdout).trim()
516
+ return judgeRealPathOf(
517
+ resolveJudgeRepositoryBoundary(result.exitCode === 0 ? toplevel : undefined),
518
+ )
519
+ }
520
+
521
+ /** Bun's deliberately narrow ambient surface has no fs declarations in conformance. */
522
+ function createIsolatedTempDirectory(): string {
523
+ const tmp = Bun.env.TMPDIR ?? '/tmp'
524
+ const result = Bun.spawnSync(['mktemp', '-d', `${tmp}/${JUDGE_TEMP_DIRECTORY_PREFIX}XXXXXX`], {
525
+ stdout: 'pipe',
526
+ stderr: 'pipe',
527
+ })
528
+ const cwd = new TextDecoder().decode(result.stdout).trim()
529
+ if (result.exitCode !== 0 || cwd === '') {
530
+ const stderr = new TextDecoder().decode(result.stderr).trim()
531
+ throw new OracleJudgeError(
532
+ `could not create isolated temp directory: ${stderr || 'mktemp failed'}`,
533
+ )
534
+ }
535
+ // Ambient TMPDIR is untrusted input: prove the created sandbox sits outside the repository
536
+ // before any child can see it, and leave nothing behind when the proof fails — carrying the
537
+ // cleanup failure too when removal itself fails (review x4why2 20708ce0baff).
538
+ try {
539
+ assertJudgeCwdOutsideRepository(judgeRealPathOf(cwd), repositoryRealPathBoundary())
540
+ } catch (error) {
541
+ throw withJudgeCleanupEvidence(error, removeIsolatedTempDirectory(cwd))
542
+ }
543
+ return cwd
544
+ }
545
+
546
+ export interface OracleJudgeCleanupError {
547
+ readonly path: string
548
+ readonly cause: string
549
+ }
550
+
551
+ /**
552
+ * Joins an execution failure with a sandbox-cleanup failure on ONE typed error, without ever
553
+ * mutating the original: a frozen error made `error.message = ...` throw a TypeError that lost
554
+ * BOTH failures (review x4why2 20708ce0baff). No cleanup failure → the original passes through
555
+ * untouched; with one, a NEW OracleJudgeError carries both texts (single prefix).
556
+ */
557
+ export function withJudgeCleanupEvidence(
558
+ error: unknown,
559
+ cleanupFailed: OracleJudgeCleanupError | undefined,
560
+ ): Error {
561
+ if (cleanupFailed === undefined) {
562
+ return error instanceof Error ? error : new OracleJudgeError(String(error))
563
+ }
564
+ const cleanup = `; cleanup failed at ${cleanupFailed.path}: ${cleanupFailed.cause}`
565
+ const body =
566
+ error instanceof OracleJudgeError
567
+ ? error.message.replace(/^oracle judge: /, '')
568
+ : causeOf(error)
569
+ return new OracleJudgeError(`${body}${cleanup}`, cleanupFailed)
570
+ }
571
+
572
+ function removeIsolatedTempDirectory(cwd: string): OracleJudgeCleanupError | undefined {
573
+ const result = Bun.spawnSync(['rm', '-rf', cwd], { stdout: 'ignore', stderr: 'pipe' })
574
+ if (result.exitCode === 0) return undefined
575
+ const stderr = new TextDecoder().decode(result.stderr).trim()
576
+ return { path: cwd, cause: stderr || `rm exited ${result.exitCode}` }
577
+ }
578
+
579
+ /**
580
+ * Builds the complete child boundary without consulting process state. PATH is the fixed system
581
+ * allowlist and cwd an empty temp directory (structural guards); HOME is the REAL home so the
582
+ * provider's authentication works — blindness is proven per-run by the transcript audit
583
+ * (ruling 2026-07-14), not by stripping credentials.
584
+ */
585
+ export function createIsolatedJudgeSpawnOptions(
586
+ command: readonly string[],
587
+ serializedBundle: string,
588
+ cwd: string,
589
+ path: string,
590
+ home: string,
591
+ ): IsolatedJudgeSpawnOptions {
592
+ return {
593
+ command: [...command],
594
+ stdin: new Blob([serializedBundle]),
595
+ stdout: 'pipe',
596
+ stderr: 'pipe',
597
+ cwd,
598
+ env: { PATH: path, HOME: home },
599
+ }
600
+ }
601
+
602
+ // The ONLY argv the CLI judge may run with (review v3xau5 m1a97ad2e): codex accepts
603
+ // context-bearing arguments (`exec resume --last`, `-C <dir>`) that expose prior-session or
604
+ // repository state while the event stream still audits clean. The argv is therefore pinned;
605
+ // a config that asks for anything else fails loudly at construction.
606
+ const FIXED_JUDGE_CLI_ARGS = ['exec', '--skip-git-repo-check'] as const
607
+
608
+ /** The process seam (test-injectable). Production is `Bun.spawn`; the trust anchor is here. */
609
+ export type JudgeSpawn = (options: IsolatedJudgeSpawnOptions) => BunJudgeProcess
610
+
611
+ /**
612
+ * The OS boundary the provider executes through — spawn, sandbox cwd lifecycle, and the real
613
+ * HOME. One injectable seam so tests fake the operating system, never the provider logic; the
614
+ * trust anchor for `cli` execution provenance is exactly this seam.
615
+ */
616
+ export interface JudgeExecutionSeam {
617
+ readonly spawn: JudgeSpawn
618
+ createCwd(): string
619
+ removeCwd(cwd: string): OracleJudgeCleanupError | undefined
620
+ home(): string | undefined
621
+ /** Places dispatch-mechanics files (the verdict output schema) inside the isolated cwd. */
622
+ writeCwdFile(cwd: string, name: string, content: string): void | Promise<void>
623
+ }
624
+
625
+ function productionExecutionSeam(): JudgeExecutionSeam {
626
+ return {
627
+ spawn: (options) => Bun.spawn(options.command, options),
628
+ createCwd: createIsolatedTempDirectory,
629
+ removeCwd: removeIsolatedTempDirectory,
630
+ home: () => Bun.env.HOME,
631
+ writeCwdFile: async (cwd, name, content) => {
632
+ await Bun.write(`${cwd}/${name}`, content)
633
+ },
634
+ }
635
+ }
636
+
637
+ // Provenance brand for CLI-executed transcripts: results in this set came off the stdout pipe
638
+ // of a process the REAL provider spawned with the pinned argv. Injected providers cannot join;
639
+ // the run record refuses their outcomes (review kpp2fg m7f2a61c9).
640
+ const cliExecutedResults = new WeakSet<object>()
641
+
642
+ /** The output schema's fixed name inside the isolated sandbox cwd (dispatch mechanics). */
643
+ const JUDGE_SCHEMA_FILENAME = 'verdict-schema.json'
644
+
645
+ /** SIGTERM→SIGKILL escalation grace on a timed-out judge child (terminal gate r3 major). */
646
+ const JUDGE_KILL_GRACE_MS = 2000
647
+
648
+ /**
649
+ * removeCwd is a seam and may THROW rather than return; an exception must become structured
650
+ * cleanup evidence, never a raw error that bypasses the cleanup-exit-2 rule (terminal gate r4).
651
+ */
652
+ function safeRemoveCwd(
653
+ execution: JudgeExecutionSeam,
654
+ cwd: string,
655
+ ): OracleJudgeCleanupError | undefined {
656
+ try {
657
+ return execution.removeCwd(cwd)
658
+ } catch (error) {
659
+ return { path: cwd, cause: `removal threw: ${causeOf(error)}` }
660
+ }
661
+ }
662
+
663
+ /** A bounded non-interactive provider whose child sees only blind bytes in an empty cwd. */
664
+ export function createCliJudgeProvider(
665
+ config: JudgeProviderConfig,
666
+ resolvers: JudgeBinaryResolvers = productionJudgeBinaryResolvers(),
667
+ execution: JudgeExecutionSeam = productionExecutionSeam(),
668
+ ): JudgeProvider {
669
+ const timeoutMs = positiveTimeout(config.timeoutMs)
670
+ if (config.binary.trim() === '') throw new OracleJudgeError('provider binary is empty')
671
+ if (
672
+ config.args.length !== FIXED_JUDGE_CLI_ARGS.length ||
673
+ config.args.some((arg, index) => arg !== FIXED_JUDGE_CLI_ARGS[index])
674
+ ) {
675
+ throw new OracleJudgeError(
676
+ `provider arguments are pinned to [${FIXED_JUDGE_CLI_ARGS.join(', ')}] — context-bearing arguments would defeat the blindness audit`,
677
+ )
678
+ }
679
+ // Resolve to the real absolute executable NOW, in the parent env — the child gets only the
680
+ // fixed system PATH and could neither find the binary nor be trusted to search for it.
681
+ const binary = resolveJudgeBinary(config.binary, resolvers)
682
+ // Snapshot + freeze the validated arguments: retaining the caller's array by reference would
683
+ // let a post-validation mutation reach the child argv (review kpp2fg m9c4e81a7).
684
+ const args = Object.freeze([...FIXED_JUDGE_CLI_ARGS]) as readonly string[]
685
+ const resolvedConfig = Object.freeze({ binary, args, timeoutMs })
686
+
687
+ // The provider object itself is frozen below: swapping `config` after construction would let
688
+ // a record claim a provider/argv that never executed (review hscuvh ma799e14e).
689
+ return Object.freeze({
690
+ config: resolvedConfig,
691
+ // This provider owns its deadline: the internal timer kills the child, both paths remove
692
+ // the sandbox, and cleanup evidence rides the thrown error — dispatchJudge must not race
693
+ // a second timer over it (review lqzkrr).
694
+ enforcesTimeout: true as const,
695
+ async judge(
696
+ serializedBundle: string,
697
+ outputSchema?: string,
698
+ ): Promise<OracleJudgeProviderResult> {
699
+ const home = execution.home()
700
+ if (home === undefined || home === '') {
701
+ throw new OracleJudgeError('provider HOME is unavailable for authenticated dispatch')
702
+ }
703
+ const cwd = execution.createCwd()
704
+ let judgeProcess: BunJudgeProcess
705
+ try {
706
+ // --json makes the child emit the auditable event stream; `-` reads the prompt from
707
+ // stdin (this codex fork otherwise waits on extra stdin input); --output-schema pins
708
+ // the emission to the trusted layer's bundle-derived shape from a file INSIDE the
709
+ // sandbox (no repo path in argv — review seuljp class). All three are dispatch
710
+ // mechanics, deliberately NOT configurable.
711
+ const schemaArgs: string[] = []
712
+ if (outputSchema !== undefined) {
713
+ // Awaited: the child reads this file at startup — an unfinished write is a race.
714
+ await execution.writeCwdFile(cwd, JUDGE_SCHEMA_FILENAME, outputSchema)
715
+ schemaArgs.push('--output-schema', `${cwd}/${JUDGE_SCHEMA_FILENAME}`)
716
+ }
717
+ const options = createIsolatedJudgeSpawnOptions(
718
+ [binary, ...args, '--json', ...schemaArgs, '-'],
719
+ serializedBundle,
720
+ cwd,
721
+ SYSTEM_JUDGE_PATH,
722
+ home,
723
+ )
724
+ judgeProcess = execution.spawn(options)
725
+ } catch (error) {
726
+ const cleanupFailed = safeRemoveCwd(execution, cwd)
727
+ const cleanup =
728
+ cleanupFailed === undefined
729
+ ? ''
730
+ : `; cleanup failed at ${cleanupFailed.path}: ${cleanupFailed.cause}`
731
+ throw new OracleJudgeError(
732
+ `provider could not start: ${causeOf(error)}${cleanup}`,
733
+ cleanupFailed,
734
+ )
735
+ }
736
+ let timer: ReturnType<typeof setTimeout> | undefined
737
+ let escalation: ReturnType<typeof setTimeout> | undefined
738
+ let rawText: string | undefined
739
+ try {
740
+ // Single-owner termination (terminal gate r3 major): on timeout the child is killed
741
+ // and its EXIT IS AWAITED before anything touches the sandbox — rejecting early let a
742
+ // retry overlap a still-running judge and removed the cwd under a live process.
743
+ // SIGTERM escalates to SIGKILL after a bounded grace; SIGKILL is non-maskable, so the
744
+ // await below settles for any userspace child (a kernel-stuck process is environment-
745
+ // trust territory).
746
+ let timedOutAfterMs: number | undefined
747
+ timer = setTimeout(() => {
748
+ timedOutAfterMs = timeoutMs
749
+ judgeProcess.kill()
750
+ escalation = setTimeout(() => judgeProcess.kill(9), JUDGE_KILL_GRACE_MS)
751
+ }, timeoutMs)
752
+ const exitCode = await judgeProcess.exited
753
+ if (timedOutAfterMs !== undefined) {
754
+ throw new OracleJudgeError(`provider timed out after ${timedOutAfterMs}ms`)
755
+ }
756
+ const stdout = await new Response(judgeProcess.stdout).text()
757
+ const stderr = await new Response(judgeProcess.stderr).text()
758
+ if (exitCode !== 0) {
759
+ throw new OracleJudgeError(`provider exited ${exitCode}: ${stderr.trim() || 'no stderr'}`)
760
+ }
761
+ rawText = stdout
762
+ } catch (error) {
763
+ // The sandbox is removed on the failure path too, and BOTH failures are carried on one
764
+ // typed error — never by mutating the original, which a frozen error turns into a
765
+ // TypeError that loses all evidence (review edscpo 9c406a7c201c, x4why2 20708ce0baff).
766
+ throw withJudgeCleanupEvidence(error, safeRemoveCwd(execution, cwd))
767
+ } finally {
768
+ if (timer !== undefined) clearTimeout(timer)
769
+ if (escalation !== undefined) clearTimeout(escalation)
770
+ }
771
+ const cleanupFailed = safeRemoveCwd(execution, cwd)
772
+ if (rawText === undefined) throw new OracleJudgeError('provider returned no stdout')
773
+ // The provider hands back the RAW transcript only; auditing happens in dispatchJudge.
774
+ // The result is provenance-branded, FROZEN, and bound to the exact bundle bytes this
775
+ // execution received plus the config that actually ran — capture-and-replay against a
776
+ // different bundle or post-capture doctoring both fail structurally (review hscuvh).
777
+ const result: OracleJudgeProviderResult = Object.freeze({
778
+ transcript: rawText,
779
+ bundleHash: oracleContentHash(serializedBundle),
780
+ executedConfig: resolvedConfig,
781
+ // Frozen COPY: the seam returned this object and may retain a reference — a shared or
782
+ // mutable cleanup record is doctorable evidence (review edscpo 9c406a7c201c).
783
+ ...(cleanupFailed === undefined
784
+ ? {}
785
+ : {
786
+ cleanupFailed: Object.freeze({
787
+ path: cleanupFailed.path,
788
+ cause: cleanupFailed.cause,
789
+ }),
790
+ }),
791
+ })
792
+ cliExecutedResults.add(result)
793
+ return result
794
+ },
795
+ })
796
+ }
797
+
798
+ /** Converts unavailable or non-responsive judge execution into the required fail-closed outcome. */
799
+ export async function dispatchJudge(
800
+ provider: JudgeProvider,
801
+ bundle: OracleJudgeBundle,
802
+ options: Pick<JudgeProviderConfig, 'timeoutMs'> = DEFAULT_JUDGE_PROVIDER_CONFIG,
803
+ ): Promise<JudgeDispatchOutcome> {
804
+ let timer: ReturnType<typeof setTimeout> | undefined
805
+ // Hoisted so the catch can still reach RESULT-borne cleanup evidence when the audit throws —
806
+ // error-borne evidence alone dropped it (terminal gate r4 major 73d12a5e0c9f).
807
+ let result: OracleJudgeProviderResult | undefined
808
+ try {
809
+ const timeoutMs = positiveTimeout(options.timeoutMs)
810
+ // The strict output schema is derived HERE, in the trusted layer, from the real bundle —
811
+ // the provider receives it as opaque bytes and never parses bundle content (transport
812
+ // ruling 2026-07-14).
813
+ const outputSchema = JSON.stringify(verdictSchemaFor(bundle))
814
+ // Exactly ONE deadline owner (review lqzkrr): a provider that enforces its own timeout is
815
+ // awaited directly — its kill/sandbox-removal runs to completion and its cleanup evidence
816
+ // arrives on the thrown error. Racing a second timer at the same deadline lets the outer
817
+ // one (scheduled first) win, returning `blocked` while the child is still alive and
818
+ // stranding that evidence. The race remains as the backstop for non-enforcing providers.
819
+ let providerResult: string | OracleJudgeProviderResult
820
+ if (provider.enforcesTimeout === true) {
821
+ providerResult = await provider.judge(serializeJudgeBundle(bundle), outputSchema)
822
+ } else {
823
+ const timedOut = new Promise<never>((_, reject) => {
824
+ timer = setTimeout(
825
+ () => reject(new OracleJudgeError(`provider timed out after ${timeoutMs}ms`)),
826
+ timeoutMs,
827
+ )
828
+ })
829
+ providerResult = await Promise.race([
830
+ provider.judge(serializeJudgeBundle(bundle), outputSchema),
831
+ timedOut,
832
+ ])
833
+ }
834
+ result = typeof providerResult === 'string' ? { transcript: providerResult } : providerResult
835
+ if (result.transcript.trim() === '')
836
+ return {
837
+ status: 'blocked',
838
+ cause: 'judge provider returned empty output',
839
+ ...(result.cleanupFailed === undefined ? {} : { cleanupFailed: result.cleanupFailed }),
840
+ }
841
+ // TRUSTED BOUNDARY: the audit runs HERE over the raw transcript, so no provider can mint
842
+ // or forge a blindness proof. Anything beyond one clean turn throws → blocked, void.
843
+ const audited = parseJudgeEventStream(result.transcript)
844
+ const isCli = cliExecutedResults.has(result)
845
+ if (isCli && result.bundleHash !== oracleContentHash(serializeJudgeBundle(bundle))) {
846
+ // A frozen CLI result replayed against a different bundle: the execution is real but it
847
+ // is not bound to THESE bundle bytes — the verdict is void for this bundle.
848
+ return {
849
+ status: 'blocked',
850
+ cause: 'judge execution result is bound to a different bundle — replay refused',
851
+ ...(result.cleanupFailed === undefined ? {} : { cleanupFailed: result.cleanupFailed }),
852
+ }
853
+ }
854
+ return {
855
+ status: 'complete',
856
+ rawText: audited.rawText,
857
+ transcript: result.transcript,
858
+ blindness: audited.blindness,
859
+ executionProvenance: isCli ? 'cli' : 'unverified',
860
+ ...(isCli && result.executedConfig !== undefined
861
+ ? { executedConfig: result.executedConfig }
862
+ : {}),
863
+ ...(result.cleanupFailed === undefined ? {} : { cleanupFailed: result.cleanupFailed }),
864
+ }
865
+ } catch (error) {
866
+ // Structured cleanup evidence survives the dispatch-level block — the message string alone
867
+ // cannot reach the retry aggregation's exit-2 rule (terminal gate r3 major). Error-borne
868
+ // evidence (provider throw paths) takes precedence; RESULT-borne evidence covers the case
869
+ // where the dispatch completed dirty and the AUDIT then threw (terminal gate r4 major).
870
+ const cleanupFailed =
871
+ error instanceof OracleJudgeError && error.cleanupFailed !== undefined
872
+ ? error.cleanupFailed
873
+ : result?.cleanupFailed
874
+ return {
875
+ status: 'blocked',
876
+ cause: `judge provider could not be verified: ${causeOf(error)}`,
877
+ ...(cleanupFailed === undefined ? {} : { cleanupFailed }),
878
+ }
879
+ } finally {
880
+ if (timer !== undefined) clearTimeout(timer)
881
+ }
882
+ }
883
+
884
+ function object(value: unknown, path: string): Record<string, unknown> {
885
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
886
+ throw new OracleJudgeError(`${path} must be an object`)
887
+ }
888
+ return value as Record<string, unknown>
889
+ }
890
+
891
+ function exactKeys(
892
+ value: Record<string, unknown>,
893
+ expected: readonly string[],
894
+ path: string,
895
+ ): void {
896
+ const actual = Object.keys(value)
897
+ if (actual.length !== expected.length || actual.some((key) => !expected.includes(key))) {
898
+ throw new OracleJudgeError(`${path} must have exactly: ${expected.join(', ')}`)
899
+ }
900
+ }
901
+
902
+ function labelsOf(bundle: OracleJudgeBundle): readonly string[] {
903
+ const labels = bundle.traces.map((trace) => trace.label)
904
+ if (new Set(labels).size !== labels.length || labels.some((label) => label.trim() === '')) {
905
+ throw new OracleJudgeError('judge bundle has invalid label vocabulary')
906
+ }
907
+ return labels
908
+ }
909
+
910
+ function stringArray(value: unknown, path: string): readonly string[] {
911
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
912
+ throw new OracleJudgeError(`${path} must be a string array`)
913
+ }
914
+ return value
915
+ }
916
+
917
+ function parseIssue(
918
+ value: unknown,
919
+ path: string,
920
+ gridTimes: ReadonlySet<number>,
921
+ ): OracleJudgeIssue {
922
+ const issue = object(value, path)
923
+ exactKeys(issue, ['timestamps', 'description'], path)
924
+ if (
925
+ !Array.isArray(issue.timestamps) ||
926
+ issue.timestamps.length === 0 ||
927
+ issue.timestamps.some((t) => typeof t !== 'number' || !Number.isFinite(t))
928
+ ) {
929
+ throw new OracleJudgeError(`${path}.timestamps must be a non-empty finite number array`)
930
+ }
931
+ if (issue.timestamps.some((timestamp) => !gridTimes.has(timestamp as number))) {
932
+ throw new OracleJudgeError(`${path}.timestamps must cite exact grid row timestamp(s)`)
933
+ }
934
+ if (
935
+ typeof issue.description !== 'string' ||
936
+ issue.description.trim() === '' ||
937
+ issue.description.includes('\n')
938
+ ) {
939
+ throw new OracleJudgeError(`${path}.description must be one non-empty line`)
940
+ }
941
+ return { timestamps: issue.timestamps as readonly number[], description: issue.description }
942
+ }
943
+
944
+ function parseDimensionVerdict(
945
+ value: unknown,
946
+ path: string,
947
+ gridTimes: ReadonlySet<number>,
948
+ ): OracleJudgeDimensionVerdict {
949
+ const dimension = object(value, path)
950
+ exactKeys(dimension, ['verdict', 'issues'], path)
951
+ if (!(ORACLE_JUDGE_VALUES as readonly string[]).includes(dimension.verdict as string)) {
952
+ throw new OracleJudgeError(`${path}.verdict must be pass, borderline, or fail`)
953
+ }
954
+ if (!Array.isArray(dimension.issues))
955
+ throw new OracleJudgeError(`${path}.issues must be an array`)
956
+ const issues = dimension.issues.map((issue, index) =>
957
+ parseIssue(issue, `${path}.issues[${index}]`, gridTimes),
958
+ )
959
+ if (dimension.verdict === 'pass' && issues.length !== 0) {
960
+ throw new OracleJudgeError(`${path}: pass verdict must have no issues`)
961
+ }
962
+ if (dimension.verdict !== 'pass' && issues.length === 0) {
963
+ throw new OracleJudgeError(`${path}: non-pass verdict requires timestamped issue(s)`)
964
+ }
965
+ return { verdict: dimension.verdict as OracleJudgeValue, issues }
966
+ }
967
+
968
+ /**
969
+ * The bundle-derived STRICT output schema handed to the provider as dispatch mechanics (ruling
970
+ * 2026-07-14, live-leg attempt 4): the judge model miscounts braces in free-form emissions and
971
+ * within-run retries are sampling-correlated, so the emission shape is closed STRUCTURALLY at
972
+ * the transport — the phase-3 precedent. Labels are exact per bundle (strict mode cannot
973
+ * express dynamic keys; the labels are already blinded and already in the prompt, so nothing
974
+ * leaks). parseJudgeVerdict remains the trusted validator — this schema constrains the model,
975
+ * it never replaces the parse.
976
+ */
977
+ export function verdictSchemaFor(bundle: OracleJudgeBundle): object {
978
+ const issue = {
979
+ type: 'object',
980
+ additionalProperties: false,
981
+ required: ['timestamps', 'description'],
982
+ properties: {
983
+ timestamps: { type: 'array', items: { type: 'number' } },
984
+ description: { type: 'string' },
985
+ },
986
+ }
987
+ const dimension = {
988
+ type: 'object',
989
+ additionalProperties: false,
990
+ required: ['verdict', 'issues'],
991
+ properties: {
992
+ verdict: { type: 'string', enum: [...ORACLE_JUDGE_VALUES] },
993
+ issues: { type: 'array', items: issue },
994
+ },
995
+ }
996
+ const labels = labelsOf(bundle)
997
+ const ranking = { type: 'array', items: { type: 'string' } }
998
+ return {
999
+ type: 'object',
1000
+ additionalProperties: false,
1001
+ required: ['rankings', 'traces'],
1002
+ properties: {
1003
+ rankings: {
1004
+ type: 'object',
1005
+ additionalProperties: false,
1006
+ required: [...ORACLE_JUDGE_DIMENSIONS],
1007
+ properties: { continuity: ranking, physicalPlausibility: ranking },
1008
+ },
1009
+ traces: {
1010
+ type: 'object',
1011
+ additionalProperties: false,
1012
+ required: labels,
1013
+ properties: Object.fromEntries(
1014
+ labels.map((label) => [
1015
+ label,
1016
+ {
1017
+ type: 'object',
1018
+ additionalProperties: false,
1019
+ required: [...ORACLE_JUDGE_DIMENSIONS],
1020
+ properties: { continuity: dimension, physicalPlausibility: dimension },
1021
+ },
1022
+ ]),
1023
+ ),
1024
+ },
1025
+ },
1026
+ }
1027
+ }
1028
+
1029
+ /** Strict REQ-ORACLE-007 parser: labels, dimensions, vocabulary, and actionable non-passes exact. */
1030
+ export function parseJudgeVerdict(rawText: string, bundle: OracleJudgeBundle): OracleJudgeVerdict {
1031
+ let parsed: unknown
1032
+ try {
1033
+ parsed = JSON.parse(rawText) as unknown
1034
+ } catch (error) {
1035
+ throw new OracleJudgeError(`malformed JSON: ${causeOf(error)}`)
1036
+ }
1037
+ const root = object(parsed, 'verdict')
1038
+ exactKeys(root, ['rankings', 'traces'], 'verdict')
1039
+ const labels = labelsOf(bundle)
1040
+ const rankingsInput = object(root.rankings, 'verdict.rankings')
1041
+ exactKeys(rankingsInput, ORACLE_JUDGE_DIMENSIONS, 'verdict.rankings')
1042
+ const rankings = {} as Record<OracleJudgeDimension, readonly string[]>
1043
+ for (const dimension of ORACLE_JUDGE_DIMENSIONS) {
1044
+ const ranking = stringArray(rankingsInput[dimension], `verdict.rankings.${dimension}`)
1045
+ if (
1046
+ ranking.length !== labels.length ||
1047
+ new Set(ranking).size !== ranking.length ||
1048
+ ranking.some((label) => !labels.includes(label))
1049
+ ) {
1050
+ throw new OracleJudgeError(
1051
+ `verdict.rankings.${dimension} must rank each bundle label exactly once`,
1052
+ )
1053
+ }
1054
+ rankings[dimension] = ranking
1055
+ }
1056
+ const tracesInput = object(root.traces, 'verdict.traces')
1057
+ exactKeys(tracesInput, labels, 'verdict.traces')
1058
+ const traces: Record<string, Record<OracleJudgeDimension, OracleJudgeDimensionVerdict>> = {}
1059
+ for (const label of labels) {
1060
+ const trace = object(tracesInput[label], `verdict.traces.${label}`)
1061
+ exactKeys(trace, ORACLE_JUDGE_DIMENSIONS, `verdict.traces.${label}`)
1062
+ const blindedTrace = bundle.traces.find((candidate) => candidate.label === label)
1063
+ if (blindedTrace === undefined) {
1064
+ throw new OracleJudgeError(`judge bundle has no grid for label ${label}`)
1065
+ }
1066
+ const gridTimes = new Set(blindedTrace.rows.map((row) => row.t))
1067
+ traces[label] = {
1068
+ continuity: parseDimensionVerdict(
1069
+ trace.continuity,
1070
+ `verdict.traces.${label}.continuity`,
1071
+ gridTimes,
1072
+ ),
1073
+ physicalPlausibility: parseDimensionVerdict(
1074
+ trace.physicalPlausibility,
1075
+ `verdict.traces.${label}.physicalPlausibility`,
1076
+ gridTimes,
1077
+ ),
1078
+ }
1079
+ }
1080
+ return { rankings, traces }
1081
+ }
1082
+
1083
+ /** Joins dispatch and strict parsing at the driver-facing seam so malformed output is never a pass. */
1084
+ // Chain-of-custody brand: the run-record builder admits ONLY outcomes this function produced,
1085
+ // so raw text, verdict, and provider claims cannot be fabricated (review seuljp m18f8b1ed).
1086
+ const judgedOutcomes = new WeakSet<object>()
1087
+
1088
+ export function assertJudgeVerdictOutcome(
1089
+ outcome: JudgeVerdictOutcome,
1090
+ ): asserts outcome is JudgeVerdictOutcome & { status: 'complete' } {
1091
+ if (outcome.status !== 'complete' || !judgedOutcomes.has(outcome)) {
1092
+ throw new OracleJudgeError('outcome was not produced by judgeBundle')
1093
+ }
1094
+ }
1095
+
1096
+ // Runtime immutability for the evidence chain: a branded outcome that could be mutated in place
1097
+ // would defeat the brand (review c6w0wg m25), so the whole verdict graph is frozen.
1098
+ function deepFreezeOutcome(outcome: JudgeVerdictOutcome & { status: 'complete' }): void {
1099
+ for (const dimension of ORACLE_JUDGE_DIMENSIONS) {
1100
+ Object.freeze(outcome.verdict.rankings[dimension])
1101
+ }
1102
+ Object.freeze(outcome.verdict.rankings)
1103
+ for (const trace of Object.values(outcome.verdict.traces)) {
1104
+ for (const dimension of ORACLE_JUDGE_DIMENSIONS) {
1105
+ for (const issue of trace[dimension].issues) {
1106
+ Object.freeze(issue.timestamps)
1107
+ Object.freeze(issue)
1108
+ }
1109
+ Object.freeze(trace[dimension].issues)
1110
+ Object.freeze(trace[dimension])
1111
+ }
1112
+ Object.freeze(trace)
1113
+ }
1114
+ Object.freeze(outcome.verdict.traces)
1115
+ Object.freeze(outcome.verdict)
1116
+ Object.freeze(outcome.provider.args)
1117
+ Object.freeze(outcome.provider)
1118
+ if (outcome.blindness !== undefined) Object.freeze(outcome.blindness)
1119
+ if (outcome.cleanupFailed !== undefined) Object.freeze(outcome.cleanupFailed)
1120
+ Object.freeze(outcome)
1121
+ }
1122
+
1123
+ export async function judgeBundle(
1124
+ provider: JudgeProvider,
1125
+ bundle: OracleJudgeBundle,
1126
+ options: Pick<JudgeProviderConfig, 'timeoutMs'> = DEFAULT_JUDGE_PROVIDER_CONFIG,
1127
+ ): Promise<JudgeVerdictOutcome> {
1128
+ const dispatched = await dispatchJudge(provider, bundle, options)
1129
+ if (dispatched.status === 'blocked') return dispatched
1130
+ try {
1131
+ const outcome: JudgeVerdictOutcome & { status: 'complete' } = {
1132
+ status: 'complete',
1133
+ rawText: dispatched.rawText,
1134
+ transcript: dispatched.transcript,
1135
+ verdict: parseJudgeVerdict(dispatched.rawText, bundle),
1136
+ bundleHash: oracleContentHash(serializeJudgeBundle(bundle)),
1137
+ // For CLI executions the branded result's executedConfig is authoritative — a swapped
1138
+ // provider.config can never reach a record (review hscuvh ma799e14e).
1139
+ provider: dispatched.executedConfig ?? provider.config,
1140
+ executionProvenance: dispatched.executionProvenance,
1141
+ ...(dispatched.blindness === undefined ? {} : { blindness: dispatched.blindness }),
1142
+ ...(dispatched.cleanupFailed === undefined
1143
+ ? {}
1144
+ : { cleanupFailed: dispatched.cleanupFailed }),
1145
+ }
1146
+ deepFreezeOutcome(outcome)
1147
+ judgedOutcomes.add(outcome)
1148
+ return outcome
1149
+ } catch (error) {
1150
+ // The rejected emission rides the block for diagnosis (blinded content only) — a live
1151
+ // integrity block without it is undiagnosable, the run discards the transcript. Cleanup
1152
+ // evidence from the completed dispatch rides too (terminal gate r2 major 3).
1153
+ return {
1154
+ status: 'blocked',
1155
+ cause: `judge verdict could not be verified: ${causeOf(error)}`,
1156
+ rawText: dispatched.rawText,
1157
+ ...(dispatched.cleanupFailed === undefined
1158
+ ? {}
1159
+ : { cleanupFailed: dispatched.cleanupFailed }),
1160
+ }
1161
+ }
1162
+ }
1163
+
1164
+ export interface OracleGuardInput {
1165
+ readonly verdict: OracleJudgeVerdict
1166
+ /** This is deliberately guard-only: no provider or parser accepts an unblinding record. */
1167
+ readonly unblinding: OracleUnblindingRecord
1168
+ readonly controlKinds: readonly OracleControlKind[]
1169
+ }
1170
+
1171
+ export interface OracleAdvisoryOrdering {
1172
+ readonly dimension: OracleJudgeDimension
1173
+ readonly bestToWorst: readonly ('native-core' | 'motion-dom')[]
1174
+ }
1175
+
1176
+ /** A real-trace failure outside A6(b)'s planted-control classes; reported but never gates. */
1177
+ export interface OracleUnclassifiedFinding {
1178
+ readonly label: string
1179
+ readonly engine: 'native-core' | 'motion-dom'
1180
+ readonly dimension: OracleJudgeDimension
1181
+ readonly timestamps: readonly number[]
1182
+ readonly description: string
1183
+ }
1184
+
1185
+ export type OracleGuardOutcome =
1186
+ | {
1187
+ readonly status: 'blocked'
1188
+ readonly cause: string
1189
+ readonly advisory: readonly OracleAdvisoryOrdering[]
1190
+ readonly unclassifiedFindings: readonly OracleUnclassifiedFinding[]
1191
+ }
1192
+ | {
1193
+ readonly status: 'fail'
1194
+ readonly cause: string
1195
+ readonly advisory: readonly OracleAdvisoryOrdering[]
1196
+ readonly unclassifiedFindings: readonly OracleUnclassifiedFinding[]
1197
+ }
1198
+ | {
1199
+ readonly status: 'pass'
1200
+ readonly advisory: readonly OracleAdvisoryOrdering[]
1201
+ readonly unclassifiedFindings: readonly OracleUnclassifiedFinding[]
1202
+ }
1203
+
1204
+ function advisoryFor(input: OracleGuardInput): readonly OracleAdvisoryOrdering[] {
1205
+ const advisories: OracleAdvisoryOrdering[] = []
1206
+ for (const dimension of ORACLE_JUDGE_DIMENSIONS) {
1207
+ const engines = input.verdict.rankings[dimension].flatMap((label) => {
1208
+ const source = input.unblinding[label]?.source
1209
+ return source !== undefined && 'engine' in source ? [source.engine] : []
1210
+ })
1211
+ if (engines.includes('native-core') && engines.includes('motion-dom')) {
1212
+ advisories.push({
1213
+ dimension,
1214
+ bestToWorst: engines as readonly ('native-core' | 'motion-dom')[],
1215
+ })
1216
+ }
1217
+ }
1218
+ return advisories
1219
+ }
1220
+
1221
+ function dimensionForControl(kind: OracleControlKind): OracleJudgeDimension {
1222
+ return kind === 'C-DISC' ? 'continuity' : 'physicalPlausibility'
1223
+ }
1224
+
1225
+ function blocked(cause: string, advisory: readonly OracleAdvisoryOrdering[]): OracleGuardOutcome {
1226
+ return { status: 'blocked', cause, advisory, unclassifiedFindings: [] }
1227
+ }
1228
+
1229
+ // Classification is STRUCTURAL — dimension enum + defect location only. Free-text descriptions
1230
+ // are for humans; matching them is bypassable by wording ("abrupt position step" is a
1231
+ // discontinuity a regex missed — review seuljp mb4d89c69) and never participates in gating.
1232
+
1233
+ function discontinuityAtMarkedEvent(
1234
+ issue: OracleJudgeIssue,
1235
+ eventTimes: readonly TraceEvent[],
1236
+ ): boolean {
1237
+ // A continuity-dimension failure timestamped AT a marked boundary IS the C-DISC defect class.
1238
+ return issue.timestamps.some((timestamp) => eventTimes.some((event) => event.t === timestamp))
1239
+ }
1240
+
1241
+ function nonSpringSettle(traceSpan: OracleTraceSpan): boolean {
1242
+ // A physical-plausibility failure on a trace that settles IS the C-LIN defect class.
1243
+ return traceSpan === 'settles'
1244
+ }
1245
+
1246
+ function isPlantedControlClassFailure(
1247
+ dimension: OracleJudgeDimension,
1248
+ issue: OracleJudgeIssue,
1249
+ eventTimes: readonly TraceEvent[],
1250
+ traceSpan: OracleTraceSpan,
1251
+ ): boolean {
1252
+ switch (dimension) {
1253
+ case 'continuity':
1254
+ return discontinuityAtMarkedEvent(issue, eventTimes)
1255
+ case 'physicalPlausibility':
1256
+ return nonSpringSettle(traceSpan)
1257
+ }
1258
+ }
1259
+
1260
+ /** A6's pure, post-judgment calibration and regression decision. */
1261
+ export function decideOracleGuard(inputs: readonly OracleGuardInput[]): OracleGuardOutcome {
1262
+ const advisory = inputs.flatMap(advisoryFor)
1263
+ const kindsSeen = new Set<OracleControlKind>()
1264
+ for (const input of inputs) {
1265
+ const labels = Object.keys(input.unblinding)
1266
+ const verdictLabels = Object.keys(input.verdict.traces)
1267
+ const labelSets = [
1268
+ labels,
1269
+ verdictLabels,
1270
+ ...ORACLE_JUDGE_DIMENSIONS.map((dimension) => input.verdict.rankings[dimension]),
1271
+ ]
1272
+ if (labelSets.some((candidate) => new Set(candidate).size !== candidate.length)) {
1273
+ return blocked('could not verify label closure: duplicate label', advisory)
1274
+ }
1275
+ if (
1276
+ labelSets.some(
1277
+ (candidate) =>
1278
+ candidate.length !== labels.length || candidate.some((label) => !labels.includes(label)),
1279
+ )
1280
+ ) {
1281
+ return blocked(
1282
+ 'could not verify label closure: bundle, verdict, and unblinding labels differ',
1283
+ advisory,
1284
+ )
1285
+ }
1286
+ for (const kind of input.controlKinds) {
1287
+ if (!isOracleControlKind(kind))
1288
+ return blocked(`could not verify unknown control kind ${kind}`, advisory)
1289
+ kindsSeen.add(kind)
1290
+ }
1291
+ for (const kind of input.controlKinds) {
1292
+ const dimension = dimensionForControl(kind)
1293
+ const controls = labels.filter((label) => {
1294
+ const source = input.unblinding[label]?.source
1295
+ return source !== undefined && 'control' in source && source.control === kind
1296
+ })
1297
+ if (controls.length !== 1)
1298
+ return blocked(`could not verify ${kind}: expected exactly one unblinded control`, advisory)
1299
+ const realLabels = labels.filter(
1300
+ (label) => 'engine' in (input.unblinding[label]?.source ?? {}),
1301
+ )
1302
+ if (realLabels.length === 0)
1303
+ return blocked(`could not verify ${kind}: bundle has no real traces`, advisory)
1304
+ const ranking = input.verdict.rankings[dimension]
1305
+ const controlRank = ranking.indexOf(controls[0]!)
1306
+ if (controlRank < 0 || realLabels.some((label) => controlRank <= ranking.indexOf(label))) {
1307
+ return blocked(
1308
+ `could not verify ${kind}: control was not ranked below every real trace on ${dimension}`,
1309
+ advisory,
1310
+ )
1311
+ }
1312
+ const verdict = input.verdict.traces[controls[0]!]![dimension]
1313
+ if (verdict.verdict === 'pass')
1314
+ return blocked(`could not verify ${kind}: control was rated pass on ${dimension}`, advisory)
1315
+ }
1316
+ }
1317
+ for (const kind of ['C-DISC', 'C-LIN'] as const) {
1318
+ if (!kindsSeen.has(kind))
1319
+ return blocked(`could not verify calibration: ${kind} was absent from the run`, advisory)
1320
+ }
1321
+ const unclassifiedFindings: OracleUnclassifiedFinding[] = []
1322
+ const unclassifiedFindingKeys = new Set<string>()
1323
+ // The FULL scan always completes before the outcome is decided: an early return on the first
1324
+ // classified gating failure would persist unclassifiedFindings that never saw later
1325
+ // dimensions, traces, or bundles — incomplete guard evidence (review lqzkrr a7d2f160b9ce).
1326
+ // The gate itself still reports the FIRST classified failure encountered.
1327
+ let gatingCause: string | undefined
1328
+ for (const input of inputs) {
1329
+ for (const [label, entry] of Object.entries(input.unblinding)) {
1330
+ if (!('engine' in entry.source)) continue
1331
+ for (const dimension of ORACLE_JUDGE_DIMENSIONS) {
1332
+ const verdict = input.verdict.traces[label]?.[dimension]
1333
+ if (verdict?.verdict === 'fail') {
1334
+ // EVERY issue matching the planted-control class is classified — first-match-only
1335
+ // would misfile same-class siblings as unclassified findings and the persisted guard
1336
+ // evidence would be inaccurate (review i7n8qn 0bce5fc70fb4).
1337
+ const classifiedIssues = new Set(
1338
+ verdict.issues.filter((issue) =>
1339
+ isPlantedControlClassFailure(dimension, issue, entry.eventTimes, entry.traceSpan),
1340
+ ),
1341
+ )
1342
+ for (const issue of verdict.issues) {
1343
+ if (!classifiedIssues.has(issue)) {
1344
+ const key = JSON.stringify([
1345
+ entry.traceHash,
1346
+ dimension,
1347
+ issue.timestamps,
1348
+ issue.description,
1349
+ ])
1350
+ if (!unclassifiedFindingKeys.has(key)) {
1351
+ unclassifiedFindingKeys.add(key)
1352
+ unclassifiedFindings.push({
1353
+ label,
1354
+ engine: entry.source.engine,
1355
+ dimension,
1356
+ timestamps: issue.timestamps,
1357
+ description: issue.description,
1358
+ })
1359
+ }
1360
+ }
1361
+ }
1362
+ const [firstClassified] = classifiedIssues
1363
+ if (firstClassified !== undefined && gatingCause === undefined) {
1364
+ gatingCause = `real ${entry.source.engine} trace ${label} failed ${dimension}: ${firstClassified.description}`
1365
+ }
1366
+ }
1367
+ }
1368
+ }
1369
+ }
1370
+ if (gatingCause !== undefined) {
1371
+ return { status: 'fail', cause: gatingCause, advisory, unclassifiedFindings }
1372
+ }
1373
+ return { status: 'pass', advisory, unclassifiedFindings }
1374
+ }