@akagilnc/pi-workflow-roles 0.1.1883 → 0.1.1918

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.
@@ -36,8 +36,8 @@ import {
36
36
  markRunResumable,
37
37
  markRunRunning,
38
38
  markRunTerminal,
39
- readTypedHttp429Observation,
40
39
  renderResumeCommand,
40
+ type TypedProviderHttpObservation,
41
41
  RESUME_TRANSPORT_ENVELOPE,
42
42
  RunWriterLeaseHeldError,
43
43
  type RunWriterLease,
@@ -52,7 +52,9 @@ import {
52
52
  isLawfulTypedTerminalOutcome,
53
53
  presentFailureTerminal,
54
54
  presentStructuralRejection,
55
- resolveAuditedRunnerKnownFailure,
55
+ resolveAuditedRunnerFailureResolution,
56
+ resolveControlledFailureResumeObservation,
57
+ controlledFailureInputFromResolution,
56
58
  explicitInternalKnownFailureClassificationInput,
57
59
  settleJudgeFailureTerminalResult,
58
60
  trySettleJudgeTerminalResult,
@@ -171,6 +173,9 @@ async function presentControlledFailure(
171
173
  stderr: string;
172
174
  thrown?: unknown;
173
175
  knownFailure?: ExplicitInternalKnownFailure;
176
+ /** Pre-resolved typed-HTTP outcome — no second sidecar read when settled. */
177
+ typedHttpObservationSettled?: true;
178
+ typedHttpObservation?: TypedProviderHttpObservation;
174
179
  },
175
180
  io: CliIo,
176
181
  ): Promise<{
@@ -180,10 +185,25 @@ async function presentControlledFailure(
180
185
  }> {
181
186
  // Own-key presence, not value: `throw undefined` must not look like "no throw".
182
187
  const hasThrown = Object.hasOwn(failureInput, "thrown");
188
+ // v1 resume observation: reuse audited resolution when present; otherwise one
189
+ // controlled read. Non-absence failures fold into knownFailure — never escape.
190
+ const resumeObservation = await resolveControlledFailureResumeObservation({
191
+ runDirectory: admitted.runDirectory,
192
+ ...(failureInput.typedHttpObservationSettled === true
193
+ ? {
194
+ typedHttpObservationSettled: true as const,
195
+ ...(failureInput.typedHttpObservation === undefined
196
+ ? {}
197
+ : { typedHttpObservation: failureInput.typedHttpObservation }),
198
+ }
199
+ : {}),
200
+ });
201
+ const knownFailure =
202
+ failureInput.knownFailure ?? resumeObservation.observationReadFailure;
183
203
  const session =
184
204
  !hasThrown &&
185
205
  !failureInput.timedOut &&
186
- failureInput.knownFailure === undefined
206
+ knownFailure === undefined
187
207
  ? await inspectJudgeSession(admitted.sessionFile)
188
208
  : undefined;
189
209
  const failure = classifyPostAdmissionFailure({
@@ -191,7 +211,7 @@ async function presentControlledFailure(
191
211
  code: failureInput.code,
192
212
  stderr: failureInput.stderr,
193
213
  ...(hasThrown ? { thrown: failureInput.thrown } : {}),
194
- ...explicitInternalKnownFailureClassificationInput(failureInput.knownFailure),
214
+ ...explicitInternalKnownFailureClassificationInput(knownFailure),
195
215
  ...(session === undefined ? {} : { session }),
196
216
  });
197
217
 
@@ -199,7 +219,7 @@ async function presentControlledFailure(
199
219
  // absence of a lawful Judge result, and a durable exact Pi session principal.
200
220
  // Lawful presence is session-owned and must not depend on artifact publication.
201
221
  const hasLawfulTerminalResult = await hasLawfulJudgeTerminalResult(admitted);
202
- const typedHttp429 = await readTypedHttp429Observation(admitted.runDirectory);
222
+ const typedHttp429 = resumeObservation.typedHttp429;
203
223
  const sessionPrincipalAvailable = await isSessionPrincipalAvailable(
204
224
  admitted.sessionFile,
205
225
  );
@@ -269,8 +289,9 @@ async function dispatchAdmittedJudge(input: {
269
289
  // and role-runtime can record typed provider HTTP observations.
270
290
  AK_ROLE_RUN_DIR: admitted.runDirectory,
271
291
  };
272
- if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
273
- childEnv.AK_CORRELATION_ID = env.correlationId;
292
+ const correlationId = admitted.correlationId ?? env.correlationId;
293
+ if (correlationId !== undefined && correlationId.trim() !== "") {
294
+ childEnv.AK_CORRELATION_ID = correlationId;
274
295
  }
275
296
 
276
297
  let result: ExplicitInternalPiResult;
@@ -355,10 +376,11 @@ async function dispatchAdmittedJudge(input: {
355
376
  env.model,
356
377
  env.credentials,
357
378
  );
358
- const knownFailure = await resolveAuditedRunnerKnownFailure({
379
+ const resolution = await resolveAuditedRunnerFailureResolution({
359
380
  runner: result.knownFailure,
360
381
  sessionFile: admitted.sessionFile,
361
382
  credential: credentialFailure,
383
+ runDirectory: admitted.runDirectory,
362
384
  });
363
385
  return await presentControlledFailure(
364
386
  admitted,
@@ -366,7 +388,7 @@ async function dispatchAdmittedJudge(input: {
366
388
  timedOut: result.timedOut,
367
389
  code: result.code,
368
390
  stderr: result.stderr,
369
- ...(knownFailure === undefined ? {} : { knownFailure }),
391
+ ...controlledFailureInputFromResolution(resolution),
370
392
  },
371
393
  io,
372
394
  );
@@ -429,7 +451,10 @@ export async function runPublicJudge(
429
451
 
430
452
  return await dispatchAdmittedJudge({
431
453
  admitted,
432
- env,
454
+ env: {
455
+ ...env,
456
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
457
+ },
433
458
  io,
434
459
  extraArgs,
435
460
  lease,
@@ -499,7 +524,10 @@ export async function runPublicResume(
499
524
 
500
525
  return await dispatchAdmittedJudge({
501
526
  admitted,
502
- env,
527
+ env: {
528
+ ...env,
529
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
530
+ },
503
531
  io,
504
532
  extraArgs,
505
533
  lease,
@@ -50,7 +50,7 @@ import {
50
50
  RESUME_TRANSPORT_ENVELOPE,
51
51
  RunWriterLeaseHeldError,
52
52
  type RunWriterLease,
53
- readTypedHttp429Observation,
53
+ type TypedProviderHttpObservation,
54
54
  } from "./run-lifecycle.ts";
55
55
  import {
56
56
  classifyPostAdmissionFailure,
@@ -62,7 +62,9 @@ import {
62
62
  presentFailureTerminal,
63
63
  presentStructuralRejection,
64
64
  explicitInternalKnownFailureClassificationInput,
65
- resolveAuditedRunnerKnownFailure,
65
+ resolveAuditedRunnerFailureResolution,
66
+ resolveControlledFailureResumeObservation,
67
+ controlledFailureInputFromResolution,
66
68
  settleFailureTerminalResult,
67
69
  trySettleMergerTerminalResult,
68
70
  } from "./settlement.ts";
@@ -192,6 +194,8 @@ async function presentControlledFailure(
192
194
  readonly code?: string | number;
193
195
  };
194
196
  knownDiagnostic?: string;
197
+ typedHttpObservationSettled?: true;
198
+ typedHttpObservation?: TypedProviderHttpObservation;
195
199
  },
196
200
  io: CliIo,
197
201
  ): Promise<{
@@ -200,10 +204,23 @@ async function presentControlledFailure(
200
204
  terminal: TerminalResult;
201
205
  }> {
202
206
  const hasThrown = Object.hasOwn(failureInput, "thrown");
207
+ const resumeObservation = await resolveControlledFailureResumeObservation({
208
+ runDirectory: admitted.runDirectory,
209
+ ...(failureInput.typedHttpObservationSettled === true
210
+ ? {
211
+ typedHttpObservationSettled: true as const,
212
+ ...(failureInput.typedHttpObservation === undefined
213
+ ? {}
214
+ : { typedHttpObservation: failureInput.typedHttpObservation }),
215
+ }
216
+ : {}),
217
+ });
218
+ const knownFailure =
219
+ failureInput.knownFailure ?? resumeObservation.observationReadFailure;
203
220
  const session =
204
221
  !hasThrown &&
205
222
  !failureInput.timedOut &&
206
- failureInput.knownFailure === undefined &&
223
+ knownFailure === undefined &&
207
224
  failureInput.knownCause === undefined
208
225
  ? await inspectJudgeSession(admitted.sessionFile)
209
226
  : undefined;
@@ -212,7 +229,7 @@ async function presentControlledFailure(
212
229
  code: failureInput.code,
213
230
  stderr: failureInput.stderr,
214
231
  ...(hasThrown ? { thrown: failureInput.thrown } : {}),
215
- ...explicitInternalKnownFailureClassificationInput(failureInput.knownFailure),
232
+ ...explicitInternalKnownFailureClassificationInput(knownFailure),
216
233
  ...(failureInput.knownCause === undefined
217
234
  ? {}
218
235
  : { knownCause: failureInput.knownCause }),
@@ -226,7 +243,7 @@ async function presentControlledFailure(
226
243
  });
227
244
 
228
245
  const hasLawfulTerminalResult = await hasLawfulMergerTerminalResult(admitted);
229
- const typedHttp429 = await readTypedHttp429Observation(admitted.runDirectory);
246
+ const typedHttp429 = resumeObservation.typedHttp429;
230
247
  const sessionPrincipalAvailable = await isSessionPrincipalAvailable(
231
248
  admitted.sessionFile,
232
249
  );
@@ -291,8 +308,9 @@ async function dispatchAdmittedMerger(input: {
291
308
  PI_CODING_AGENT_DIR: env.agentDir,
292
309
  AK_ROLE_RUN_DIR: admitted.runDirectory,
293
310
  };
294
- if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
295
- childEnv.AK_CORRELATION_ID = env.correlationId;
311
+ const correlationId = admitted.correlationId ?? env.correlationId;
312
+ if (correlationId !== undefined && correlationId.trim() !== "") {
313
+ childEnv.AK_CORRELATION_ID = correlationId;
296
314
  }
297
315
 
298
316
  let result: ExplicitInternalPiResult;
@@ -367,10 +385,11 @@ async function dispatchAdmittedMerger(input: {
367
385
  env.model,
368
386
  env.credentials,
369
387
  );
370
- const knownFailure = await resolveAuditedRunnerKnownFailure({
388
+ const resolution = await resolveAuditedRunnerFailureResolution({
371
389
  runner: result.knownFailure,
372
390
  sessionFile: admitted.sessionFile,
373
391
  credential: credentialFailure,
392
+ runDirectory: admitted.runDirectory,
374
393
  });
375
394
  return await presentControlledFailure(
376
395
  admitted,
@@ -378,7 +397,7 @@ async function dispatchAdmittedMerger(input: {
378
397
  timedOut: result.timedOut,
379
398
  code: result.code,
380
399
  stderr: result.stderr,
381
- ...(knownFailure === undefined ? {} : { knownFailure }),
400
+ ...controlledFailureInputFromResolution(resolution),
382
401
  },
383
402
  io,
384
403
  );
@@ -571,7 +590,10 @@ export async function runPublicMerger(
571
590
 
572
591
  return await dispatchAdmittedMerger({
573
592
  admitted,
574
- env,
593
+ env: {
594
+ ...env,
595
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
596
+ },
575
597
  io,
576
598
  extraArgs,
577
599
  lease,
@@ -658,7 +680,10 @@ export async function runPublicMergerResume(
658
680
 
659
681
  return await dispatchAdmittedMerger({
660
682
  admitted,
661
- env,
683
+ env: {
684
+ ...env,
685
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
686
+ },
662
687
  io,
663
688
  extraArgs,
664
689
  lease,
@@ -44,11 +44,11 @@ import {
44
44
  markRunResumable,
45
45
  markRunRunning,
46
46
  markRunTerminal,
47
- readTypedHttp429Observation,
48
47
  renderResumeCommand,
49
48
  RESUME_TRANSPORT_ENVELOPE,
50
49
  RunWriterLeaseHeldError,
51
50
  type RunWriterLease,
51
+ type TypedProviderHttpObservation,
52
52
  } from "./run-lifecycle.ts";
53
53
  import {
54
54
  classifyPostAdmissionFailure,
@@ -60,7 +60,9 @@ import {
60
60
  isLawfulTypedTerminalOutcome,
61
61
  presentFailureTerminal,
62
62
  presentStructuralRejection,
63
- resolveAuditedRunnerKnownFailure,
63
+ resolveAuditedRunnerFailureResolution,
64
+ resolveControlledFailureResumeObservation,
65
+ controlledFailureInputFromResolution,
64
66
  explicitInternalKnownFailureClassificationInput,
65
67
  settleFailureTerminalResult,
66
68
  trySettleReviewerTerminalResult,
@@ -186,6 +188,8 @@ async function presentControlledFailure(
186
188
  stderr: string;
187
189
  thrown?: unknown;
188
190
  knownFailure?: ExplicitInternalKnownFailure;
191
+ typedHttpObservationSettled?: true;
192
+ typedHttpObservation?: TypedProviderHttpObservation;
189
193
  },
190
194
  io: CliIo,
191
195
  ): Promise<{
@@ -194,10 +198,23 @@ async function presentControlledFailure(
194
198
  terminal: TerminalResult;
195
199
  }> {
196
200
  const hasThrown = Object.hasOwn(failureInput, "thrown");
201
+ const resumeObservation = await resolveControlledFailureResumeObservation({
202
+ runDirectory: admitted.runDirectory,
203
+ ...(failureInput.typedHttpObservationSettled === true
204
+ ? {
205
+ typedHttpObservationSettled: true as const,
206
+ ...(failureInput.typedHttpObservation === undefined
207
+ ? {}
208
+ : { typedHttpObservation: failureInput.typedHttpObservation }),
209
+ }
210
+ : {}),
211
+ });
212
+ const knownFailure =
213
+ failureInput.knownFailure ?? resumeObservation.observationReadFailure;
197
214
  const session =
198
215
  !hasThrown &&
199
216
  !failureInput.timedOut &&
200
- failureInput.knownFailure === undefined
217
+ knownFailure === undefined
201
218
  ? await inspectJudgeSession(admitted.sessionFile)
202
219
  : undefined;
203
220
  const failure = classifyPostAdmissionFailure({
@@ -205,12 +222,12 @@ async function presentControlledFailure(
205
222
  code: failureInput.code,
206
223
  stderr: failureInput.stderr,
207
224
  ...(hasThrown ? { thrown: failureInput.thrown } : {}),
208
- ...explicitInternalKnownFailureClassificationInput(failureInput.knownFailure),
225
+ ...explicitInternalKnownFailureClassificationInput(knownFailure),
209
226
  ...(session === undefined ? {} : { session }),
210
227
  });
211
228
 
212
229
  const hasLawfulTerminalResult = await hasLawfulReviewerTerminalResult(admitted);
213
- const typedHttp429 = await readTypedHttp429Observation(admitted.runDirectory);
230
+ const typedHttp429 = resumeObservation.typedHttp429;
214
231
  const sessionPrincipalAvailable = await isSessionPrincipalAvailable(
215
232
  admitted.sessionFile,
216
233
  );
@@ -276,8 +293,9 @@ async function dispatchAdmittedReviewer(input: {
276
293
  PI_CODING_AGENT_DIR: env.agentDir,
277
294
  AK_ROLE_RUN_DIR: admitted.runDirectory,
278
295
  };
279
- if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
280
- childEnv.AK_CORRELATION_ID = env.correlationId;
296
+ const correlationId = admitted.correlationId ?? env.correlationId;
297
+ if (correlationId !== undefined && correlationId.trim() !== "") {
298
+ childEnv.AK_CORRELATION_ID = correlationId;
281
299
  }
282
300
 
283
301
  let result: ExplicitInternalPiResult;
@@ -367,7 +385,7 @@ async function dispatchAdmittedReviewer(input: {
367
385
  env.model,
368
386
  env.credentials,
369
387
  );
370
- const knownFailure = await resolveAuditedRunnerKnownFailure({
388
+ const resolution = await resolveAuditedRunnerFailureResolution({
371
389
  runner: result.knownFailure,
372
390
  sessionFile: admitted.sessionFile,
373
391
  credential: credentialFailure,
@@ -379,7 +397,7 @@ async function dispatchAdmittedReviewer(input: {
379
397
  timedOut: result.timedOut,
380
398
  code: result.code,
381
399
  stderr: result.stderr,
382
- ...(knownFailure === undefined ? {} : { knownFailure }),
400
+ ...controlledFailureInputFromResolution(resolution),
383
401
  },
384
402
  io,
385
403
  );
@@ -467,7 +485,10 @@ export async function runPublicReviewer(
467
485
 
468
486
  return await dispatchAdmittedReviewer({
469
487
  admitted,
470
- env,
488
+ env: {
489
+ ...env,
490
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
491
+ },
471
492
  io,
472
493
  extraArgs,
473
494
  lease,
@@ -553,7 +574,10 @@ export async function runPublicReviewerResume(
553
574
 
554
575
  return await dispatchAdmittedReviewer({
555
576
  admitted,
556
- env,
577
+ env: {
578
+ ...env,
579
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
580
+ },
557
581
  io,
558
582
  extraArgs,
559
583
  lease,
@@ -12,6 +12,15 @@ import {
12
12
  resolveActivationLedgerHome,
13
13
  } from "../activation-ledger-topology.ts";
14
14
  import { CliUsageError } from "./cli-errors.ts";
15
+ import {
16
+ readLatestTypedProviderHttpObservation,
17
+ } from "../typed-provider-http.ts";
18
+ export {
19
+ clearTypedProviderHttpObservation,
20
+ recordTypedProviderHttpStatus,
21
+ readLatestTypedProviderHttpObservation,
22
+ type TypedProviderHttpObservation,
23
+ } from "../typed-provider-http.ts";
15
24
  import type { FixerPhase } from "../package-contracts/fixer-output.ts";
16
25
  import type { FixerPrerequisite } from "../package-contracts/fixer-packet.ts";
17
26
  import {
@@ -65,7 +74,6 @@ export type RoleRunRecord = {
65
74
  export const RESUME_TRANSPORT_ENVELOPE = "[ak-role:resume-continue]" as const;
66
75
 
67
76
  const RUN_STATE_FILE = "run-state.json";
68
- const TYPED_HTTP_FILE = "typed-provider-http.json";
69
77
  const WRITER_LOCK_FILE = "writer.lock";
70
78
 
71
79
  export function isV1ResumableProvider(
@@ -74,84 +82,14 @@ export function isV1ResumableProvider(
74
82
  return (V1_RESUMABLE_PROVIDERS as readonly string[]).includes(provider);
75
83
  }
76
84
 
77
- function typedProviderHttpPath(runDirectory: string): string {
78
- return join(runDirectory, TYPED_HTTP_FILE);
79
- }
80
-
81
- /**
82
- * Clear any prior attempt's typed provider HTTP observation.
83
- * Each initial/resume dispatch must start without inherited 429 evidence so
84
- * only the current attempt can qualify v1 resume.
85
- */
86
- export async function clearTypedProviderHttpObservation(
87
- runDirectory: string,
88
- ): Promise<void> {
89
- try {
90
- await unlink(typedProviderHttpPath(runDirectory));
91
- } catch (error) {
92
- if (
93
- error instanceof Error &&
94
- "code" in error &&
95
- (error as { code?: unknown }).code === "ENOENT"
96
- ) {
97
- return;
98
- }
99
- throw error;
100
- }
101
- }
102
-
103
- /**
104
- * Record a typed provider HTTP status observation for the admitted run.
105
- * The latest observation is authoritative: only a current HTTP 429 from
106
- * Codex/xAI is retained for v1 resume; any other status or provider clears
107
- * prior within-attempt 429 evidence. Never inspects diagnostic prose.
108
- */
109
- export async function recordTypedProviderHttpStatus(
110
- runDirectory: string,
111
- observation: { readonly httpStatus: number; readonly provider: string },
112
- ): Promise<void> {
113
- if (
114
- observation.httpStatus === 429 &&
115
- isV1ResumableProvider(observation.provider)
116
- ) {
117
- const body: TypedHttp429Observation = {
118
- httpStatus: 429,
119
- provider: observation.provider,
120
- };
121
- await writeFile(
122
- typedProviderHttpPath(runDirectory),
123
- `${JSON.stringify(body)}\n`,
124
- "utf8",
125
- );
126
- return;
127
- }
128
- // Non-qualifying latest response supersedes any earlier 429 in this attempt.
129
- await clearTypedProviderHttpObservation(runDirectory);
130
- }
131
-
132
- /**
133
- * Read a durable typed HTTP 429 observation. Returns undefined unless both
134
- * httpStatus === 429 and provider is a v1-resumable provider are present as
135
- * typed fields (never inferred from prose).
136
- */
137
85
  export async function readTypedHttp429Observation(
138
86
  runDirectory: string,
139
87
  ): Promise<TypedHttp429Observation | undefined> {
140
- try {
141
- const raw: unknown = JSON.parse(
142
- await readFile(typedProviderHttpPath(runDirectory), "utf8"),
143
- );
144
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
145
- return undefined;
146
- }
147
- const record = raw as Record<string, unknown>;
148
- if (record.httpStatus !== 429) return undefined;
149
- if (typeof record.provider !== "string") return undefined;
150
- if (!isV1ResumableProvider(record.provider)) return undefined;
151
- return { httpStatus: 429, provider: record.provider };
152
- } catch {
153
- return undefined;
154
- }
88
+ const observation = await readLatestTypedProviderHttpObservation(runDirectory);
89
+ if (observation === undefined) return undefined;
90
+ if (observation.httpStatus !== 429) return undefined;
91
+ if (!isV1ResumableProvider(observation.provider)) return undefined;
92
+ return { httpStatus: 429, provider: observation.provider };
155
93
  }
156
94
 
157
95
  /**
@@ -453,8 +391,40 @@ type LoadedAdmittedRequestFields = {
453
391
  readonly baseRevision?: string;
454
392
  readonly mergerInputPath?: string;
455
393
  readonly derived?: DerivedMergerEnvelope;
394
+ readonly correlationId?: string;
395
+ readonly ticketNumber?: number;
456
396
  };
457
397
 
398
+ /** Restore optional correlation + typed ticketNumber from a durable admitted page. */
399
+ function parsePersistedTicketIdentity(
400
+ record: Record<string, unknown>,
401
+ ): { correlationId?: string; ticketNumber?: number } {
402
+ const correlationId =
403
+ typeof record.correlationId === "string" && record.correlationId.trim() !== ""
404
+ ? record.correlationId
405
+ : undefined;
406
+ const ticketNumber =
407
+ typeof record.ticketNumber === "number" &&
408
+ Number.isInteger(record.ticketNumber) &&
409
+ record.ticketNumber >= 1
410
+ ? record.ticketNumber
411
+ : undefined;
412
+ return {
413
+ ...(correlationId === undefined ? {} : { correlationId }),
414
+ ...(ticketNumber === undefined ? {} : { ticketNumber }),
415
+ };
416
+ }
417
+
418
+ function restoredTicketFields(fields: LoadedAdmittedRequestFields): {
419
+ correlationId?: string;
420
+ ticketNumber?: number;
421
+ } {
422
+ return {
423
+ ...(fields.correlationId === undefined ? {} : { correlationId: fields.correlationId }),
424
+ ...(fields.ticketNumber === undefined ? {} : { ticketNumber: fields.ticketNumber }),
425
+ };
426
+ }
427
+
458
428
  async function loadResumableRunRecord(
459
429
  home: string,
460
430
  runId: string,
@@ -495,6 +465,8 @@ async function loadResumableRunRecord(
495
465
  let baseRevision: string | undefined;
496
466
  let mergerInputPath: string | undefined;
497
467
  let derived: DerivedMergerEnvelope | undefined;
468
+ let correlationId: string | undefined;
469
+ let ticketNumber: number | undefined;
498
470
  try {
499
471
  const raw: unknown = JSON.parse(
500
472
  await readFile(run.admittedRequestPath, "utf8"),
@@ -564,12 +536,46 @@ async function loadResumableRunRecord(
564
536
  };
565
537
  }
566
538
  }
539
+ const fromAdmitted = parsePersistedTicketIdentity(record);
540
+ correlationId = fromAdmitted.correlationId;
541
+ ticketNumber = fromAdmitted.ticketNumber;
567
542
  }
568
543
  } catch {
569
544
  throw new CliUsageError(
570
545
  `role run admitted request is unreadable: ${runId}`,
571
546
  );
572
547
  }
548
+ if (correlationId === undefined || ticketNumber === undefined) {
549
+ try {
550
+ const invocationRaw: unknown = JSON.parse(
551
+ await readFile(join(run.runDirectory, "invocation.json"), "utf8"),
552
+ );
553
+ if (
554
+ invocationRaw !== null &&
555
+ typeof invocationRaw === "object" &&
556
+ !Array.isArray(invocationRaw)
557
+ ) {
558
+ const fromInvocation = parsePersistedTicketIdentity(
559
+ invocationRaw as Record<string, unknown>,
560
+ );
561
+ if (correlationId === undefined) correlationId = fromInvocation.correlationId;
562
+ if (ticketNumber === undefined) ticketNumber = fromInvocation.ticketNumber;
563
+ }
564
+ } catch (error) {
565
+ if (
566
+ !(
567
+ error instanceof Error &&
568
+ "code" in error &&
569
+ (error as { code?: unknown }).code === "ENOENT"
570
+ )
571
+ ) {
572
+ throw new CliUsageError(
573
+ `role run invocation identity is unreadable: ${runId}`,
574
+ { cause: error },
575
+ );
576
+ }
577
+ }
578
+ }
573
579
  return {
574
580
  run,
575
581
  observation: run.resumable,
@@ -585,6 +591,8 @@ async function loadResumableRunRecord(
585
591
  ...(baseRevision === undefined ? {} : { baseRevision }),
586
592
  ...(mergerInputPath === undefined ? {} : { mergerInputPath }),
587
593
  ...(derived === undefined ? {} : { derived }),
594
+ ...(correlationId === undefined ? {} : { correlationId }),
595
+ ...(ticketNumber === undefined ? {} : { ticketNumber }),
588
596
  },
589
597
  };
590
598
  }
@@ -639,6 +647,7 @@ export async function loadResumableJudgeRun(
639
647
  sessionDirectory: loaded.run.sessionDirectory,
640
648
  sessionFile: loaded.run.sessionFile,
641
649
  admittedRequestPath: loaded.run.admittedRequestPath,
650
+ ...restoredTicketFields(loaded.admittedFields),
642
651
  };
643
652
  return {
644
653
  admitted,
@@ -692,6 +701,7 @@ export async function loadResumableCoderRun(
692
701
  sessionFile: loaded.run.sessionFile,
693
702
  admittedRequestPath: loaded.run.admittedRequestPath,
694
703
  taskPath,
704
+ ...restoredTicketFields(loaded.admittedFields),
695
705
  };
696
706
  return {
697
707
  admitted,
@@ -750,6 +760,7 @@ export async function loadResumableFixerRun(
750
760
  ? {}
751
761
  : { prerequisitesPath: loaded.admittedFields.prerequisitesPath }),
752
762
  prerequisites,
763
+ ...restoredTicketFields(loaded.admittedFields),
753
764
  };
754
765
  return {
755
766
  admitted,
@@ -795,6 +806,7 @@ export async function loadResumableReviewerRun(
795
806
  sessionFile: loaded.run.sessionFile,
796
807
  admittedRequestPath: loaded.run.admittedRequestPath,
797
808
  baseRevision,
809
+ ...restoredTicketFields(loaded.admittedFields),
798
810
  };
799
811
  return {
800
812
  admitted,
@@ -854,6 +866,7 @@ export async function loadResumableMergerRun(
854
866
  admittedRequestPath: loaded.run.admittedRequestPath,
855
867
  mergerInputPath,
856
868
  derived,
869
+ ...restoredTicketFields(loaded.admittedFields),
857
870
  };
858
871
  return {
859
872
  admitted,