@akagilnc/pi-workflow-roles 0.1.3661 → 0.1.3669

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.
@@ -1,10 +1,18 @@
1
+ /**
2
+ * Public Notary Role run: admit source-run locator → shared post-admission coordinator
3
+ * → settle Terminal result (#448 / #517). Zero caller prompt/attachment. Lifecycle is
4
+ * the shared post-admission seam; this module keeps only Notary adapters.
5
+ * #637 / #747: same-parent (--source-run) re-summons resume the seat's previous run.
6
+ */
7
+ import { existsSync } from "node:fs";
8
+ import { gateSubmissionCandidatePath } from "../auditor-dossier-tool.js";
1
9
  import { NotarySourceRunError, resolveNotarySourceRunLocator, } from "../notary-source-run.js";
2
10
  import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
3
11
  import { CliUsageError } from "./cli-errors.js";
4
12
  import { admitNotaryInvocation, buildNotaryTransportPrompt, } from "./invocation.js";
5
13
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
6
14
  import { runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
7
- import { loadResumableNotaryRun, markRunAdmitted, } from "./run-lifecycle.js";
15
+ import { appendResumeMaterialReread, loadResumableNotaryRun, markRunAdmitted, } from "./run-lifecycle.js";
8
16
  import { presentStructuralRejection, trySettleNotaryTerminalResult, } from "./settlement.js";
9
17
  import { projectRoleTurnRequest, } from "./turn-request.js";
10
18
  /** Project admitted invocation onto the host-neutral turn request. */
@@ -153,10 +161,17 @@ export async function runPublicNotaryResume(request, env, io) {
153
161
  }
154
162
  return loaded;
155
163
  },
156
- buildTurnRequest: (admitted, effective) =>
157
- // #755: review continuation is plain dialogue / resume envelope — no
158
- //「重新读」candidate line, no engine handbook packaging.
159
- buildNotaryTurnRequest(admitted, resumeTurnRequestProjectionOptions(admitted, effective, env)),
164
+ buildTurnRequest: (admitted, effective) => {
165
+ const projection = resumeTurnRequestProjectionOptions(admitted, effective, env);
166
+ const candidatePath = gateSubmissionCandidatePath(admitted.sourceRunPath);
167
+ const prompt = existsSync(candidatePath)
168
+ ? appendResumeMaterialReread(projection.continuation.prompt, candidatePath)
169
+ : projection.continuation.prompt;
170
+ return buildNotaryTurnRequest(admitted, {
171
+ ...projection,
172
+ continuation: { kind: "resume", prompt },
173
+ });
174
+ },
160
175
  adapters: notaryAdapters(),
161
176
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
162
177
  });
@@ -8,10 +8,11 @@
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { readFile, writeFile } from "node:fs/promises";
10
10
  import { isAbsolute, join, resolve } from "node:path";
11
- import { buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
11
+ import { buildResumeContinuationPrompt, instructResumeHandbookRead, } from "./run-lifecycle.js";
12
12
  import { CliUsageError } from "./cli-errors.js";
13
13
  import { buildInstructionTransportPrompt, freezeAttachmentsIntoRun, } from "./invocation.js";
14
14
  import { pathContainedIn } from "../activation-ledger-topology.js";
15
+ import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
15
16
  import { projectCaseDossierPointerSection } from "./case-dossier-delivery.js";
16
17
  /** Append one system section to a continuation prompt, keeping its kind. */
17
18
  function appendContinuationSection(continuation, section) {
@@ -162,11 +163,11 @@ export async function dispatchPostAdmissionTurn(input) {
162
163
  }
163
164
  }
164
165
  // Turn request is assembled after beforeDispatch so this turn sees whatever it
165
- // settled — the seat's ticket bind re-projection. Case dossier delivery
166
- // (ADR 0081) rides here for every public entry: first call, same-ticket
167
- // re-summons and manual resume alike. System refs append their own neutral
168
- // section; caller frozen attachments and the seat's own prompt bytes are
169
- // never rewritten.
166
+ // settled — the seat's ticket bind re-projection and any court diarist station
167
+ // writes (#742). Case dossier delivery (ADR 0081 / #709) rides here once for
168
+ // every public entry: first call, same-ticket re-summons and manual resume
169
+ // alike. System refs append their own neutral section; caller frozen
170
+ // attachments and the seat's own prompt bytes are never rewritten.
170
171
  let turnRequest = env.signal === undefined ? request : { ...request, signal: env.signal };
171
172
  if (hostTransition !== undefined) {
172
173
  turnRequest = { ...turnRequest, hostTransition };
@@ -258,37 +259,36 @@ export async function dispatchPostAdmissionTurn(input) {
258
259
  }
259
260
  }
260
261
  /**
261
- * Shared resume continuation projection (#471 / #600 / #633 / #637 / #755):
262
- * seat-table model/engine/timeout axes, restored correlation, and either
263
- * - manual resume (no same-ticket summons): package envelope / optional caller
264
- * message, with engine-axis handbook via buildResumeContinuationPrompt, or
265
- * - same-ticket summons (审核循环续话): caller/peer words + optional frozen
266
- * attachment paths only — no「重新读」、no engine handbook packaging
267
- * (#750/#755), whether or not attachments are present.
268
- * Caller message wins as prompt base when supplied (bytes unchanged, including
269
- * blank/whitespace); else summons instruction. Attachment projection must not
270
- * re-interpret the caller message as instructionEmpty.
262
+ * Shared resume continuation projection (#471 / #600 / #633 / #637): seat-table
263
+ * model/engine/timeout axes, restored correlation, and either
264
+ * - manual resume: package envelope / optional caller message (unchanged), or
265
+ * - same-ticket summons: this turn's instruction + frozen attachment paths.
266
+ * Caller message and summons materials each keep their place: a resume message
267
+ * keeps manual-resume prompt semantics while open-court frozen attachments still
268
+ * ride; summons instruction is only the prompt base when no caller message.
271
269
  * Seats add only their activation projection. Call prepareSummonsResumeMaterials
272
- * first when request.summons carries instruction or attachment paths.
270
+ * first when request.summons carries attachment paths.
273
271
  */
274
272
  export function resumeTurnRequestProjectionOptions(admitted, request, env, summonsPrepared) {
273
+ const engineMaterial = engineSessionMaterialFromOptions({
274
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
275
+ packageRoot: env.packageRoot,
276
+ });
275
277
  let prompt;
276
278
  if (request.message !== undefined) {
277
- if (summonsPrepared !== undefined) {
278
- // #755: same-ticket review / open-court — caller words + optional paths.
279
- // Attachments are not a gate: message-only summons must stay plain too.
280
- prompt = buildInstructionTransportPrompt({
279
+ // Manual resume caller-message semantics stay authoritative for the prompt:
280
+ // message present → base bytes unchanged (including blank/whitespace).
281
+ // Open-court frozen attachments still continue when present; attachment
282
+ // projection must not re-interpret the caller message as instructionEmpty.
283
+ if (summonsPrepared !== undefined &&
284
+ summonsPrepared.attachments.length > 0) {
285
+ prompt = instructResumeHandbookRead(buildInstructionTransportPrompt({
281
286
  instruction: request.message,
282
287
  instructionEmpty: false,
283
288
  attachments: summonsPrepared.attachments,
284
- });
285
- }
286
- else if (request.summons !== undefined) {
287
- // #755: same-ticket summons without prepared materials — caller words only.
288
- prompt = request.message;
289
+ }, engineMaterial), engineMaterial);
289
290
  }
290
291
  else {
291
- // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
292
292
  prompt = buildResumeContinuationPrompt({
293
293
  packageRoot: env.packageRoot,
294
294
  ...(env.engine === undefined ? {} : { engine: env.engine }),
@@ -297,16 +297,9 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
297
297
  }
298
298
  }
299
299
  else if (summonsPrepared !== undefined) {
300
- // #755: same-ticket review summons — instruction/attachments only.
301
- prompt = buildInstructionTransportPrompt(summonsPrepared);
302
- }
303
- else if (request.summons !== undefined) {
304
- // #755: same-ticket summons with no instruction/attachments (e.g. notary
305
- // source-run pointer) — plain resume envelope, no handbook / 重新读.
306
- prompt = RESUME_TRANSPORT_ENVELOPE;
300
+ prompt = instructResumeHandbookRead(buildInstructionTransportPrompt(summonsPrepared, engineMaterial), engineMaterial);
307
301
  }
308
302
  else {
309
- // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
310
303
  prompt = buildResumeContinuationPrompt({
311
304
  packageRoot: env.packageRoot,
312
305
  ...(env.engine === undefined ? {} : { engine: env.engine }),
@@ -32,10 +32,11 @@ function resumeRereadInstruction(materialPath, handbook) {
32
32
  : `重新读 ${materialPath}`;
33
33
  }
34
34
  /**
35
- * Engine-axis resume-only material lines (#736 / ADR 0069·0071).
36
- * #755 withdrew this rewrite from 审核循环续话; review same-ticket resume stays
37
- * plain dialogue via resumeTurnRequestProjectionOptions. Outsourcing resume
38
- * still rewrites neutral handbook/path pointers into 重新读 instructions.
35
+ * Resume-only material lines (#736 ticket exemption to ADR 0073 §3).
36
+ * Neutral `- <absolute path>` pointers already on the continuation
37
+ * (handbook from appendEngineSessionMaterial; frozen attachments from
38
+ * instruction-seat transport) become reread instructions.
39
+ * Handbook keeps the argv suffix; other materials are path-only.
39
40
  * First-round delivery is unchanged; never pastes material body.
40
41
  */
41
42
  export function instructResumeHandbookRead(prompt, engineMaterial) {
@@ -55,9 +56,16 @@ export function instructResumeHandbookRead(prompt, engineMaterial) {
55
56
  })
56
57
  .join("\n");
57
58
  }
59
+ /** Append a live-path reread instruction when the pointer is not already on the continuation. */
60
+ export function appendResumeMaterialReread(prompt, materialPath) {
61
+ const instruction = resumeRereadInstruction(materialPath, false);
62
+ const lines = prompt.split("\n");
63
+ if (lines.includes(instruction))
64
+ return prompt;
65
+ return prompt.length === 0 ? instruction : `${prompt}\n${instruction}`;
66
+ }
58
67
  /**
59
- * Unique continuation-prompt selector for manual/auto engine-axis resume
60
- * (#471 / #600 / #736). Not used for 审核循环 same-ticket summons (#755).
68
+ * Unique continuation-prompt selector for manual/auto resume (#471 / #600 / #736).
61
69
  * Message present → base bytes unchanged; absent → package transport envelope.
62
70
  * When engine material is present, append structured engine coordinates then
63
71
  * rewrite absolute material pointer lines into 重新读 instructions. Zero parse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3661",
3
+ "version": "0.1.3669",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1,15 +1,18 @@
1
1
  /**
2
2
  * 系统随案递送本票起居录的中立指针段(ADR 0081 `automatic-case-material`;
3
+ * #709 公共入口通用递送与 #742 给事中受理链路同源;
3
4
  * 指针输入沿 ADR 0079 `summons-pointer-input`,不把卷宗正文塞进提示词)。
4
5
  * 只读已有案卷:不刷新、不生成、不校验内容、不新增拒收或停工条件。
5
6
  * 机器文本仅中立标识材料(ADR 0073),用途说明归角色材料所有。
7
+ *
8
+ * 递送挂载点唯一:`post-admission` 在 beforeDispatch 之后为每个公共入口追加本段。
6
9
  */
7
10
  import { stat } from "node:fs/promises";
8
11
 
9
12
  import { resolveTicketProvenanceVolume } from "../ticket-provenance.ts";
10
13
 
11
- /** Section heading of the system-delivered dossier pointer. */
12
- export const CASE_DOSSIER_SECTION_HEADING = "## 本票起居录(系统随案提供)" as const;
14
+ /** Section heading of the system-delivered dossier pointer (presentation only). */
15
+ const CASE_DOSSIER_SECTION_HEADING = "## 本票起居录(系统随案提供)" as const;
13
16
 
14
17
  /** Honest one-line state of one dossier file: present, absent, or unreadable. */
15
18
  async function describeDossierFile(path: string): Promise<string> {
@@ -1,9 +1,16 @@
1
1
  /**
2
- * Public Countersign Role run: admit ticket materials → shared post-admission
3
- * coordinator → settle Terminal result (#572 / ADR 0074). #599: manual resume
4
- * continues the exact session. Unbound admission reuses a known ticket identity
5
- * (#709). Who runs 起居郎, and when, is the caller's composition
6
- * (ADR 0010 / ADR 0075 `no-call-rule`) — not this seat's business.
2
+ * Public Countersign Role run: admit ticket materials → court-pipeline prior
3
+ * station (起居郎) → shared post-admission coordinator → settle Terminal result
4
+ * (#572 / ADR 0074 / ADR 0075 / #742). #599: manual resume continues the exact
5
+ * session. Unbound admission reuses a known ticket identity (#709) before the
6
+ * diary station.
7
+ *
8
+ * Court admission auto-runs 起居郎 first, then the countersign body — caller
9
+ * adds no diarist argv (L66672). This is the admission pipeline's prior station,
10
+ * not the countersign role calling 起居郎 (L66958 / L66966). Who may call 起居郎
11
+ * and in what order is not written into law (ADR 0075 `no-call-rule`); the
12
+ * present admission effect is what this seat currently does (L108315 目前是这样用的).
13
+ * 起居录 path delivery is owned once by post-admission (#709 / ADR 0081).
7
14
  */
8
15
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
9
16
  import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.ts";
@@ -11,6 +18,7 @@ import { CliUsageError } from "./cli-errors.ts";
11
18
  import {
12
19
  admitCountersignInvocation,
13
20
  buildCountersignTransportPrompt,
21
+ parseDiaristArgv,
14
22
  type AdmittedCountersignInvocation,
15
23
  type ParseCountersignArgvResult,
16
24
  } from "./invocation.ts";
@@ -46,6 +54,13 @@ import {
46
54
  export type CountersignRunEnv = PostAdmissionEnv & {
47
55
  principalAuthority: DurablePrincipalAuthority;
48
56
  createRunId?: () => string;
57
+ /**
58
+ * Test seam: replace the court-pipeline 起居郎 station.
59
+ * Production leaves this unset and runs the public diarist seat.
60
+ */
61
+ runCourtDiaristStation?: (
62
+ admitted: AdmittedCountersignInvocation,
63
+ ) => Promise<void>;
49
64
  };
50
65
 
51
66
  /** Project admitted invocation onto the host-neutral turn request. */
@@ -68,6 +83,75 @@ export function buildCountersignTurnRequest(
68
83
  );
69
84
  }
70
85
 
86
+ /**
87
+ * Court-pipeline prior station: refresh this ticket's 起居录 before the
88
+ * countersign body turn (ADR 0075 `refresh-every-court`; #742 restore).
89
+ * Caller-invisible — no diarist argv on the countersign command line.
90
+ * Missing ticketNumber (true-unbound) skips the station — no diary is minted
91
+ * for a true-unbound run.
92
+ *
93
+ * Invokes the public 起居郎 seat so the book carries an `@diarist` run ahead of
94
+ * the countersign body. Station failure propagates (失败诚实 — no wash).
95
+ * Path delivery onto materials is not this station's job — post-admission owns it.
96
+ */
97
+ export async function runCountersignCourtDiaristStation(
98
+ admitted: AdmittedCountersignInvocation,
99
+ env: CountersignRunEnv,
100
+ io: CliIo,
101
+ ): Promise<void> {
102
+ if (admitted.ticketNumber === undefined) return;
103
+ if (env.runCourtDiaristStation !== undefined) {
104
+ await env.runCourtDiaristStation(admitted);
105
+ return;
106
+ }
107
+
108
+ // Quiet face: the countersign caller must not see diarist CLI chatter.
109
+ const quietIo: CliIo = {
110
+ stdout() {},
111
+ stderr(text: string) {
112
+ // Surface nested failures onto the parent stderr only; no success noise.
113
+ if (text.trim() !== "") io.stderr(text);
114
+ },
115
+ };
116
+
117
+ const { runPublicDiarist } = await import("./diarist-run.ts");
118
+ const result = await runPublicDiarist(
119
+ [
120
+ "--project",
121
+ admitted.projectRoot,
122
+ `整理 #${admitted.ticketNumber} 的本案依据。`,
123
+ ],
124
+ {
125
+ home: env.home,
126
+ agentDir: env.agentDir,
127
+ packageRoot: env.packageRoot,
128
+ cwd: env.cwd,
129
+ principalAuthority: env.principalAuthority,
130
+ roleTurnHost: env.roleTurnHost,
131
+ sessionAppender: env.sessionAppender,
132
+ ...(env.model === undefined ? {} : { model: env.model }),
133
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
134
+ ...(env.host === undefined ? {} : { host: env.host }),
135
+ ...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
136
+ ...(env.credentials === undefined ? {} : { credentials: env.credentials }),
137
+ ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
138
+ ...(env.signal === undefined ? {} : { signal: env.signal }),
139
+ },
140
+ quietIo,
141
+ parseDiaristArgv,
142
+ );
143
+
144
+ if (result.exitCode !== 0) {
145
+ const detail =
146
+ result.terminal?.roleOutcome.kind === "failure"
147
+ ? ` (roleOutcome=failure)`
148
+ : "";
149
+ throw new Error(
150
+ `court diarist station failed for ticket #${admitted.ticketNumber}: exit ${result.exitCode}${detail}`,
151
+ );
152
+ }
153
+ }
154
+
71
155
  export async function runPublicCountersign(
72
156
  argv: readonly string[],
73
157
  env: CountersignRunEnv,
@@ -174,13 +258,16 @@ export async function runPublicCountersign(
174
258
  io,
175
259
  request: turnRequest,
176
260
  adapters: countersignAdapters({
177
- beforeDispatch: async (admitted) => {
261
+ beforeDispatch: async (admittedSeat) => {
178
262
  // #635/#709: bind the reused identity inside the controlled-failure boundary.
179
- await bindReusedTicketNumber(admitted, reusedTicketNumber);
263
+ await bindReusedTicketNumber(admittedSeat, reusedTicketNumber);
180
264
  Object.assign(
181
265
  turnRequest,
182
- buildCountersignTurnRequest(admitted, turnProjection),
266
+ buildCountersignTurnRequest(admittedSeat, turnProjection),
183
267
  );
268
+ // Court station after bind so the diarist round sees the ticket.
269
+ // Dossier pointer delivery rides post-admission after this hook (#709).
270
+ await runCountersignCourtDiaristStation(admittedSeat, env, io);
184
271
  },
185
272
  }),
186
273
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
@@ -208,9 +295,11 @@ function countersignAdapters(options?: {
208
295
 
209
296
  /**
210
297
  * Resume a previously admitted Countersign run (#599 / DK-3 / #637).
211
- * Restores role/ticket/session identity. Same-ticket summons deliver this turn's
212
- * instruction + frozen attachments on the resume prompt; manual resume keeps
213
- * package-envelope / caller-message semantics and birth attachments.
298
+ * Restores role/ticket/session identity. Every court re-entry runs the diarist
299
+ * station first (ADR 0075 `refresh-every-court`). Same-ticket summons deliver this
300
+ * turn's instruction + frozen attachments on the resume prompt; manual resume
301
+ * keeps package-envelope / caller-message semantics and birth attachments.
302
+ * 起居录 path delivery remains post-admission's single mount (#709).
214
303
  */
215
304
  export async function runPublicCountersignResume(
216
305
  request: PublicResumeRequest,
@@ -246,7 +335,11 @@ export async function runPublicCountersignResume(
246
335
  ),
247
336
  );
248
337
  },
249
- adapters: countersignAdapters(),
338
+ adapters: countersignAdapters({
339
+ beforeDispatch: async (admitted) => {
340
+ await runCountersignCourtDiaristStation(admitted, env, io);
341
+ },
342
+ }),
250
343
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
251
344
  });
252
345
  }
@@ -4,6 +4,9 @@
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";
7
10
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
8
11
  import {
9
12
  NotarySourceRunError,
@@ -26,6 +29,7 @@ import {
26
29
  resumeTurnRequestProjectionOptions,
27
30
  } from "./post-admission.ts";
28
31
  import {
32
+ appendResumeMaterialReread,
29
33
  loadResumableNotaryRun,
30
34
  markRunAdmitted,
31
35
  type PublicResumeRequest,
@@ -234,13 +238,21 @@ export async function runPublicNotaryResume(
234
238
  }
235
239
  return loaded;
236
240
  },
237
- buildTurnRequest: (admitted, effective) =>
238
- // #755: review continuation is plain dialogue / resume envelope — no
239
- //「重新读」candidate line, no engine handbook packaging.
240
- buildNotaryTurnRequest(
241
+ buildTurnRequest: (admitted, effective) => {
242
+ const projection = resumeTurnRequestProjectionOptions(
241
243
  admitted,
242
- resumeTurnRequestProjectionOptions(admitted, effective, env),
243
- ),
244
+ effective,
245
+ env,
246
+ );
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
+ },
244
256
  adapters: notaryAdapters(),
245
257
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
246
258
  });
@@ -11,7 +11,7 @@ import { isAbsolute, join, resolve } from "node:path";
11
11
 
12
12
  import {
13
13
  buildResumeContinuationPrompt,
14
- RESUME_TRANSPORT_ENVELOPE,
14
+ instructResumeHandbookRead,
15
15
  type PublicResumeRequest,
16
16
  type SameTicketSummonsMaterials,
17
17
  } from "./run-lifecycle.ts";
@@ -22,6 +22,7 @@ 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";
25
26
 
26
27
  import type {
27
28
  ControlledFailureCause,
@@ -361,11 +362,11 @@ export async function dispatchPostAdmissionTurn<
361
362
  }
362
363
 
363
364
  // Turn request is assembled after beforeDispatch so this turn sees whatever it
364
- // settled — the seat's ticket bind re-projection. Case dossier delivery
365
- // (ADR 0081) rides here for every public entry: first call, same-ticket
366
- // re-summons and manual resume alike. System refs append their own neutral
367
- // section; caller frozen attachments and the seat's own prompt bytes are
368
- // never rewritten.
365
+ // settled — the seat's ticket bind re-projection and any court diarist station
366
+ // writes (#742). Case dossier delivery (ADR 0081 / #709) rides here once for
367
+ // every public entry: first call, same-ticket re-summons and manual resume
368
+ // alike. System refs append their own neutral section; caller frozen
369
+ // attachments and the seat's own prompt bytes are never rewritten.
369
370
  let turnRequest: RoleTurnRequest =
370
371
  env.signal === undefined ? request : { ...request, signal: env.signal };
371
372
  if (hostTransition !== undefined) {
@@ -493,18 +494,15 @@ export async function dispatchPostAdmissionTurn<
493
494
  }
494
495
 
495
496
  /**
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.
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.
506
504
  * Seats add only their activation projection. Call prepareSummonsResumeMaterials
507
- * first when request.summons carries instruction or attachment paths.
505
+ * first when request.summons carries attachment paths.
508
506
  */
509
507
  export function resumeTurnRequestProjectionOptions(
510
508
  admitted: AdmittedRoleInvocation,
@@ -516,21 +514,32 @@ export function resumeTurnRequestProjectionOptions(
516
514
  readonly attachments: readonly { frozenPath: string }[];
517
515
  },
518
516
  ): RoleTurnRequestProjectionOptions {
517
+ const engineMaterial = engineSessionMaterialFromOptions({
518
+ ...(env.engine === undefined ? {} : { engine: env.engine }),
519
+ packageRoot: env.packageRoot,
520
+ });
519
521
  let prompt: string;
520
522
  if (request.message !== undefined) {
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;
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
+ );
532
542
  } else {
533
- // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
534
543
  prompt = buildResumeContinuationPrompt({
535
544
  packageRoot: env.packageRoot,
536
545
  ...(env.engine === undefined ? {} : { engine: env.engine }),
@@ -538,14 +547,11 @@ export function resumeTurnRequestProjectionOptions(
538
547
  });
539
548
  }
540
549
  } else if (summonsPrepared !== undefined) {
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;
550
+ prompt = instructResumeHandbookRead(
551
+ buildInstructionTransportPrompt(summonsPrepared, engineMaterial),
552
+ engineMaterial,
553
+ );
547
554
  } else {
548
- // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
549
555
  prompt = buildResumeContinuationPrompt({
550
556
  packageRoot: env.packageRoot,
551
557
  ...(env.engine === undefined ? {} : { engine: env.engine }),
@@ -163,10 +163,11 @@ function resumeRereadInstruction(materialPath: string, handbook: boolean): strin
163
163
  }
164
164
 
165
165
  /**
166
- * Engine-axis resume-only material lines (#736 / ADR 0069·0071).
167
- * #755 withdrew this rewrite from 审核循环续话; review same-ticket resume stays
168
- * plain dialogue via resumeTurnRequestProjectionOptions. Outsourcing resume
169
- * still rewrites neutral handbook/path pointers into 重新读 instructions.
166
+ * Resume-only material lines (#736 ticket exemption to ADR 0073 §3).
167
+ * Neutral `- <absolute path>` pointers already on the continuation
168
+ * (handbook from appendEngineSessionMaterial; frozen attachments from
169
+ * instruction-seat transport) become reread instructions.
170
+ * Handbook keeps the argv suffix; other materials are path-only.
170
171
  * First-round delivery is unchanged; never pastes material body.
171
172
  */
172
173
  export function instructResumeHandbookRead(
@@ -188,9 +189,19 @@ export function instructResumeHandbookRead(
188
189
  .join("\n");
189
190
  }
190
191
 
192
+ /** Append a live-path reread instruction when the pointer is not already on the continuation. */
193
+ export function appendResumeMaterialReread(
194
+ prompt: string,
195
+ materialPath: string,
196
+ ): string {
197
+ const instruction = resumeRereadInstruction(materialPath, false);
198
+ const lines = prompt.split("\n");
199
+ if (lines.includes(instruction)) return prompt;
200
+ return prompt.length === 0 ? instruction : `${prompt}\n${instruction}`;
201
+ }
202
+
191
203
  /**
192
- * Unique continuation-prompt selector for manual/auto engine-axis resume
193
- * (#471 / #600 / #736). Not used for 审核循环 same-ticket summons (#755).
204
+ * Unique continuation-prompt selector for manual/auto resume (#471 / #600 / #736).
194
205
  * Message present → base bytes unchanged; absent → package transport envelope.
195
206
  * When engine material is present, append structured engine coordinates then
196
207
  * rewrite absolute material pointer lines into 重新读 instructions. Zero parse,