@akagilnc/pi-workflow-roles 0.1.2521 → 0.1.2653-next.71811ca

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 (109) hide show
  1. package/README.md +129 -39
  2. package/README.zh-CN.md +128 -39
  3. package/dist/activation-ledger-topology.js +1 -1
  4. package/dist/archivist-record-entry.js +47 -26
  5. package/dist/auditor-dossier-tool.js +3 -1
  6. package/dist/compliance-transport.js +17 -26
  7. package/dist/doctor-contracts.js +1 -2
  8. package/dist/dossier-resolution.js +4 -8
  9. package/dist/engine-detour-tool.js +33 -8
  10. package/dist/engine-detour.js +1 -0
  11. package/dist/evidence-child-executor.js +277 -424
  12. package/dist/host-contracts.js +22 -0
  13. package/dist/institutional-resolution.js +88 -0
  14. package/dist/merger-contracts.js +1 -2
  15. package/dist/navigator-attendance.js +16 -0
  16. package/dist/notary-contracts.js +2 -3
  17. package/dist/package-contracts/fixer-output.js +1 -2
  18. package/dist/packaged-role-registry.js +102 -9
  19. package/dist/pi/in-process-session.js +671 -0
  20. package/dist/public-cli/config.js +7 -5
  21. package/dist/public-cli/main.js +3767 -4113
  22. package/dist/reviewer-child-executor.js +6 -1
  23. package/dist/session-opening-materials.js +10 -38
  24. package/dist/sitian-appender.js +146 -0
  25. package/dist/sitian-contracts.js +21 -0
  26. package/dist/sitian-facade.js +15 -0
  27. package/dist/sitian-reader.js +52 -0
  28. package/extensions/role-runtime.ts +9 -7
  29. package/package.json +5 -5
  30. package/resources/engines/cursor.md +12 -26
  31. package/resources/engines/hermes.md +2 -23
  32. package/resources/engines/opus.md +1 -16
  33. package/resources/navigator-route-playbook.md +2 -2
  34. package/scripts/build-package.mjs +0 -1
  35. package/souls/quality-law.md +1 -1
  36. package/src/activation-ledger-topology.ts +1 -1
  37. package/src/archivist-record-entry.ts +57 -45
  38. package/src/auditor-dossier-tool.ts +4 -2
  39. package/src/canonical-skill-binding.ts +37 -27
  40. package/src/collector-role.ts +11 -20
  41. package/src/collector-tool-schemas.ts +1 -4
  42. package/src/compliance-transport.ts +19 -44
  43. package/src/doctor-auditor.ts +1 -5
  44. package/src/doctor-contracts.ts +1 -4
  45. package/src/doctor-role.ts +31 -8
  46. package/src/dossier-resolution.ts +8 -8
  47. package/src/engine-detour-tool.ts +48 -19
  48. package/src/engine-detour.ts +3 -0
  49. package/src/evidence-child-executor.ts +277 -485
  50. package/src/factory-board.ts +3 -3
  51. package/src/gatekeeper-role.ts +21 -10
  52. package/src/host-contracts.ts +386 -0
  53. package/src/in-process-session.ts +1 -115
  54. package/src/institutional-resolution.ts +135 -0
  55. package/src/judge-auditor.ts +1 -5
  56. package/src/judge-role.ts +37 -51
  57. package/src/merger-contracts.ts +1 -4
  58. package/src/merger-role.ts +7 -11
  59. package/src/navigator-attendance.ts +20 -1
  60. package/src/notary-contracts.ts +8 -11
  61. package/src/notary-role.ts +7 -23
  62. package/src/notary-source-run.ts +4 -3
  63. package/src/package-contracts/fixer-output.ts +1 -4
  64. package/src/package-resources/method-skill-binding.ts +10 -34
  65. package/src/packaged-role-registry.ts +122 -12
  66. package/src/pi/adapter.ts +192 -0
  67. package/src/pi/durable-principal.ts +98 -0
  68. package/src/pi/in-process-session.ts +819 -0
  69. package/src/pi/known-failure.ts +77 -0
  70. package/src/pi/pi-normalization.ts +284 -0
  71. package/src/pi/role-turn-host.ts +459 -0
  72. package/src/public-cli/auto-resume.ts +42 -44
  73. package/src/public-cli/cli.ts +171 -257
  74. package/src/public-cli/coder-run.ts +115 -433
  75. package/src/public-cli/collector-run.ts +62 -314
  76. package/src/public-cli/config.ts +7 -5
  77. package/src/public-cli/doctor-run.ts +41 -52
  78. package/src/public-cli/fixer-run.ts +122 -427
  79. package/src/public-cli/invocation.ts +287 -96
  80. package/src/public-cli/judge-run.ts +92 -422
  81. package/src/public-cli/merger-run.ts +167 -429
  82. package/src/public-cli/notary-run.ts +41 -47
  83. package/src/public-cli/option-definitions.ts +136 -3
  84. package/src/public-cli/post-admission.ts +497 -0
  85. package/src/public-cli/public-run-credentials.ts +4 -4
  86. package/src/public-cli/reviewer-dispatch-rejection.ts +106 -0
  87. package/src/public-cli/reviewer-run.ts +132 -449
  88. package/src/public-cli/run-lifecycle.ts +339 -327
  89. package/src/public-cli/settlement.ts +251 -134
  90. package/src/public-cli/terminal.ts +3 -9
  91. package/src/public-cli/turn-request.ts +87 -0
  92. package/src/reviewer-child-executor.ts +9 -1
  93. package/src/reviewer-role.ts +11 -20
  94. package/src/role-runtime.ts +129 -78
  95. package/src/session-opening-materials.ts +25 -39
  96. package/src/sitian-appender.ts +171 -0
  97. package/src/sitian-contracts.ts +106 -0
  98. package/src/sitian-facade.ts +18 -0
  99. package/src/sitian-reader.ts +62 -0
  100. package/src/worker-role.ts +42 -40
  101. package/src/worker-submission-gates.ts +33 -0
  102. package/dist/archivist-role-run-coordinates.js +0 -28
  103. package/dist/package-contracts/terminating-infrastructure.js +0 -90
  104. package/resources/engines/sonnet.md +0 -27
  105. package/resources/engines/zcode.md +0 -63
  106. package/src/archivist-role-run-coordinates.ts +0 -45
  107. package/src/package-contracts/terminating-infrastructure.ts +0 -132
  108. package/src/public-cli/explicit-internal.ts +0 -427
  109. package/src/public-cli/one-shot-dispatch.ts +0 -300
@@ -0,0 +1,497 @@
1
+ /**
2
+ * Unified post-admission Role lifecycle coordinator (stages ③–⑤; ADR 0018 / #505 / #517 / #526).
3
+ * Owns writer lease → running → ③ dispatch → ④ tool loop / gates → ⑤ settle / fail →
4
+ * terminal → release. The durable admitted mark (markRunAdmitted) is owned by the
5
+ * initial role facades before entering; manual resume never re-admits.
6
+ * Role runners supply only turn request projection and narrow settlement adapters.
7
+ */
8
+ import { writeFile } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+
11
+ import type {
12
+ ControlledFailureCause,
13
+ DurablePrincipal,
14
+ DurablePrincipalAuthority,
15
+ RoleTurnHost,
16
+ RoleTurnKnownFailure,
17
+ RoleTurnRequest,
18
+ RoleTurnResult,
19
+ SessionCustomEntryAppender,
20
+ } from "../host-contracts.ts";
21
+ import type { CredentialProviders, SeatModelConfig } from "./config.ts";
22
+ import { resolveResumeModel } from "./turn-request.ts";
23
+ import {
24
+ missingCredentialPreDispatchFailure,
25
+ postRunMissingCredentialFailure,
26
+ } from "./public-run-credentials.ts";
27
+ import {
28
+ acquireRunWriterLease,
29
+ clearTypedProviderHttpObservation,
30
+ isV1ResumableFailure,
31
+ markRunResumable,
32
+ markRunRunning,
33
+ markRunTerminal,
34
+ renderResumeCommand,
35
+ RunWriterLeaseHeldError,
36
+ type RunWriterLease,
37
+ type TypedProviderHttpObservation,
38
+ } from "./run-lifecycle.ts";
39
+ import {
40
+ classifyPostAdmissionFailure,
41
+ controlledFailureInputFromResolution,
42
+ exitCodeForTerminalOutcome,
43
+ explicitInternalKnownFailureClassificationInput,
44
+ formatCliDiagnostic,
45
+ formatTerminalResult,
46
+ inspectJudgeSession,
47
+ isLawfulTypedTerminalOutcome,
48
+ presentFailureTerminal,
49
+ presentStructuralRejection,
50
+ resolveAuditedRunnerFailureResolution,
51
+ resolveControlledFailureResumeObservation,
52
+ settleFailureTerminalResult,
53
+ } from "./settlement.ts";
54
+ import type { CliIo } from "./cli-io.ts";
55
+ import type { AdmittedRoleInvocation } from "./invocation.ts";
56
+ import {
57
+ type TerminalResult,
58
+ } from "./terminal.ts";
59
+ import { runWithAutoResumeLoop } from "./auto-resume.ts";
60
+
61
+ export type PostAdmissionEnv = {
62
+ home: string;
63
+ agentDir: string;
64
+ packageRoot: string;
65
+ cwd: string;
66
+ correlationId?: string;
67
+ roleTurnHost: RoleTurnHost;
68
+ model?: SeatModelConfig;
69
+ engine?: string;
70
+ credentials?: CredentialProviders;
71
+ timeoutMs?: number;
72
+ principalAuthority: DurablePrincipalAuthority;
73
+ sessionAppender: SessionCustomEntryAppender;
74
+ autoResumeLimit?: number;
75
+ createRunId?: () => string;
76
+ };
77
+
78
+ /**
79
+ * Role-specific settlement hooks and failure resolvers.
80
+ * Lifecycle coordination stays in this coordinator module.
81
+ */
82
+ export type PostAdmissionAdapters<
83
+ A extends AdmittedRoleInvocation = AdmittedRoleInvocation,
84
+ T extends TerminalResult = TerminalResult,
85
+ > = {
86
+ trySettle: (admitted: A, authority: DurablePrincipalAuthority) => Promise<T | undefined>;
87
+ /** Default: isLawfulTypedTerminalOutcome(terminal.roleOutcome). */
88
+ shouldPresentSettled?: (terminal: T) => boolean;
89
+ trySettleSecondary?: (admitted: A, authority: DurablePrincipalAuthority) => Promise<TerminalResult | undefined>;
90
+ resolveRunnerKnownFailure?: (input: {
91
+ result: RoleTurnResult;
92
+ sessionFile: string;
93
+ }) => Promise<RoleTurnKnownFailure | undefined>;
94
+ hasLawfulTerminalResult?: (admitted: A, authority: DurablePrincipalAuthority) => Promise<boolean>;
95
+ isResumableRole?: boolean;
96
+ beforeDispatch?: (admitted: A) => Promise<void> | void;
97
+ };
98
+
99
+ export type ControlledFailureInput = {
100
+ timedOut: boolean;
101
+ code: number | null;
102
+ stderr: string;
103
+ thrown?: unknown;
104
+ knownFailure?: RoleTurnKnownFailure;
105
+ knownCause?: ControlledFailureCause;
106
+ knownIdentity?: {
107
+ readonly name?: string;
108
+ readonly code?: string | number;
109
+ };
110
+ knownDiagnostic?: string;
111
+ typedHttpObservationSettled?: true;
112
+ typedHttpObservation?: TypedProviderHttpObservation;
113
+ };
114
+
115
+ export async function presentControlledFailure<
116
+ A extends AdmittedRoleInvocation,
117
+ T extends TerminalResult = TerminalResult,
118
+ >(
119
+ admitted: A,
120
+ failureInput: ControlledFailureInput,
121
+ adapters: PostAdmissionAdapters<A, T>,
122
+ authority: DurablePrincipalAuthority,
123
+ io: CliIo,
124
+ ): Promise<{
125
+ exitCode: number;
126
+ admitted: A;
127
+ terminal: TerminalResult;
128
+ }> {
129
+ const hasThrown = Object.hasOwn(failureInput, "thrown");
130
+ const resumeObservation = await resolveControlledFailureResumeObservation({
131
+ runDirectory: admitted.runDirectory,
132
+ ...(failureInput.typedHttpObservationSettled === true
133
+ ? {
134
+ typedHttpObservationSettled: true as const,
135
+ ...(failureInput.typedHttpObservation === undefined
136
+ ? {}
137
+ : { typedHttpObservation: failureInput.typedHttpObservation }),
138
+ }
139
+ : {}),
140
+ });
141
+ const knownFailure =
142
+ failureInput.knownFailure ?? resumeObservation.observationReadFailure;
143
+ const session =
144
+ !hasThrown &&
145
+ !failureInput.timedOut &&
146
+ knownFailure === undefined &&
147
+ failureInput.knownCause === undefined &&
148
+ admitted.principal !== undefined
149
+ ? await inspectJudgeSession(authority.decode(admitted.principal).sessionFile)
150
+ : undefined;
151
+ const failure = classifyPostAdmissionFailure({
152
+ timedOut: failureInput.timedOut,
153
+ code: failureInput.code,
154
+ stderr: failureInput.stderr,
155
+ ...(hasThrown ? { thrown: failureInput.thrown } : {}),
156
+ ...explicitInternalKnownFailureClassificationInput(knownFailure),
157
+ ...(failureInput.knownCause === undefined
158
+ ? {}
159
+ : { knownCause: failureInput.knownCause }),
160
+ ...(failureInput.knownIdentity === undefined
161
+ ? {}
162
+ : { knownIdentity: failureInput.knownIdentity }),
163
+ ...(failureInput.knownDiagnostic === undefined
164
+ ? {}
165
+ : { knownDiagnostic: failureInput.knownDiagnostic }),
166
+ ...(session === undefined ? {} : { session }),
167
+ });
168
+
169
+ let resumable = false;
170
+ const typedHttp429 = resumeObservation.typedHttp429;
171
+ if (adapters.isResumableRole === true && admitted.principal !== undefined) {
172
+ const sessionPrincipalAvailable = await authority.isAvailable(admitted.principal);
173
+ const hasLawful = adapters.hasLawfulTerminalResult !== undefined
174
+ ? await adapters.hasLawfulTerminalResult(admitted, authority)
175
+ : false;
176
+ resumable =
177
+ sessionPrincipalAvailable &&
178
+ isV1ResumableFailure({
179
+ hasLawfulTerminalResult: hasLawful,
180
+ ...(typedHttp429 === undefined ? {} : { typedHttp429 }),
181
+ });
182
+ }
183
+
184
+ if (resumable && typedHttp429 !== undefined) {
185
+ await markRunResumable(admitted.runDirectory, typedHttp429);
186
+ } else {
187
+ await markRunTerminal(admitted.runDirectory);
188
+ }
189
+
190
+ const terminal = await settleFailureTerminalResult(
191
+ admitted,
192
+ failure,
193
+ authority,
194
+ resumable
195
+ ? { resume: { command: renderResumeCommand(admitted.runId) } }
196
+ : {},
197
+ );
198
+ presentFailureTerminal(terminal, io);
199
+ return {
200
+ exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
201
+ admitted,
202
+ terminal,
203
+ };
204
+ }
205
+
206
+ function presentSecondaryTerminal(terminal: TerminalResult, io: CliIo): void {
207
+ if (terminal.roleOutcome.kind === "failure" || terminal.roleOutcome.kind === "no_receipt") {
208
+ presentFailureTerminal(terminal, io);
209
+ } else {
210
+ io.stdout(formatTerminalResult(terminal));
211
+ }
212
+ }
213
+
214
+ export async function dispatchPostAdmissionTurn<
215
+ A extends AdmittedRoleInvocation,
216
+ T extends TerminalResult = TerminalResult,
217
+ >(input: {
218
+ admitted: A;
219
+ env: PostAdmissionEnv;
220
+ io: CliIo;
221
+ request: RoleTurnRequest;
222
+ lease: RunWriterLease;
223
+ adapters: PostAdmissionAdapters<A, T>;
224
+ effectiveEngine?: string;
225
+ }): Promise<{
226
+ exitCode: number;
227
+ admitted: A;
228
+ terminal?: T;
229
+ }> {
230
+ const { admitted, env, io, request, lease, adapters, effectiveEngine } = input;
231
+ const shouldPresent =
232
+ adapters.shouldPresentSettled ?? ((terminal: T) => isLawfulTypedTerminalOutcome(terminal.roleOutcome));
233
+ try {
234
+ const missingCredential = missingCredentialPreDispatchFailure(
235
+ env.model,
236
+ env.credentials,
237
+ );
238
+ if (missingCredential !== undefined) {
239
+ return (await presentControlledFailure(
240
+ admitted,
241
+ missingCredential,
242
+ adapters,
243
+ env.principalAuthority,
244
+ io,
245
+ )) as { exitCode: number; admitted: A; terminal: T };
246
+ }
247
+ await markRunRunning(admitted.runDirectory, env.model, effectiveEngine);
248
+ await clearTypedProviderHttpObservation(admitted.runDirectory);
249
+ if (adapters.beforeDispatch !== undefined) {
250
+ await adapters.beforeDispatch(admitted);
251
+ }
252
+
253
+ let result: RoleTurnResult;
254
+ try {
255
+ result = await env.roleTurnHost.executeTurn(request);
256
+ } catch (error) {
257
+ return (await presentControlledFailure(
258
+ admitted,
259
+ {
260
+ timedOut: false,
261
+ code: null,
262
+ stderr: "",
263
+ thrown: error,
264
+ },
265
+ adapters,
266
+ env.principalAuthority,
267
+ io,
268
+ )) as { exitCode: number; admitted: A; terminal: T };
269
+ }
270
+
271
+ try {
272
+ await writeFile(
273
+ join(admitted.runDirectory, "stderr.log"),
274
+ result.stderr,
275
+ "utf8",
276
+ );
277
+ } catch {
278
+ // continue to lawful / controlled-failure settlement
279
+ }
280
+
281
+ let settled: T | undefined;
282
+ try {
283
+ settled = await adapters.trySettle(admitted, env.principalAuthority);
284
+ } catch (error) {
285
+ return (await presentControlledFailure(
286
+ admitted,
287
+ {
288
+ timedOut: false,
289
+ code: result.code,
290
+ stderr: result.stderr,
291
+ thrown: error,
292
+ },
293
+ adapters,
294
+ env.principalAuthority,
295
+ io,
296
+ )) as { exitCode: number; admitted: A; terminal: T };
297
+ }
298
+ if (settled !== undefined && shouldPresent(settled)) {
299
+ await markRunTerminal(admitted.runDirectory);
300
+ io.stdout(formatTerminalResult(settled));
301
+ return {
302
+ exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
303
+ admitted,
304
+ terminal: settled,
305
+ };
306
+ }
307
+
308
+ if (adapters.trySettleSecondary !== undefined) {
309
+ const secondary = await adapters.trySettleSecondary(admitted, env.principalAuthority);
310
+ if (secondary !== undefined) {
311
+ await markRunTerminal(admitted.runDirectory);
312
+ presentSecondaryTerminal(secondary, io);
313
+ return {
314
+ exitCode: exitCodeForTerminalOutcome(secondary.roleOutcome),
315
+ admitted,
316
+ terminal: secondary as unknown as T,
317
+ };
318
+ }
319
+ }
320
+
321
+ const sessionFile =
322
+ admitted.principal !== undefined
323
+ ? env.principalAuthority.decode(admitted.principal).sessionFile
324
+ : "";
325
+ const runnerKnownFailure =
326
+ adapters.resolveRunnerKnownFailure !== undefined && sessionFile !== ""
327
+ ? await adapters.resolveRunnerKnownFailure({ result, sessionFile })
328
+ : result.knownFailure;
329
+ const credentialFailure = postRunMissingCredentialFailure(
330
+ result,
331
+ env.model,
332
+ env.credentials,
333
+ );
334
+ const resolution = await resolveAuditedRunnerFailureResolution({
335
+ runner: runnerKnownFailure,
336
+ sessionFile,
337
+ credential: credentialFailure,
338
+ runDirectory: admitted.runDirectory,
339
+ });
340
+ return (await presentControlledFailure(
341
+ admitted,
342
+ {
343
+ timedOut: result.timedOut,
344
+ code: result.code,
345
+ stderr: result.stderr,
346
+ ...controlledFailureInputFromResolution(resolution),
347
+ },
348
+ adapters,
349
+ env.principalAuthority,
350
+ io,
351
+ )) as { exitCode: number; admitted: A; terminal: T };
352
+ } finally {
353
+ await lease.release();
354
+ }
355
+ }
356
+
357
+ /**
358
+ * Shared post-admission one-shot path: writer lease, then turn dispatch.
359
+ * Initial facades own the durable admitted mark (markRunAdmitted) before
360
+ * entering; manual resume never re-admits.
361
+ */
362
+ export async function runPostAdmissionOneShot<
363
+ A extends AdmittedRoleInvocation,
364
+ T extends TerminalResult = TerminalResult,
365
+ >(input: {
366
+ admitted: A;
367
+ env: PostAdmissionEnv;
368
+ io: CliIo;
369
+ request: RoleTurnRequest;
370
+ adapters: PostAdmissionAdapters<A, T>;
371
+ effectiveEngine?: string;
372
+ }): Promise<{
373
+ exitCode: number;
374
+ admitted?: A;
375
+ terminal?: T;
376
+ }> {
377
+ const { admitted, env, io, request, adapters, effectiveEngine } = input;
378
+
379
+ let lease: RunWriterLease;
380
+ try {
381
+ lease = await acquireRunWriterLease(admitted.runDirectory, (diagnostic) =>
382
+ io.stderr(diagnostic),
383
+ );
384
+ } catch (error) {
385
+ if (error instanceof RunWriterLeaseHeldError) {
386
+ presentStructuralRejection(error, io);
387
+ return { exitCode: 2, admitted };
388
+ }
389
+ throw error;
390
+ }
391
+
392
+ return await dispatchPostAdmissionTurn({
393
+ admitted,
394
+ env,
395
+ io,
396
+ request,
397
+ lease,
398
+ adapters,
399
+ ...(effectiveEngine === undefined ? {} : { effectiveEngine }),
400
+ });
401
+ }
402
+
403
+ /**
404
+ * Shared post-admission resumable path with auto-resume retry loop.
405
+ * Initial facades own the durable admitted mark before entering.
406
+ */
407
+ export async function runPostAdmissionResumable<
408
+ A extends AdmittedRoleInvocation,
409
+ T extends TerminalResult = TerminalResult,
410
+ >(input: {
411
+ admitted: A;
412
+ env: PostAdmissionEnv;
413
+ io: CliIo;
414
+ buildInitialRequest: () => RoleTurnRequest;
415
+ buildResumeRequest: () => RoleTurnRequest;
416
+ adapters: PostAdmissionAdapters<A, T>;
417
+ effectiveEngine?: string;
418
+ }): Promise<{
419
+ exitCode: number;
420
+ admitted?: A;
421
+ terminal?: T;
422
+ }> {
423
+ const { admitted, env, io, buildInitialRequest, buildResumeRequest, adapters, effectiveEngine } = input;
424
+
425
+ return runWithAutoResumeLoop({
426
+ admitted,
427
+ principalAuthority: env.principalAuthority,
428
+ io,
429
+ sessionAppender: env.sessionAppender,
430
+ autoResumeLimit: env.autoResumeLimit,
431
+ buildInitialPayload: buildInitialRequest,
432
+ buildResumePayload: buildResumeRequest,
433
+ dispatch: (request, lease, isFirst, attemptIo) =>
434
+ dispatchPostAdmissionTurn({
435
+ admitted,
436
+ env: {
437
+ ...env,
438
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
439
+ },
440
+ io: attemptIo,
441
+ request,
442
+ lease,
443
+ adapters,
444
+ ...(isFirst && effectiveEngine !== undefined ? { effectiveEngine } : {}),
445
+ }),
446
+ });
447
+ }
448
+
449
+ /**
450
+ * Shared post-admission manual resume path: acquire writer lease and dispatch turn.
451
+ */
452
+ export async function runPostAdmissionManualResume<
453
+ A extends AdmittedRoleInvocation,
454
+ T extends TerminalResult = TerminalResult,
455
+ >(input: {
456
+ admitted: A;
457
+ env: PostAdmissionEnv;
458
+ io: CliIo;
459
+ request: RoleTurnRequest;
460
+ adapters: PostAdmissionAdapters<A, T>;
461
+ }): Promise<{
462
+ exitCode: number;
463
+ admitted?: A;
464
+ terminal?: T;
465
+ }> {
466
+ const { admitted, env, io, request, adapters } = input;
467
+ // Single seam: explicit env model wins; otherwise restore admitted.model
468
+ // (including thinking) so a model-less manual resume reuses the recorded model.
469
+ const effectiveModel = resolveResumeModel(env.model, admitted.model);
470
+ let lease: RunWriterLease;
471
+ try {
472
+ lease = await acquireRunWriterLease(admitted.runDirectory, (diagnostic) => io.stderr(diagnostic));
473
+ } catch (error) {
474
+ if (error instanceof RunWriterLeaseHeldError) {
475
+ io.stderr(formatCliDiagnostic(error.message));
476
+ return { exitCode: 1 };
477
+ }
478
+ throw error;
479
+ }
480
+
481
+ const result = await dispatchPostAdmissionTurn({
482
+ admitted,
483
+ env: {
484
+ ...env,
485
+ ...(effectiveModel === undefined ? {} : { model: effectiveModel }),
486
+ ...(admitted.correlationId === undefined ? {} : { correlationId: admitted.correlationId }),
487
+ },
488
+ io,
489
+ request,
490
+ lease,
491
+ adapters,
492
+ });
493
+ if (result.terminal !== undefined) {
494
+ (result.terminal as { autoResumeCount?: number }).autoResumeCount = 0;
495
+ }
496
+ return result;
497
+ }
@@ -8,7 +8,7 @@ import {
8
8
  type CredentialProviders,
9
9
  type SeatModelConfig,
10
10
  } from "./config.ts";
11
- import type { ExplicitInternalKnownFailure } from "./explicit-internal.ts";
11
+ import type { RoleTurnKnownFailure } from "../host-contracts.ts";
12
12
 
13
13
  /**
14
14
  * Production-owned provider failure when the selected public seat provider has
@@ -18,7 +18,7 @@ import type { ExplicitInternalKnownFailure } from "./explicit-internal.ts";
18
18
  export function knownFailureForMissingProviderCredential(
19
19
  model: SeatModelConfig | undefined,
20
20
  credentials: CredentialProviders | undefined,
21
- ): ExplicitInternalKnownFailure | undefined {
21
+ ): RoleTurnKnownFailure | undefined {
22
22
  if (model === undefined || credentials === undefined) return undefined;
23
23
  // Only the public credential catalog is fail-closed here; offline/test providers
24
24
  // are not represented in auth.json shape and must not be washed into MissingProviderCredential.
@@ -40,7 +40,7 @@ export type MissingCredentialPreDispatchFailure = Readonly<{
40
40
  timedOut: false;
41
41
  code: 1;
42
42
  stderr: string;
43
- knownFailure: ExplicitInternalKnownFailure;
43
+ knownFailure: RoleTurnKnownFailure;
44
44
  }>;
45
45
 
46
46
  export function missingCredentialPreDispatchFailure(
@@ -65,7 +65,7 @@ export function postRunMissingCredentialFailure(
65
65
  result: Readonly<{ timedOut: boolean; code: number | null }>,
66
66
  model: SeatModelConfig | undefined,
67
67
  credentials: CredentialProviders | undefined,
68
- ): ExplicitInternalKnownFailure | undefined {
68
+ ): RoleTurnKnownFailure | undefined {
69
69
  if (!(result.timedOut || result.code !== 0)) return undefined;
70
70
  return knownFailureForMissingProviderCredential(model, credentials);
71
71
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Durable Reviewer-rejection child→parent page under AK_ROLE_RUN_DIR.
3
+ * Stays in public-cli (AK artifact owner); role-runtime imports this neighbor.
4
+ */
5
+ import { writeFileSync } from "node:fs";
6
+ import { readFile, unlink } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+
9
+ import type { RoleTurnKnownFailure } from "../host-contracts.ts";
10
+ import {
11
+ REVIEWER_PREFLIGHT_VIOLATIONS,
12
+ type ReviewerPreflightViolation,
13
+ } from "../reviewer-dispatch.ts";
14
+
15
+ const REVIEWER_DISPATCH_REJECTION_FILE = "typed-known-failure.json";
16
+
17
+ function isReviewerPreflightViolation(value: unknown): value is ReviewerPreflightViolation {
18
+ return (
19
+ typeof value === "string" &&
20
+ (REVIEWER_PREFLIGHT_VIOLATIONS as readonly string[]).includes(value)
21
+ );
22
+ }
23
+
24
+ function reviewerDispatchRejectionPath(runDirectory: string): string {
25
+ return join(runDirectory, REVIEWER_DISPATCH_REJECTION_FILE);
26
+ }
27
+
28
+ /**
29
+ * Clear any prior attempt's Reviewer rejection page so resume/retry cannot
30
+ * inherit a stale knownFailure.details.
31
+ */
32
+ export async function clearReviewerDispatchRejection(runDirectory: string): Promise<void> {
33
+ try {
34
+ await unlink(reviewerDispatchRejectionPath(runDirectory));
35
+ } catch (error) {
36
+ if (
37
+ error instanceof Error &&
38
+ "code" in error &&
39
+ (error as { code?: unknown }).code === "ENOENT"
40
+ ) {
41
+ return;
42
+ }
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Synchronous durable write for Reviewer dispatch rejection (child process exit is sync).
49
+ * Parent public CLI recovers via readReviewerDispatchRejection into knownFailure.
50
+ */
51
+ export function recordReviewerDispatchRejectionSync(
52
+ runDirectory: string,
53
+ rejection: Readonly<{
54
+ diagnostic: string;
55
+ violations: readonly ReviewerPreflightViolation[];
56
+ }>,
57
+ ): void {
58
+ writeFileSync(
59
+ reviewerDispatchRejectionPath(runDirectory),
60
+ `${JSON.stringify(rejection)}\n`,
61
+ "utf8",
62
+ );
63
+ }
64
+
65
+ /** Recover a child-written Reviewer dispatch rejection through a fixed mapping. */
66
+ export async function readReviewerDispatchRejection(
67
+ runDirectory: string,
68
+ ): Promise<RoleTurnKnownFailure | undefined> {
69
+ let raw: string;
70
+ try {
71
+ raw = await readFile(reviewerDispatchRejectionPath(runDirectory), "utf8");
72
+ } catch (error) {
73
+ if (
74
+ error instanceof Error &&
75
+ "code" in error &&
76
+ (error as { code?: unknown }).code === "ENOENT"
77
+ ) {
78
+ return undefined;
79
+ }
80
+ throw error;
81
+ }
82
+ const parsed: unknown = JSON.parse(raw);
83
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
84
+ const error = new Error("Reviewer dispatch rejection page must be a JSON object");
85
+ error.name = "ReviewerDispatchRejectionContractError";
86
+ throw error;
87
+ }
88
+ const record = parsed as Record<string, unknown>;
89
+ if (
90
+ typeof record.diagnostic !== "string" ||
91
+ record.diagnostic.trim() === "" ||
92
+ !Array.isArray(record.violations) ||
93
+ record.violations.length === 0 ||
94
+ record.violations.some((value) => !isReviewerPreflightViolation(value))
95
+ ) {
96
+ const error = new Error("Reviewer dispatch rejection page has unusable required fields");
97
+ error.name = "ReviewerDispatchRejectionContractError";
98
+ throw error;
99
+ }
100
+ return {
101
+ cause: "activation",
102
+ diagnostic: record.diagnostic,
103
+ identity: { name: "ReviewerDispatchRejectionError" },
104
+ details: { violations: Object.freeze([...record.violations]) },
105
+ };
106
+ }