@akagilnc/pi-workflow-roles 0.1.3685 → 0.1.3702

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 (54) hide show
  1. package/dist/acp-host/production-host.js +499 -559
  2. package/dist/analyst-gate-cycles-read.js +11 -1
  3. package/dist/archivist-record-entry.js +1 -1
  4. package/dist/auditor-dossier-tool.js +12 -0
  5. package/dist/dossier-resolution.js +0 -35
  6. package/dist/gatekeeper-pass-envelope.js +39 -34
  7. package/dist/gatekeeper-role.js +70 -77
  8. package/dist/judge-auditor.js +5 -24
  9. package/dist/judge-role.js +39 -42
  10. package/dist/notary-contracts.js +2 -17
  11. package/dist/notary-role.js +3 -6
  12. package/dist/package-contracts/judge-output.js +0 -2
  13. package/dist/package-contracts/terminating-tools.js +2 -2
  14. package/dist/public-cli/inspector-run.js +16 -2
  15. package/dist/public-cli/instruction-seat-run.js +14 -2
  16. package/dist/public-cli/main.js +189 -214
  17. package/dist/public-cli/notary-run.js +23 -20
  18. package/dist/public-cli/post-admission.js +30 -23
  19. package/dist/public-cli/run-lifecycle.js +6 -14
  20. package/dist/public-role-summons.js +38 -11
  21. package/dist/role-runtime.js +28 -3
  22. package/dist/submission-correctable-error.js +2 -1
  23. package/dist/submission-errors.js +26 -11
  24. package/dist/submission-ledger.js +7 -23
  25. package/extensions/role-runtime.ts +0 -2
  26. package/package.json +1 -1
  27. package/souls/countersign.md +12 -11
  28. package/souls/notary.md +5 -0
  29. package/src/acp-host/production-host.ts +1 -4
  30. package/src/acp-host/role-envelope.ts +1 -1
  31. package/src/analyst-gate-cycles-read.ts +10 -1
  32. package/src/archivist-record-entry.ts +7 -3
  33. package/src/auditor-dossier-tool.ts +21 -0
  34. package/src/dossier-resolution.ts +0 -39
  35. package/src/gatekeeper-pass-envelope.ts +59 -43
  36. package/src/gatekeeper-role.ts +114 -113
  37. package/src/host-contracts.ts +3 -3
  38. package/src/judge-auditor.ts +5 -43
  39. package/src/judge-role.ts +41 -54
  40. package/src/notary-contracts.ts +8 -27
  41. package/src/notary-role.ts +2 -7
  42. package/src/package-contracts/judge-output.ts +0 -1
  43. package/src/package-contracts/terminating-tools.ts +2 -2
  44. package/src/public-cli/inspector-run.ts +32 -2
  45. package/src/public-cli/instruction-seat-run.ts +30 -2
  46. package/src/public-cli/notary-run.ts +48 -16
  47. package/src/public-cli/post-admission.ts +31 -37
  48. package/src/public-cli/run-lifecycle.ts +6 -17
  49. package/src/public-role-summons.ts +76 -12
  50. package/src/role-runtime.ts +34 -9
  51. package/src/submission-correctable-error.ts +2 -0
  52. package/src/submission-errors.ts +28 -11
  53. package/src/submission-ledger.ts +8 -32
  54. package/dist/shape-unreadable-failure.js +0 -34
@@ -1,45 +1,7 @@
1
- import {
2
- runComplianceAudit,
3
- type AuditorSummon,
4
- type ComplianceDecision,
5
- } from "./compliance-transport.ts";
6
- import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
7
- import {
8
- readJudgeAuditSubjects,
9
- requireAuditMaterials,
10
- resolveAuditDossier,
11
- } from "./dossier-resolution.ts";
12
- import type { HostContext } from "./host-contracts.ts";
13
-
14
- export const JUDGE_AUDIT_TOOL_NAME = "ak_soul_audit_decision";
15
- export const SOUL_AUDIT_TOOL_NAME = JUDGE_AUDIT_TOOL_NAME;
16
-
17
- export type JudgeAuditOptions = {
18
- context: HostContext;
19
- signal?: AbortSignal;
20
- /** Same seam as runComplianceAudit options — offline tracers only. */
21
- summonAuditor?: AuditorSummon;
22
- };
23
-
24
1
  /**
25
- * Judge compliance via public auditor activation (#675 / ADR 0062 / owner r11).
26
- * 审刑院 is an independent role; subject=judge selects souls/judge-auditor.md.
2
+ * Judge nested-audit tool name retained for settlement / historical session faces.
3
+ * Live judge↔auditor review queue is the shared gate envelope (#756) — no
4
+ * createPiJudgeAuditor compliance wrapper remains.
27
5
  */
28
- export function createPiJudgeAuditor(): (options: JudgeAuditOptions) => Promise<ComplianceDecision> {
29
- return async (options) => {
30
- const dossier = resolveAuditDossier();
31
- requireAuditMaterials(dossier);
32
- const subjects = readJudgeAuditSubjects(options.context);
33
- requireAuditMaterials(subjects);
34
-
35
- return runComplianceAudit({
36
- subject: "judge",
37
- context: options.context,
38
- ...(auditorRunDirectory(options.context) === undefined
39
- ? {}
40
- : { runDirectory: auditorRunDirectory(options.context) }),
41
- ...(options.signal === undefined ? {} : { signal: options.signal }),
42
- ...(options.summonAuditor === undefined ? {} : { summonAuditor: options.summonAuditor }),
43
- });
44
- };
45
- }
6
+ export const JUDGE_AUDIT_TOOL_NAME = "ak_soul_audit_decision";
7
+ export const SOUL_AUDIT_TOOL_NAME = JUDGE_AUDIT_TOOL_NAME;
package/src/judge-role.ts CHANGED
@@ -2,19 +2,20 @@ import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } fro
2
2
  import { stringEnum } from "./host-contracts.ts";
3
3
  import { Type, type Static } from "typebox";
4
4
 
5
- import { disposeComplianceDecision } from "./audit-escalation.ts";
6
- import type { ComplianceDecision } from "./compliance-transport.ts";
7
- import { joinReadableGateItems } from "./readable-gate-item.ts";
8
5
  import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.ts";
6
+ import { ParentQueueReaskError } from "./submission-errors.ts";
9
7
 
10
8
  import {
11
- JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT,
12
9
  JUDGE_ACCEPTED_TEXT,
13
10
  JUDGE_OUTPUT_TOOL_NAME,
14
11
  validateAcceptedJudgeDetails,
15
12
  type JudgeVerdict,
16
13
  } from "./package-contracts/judge-output.ts";
17
14
 
15
+ const JUDGE_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
16
+ const JUDGE_STATUS_REASK =
17
+ "judgeStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
18
+
18
19
  export { JUDGE_OUTPUT_TOOL_NAME };
19
20
  export type { JudgeVerdict };
20
21
 
@@ -53,13 +54,8 @@ export const judgeVerdictSchema = withInfrastructureFailureDeclaration(
53
54
 
54
55
  type JudgeVerdictParameters = Static<typeof judgeVerdictSchema>;
55
56
 
56
- export type SoulAuditResult = ComplianceDecision;
57
-
58
57
  export type JudgeRoleDependencies = {
59
58
  loadSoul(): Promise<string>;
60
- auditSoulCompliance(
61
- options: { context: HostContext; signal?: AbortSignal },
62
- ): Promise<SoulAuditResult>;
63
59
  };
64
60
 
65
61
  export type JudgeRoleHostActions = HostGatekeeperActions;
@@ -86,15 +82,34 @@ export function createJudgeRoleRuntime(
86
82
  pi.registerTool({
87
83
  name: JUDGE_OUTPUT_TOOL_NAME,
88
84
  label: "大理寺输出",
89
- description: "提交大理寺终局判词;受理前经审刑院审计。",
85
+ description: "提交大理寺终局判词;受理前经符宝郎内闸与审刑院合规审核。",
90
86
  promptSnippet: "提交大理寺终局判词",
91
87
  parameters: judgeVerdictSchema,
92
88
  async execute(toolCallId: string, parameters: Static<typeof judgeVerdictSchema>, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
93
89
  if (soul === undefined) throw new Error("大理寺职分未装载");
90
+ // #756 queue: read judgeStatus only — escalate skips gates (thrown to caller);
91
+ // unreadable status returns to judge; else 符宝郎内闸 then 审刑院合规.
92
+ const rawStatus =
93
+ parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
94
+ && typeof (parameters as Record<string, unknown>).judgeStatus === "string"
95
+ ? (parameters as Record<string, unknown>).judgeStatus as string
96
+ : undefined;
97
+ if (rawStatus === undefined || !JUDGE_QUEUE_STATUSES.has(rawStatus)) {
98
+ throw new ParentQueueReaskError(JUDGE_STATUS_REASK);
99
+ }
100
+ if (rawStatus === "escalate") {
101
+ // Parent escalate → throw to caller as-is; officers do not attend (#753 / #756).
102
+ const verdict = validateVerdict(parameters);
103
+ return {
104
+ content: [{ type: "text" as const, text: JUDGE_ACCEPTED_TEXT }],
105
+ details: verdict,
106
+ terminate: true as const,
107
+ };
108
+ }
94
109
  const verdict = validateVerdict(parameters);
95
110
  // Candidate verdict is already on the parent session books as this
96
111
  // tool-call leaf (first-record-then-audit; run 019fea05 L61/L62).
97
- // Gatekeeper runs after the draft is booked and before existing auditor.
112
+ // #753: 符宝郎内闸 — queue only, raw receipt on bounce/escalate.
98
113
  await pi.requireGatekeeperPass!({
99
114
  context: ctx,
100
115
  subject: { kind: "judge_draft" },
@@ -102,49 +117,21 @@ export function createJudgeRoleRuntime(
102
117
  hostActions,
103
118
  toolCallId,
104
119
  });
105
- let audit: SoulAuditResult;
106
- try {
107
- audit = await dependencies.auditSoulCompliance(
108
- signal === undefined
109
- ? { context: ctx }
110
- : { context: ctx, signal },
111
- );
112
- } catch (error) {
113
- hostActions.failInfrastructure(error, ctx, toolCallId);
114
- }
115
- const acceptedDetails = verdict;
116
- return disposeComplianceDecision<HostToolResult<unknown>>(
117
- audit,
118
- {
119
- pass: (usage) => ({
120
- content: [{ type: "text" as const, text: JUDGE_ACCEPTED_TEXT }],
121
- details: acceptedDetails,
122
- terminate: true as const,
123
- ...(usage === undefined ? {} : { usage }),
124
- }),
125
- noReceipt: (auditNoReceipt, usageProjection) => ({
126
- content: [{ type: "text" as const, text: JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }],
127
- details: { ...acceptedDetails, auditNoReceipt },
128
- terminate: true as const,
129
- ...usageProjection,
130
- }),
131
- // #757: parent stands with auditor raw reply — no unreadable judgment.
132
- received: (auditReceived, usageProjection) => ({
133
- content: [{ type: "text" as const, text: JUDGE_ACCEPTED_TEXT }],
134
- details: { ...acceptedDetails, audit: auditReceived.reply },
135
- terminate: true as const,
136
- ...usageProjection,
137
- }),
138
- bounce: (violations) => {
139
- throw new Error(
140
- `大理寺回执违 soul:${joinReadableGateItems(violations)}`,
141
- );
142
- },
143
- escalate: (result) => result,
144
- },
145
- // #380: escalate deliveredOutput must carry the same mechanical projection.
146
- acceptedDetails,
147
- );
120
+ // #756: 审刑院合规路径 — same review-queue law as 符宝郎/察院.
121
+ // pass → accept; bounce|escalate → raw auditor receipt back to judge;
122
+ // not three-state → resume auditor; no round cap; no disposeCompliance mapping.
123
+ await pi.requireGatekeeperPass!({
124
+ context: ctx,
125
+ subject: { kind: "judge_compliance" },
126
+ ...(signal === undefined ? {} : { signal }),
127
+ hostActions,
128
+ toolCallId,
129
+ });
130
+ return {
131
+ content: [{ type: "text" as const, text: JUDGE_ACCEPTED_TEXT }],
132
+ details: verdict,
133
+ terminate: true as const,
134
+ };
148
135
  },
149
136
  });
150
137
  pi.on("before_agent_start", (event) => {
@@ -53,13 +53,10 @@ export type NotarySourceRunLocator = {
53
53
  readonly role: string;
54
54
  };
55
55
 
56
+ /** Recognition face only — fields retained as submitted (#753, no disposition/findings forge). */
56
57
  export type NotaryOutput =
57
- | { readonly status: "pass"; readonly findings: readonly string[] }
58
- | {
59
- readonly status: "bounce";
60
- readonly disposition: "rewrite";
61
- readonly findings: readonly string[];
62
- }
58
+ | { readonly status: "pass"; readonly findings?: unknown }
59
+ | { readonly status: "bounce"; readonly findings?: unknown }
63
60
  | {
64
61
  readonly status: "escalate";
65
62
  readonly reason?: unknown;
@@ -70,32 +67,16 @@ function isRecord(value: unknown): value is Record<string, unknown> {
70
67
  return typeof value === "object" && value !== null && !Array.isArray(value);
71
68
  }
72
69
 
73
- function asStringArray(value: unknown): readonly string[] {
74
- if (!Array.isArray(value)) return [];
75
- return value.filter((item): item is string => typeof item === "string");
76
- }
77
-
78
70
  /**
79
- * Project one lawful explicit Notary release (pass | bounce | escalate).
80
- * No throw on shape — ADR 0055 / 第 0 条: already-submitted params are retained as-is;
81
- * public-terminal projects non-usable releases via typed failure cause.
71
+ * Recognize one lawful explicit Notary release (pass | bounce | escalate).
72
+ * #753: no field rewrite — submitted params retained as-is (no disposition forge,
73
+ * no findings array rewrite). Recognition only for recording/settlement callers.
82
74
  */
83
75
  export function projectLawfulNotaryOutput(value: unknown): NotaryOutput | undefined {
84
76
  if (!isRecord(value)) return undefined;
85
77
  const status = typeof value.status === "string" ? value.status : undefined;
86
- if (status === "bounce") {
87
- const clone = structuredClone(value) as Record<string, unknown>;
88
- if (clone.disposition === undefined) clone.disposition = "rewrite";
89
- if (!Array.isArray(clone.findings)) clone.findings = asStringArray(clone.findings);
90
- return clone as NotaryOutput;
91
- }
92
- if (status === "pass") {
93
- const clone = structuredClone(value) as Record<string, unknown>;
94
- if (!Array.isArray(clone.findings)) clone.findings = asStringArray(clone.findings);
95
- return clone as NotaryOutput;
96
- }
97
- if (status === "escalate") {
98
- return structuredClone(value) as NotaryOutput;
78
+ if (status === "bounce" || status === "pass" || status === "escalate") {
79
+ return value as NotaryOutput;
99
80
  }
100
81
  return undefined;
101
82
  }
@@ -12,8 +12,6 @@ import {
12
12
  NOTARY_SOURCE_RUN_FLAG,
13
13
  NOTARY_TICKET_FLAG,
14
14
  notaryOutputSchema,
15
- projectLawfulNotaryOutput,
16
- retainNotarySubmission,
17
15
  type NotarySourceRunLocator,
18
16
  } from "./notary-contracts.ts";
19
17
 
@@ -170,14 +168,11 @@ export function createNotaryRoleRuntime(
170
168
  if (activation === undefined) {
171
169
  throw new Error("符宝郎未激活");
172
170
  }
173
- // Unique submission + terminate only. Shape is not an admission gate
174
- // (第 0 条 / ADR 0055): lawful pass/bounce/escalate projected; else params as-is.
171
+ // #753: handler only records — params as submitted, no findings/disposition rewrite.
175
172
  // #541 infra declaration + sole-final barrier are ledger-owned (#575).
176
- const lawful = projectLawfulNotaryOutput(parameters);
177
- const details = lawful ?? retainNotarySubmission(parameters);
178
173
  return {
179
174
  content: [{ type: "text" as const, text: NOTARY_ACCEPTED_TEXT }],
180
- details,
175
+ details: parameters,
181
176
  terminate: true as const,
182
177
  };
183
178
  },
@@ -3,7 +3,6 @@
3
3
 
4
4
  export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
5
5
  export const JUDGE_ACCEPTED_TEXT = "大理寺回执已接受";
6
- export const JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT = "大理寺回执已接受;审计无回执";
7
6
 
8
7
  export type JudgeClass = {
9
8
  name: string;
@@ -221,10 +221,10 @@ export function validateAcceptedDetails(
221
221
  [COLLECTOR_OUTPUT_TOOL]: [],
222
222
  [DOCTOR_OUTPUT_TOOL_NAME]: ["completed", "refused"],
223
223
  [MERGER_OUTPUT_TOOL_NAME]: ["completed", "escalate"],
224
- [NOTARY_OUTPUT_TOOL_NAME]: ["pass", "bounce"],
224
+ [NOTARY_OUTPUT_TOOL_NAME]: ["pass", "bounce", "escalate"],
225
225
  [COUNTERSIGN_OUTPUT_TOOL_NAME]: ["converged", "continue", "escalate"],
226
226
  [GLEANER_LEFT_OUTPUT_TOOL_NAME]: ["completed"],
227
- [INSPECTOR_OUTPUT_TOOL_NAME]: ["pass", "bounce"],
227
+ [INSPECTOR_OUTPUT_TOOL_NAME]: ["pass", "bounce", "escalate"],
228
228
  [GATEKEEPER_OUTPUT_TOOL_NAME]: ["dispatch", "pass"],
229
229
  [NAVIGATOR_OUTPUT_TOOL_NAME]: ["advice"],
230
230
  [AUDITOR_OUTPUT_TOOL_NAME]: ["pass", "bounce", "escalate"],
@@ -44,6 +44,17 @@ import {
44
44
  export type InspectorRunEnv = PostAdmissionEnv & {
45
45
  principalAuthority: DurablePrincipalAuthority;
46
46
  createRunId?: () => string;
47
+ /**
48
+ * #753 nested gate re-ask: plain-language instruction on same-ticket resume.
49
+ * Only set by summonPublicRole when the prior officer reply was not three-state.
50
+ * Never a public CLI argv — must not pollute the 卷宗指针 parentRunPath key (#747).
51
+ */
52
+ reviewReask?: string;
53
+ /**
54
+ * #753/#750 same-parent re-summons: human-readable new-submission pointers.
55
+ * Rides summons.instruction when reviewReask is absent. Fresh mint keeps argv 卷宗指针.
56
+ */
57
+ gateReviewInstruction?: string;
47
58
  };
48
59
 
49
60
  /** Project admitted invocation onto the host-neutral turn request. */
@@ -87,11 +98,18 @@ export async function runPublicInspector(
87
98
  // #771: no mechanical ticket match against instruction text; parent-run path only.
88
99
  // No bare catch→fresh: lookup/resume failures surface; only true absence mints new.
89
100
  const projectRoot = parsed.project ?? env.cwd;
101
+ // #747: parentRunPath is the pure 卷宗指针 path only — never reask/materials text.
102
+ // #753: gate re-ask / new-submission pointers ride summons.instruction on resume.
90
103
  const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
91
104
  if (parentRunPath !== undefined) {
105
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
92
106
  const summons: SameTicketSummonsMaterials = {
93
- instruction: parsed.instruction,
94
- instructionEmpty: parsed.instruction.trim() === "",
107
+ ...(resumeInstruction === undefined
108
+ ? {
109
+ instruction: parsed.instruction,
110
+ instructionEmpty: parsed.instruction.trim() === "",
111
+ }
112
+ : { instruction: resumeInstruction, instructionEmpty: false }),
95
113
  attachmentPaths: parsed.attachmentPaths,
96
114
  };
97
115
  const resumed = await tryResumeSameTicketSeatRun({
@@ -109,6 +127,18 @@ export async function runPublicInspector(
109
127
  ),
110
128
  });
111
129
  if (resumed !== undefined) return resumed;
130
+ // Reask without a prior same-parent run cannot deliver the plain-language ask
131
+ // on a fresh mint without inventing a second prompt path — fail loud (#753).
132
+ // gateReviewInstruction alone is resume-only; fresh mint keeps argv 卷宗指针.
133
+ if (env.reviewReask !== undefined) {
134
+ presentStructuralRejection(
135
+ new CliUsageError(
136
+ "inspector review reask requires a prior same-parent run to resume",
137
+ ),
138
+ io,
139
+ );
140
+ return { exitCode: 2 };
141
+ }
112
142
  }
113
143
 
114
144
  let admitted: AdmittedInspectorInvocation;
@@ -63,6 +63,17 @@ export type AdmittedInstructionSeatInvocation =
63
63
  export type InstructionSeatRunEnv = PostAdmissionEnv & {
64
64
  principalAuthority: DurablePrincipalAuthority;
65
65
  createRunId?: () => string;
66
+ /**
67
+ * #756 nested gate re-ask for auditor: plain-language instruction on same-ticket resume.
68
+ * Only set by summonPublicRole when the prior officer reply was not three-state.
69
+ * Never a public CLI argv — must not pollute the --source-run parent lookup key (#747).
70
+ */
71
+ reviewReask?: string;
72
+ /**
73
+ * #753/#750 same-parent re-summons: human-readable new-submission pointers.
74
+ * Rides summons.instruction when reviewReask is absent. Fresh mint keeps argv instruction.
75
+ */
76
+ gateReviewInstruction?: string;
66
77
  };
67
78
 
68
79
  /** Project an admitted instruction-seat invocation onto the host-neutral turn request. */
@@ -283,9 +294,15 @@ export async function runPublicInstructionSeat(
283
294
  }
284
295
  }
285
296
 
297
+ // #756: auditor reask / new-submission pointers ride summons.instruction on resume.
298
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
286
299
  const summons: SameTicketSummonsMaterials = {
287
- instruction: parsed.instruction,
288
- instructionEmpty: parsed.instruction.trim() === "",
300
+ ...(resumeInstruction === undefined
301
+ ? {
302
+ instruction: parsed.instruction,
303
+ instructionEmpty: parsed.instruction.trim() === "",
304
+ }
305
+ : { instruction: resumeInstruction, instructionEmpty: false }),
289
306
  attachmentPaths: parsed.attachmentPaths,
290
307
  };
291
308
  if (role === "auditor" && auditorSourceRun !== undefined) {
@@ -309,6 +326,17 @@ export async function runPublicInstructionSeat(
309
326
  }),
310
327
  });
311
328
  if (resumed !== undefined) return resumed;
329
+ // Reask without a prior same-parent run cannot deliver the plain-language ask
330
+ // on a fresh mint without inventing a second prompt path — fail loud (#753 / #756).
331
+ if (env.reviewReask !== undefined) {
332
+ presentStructuralRejection(
333
+ new CliUsageError(
334
+ "auditor review reask requires a prior same-parent run to resume",
335
+ ),
336
+ io,
337
+ );
338
+ return { exitCode: 2 };
339
+ }
312
340
  }
313
341
 
314
342
  let admitted: AdmittedInstructionSeatInvocation;
@@ -4,9 +4,6 @@
4
4
  * the shared post-admission seam; this module keeps only Notary adapters.
5
5
  * #637 / #747: same-parent (--source-run) re-summons resume the seat's previous run.
6
6
  */
7
- import { existsSync } from "node:fs";
8
-
9
- import { gateSubmissionCandidatePath } from "../auditor-dossier-tool.ts";
10
7
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
11
8
  import {
12
9
  NotarySourceRunError,
@@ -22,6 +19,7 @@ import {
22
19
  } from "./invocation.ts";
23
20
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
24
21
  import {
22
+ prepareSummonsResumeMaterials,
25
23
  runPostAdmissionOneShot,
26
24
  type PostAdmissionAdapters,
27
25
  type PostAdmissionEnv,
@@ -29,7 +27,6 @@ import {
29
27
  resumeTurnRequestProjectionOptions,
30
28
  } from "./post-admission.ts";
31
29
  import {
32
- appendResumeMaterialReread,
33
30
  loadResumableNotaryRun,
34
31
  markRunAdmitted,
35
32
  type PublicResumeRequest,
@@ -49,6 +46,17 @@ import {
49
46
  export type NotaryRunEnv = PostAdmissionEnv & {
50
47
  principalAuthority: DurablePrincipalAuthority;
51
48
  createRunId?: () => string;
49
+ /**
50
+ * #753 nested gate re-ask: plain-language instruction on same-ticket resume.
51
+ * Only set by summonPublicRole when the prior officer reply was not three-state.
52
+ * Never a public CLI argv — external callers still have zero prompt.
53
+ */
54
+ reviewReask?: string;
55
+ /**
56
+ * #753/#750 same-parent re-summons: human-readable new-submission pointers.
57
+ * Rides summons.instruction when reviewReask is absent. Fresh mint ignores it.
58
+ */
59
+ gateReviewInstruction?: string;
52
60
  };
53
61
 
54
62
  /** Project admitted invocation onto the host-neutral turn request. */
@@ -112,10 +120,16 @@ export async function runPublicNotary(
112
120
  throw error;
113
121
  }
114
122
  // #747: officer resume key is this parent source-run path (not ticket number).
123
+ // #753: gate re-ask and new-submission pointers share summons.instruction
124
+ // (reask wins when both present; no parallel stack).
115
125
  {
126
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
116
127
  const summons: SameTicketSummonsMaterials = {
117
128
  sourceRunPath: source.runDirectory,
118
129
  sourceRun: source,
130
+ ...(resumeInstruction === undefined
131
+ ? {}
132
+ : { instruction: resumeInstruction, instructionEmpty: false }),
119
133
  };
120
134
  const resumed = await tryResumeSameTicketSeatRun({
121
135
  home: env.home,
@@ -132,6 +146,18 @@ export async function runPublicNotary(
132
146
  ),
133
147
  });
134
148
  if (resumed !== undefined) return resumed;
149
+ // Reask without a prior same-parent run cannot deliver the plain-language ask
150
+ // on a fresh mint without inventing a second prompt path — fail loud (#753).
151
+ // gateReviewInstruction alone is resume-only materials; fresh mint ignores it.
152
+ if (env.reviewReask !== undefined) {
153
+ presentStructuralRejection(
154
+ new CliUsageError(
155
+ "notary review reask requires a prior same-parent run to resume",
156
+ ),
157
+ io,
158
+ );
159
+ return { exitCode: 2 };
160
+ }
135
161
  }
136
162
 
137
163
  let admitted: AdmittedNotaryInvocation;
@@ -238,20 +264,26 @@ export async function runPublicNotaryResume(
238
264
  }
239
265
  return loaded;
240
266
  },
241
- buildTurnRequest: (admitted, effective) => {
242
- const projection = resumeTurnRequestProjectionOptions(
267
+ buildTurnRequest: async (admitted, effective) => {
268
+ // #755: review continuation is plain dialogue / resume envelope — no
269
+ //「重新读」candidate line, no engine handbook packaging.
270
+ // #753: non-three-state re-ask rides as summons.instruction (人话重问).
271
+ const summonsPrepared =
272
+ effective.summons === undefined
273
+ ? undefined
274
+ : await prepareSummonsResumeMaterials(
275
+ admitted.runDirectory,
276
+ effective.summons,
277
+ );
278
+ return buildNotaryTurnRequest(
243
279
  admitted,
244
- effective,
245
- env,
280
+ resumeTurnRequestProjectionOptions(
281
+ admitted,
282
+ effective,
283
+ env,
284
+ summonsPrepared,
285
+ ),
246
286
  );
247
- const candidatePath = gateSubmissionCandidatePath(admitted.sourceRunPath);
248
- const prompt = existsSync(candidatePath)
249
- ? appendResumeMaterialReread(projection.continuation.prompt, candidatePath)
250
- : projection.continuation.prompt;
251
- return buildNotaryTurnRequest(admitted, {
252
- ...projection,
253
- continuation: { kind: "resume", prompt },
254
- });
255
287
  },
256
288
  adapters: notaryAdapters(),
257
289
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
@@ -11,7 +11,7 @@ import { isAbsolute, join, resolve } from "node:path";
11
11
 
12
12
  import {
13
13
  buildResumeContinuationPrompt,
14
- instructResumeHandbookRead,
14
+ RESUME_TRANSPORT_ENVELOPE,
15
15
  type PublicResumeRequest,
16
16
  type SameTicketSummonsMaterials,
17
17
  } from "./run-lifecycle.ts";
@@ -22,7 +22,6 @@ import {
22
22
  freezeAttachmentsIntoRun,
23
23
  } from "./invocation.ts";
24
24
  import { pathContainedIn } from "../activation-ledger-topology.ts";
25
- import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
26
25
 
27
26
  import type {
28
27
  ControlledFailureCause,
@@ -494,15 +493,18 @@ export async function dispatchPostAdmissionTurn<
494
493
  }
495
494
 
496
495
  /**
497
- * Shared resume continuation projection (#471 / #600 / #633 / #637): seat-table
498
- * model/engine/timeout axes, restored correlation, and either
499
- * - manual resume: package envelope / optional caller message (unchanged), or
500
- * - same-ticket summons: this turn's instruction + frozen attachment paths.
501
- * Caller message and summons materials each keep their place: a resume message
502
- * keeps manual-resume prompt semantics while open-court frozen attachments still
503
- * ride; summons instruction is only the prompt base when no caller message.
496
+ * Shared resume continuation projection (#471 / #600 / #633 / #637 / #755):
497
+ * seat-table model/engine/timeout axes, restored correlation, and either
498
+ * - manual resume (no same-ticket summons): package envelope / optional caller
499
+ * message, with engine-axis handbook via buildResumeContinuationPrompt, or
500
+ * - same-ticket summons (审核循环续话): caller/peer words + optional frozen
501
+ * attachment paths only — no「重新读」、no engine handbook packaging
502
+ * (#750/#755), whether or not attachments are present.
503
+ * Caller message wins as prompt base when supplied (bytes unchanged, including
504
+ * blank/whitespace); else summons instruction. Attachment projection must not
505
+ * re-interpret the caller message as instructionEmpty.
504
506
  * Seats add only their activation projection. Call prepareSummonsResumeMaterials
505
- * first when request.summons carries attachment paths.
507
+ * first when request.summons carries instruction or attachment paths.
506
508
  */
507
509
  export function resumeTurnRequestProjectionOptions(
508
510
  admitted: AdmittedRoleInvocation,
@@ -514,32 +516,21 @@ export function resumeTurnRequestProjectionOptions(
514
516
  readonly attachments: readonly { frozenPath: string }[];
515
517
  },
516
518
  ): RoleTurnRequestProjectionOptions {
517
- const engineMaterial = engineSessionMaterialFromOptions({
518
- ...(env.engine === undefined ? {} : { engine: env.engine }),
519
- packageRoot: env.packageRoot,
520
- });
521
519
  let prompt: string;
522
520
  if (request.message !== undefined) {
523
- // Manual resume caller-message semantics stay authoritative for the prompt:
524
- // message present → base bytes unchanged (including blank/whitespace).
525
- // Open-court frozen attachments still continue when present; attachment
526
- // projection must not re-interpret the caller message as instructionEmpty.
527
- if (
528
- summonsPrepared !== undefined &&
529
- summonsPrepared.attachments.length > 0
530
- ) {
531
- prompt = instructResumeHandbookRead(
532
- buildInstructionTransportPrompt(
533
- {
534
- instruction: request.message,
535
- instructionEmpty: false,
536
- attachments: summonsPrepared.attachments,
537
- },
538
- engineMaterial,
539
- ),
540
- engineMaterial,
541
- );
521
+ if (summonsPrepared !== undefined) {
522
+ // #755: same-ticket review / open-court — caller words + optional paths.
523
+ // Attachments are not a gate: message-only summons must stay plain too.
524
+ prompt = buildInstructionTransportPrompt({
525
+ instruction: request.message,
526
+ instructionEmpty: false,
527
+ attachments: summonsPrepared.attachments,
528
+ });
529
+ } else if (request.summons !== undefined) {
530
+ // #755: same-ticket summons without prepared materials — caller words only.
531
+ prompt = request.message;
542
532
  } else {
533
+ // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
543
534
  prompt = buildResumeContinuationPrompt({
544
535
  packageRoot: env.packageRoot,
545
536
  ...(env.engine === undefined ? {} : { engine: env.engine }),
@@ -547,11 +538,14 @@ export function resumeTurnRequestProjectionOptions(
547
538
  });
548
539
  }
549
540
  } else if (summonsPrepared !== undefined) {
550
- prompt = instructResumeHandbookRead(
551
- buildInstructionTransportPrompt(summonsPrepared, engineMaterial),
552
- engineMaterial,
553
- );
541
+ // #755: same-ticket review summons — instruction/attachments only.
542
+ prompt = buildInstructionTransportPrompt(summonsPrepared);
543
+ } else if (request.summons !== undefined) {
544
+ // #755: same-ticket summons with no instruction/attachments (e.g. notary
545
+ // source-run pointer) — plain resume envelope, no handbook / 重新读.
546
+ prompt = RESUME_TRANSPORT_ENVELOPE;
554
547
  } else {
548
+ // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
555
549
  prompt = buildResumeContinuationPrompt({
556
550
  packageRoot: env.packageRoot,
557
551
  ...(env.engine === undefined ? {} : { engine: env.engine }),