@akagilnc/pi-workflow-roles 0.1.5756 → 0.1.5775

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.
@@ -99,10 +99,3 @@ export function runIdFromRunDirectory(runDirectory) {
99
99
  export function roleFromRunDirectory(runDirectory) {
100
100
  return parseRunLeaf(basename(runDirectory))?.role;
101
101
  }
102
- /**
103
- * #1195 ticket-court blind review: notary source is a countersign run.
104
- * Identity still binds; countersign body is not preloaded into dialogue.
105
- */
106
- export function isTicketCourtCountersignSource(runDirectory) {
107
- return roleFromRunDirectory(runDirectory) === "countersign";
108
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5756",
3
+ "version": "0.1.5775",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -44,11 +44,11 @@
44
44
  "scripts": {
45
45
  "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json && node scripts/build-package.mjs",
46
46
  "prepack": "npm run build",
47
- "test": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
48
- "test:fast": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-timeout=300000 --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
49
- "test:integration": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test test/unit/**/*.test.ts test/contract/**/*.test.ts test/integration/**/*.test.ts",
47
+ "test": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
48
+ "test:fast": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-timeout=300000 --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
49
+ "test:integration": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/unit/**/*.test.ts test/contract/**/*.test.ts test/integration/**/*.test.ts",
50
50
  "test:all": "node scripts/run-test-all.mjs",
51
- "test:adjudication": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test test/adjudication/**/*.test.ts",
51
+ "test:adjudication": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/adjudication/**/*.test.ts",
52
52
  "typecheck": "tsc --noEmit",
53
53
  "migrate:book-topology": "node dist/migrate-book-topology.js"
54
54
  },
@@ -444,9 +444,10 @@ export function createNavigatorAttendance(options: NavigatorAttendanceOptions) {
444
444
  };
445
445
  await promptAllowingRejectedPrepare(request);
446
446
  // Correction after rejected prepare on the sole model round (#1160).
447
+ // #1208: resend this turn's prepare request; delivery-state stays on policy facts.
447
448
  while (output === undefined && prepareBatchRejected && delivery.nextAction() === "request-delivery") {
448
449
  delivery.recordDeliveryRequest();
449
- await promptAllowingRejectedPrepare(JSON.stringify(delivery.deliveryState()));
450
+ await promptAllowingRejectedPrepare(request);
450
451
  }
451
452
  if (output === undefined && delivery.nextAction() === "request-delivery") {
452
453
  delivery.closeBudget();
@@ -294,9 +294,8 @@ export const PUBLIC_ROLE_RECORDS = [
294
294
  bareCommand: false,
295
295
  outputTool: NOTARY_OUTPUT_TOOL_NAME,
296
296
  settlement: "accepted",
297
- /** Court reask replaces the initial prompt. Otherwise the fixed kickoff. */
297
+ /** Dialogue via shared resolveReviewSeatDialogueBody (review = fixed dispatch). */
298
298
  reaskPrompt: true,
299
- transportPrompt: "fixed-kickoff",
300
299
  gateStageLabel: "符宝郎",
301
300
  /** Gate summon binds --source-run and keeps dialogue off the argv. */
302
301
  gateSummon: "source-run",
@@ -319,6 +318,8 @@ export const PUBLIC_ROLE_RECORDS = [
319
318
  phases: [null],
320
319
  outputTool: COUNTERSIGN_OUTPUT_TOOL_NAME,
321
320
  settlement: "accepted",
321
+ /** Same shared dialogue resolver as other review seats (#1208 C1). */
322
+ reaskPrompt: true,
322
323
  gateStageLabel: "给事中",
323
324
  /** Gate summon carries the parent payload as the instruction and a parent run id. */
324
325
  gateSummon: "parent-instruction",
@@ -75,7 +75,6 @@ import {
75
75
  } from "./post-admission.ts";
76
76
  import { presentTerminal } from "./auto-resume.ts";
77
77
  import {
78
- buildAutoResumeContinuationPrompt,
79
78
  loadResumablePublicRole,
80
79
  markRunAdmitted,
81
80
  readCurrentCourt,
@@ -138,6 +137,20 @@ const POST_SUBMISSION_ROUTING: Partial<Record<PackagedRole, {
138
137
  judge: {
139
138
  statuses: REVIEW_QUEUE_STATUSES,
140
139
  },
140
+ // #1208 C1 / ADR 0055: every review-queue seat uses the shared
141
+ // unreadable-status seam (officerConclusionReask). Independent public
142
+ // entry for notary/auditor/inspector was the same miss — route here, do
143
+ // not invent a parallel loop. Gate summons still own the outer budget
144
+ // via nestedIncomplete when this seat exhausts unsettled.
145
+ notary: {
146
+ statuses: REVIEW_QUEUE_STATUSES,
147
+ },
148
+ auditor: {
149
+ statuses: REVIEW_QUEUE_STATUSES,
150
+ },
151
+ inspector: {
152
+ statuses: REVIEW_QUEUE_STATUSES,
153
+ },
141
154
  coder: {
142
155
  statuses: WORKER_ROUTING_STATUSES,
143
156
  reask: WORKER_STATUS_REASK,
@@ -271,11 +284,14 @@ async function reaskUnreadablePostSubmissionStatus(
271
284
  if (message === undefined) return undefined;
272
285
  const next = withUnreadableReask(env);
273
286
  if (next === undefined) return UNREADABLE_REASK_EXHAUSTED;
287
+ // Named status-reask: summons opens a new court; message carries the diagnostic
288
+ // (not arbitrary summons.instruction passthrough through the review resolver).
274
289
  return runPublicInstructionSeatResume(
275
290
  {
276
291
  runId: admitted.runId,
277
292
  runDirectory: admitted.runDirectory,
278
- summons: { instruction: message },
293
+ message,
294
+ summons: { instructionEmpty: true },
279
295
  },
280
296
  next,
281
297
  io,
@@ -329,11 +345,14 @@ async function reaskMissingTicketOnce(
329
345
  softTicketReaskTurn: true,
330
346
  softTicketReaskOwnership,
331
347
  };
348
+ // Named #1171 missing-ticket: summons opens a new court; message carries the
349
+ // package reask copy (not arbitrary summons.instruction passthrough).
332
350
  const result = await runPublicInstructionSeatResume(
333
351
  {
334
352
  runId: admitted.runId,
335
353
  runDirectory: admitted.runDirectory,
336
- summons: { instruction },
354
+ message: instruction,
355
+ summons: { instructionEmpty: true },
337
356
  },
338
357
  softReaskEnv,
339
358
  io,
@@ -505,8 +524,8 @@ export function buildInstructionSeatTurnRequest(
505
524
 
506
525
  /**
507
526
  * Direct review-seat call: reconnect ledger peer-body delivery (ADR 0085 / #1166).
508
- * Gate summons already ride gateReviewInstruction; direct --source-run must not
509
- * leave a blank fixed-kickoff.
527
+ * Gate summons already ride gateReviewInstruction; direct --source-run falls
528
+ * through the shared dialogue resolver (notary: fixed dispatch).
510
529
  */
511
530
  async function loadLedgerPeerBody(
512
531
  sourceRunDirectory: string,
@@ -529,34 +548,40 @@ async function loadLedgerPeerBody(
529
548
  return readableGateItem(latest.accepted);
530
549
  }
531
550
 
532
- /** Owner-approved fixed Notary dispatch (2026-10-09); never the parent verdict. */
551
+ /** Owner-approved fixed Notary review dispatch (2026-10-09 #1208); never the parent verdict. */
533
552
  export const NOTARY_FIXED_DISPATCH = "请审本轮受审对象,按符宝郎职掌交卷。";
534
553
 
535
554
  /**
536
555
  * One delivery rule for review-seat dialogue on new turns and same-parent resume
537
- * (#1166 / ADR 0085 / ADR 0087): explicit reask → non-empty caller dispatch →
538
- * source-only ledger peer body. Identity stays in startup materials.
539
- * #1195: ticket-court notary skips peer-body preload (countersign source only) and
540
- * receives the fixed dispatch instead (codex rejects an empty prompt).
556
+ * (#1166 / ADR 0085 / ADR 0087 / #1208): notary review uses fixed dispatch;
557
+ * named operational reasks ride reviewReask (first-mint / gate) or the resume
558
+ * request message seam — never arbitrary non-empty caller/summons passthrough.
559
+ * Other seats prefer format reask, then gate body, then caller dispatch, then
560
+ * ledger peer. Identity stays in startup materials. Notary never takes
561
+ * gate/parent verdict as dialogue (gateReviewInstruction stays unread for notary).
541
562
  */
542
- async function resolveReviewSeatDialogueBody(input: {
563
+ export async function resolveReviewSeatDialogueBody(input: {
543
564
  readonly role?: string;
544
- readonly reask?: string;
565
+ readonly reviewReask?: string;
566
+ readonly gateReviewInstruction?: string;
545
567
  readonly callerInstruction?: string;
546
568
  readonly sourceRunPath?: string;
547
569
  readonly projectRoot: string;
548
570
  readonly home: string;
549
571
  }): Promise<string | undefined> {
550
- const reask = input.reask;
551
- if (reask !== undefined && reask.length > 0) return reask;
572
+ // #1208: fixed sentence is the review function only (owner 37bfe80d /
573
+ // 393daf98). reviewReask is the named operational override; caller/summons
574
+ // body is not a free passthrough (countersign rejected that prescription).
575
+ if (input.role === "notary") {
576
+ if (input.reviewReask !== undefined) return input.reviewReask;
577
+ return NOTARY_FIXED_DISPATCH;
578
+ }
579
+ const preferred = input.reviewReask ?? input.gateReviewInstruction;
580
+ if (preferred !== undefined && preferred.length > 0) return preferred;
552
581
  const caller = input.callerInstruction ?? "";
553
582
  if (caller.length > 0) return caller;
554
583
  const sourceRunPath = admittedSourceRunPath(input)?.trim();
555
584
  if (sourceRunPath === undefined) return undefined;
556
- if (input.role === "notary") {
557
- const { isTicketCourtCountersignSource } = await import("../run-terminal-artifacts.ts");
558
- if (isTicketCourtCountersignSource(sourceRunPath)) return NOTARY_FIXED_DISPATCH;
559
- }
560
585
  return await loadLedgerPeerBody(sourceRunPath, input.projectRoot, input.home);
561
586
  }
562
587
 
@@ -567,10 +592,12 @@ async function resolveInitialPrompt(
567
592
  const record = roleRecord(admitted.role);
568
593
  if ("reaskPrompt" in record && record.reaskPrompt === true) {
569
594
  const sourceRunPath = admittedSourceRunPath(admitted);
570
- const reask = env.reviewReask ?? env.gateReviewInstruction;
571
595
  const body = await resolveReviewSeatDialogueBody({
572
596
  role: admitted.role,
573
- ...(reask === undefined ? {} : { reask }),
597
+ ...(env.reviewReask === undefined ? {} : { reviewReask: env.reviewReask }),
598
+ ...(env.gateReviewInstruction === undefined
599
+ ? {}
600
+ : { gateReviewInstruction: env.gateReviewInstruction }),
574
601
  callerInstruction: admitted.instruction,
575
602
  ...(sourceRunPath === undefined ? {} : { sourceRunPath }),
576
603
  projectRoot: admitted.projectRoot,
@@ -702,6 +729,7 @@ async function dispatchAdmitted(
702
729
  const initialPromptText = await resolveInitialPrompt(admitted, activeEnv);
703
730
  const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
704
731
  if (auto) {
732
+ // #1208: auto-resume resends this turn's actual content, never a filler phrase.
705
733
  return await runPostAdmissionResumable({
706
734
  admitted,
707
735
  env: activeEnv,
@@ -717,7 +745,7 @@ async function dispatchAdmitted(
717
745
  admitted,
718
746
  roleTurnOptions(activeEnv, admitted, {
719
747
  kind: "resume",
720
- prompt: buildAutoResumeContinuationPrompt(),
748
+ prompt: initialPromptText,
721
749
  }),
722
750
  ),
723
751
  adapters,
@@ -851,21 +879,28 @@ function resumeSameParentInstructionSeat(input: {
851
879
  ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
852
880
  freshSummons: input.env.freshSummons,
853
881
  summons: input.summons,
854
- resume: (runId, materials, runDirectory) => runPublicInstructionSeatResume(
855
- {
856
- runId,
857
- runDirectory,
858
- ...(materials === undefined ? {} : { summons: materials }),
859
- },
860
- input.env,
861
- input.io,
862
- ),
882
+ resume: (runId, materials, runDirectory) => {
883
+ // Named operational reask on this summon: explicit message seam.
884
+ // Review dialogue stays on summons → shared resolver (notary: fixed).
885
+ // Do not stuff reask into summons.instruction for later passthrough.
886
+ const reask = input.env.reviewReask;
887
+ return runPublicInstructionSeatResume(
888
+ {
889
+ runId,
890
+ runDirectory,
891
+ ...(reask === undefined ? {} : { message: reask }),
892
+ ...(materials === undefined ? {} : { summons: materials }),
893
+ },
894
+ input.env,
895
+ input.io,
896
+ );
897
+ },
863
898
  });
864
899
  }
865
900
 
866
901
  /**
867
902
  * Same delivery rule as resolveInitialPrompt for same-parent resume summons.
868
- * Reask / explicit caller text / source-only ledger peer — one authority.
903
+ * Shared resolver owns the input choice — callers only pass env fields.
869
904
  */
870
905
  async function sameParentDialogue(
871
906
  env: InstructionSeatRunEnv,
@@ -874,10 +909,12 @@ async function sameParentDialogue(
874
909
  projectRoot: string,
875
910
  role: string,
876
911
  ): Promise<{ readonly instruction: string; readonly instructionEmpty: boolean }> {
877
- const reask = env.reviewReask ?? env.gateReviewInstruction;
878
912
  const body = await resolveReviewSeatDialogueBody({
879
913
  role,
880
- ...(reask === undefined ? {} : { reask }),
914
+ ...(env.reviewReask === undefined ? {} : { reviewReask: env.reviewReask }),
915
+ ...(env.gateReviewInstruction === undefined
916
+ ? {}
917
+ : { gateReviewInstruction: env.gateReviewInstruction }),
881
918
  callerInstruction: fallback.instruction,
882
919
  sourceRunPath,
883
920
  projectRoot,
@@ -898,16 +935,27 @@ async function runCountersignBody(
898
935
  env: InstructionSeatRunEnv,
899
936
  io: CliIo,
900
937
  ): Promise<SeatRunResult> {
938
+ const projectRoot = resolve(parsed.project ?? env.cwd);
901
939
  const gateParentRunPath =
902
940
  typeof env.parentRunPath === "string" && env.parentRunPath.trim() !== ""
903
941
  ? env.parentRunPath
904
942
  : undefined;
905
943
  if (gateParentRunPath !== undefined) {
906
- const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction ?? parsed.instruction ?? "";
944
+ // #1208 C1: same shared dialogue resolver as every other same-parent seat.
945
+ const dialogue = await sameParentDialogue(
946
+ env,
947
+ {
948
+ instruction: parsed.instruction ?? "",
949
+ instructionEmpty: (parsed.instruction ?? "").length === 0,
950
+ },
951
+ gateParentRunPath,
952
+ projectRoot,
953
+ "countersign",
954
+ );
907
955
  const resumed = await resumeSameParentInstructionSeat({
908
956
  env,
909
957
  io,
910
- projectRoot: resolve(parsed.project ?? env.cwd),
958
+ projectRoot,
911
959
  role: "countersign",
912
960
  parentRunPath: gateParentRunPath,
913
961
  ...(isSafePositiveTicketNumber(env.boundTicketNumber)
@@ -915,8 +963,8 @@ async function runCountersignBody(
915
963
  : {}),
916
964
  summons: {
917
965
  sourceRunPath: gateParentRunPath,
918
- instruction: resumeInstruction,
919
- instructionEmpty: resumeInstruction.length === 0,
966
+ instruction: dialogue.instruction,
967
+ instructionEmpty: dialogue.instructionEmpty,
920
968
  // #1166 J11: same-parent resume delivers this call's file-flag paths
921
969
  // through the shared summons → appendCallerFileFlagPaths rule.
922
970
  attachmentPaths: parsed.attachmentPaths ?? [],
@@ -954,14 +1002,10 @@ async function runCountersignBody(
954
1002
  await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
955
1003
  admitted = { ...admitted, sourceRunPath: gateParentRunPath };
956
1004
  }
957
- // #1166 J11: review-body branch must merge the same file-flag delivery as
958
- // resolveInitialPrompt / resumeTurnRequestProjectionOptions — never body alone.
959
- const reviewBody = env.reviewReask ?? env.gateReviewInstruction;
1005
+ // #1208 C1 / #1166 J11: one shared initial-prompt seam (incl. file flags).
960
1006
  const turnProjection = roleTurnOptions(env, admitted, {
961
1007
  kind: "initial",
962
- prompt: reviewBody !== undefined
963
- ? appendCallerFileFlagPaths(reviewBody, admitted.attachments)
964
- : buildInstructionTransportPrompt(admitted),
1008
+ prompt: await resolveInitialPrompt(admitted, env),
965
1009
  });
966
1010
  const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
967
1011
  const result = await runPostAdmissionOneShot({
@@ -1187,6 +1231,10 @@ export async function runPublicInstructionSeat(
1187
1231
  await persistAdmittedSourceRunPath(admitted, auditorSource, auditorSubject);
1188
1232
  admitted = { ...admitted, sourceRunPath: auditorSource } as AdmittedRoleInvocation;
1189
1233
  }
1234
+ // #1208 C2: same helper as resume — known board ticket on the audited
1235
+ // source binds and relocates before the missing-ticket soft reask can
1236
+ // fire. True no-ticket sources stay unbound (#1171 asks once).
1237
+ admitted = await inheritKnownTicketFromSource(admitted, env.principalAuthority);
1190
1238
 
1191
1239
  const runAdmitted = async (): Promise<SeatRunResult> => {
1192
1240
  await markRunAdmitted(admitted, env.principalAuthority);
@@ -1235,6 +1283,25 @@ export async function runPublicInstructionSeat(
1235
1283
  return runAdmitted();
1236
1284
  }
1237
1285
 
1286
+ /**
1287
+ * #1208 C2: same as new source-locator admission — when this leg is still
1288
+ * unbound, inherit the board ticket already on the audited source and relocate.
1289
+ * True no-ticket sources stay unbound so #1171 still asks once.
1290
+ */
1291
+ async function inheritKnownTicketFromSource(
1292
+ admitted: AdmittedRoleInvocation,
1293
+ authority: DurablePrincipalAuthority,
1294
+ ): Promise<AdmittedRoleInvocation> {
1295
+ if (admitted.ticketNumber !== undefined) return admitted;
1296
+ const sourcePath = admittedSourceRunPath(admitted);
1297
+ if (sourcePath === undefined) return admitted;
1298
+ const sourceTicket = await readBoardTicketNumber(sourcePath);
1299
+ if (sourceTicket === undefined) return admitted;
1300
+ await bindAdmittedTicketNumber(admitted, sourceTicket);
1301
+ await relocateAdmittedRunToTicket(admitted, authority);
1302
+ return admitted;
1303
+ }
1304
+
1238
1305
  export async function runPublicInstructionSeatResume(
1239
1306
  request: PublicResumeRequest,
1240
1307
  env: InstructionSeatRunEnv,
@@ -1259,30 +1326,53 @@ export async function runPublicInstructionSeatResume(
1259
1326
  env.principalAuthority,
1260
1327
  effective.runDirectory === undefined ? undefined : { runDirectory: effective.runDirectory },
1261
1328
  );
1329
+ let admitted = loaded.admitted;
1262
1330
  if (
1263
- packagedRebindSourceOnResume(loaded.admitted.role)
1331
+ packagedRebindSourceOnResume(admitted.role)
1264
1332
  && effective.summons?.sourceRunPath !== undefined
1265
1333
  && effective.summons.sourceRun !== undefined
1266
1334
  ) {
1267
- return {
1268
- ...loaded,
1269
- admitted: {
1270
- ...loaded.admitted,
1271
- sourceRunPath: effective.summons.sourceRunPath,
1272
- sourceRun: effective.summons.sourceRun,
1273
- },
1274
- };
1335
+ // source-locator seats only (notary); sourceRun is on that admission face.
1336
+ admitted = {
1337
+ ...admitted,
1338
+ sourceRunPath: effective.summons.sourceRunPath,
1339
+ sourceRun: effective.summons.sourceRun,
1340
+ } as AdmittedRoleInvocation;
1275
1341
  }
1276
- return loaded;
1342
+ admitted = await inheritKnownTicketFromSource(admitted, env.principalAuthority);
1343
+ return { ...loaded, admitted };
1277
1344
  },
1278
1345
  buildTurnRequest: async (admitted, effective) => {
1279
1346
  const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
1280
- return buildInstructionSeatTurnRequest(
1281
- admitted,
1282
- {
1283
- ...resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared),
1284
- },
1347
+ const projection = resumeTurnRequestProjectionOptions(
1348
+ admitted, effective, env, summonsPrepared,
1285
1349
  );
1350
+ // #1208: internal summons only — shared resolver owns review dialogue.
1351
+ // Public bare resume / explicit message keep projection (operational
1352
+ // reasks use the message seam). Do not reselect stale env dialogue over
1353
+ // this-round summons (#1208 R1).
1354
+ if (effective.summons === undefined || effective.message !== undefined) {
1355
+ return buildInstructionSeatTurnRequest(admitted, { ...projection });
1356
+ }
1357
+ const sourceRunPath = admittedSourceRunPath(admitted);
1358
+ const body = await resolveReviewSeatDialogueBody({
1359
+ role: admitted.role,
1360
+ callerInstruction: summonsPrepared?.instruction ?? "",
1361
+ ...(sourceRunPath === undefined ? {} : { sourceRunPath }),
1362
+ projectRoot: admitted.projectRoot,
1363
+ home: env.home,
1364
+ });
1365
+ if (body === undefined) {
1366
+ return buildInstructionSeatTurnRequest(admitted, { ...projection });
1367
+ }
1368
+ const fileFlags = summonsPrepared?.attachments ?? [];
1369
+ const prompt = fileFlags.length === 0
1370
+ ? body
1371
+ : appendCallerFileFlagPaths(body, fileFlags);
1372
+ return buildInstructionSeatTurnRequest(admitted, {
1373
+ ...projection,
1374
+ continuation: { kind: "resume", prompt },
1375
+ });
1286
1376
  },
1287
1377
  adapters: {
1288
1378
  trySettle: async () => undefined,
@@ -1361,9 +1451,12 @@ async function queueConclusionFromChild(
1361
1451
  return { stop: { exitCode: 0, ...(terminal === undefined ? {} : { terminal }) } };
1362
1452
  }
1363
1453
  let current: AdmittedRoleInvocation = child;
1364
- // Notary, auditor, and inspector have no seat-local status reask. This loop
1365
- // is their one budget. The child resume keeps the caller's env so delivery,
1366
- // failure recovery, and other reask loops stay separate.
1454
+ // #1208: review-queue gate children also have seat-local
1455
+ // POST_SUBMISSION_ROUTING. This parent-side loop remains the
1456
+ // gate/correlation budget; nestedIncomplete from an exhausted seat-local
1457
+ // chain surfaces without a second spend. The child resume keeps the
1458
+ // caller's env so delivery, failure recovery, and other reask loops
1459
+ // stay separate.
1367
1460
  let budgetEnv: InstructionSeatRunEnv = envForCourtContinue(env);
1368
1461
  for (;;) {
1369
1462
  const terminal = await trySettlePublicSeat(
@@ -1548,7 +1641,8 @@ async function auditSubmittedRole(
1548
1641
  return runPublicInstructionSeatResume({
1549
1642
  runId: admitted.runId,
1550
1643
  runDirectory: admitted.runDirectory,
1551
- summons: { instruction: SECRETARIAT_STATUS_REASK },
1644
+ message: SECRETARIAT_STATUS_REASK,
1645
+ summons: { instructionEmpty: true },
1552
1646
  }, next, io);
1553
1647
  }
1554
1648
  if (admitted.role === "countersign" && (typeof status !== "string" || !REVIEW_QUEUE_STATUSES.has(status))) {
@@ -1562,7 +1656,8 @@ async function auditSubmittedRole(
1562
1656
  return runPublicInstructionSeatResume({
1563
1657
  runId: admitted.runId,
1564
1658
  runDirectory: admitted.runDirectory,
1565
- summons: { instruction: officerConclusionReask(status) },
1659
+ message: officerConclusionReask(status),
1660
+ summons: { instructionEmpty: true },
1566
1661
  }, next, io);
1567
1662
  }
1568
1663
  // Self-escalation is a valid open routing state for these seats only.
@@ -1839,7 +1839,7 @@ type InstructionTransportSource = {
1839
1839
 
1840
1840
  function admittedTransportPromptKind(
1841
1841
  admitted: InstructionTransportSource,
1842
- ): "instruction" | "fixed-kickoff" | "baseline" | "skill-args" {
1842
+ ): "instruction" | "baseline" | "skill-args" {
1843
1843
  if (admitted.role === undefined) return "instruction";
1844
1844
  const record = packagedRoleMetadata(admitted.role);
1845
1845
  if (record !== undefined && "transportPrompt" in record) return record.transportPrompt;
@@ -1870,19 +1870,15 @@ export function appendCallerFileFlagPaths(
1870
1870
 
1871
1871
  /**
1872
1872
  * One initial prompt transport. The registry `transportPrompt` leaf selects
1873
- * a fixed kickoff, a bound baseline, or skill args. Absent means the caller
1874
- * instruction plus caller file-flag paths (ADR 0087).
1873
+ * a bound baseline or skill args. Absent means the caller instruction plus
1874
+ * caller file-flag paths (ADR 0087). Notary dialogue is owned by
1875
+ * resolveReviewSeatDialogueBody (review fixed dispatch), not an empty transport leaf.
1875
1876
  * Engine / outsourcing material rides startup readingMaterial (#1167), not here.
1876
1877
  */
1877
1878
  export function buildInstructionTransportPrompt(
1878
1879
  admitted: InstructionTransportSource,
1879
1880
  ): string {
1880
1881
  const kind = admittedTransportPromptKind(admitted);
1881
- if (kind === "fixed-kickoff") {
1882
- // #1166 / ADR 0087: audited-run identity rides startup materials; dialogue
1883
- // prompt is peer body / reask only — never a package-computed directory path.
1884
- return "";
1885
- }
1886
1882
  if (kind === "baseline") {
1887
1883
  if (admitted.baseRevision === undefined) {
1888
1884
  throw new Error("baseline transport prompt is missing the bound revision");
@@ -606,8 +606,8 @@ const NOTARY_OPTIONS = [
606
606
  repeatable: false,
607
607
  form: "option",
608
608
  description: {
609
- en: "Required source run locator (runId@role under the book home, or path to that run directory). Zero prompt/attachment projection.",
610
- zh: "必填源 run 定位符(簿内 runId@role,或该 run 目录路径)。零 prompt/附件投影。",
609
+ en: "Required source run locator (runId@role under the book home, or path to that run directory). Binds the audited identity; review dialogue is the fixed Notary dispatch (verdict stays in the ledger). Missing-ticket and unreadable-status reasks keep their existing diagnostic text.",
610
+ zh: "必填源 run 定位符(簿内 runId@role,或该 run 目录路径)。绑定受审身份;审稿对话为符宝郎固定派单文(判词留在卷宗自行读取)。缺票号与读不出 status 的重交仍递既有诊断原文。",
611
611
  },
612
612
  },
613
613
  ] as const satisfies readonly PublicOptionDefinition[];
@@ -1256,7 +1256,8 @@ const ROLE_COMMAND_HELP = {
1256
1256
  },
1257
1257
  notary: {
1258
1258
  command: "notary",
1259
- summary: "Direct Notary summons; duties: souls/notary.md. Zero prompt/attachment.",
1259
+ summary:
1260
+ "Direct Notary summons; duties: souls/notary.md. Review uses fixed dispatch — binds --source-run identity; does not accept caller review prompt; verdict stays in the ledger. Missing-ticket (#1171) and unreadable-status reasks deliver their existing diagnostic text on the same seat, not the review sentence.",
1260
1261
  usage: ["ak-role notary --source-run <runId@role|path> [options]"],
1261
1262
  examples: [
1262
1263
  "ak-role notary --source-run 01a034f1-75bf-71a6-bcf5-d1299145b1a5@judge",
@@ -1319,7 +1320,7 @@ const SUPPORT_COMMAND_HELP = {
1319
1320
  resume: {
1320
1321
  command: "resume",
1321
1322
  summary:
1322
- "Resume a role run under the live seat table (model/host/engine); session principal must still exist. Every callable seat, including Notary/符宝郎, accepts the optional caller message and passes it through unchanged as the continuation prompt. Notary explicit new still accepts only its source-run locator, not a caller prompt. Global --model/--thinking/--host/--engine must be placed before <runId> (either before `resume` or between `resume` and <runId>); the one argv after <runId> is the opaque message, not a flag position (#471).",
1323
+ "Resume a role run under the live seat table (model/host/engine); session principal must still exist. Every callable seat accepts the optional caller message and passes it through unchanged as the continuation prompt. Notary new/direct review calls use only --source-run plus the fixed review dispatch (no caller review prompt); public manual resume still passes the caller message. Global --model/--thinking/--host/--engine must be placed before <runId> (either before `resume` or between `resume` and <runId>); the one argv after <runId> is the opaque message, not a flag position (#471).",
1323
1324
  usage: ["ak-role resume <runId> [message]"],
1324
1325
  examples: [
1325
1326
  "ak-role resume 01abc…",
@@ -9,11 +9,9 @@ import { randomUUID } from "node:crypto";
9
9
  import { writeFile } from "node:fs/promises";
10
10
 
11
11
  import {
12
- buildAutoResumeContinuationPrompt,
13
12
  findRunDirectoryById,
14
13
  resolveLiveRunDirectoryPath,
15
14
  readRoleRunState,
16
- RESUME_TRANSPORT_ENVELOPE,
17
15
  type PublicResumeRequest,
18
16
  type SameTicketSummonsMaterials,
19
17
  } from "./run-lifecycle.ts";
@@ -820,17 +818,14 @@ function settlementScopeForTurn(request: RoleTurnRequest): SettlementCourtScope
820
818
  }
821
819
 
822
820
  /**
823
- * #1132: one 没交卷催交 turn, dispatched through the SAME settlement path as the
824
- * first turn.
821
+ * #1132 / #1208: one 没交卷催交 turn, dispatched through the SAME settlement path
822
+ * as the first turn.
825
823
  *
826
- * There is deliberately no second settlement implementation here. A delivery
827
- * turn is an ordinary host turn on this same run/session: this builds the
828
- * host-neutral resume request (the adapter's stored native session id, ADR 0082;
829
- * pi's existing delivery-state content reused verbatim — no new prompt wording,
830
- * no new host capability). The caller settles it through the same
831
- * `settleCompletedHostTurn` the first turn uses (ADR 0080): the runner/host
832
- * failure re-read, the shouldPresent gate, the fresh-seal check, open-court
833
- * cleanup, cancel re-reads, run-state persist, and the stderr mirror.
824
+ * A delivery turn is an ordinary host turn on this same run/session: this builds
825
+ * the host-neutral resume request (the adapter's stored native session id,
826
+ * ADR 0082). #1208: resend this turn's actual prompt — never substitute
827
+ * delivery-state / run-stats JSON for dialogue. Counts, rejections, and
828
+ * no_receipt facts stay on receiptDelivery / lifecycle settlement.
834
829
  *
835
830
  * The caller owns the live delivery policy; the request projects its next send.
836
831
  */
@@ -844,10 +839,7 @@ function buildReceiptDeliveryRequest(input: {
844
839
  ...request,
845
840
  continuation: {
846
841
  kind: "resume",
847
- prompt: JSON.stringify({
848
- ...receiptDelivery.deliveryState(),
849
- deliveryTurns: receiptDelivery.issuedDeliveryRequests() + 1,
850
- }),
842
+ prompt: request.continuation.prompt,
851
843
  },
852
844
  deliveryRequestLimit: receiptDelivery.limit,
853
845
  };
@@ -1723,8 +1715,8 @@ export function resumeTurnRequestProjectionOptions(
1723
1715
  ? body
1724
1716
  : appendCallerFileFlagPaths(body, fileFlags);
1725
1717
  } else if (request.summons !== undefined) {
1726
- // #879: same-ticket summons with no instruction (e.g. notary source-run binding
1727
- // only). Pointer is activation/sourceRun material — not dialogue content.
1718
+ // Same-ticket summons without prepared dialogue. Notary review fills via
1719
+ // resolveReviewSeatDialogueBody (#1208); other seats carry parent/caller words.
1728
1720
  prompt = "";
1729
1721
  } else {
1730
1722
  prompt = "";
@@ -2050,21 +2042,15 @@ export async function runPostAdmissionSeatResume<
2050
2042
  buildInitialPayload: (): StationChildAttempt => ({ resumeTurn: false }),
2051
2043
  buildResumePayload: (): StationChildAttempt => ({ resumeTurn: true }),
2052
2044
  toRequest: async (payload) => {
2053
- // #840 r8 判词 class 2: this call-local retry must keep this
2054
- // court's frozen summons / 交卷 body / attachments verbatim
2055
- // (same object as firstTurn) and project only the minimal
2056
- // host-needed resume trigger — never engine handbook material,
2057
- // which would replace a 审核循环 same-ticket continuation with a bare outsourcing
2058
- // 「重新读」 envelope (#755 contract, resumeTurnRequestProjectionOptions
2059
- // above). RESUME_TRANSPORT_ENVELOPE is the same package-owned,
2060
- // non-semantic trigger that projection already uses for a
2061
- // same-ticket summons carrying no instruction/attachments.
2045
+ // #840 r8 / #1208: call-local retry keeps this court's frozen summons /
2046
+ // 交卷 body / attachments and resends the same turn prompt — never a
2047
+ // filler phrase or engine handbook substitute.
2062
2048
  if (payload.resumeTurn && firstTurn !== undefined) {
2063
2049
  return {
2064
2050
  ...firstTurn,
2065
2051
  continuation: {
2066
2052
  kind: "resume",
2067
- prompt: RESUME_TRANSPORT_ENVELOPE,
2053
+ prompt: firstTurn.continuation.prompt,
2068
2054
  },
2069
2055
  };
2070
2056
  }
@@ -2227,6 +2213,7 @@ export async function runPostAdmissionOneShot<
2227
2213
  admitted?: A;
2228
2214
  terminal?: T;
2229
2215
  }> {
2216
+ // #1208: auto-resume resends this turn's actual prompt, never a filler phrase.
2230
2217
  return await runPostAdmissionResumable({
2231
2218
  admitted: input.admitted,
2232
2219
  env: input.env,
@@ -2236,7 +2223,7 @@ export async function runPostAdmissionOneShot<
2236
2223
  ...input.request,
2237
2224
  continuation: {
2238
2225
  kind: "resume",
2239
- prompt: buildAutoResumeContinuationPrompt(),
2226
+ prompt: input.request.continuation.prompt,
2240
2227
  },
2241
2228
  }),
2242
2229
  adapters: input.adapters,