@akagilnc/pi-workflow-roles 0.1.5598 → 0.1.5614

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 (43) hide show
  1. package/dist/acp-host/production-host.js +406 -366
  2. package/dist/compliance-transport.js +2 -5
  3. package/dist/doctor-auditor.js +1 -1
  4. package/dist/gatekeeper-role.js +1 -1
  5. package/dist/headless-host/production-host.js +370 -330
  6. package/dist/migrate-book-topology.js +301 -291
  7. package/dist/navigator-public-session.js +4 -4
  8. package/dist/navigator-work-base.js +5 -30
  9. package/dist/packaged-role-registry.js +4 -2
  10. package/dist/pi/role-turn-host.js +10 -1
  11. package/dist/public-cli/instruction-seat-run.js +133 -38
  12. package/dist/public-cli/invocation.js +23 -26
  13. package/dist/public-cli/main.js +250 -179
  14. package/dist/public-cli/option-definitions.js +19 -0
  15. package/dist/public-cli/post-admission.js +23 -22
  16. package/dist/public-cli/run-lifecycle.js +2 -14
  17. package/dist/public-cli/turn-request.js +4 -9
  18. package/dist/public-role-summons.js +5 -3
  19. package/dist/role-run-placement.js +7 -1
  20. package/package.json +1 -1
  21. package/scripts/build-package.mjs +2 -3
  22. package/src/audited-run-identity.ts +30 -0
  23. package/src/compliance-transport.ts +1 -4
  24. package/src/doctor-auditor.ts +1 -1
  25. package/src/gatekeeper-role.ts +1 -1
  26. package/src/host-contracts.ts +6 -1
  27. package/src/navigator-public-session.ts +9 -5
  28. package/src/navigator-work-base.ts +19 -34
  29. package/src/notary-role.ts +12 -21
  30. package/src/packaged-role-registry.ts +4 -4
  31. package/src/pi/role-turn-host.ts +11 -1
  32. package/src/public-cli/instruction-seat-run.ts +162 -42
  33. package/src/public-cli/invocation.ts +35 -26
  34. package/src/public-cli/option-definitions.ts +20 -0
  35. package/src/public-cli/post-admission.ts +28 -22
  36. package/src/public-cli/run-lifecycle.ts +2 -17
  37. package/src/public-cli/turn-request.ts +9 -8
  38. package/src/public-role-summons.ts +17 -6
  39. package/src/role-envelope.ts +4 -0
  40. package/src/role-run-placement.ts +10 -1
  41. package/src/role-runtime.ts +54 -31
  42. package/dist/auditor-dossier-tool.js +0 -47
  43. package/src/auditor-dossier-tool.ts +0 -63
@@ -4,8 +4,7 @@
4
4
  * starts two ordinary single-axis runs here.
5
5
  */
6
6
  import { dirname, resolve } from "node:path";
7
- import { isUnboundRunDirectory } from "../role-run-placement.ts";
8
- import { AUDITOR_DOSSIER_PROMPT } from "../compliance-transport.ts";
7
+ import { isUnboundRunDirectory, sessionFileOf } from "../role-run-placement.ts";
9
8
  import { bookDirectOfficerRunPointer, readDirectOfficerRunPointer } from "../archivist-record-pointer.ts";
10
9
  import { createPiDoctorAuditor } from "../doctor-auditor.ts";
11
10
 
@@ -42,6 +41,7 @@ import { CliUsageError } from "./cli-errors.ts";
42
41
  import {
43
42
  admitPublicRole,
44
43
  bindAdmittedTicketNumber,
44
+ appendCallerFileFlagPaths,
45
45
  buildInstructionTransportPrompt,
46
46
  materializeCountersignInvocation,
47
47
  persistAdmittedSourceRunPath,
@@ -72,7 +72,6 @@ import {
72
72
  buildAutoResumeContinuationPrompt,
73
73
  loadResumablePublicRole,
74
74
  markRunAdmitted,
75
- parentRunPathFromGatePointerInstruction,
76
75
  readCurrentCourt,
77
76
  type PublicResumeRequest,
78
77
  type RunWriterLease,
@@ -291,14 +290,80 @@ export function buildInstructionSeatTurnRequest(
291
290
  );
292
291
  }
293
292
 
294
- function initialPrompt(
293
+ /**
294
+ * Direct review-seat call: reconnect ledger peer-body delivery (ADR 0085 / #1166).
295
+ * Gate summons already ride gateReviewInstruction; direct --source-run must not
296
+ * leave a blank fixed-kickoff.
297
+ */
298
+ async function loadLedgerPeerBody(
299
+ sourceRunDirectory: string,
300
+ projectRoot: string,
301
+ home: string,
302
+ ): Promise<string | undefined> {
303
+ const runId = runIdFromRunDirectory(sourceRunDirectory);
304
+ if (runId === undefined) return undefined;
305
+ // Bind to the already-resolved source directory — never re-discover by runId
306
+ // alone (another book may lawfully share the same id; #637 / #1166).
307
+ const rows = await readRecordedSubmissionRows(projectRoot, runId, {
308
+ home,
309
+ sessionParent: sessionFileOf(sourceRunDirectory),
310
+ });
311
+ // ADR 0085 / #1166 J10: ledger already keeps original words for every class —
312
+ // present the latest row as-is. Never filter current manuscript by accepted /
313
+ // terminal outcome kind (that resurrects a prior sealed draft).
314
+ const latest = rows.at(-1);
315
+ if (latest === undefined) return undefined;
316
+ return readableGateItem(latest.accepted);
317
+ }
318
+
319
+ /**
320
+ * One delivery rule for review-seat dialogue on new turns and same-parent resume
321
+ * (#1166 / ADR 0085 / ADR 0087): explicit reask → non-empty caller dispatch →
322
+ * source-only ledger peer body. Identity stays in startup materials.
323
+ */
324
+ async function resolveReviewSeatDialogueBody(input: {
325
+ readonly reask?: string;
326
+ readonly callerInstruction?: string;
327
+ readonly sourceRunPath?: string;
328
+ readonly projectRoot: string;
329
+ readonly home: string;
330
+ }): Promise<string | undefined> {
331
+ const reask = input.reask;
332
+ if (reask !== undefined && reask.length > 0) return reask;
333
+ const caller = input.callerInstruction ?? "";
334
+ if (caller.length > 0) return caller;
335
+ const sourceRunPath = input.sourceRunPath?.trim() ?? "";
336
+ if (sourceRunPath === "") return undefined;
337
+ return await loadLedgerPeerBody(sourceRunPath, input.projectRoot, input.home);
338
+ }
339
+
340
+ function admittedSourceRunPath(admitted: AdmittedRoleInvocation): string {
341
+ return "sourceRunPath" in admitted
342
+ && typeof (admitted as { sourceRunPath?: unknown }).sourceRunPath === "string"
343
+ ? (admitted as { sourceRunPath: string }).sourceRunPath.trim()
344
+ : "";
345
+ }
346
+
347
+ async function resolveInitialPrompt(
295
348
  admitted: AdmittedRoleInvocation,
296
349
  env: InstructionSeatRunEnv,
297
- ): string {
350
+ ): Promise<string> {
298
351
  const record = roleRecord(admitted.role);
299
- const reask = env.reviewReask ?? env.gateReviewInstruction;
300
- if (reask !== undefined && "reaskPrompt" in record && record.reaskPrompt === true) {
301
- return reask;
352
+ if ("reaskPrompt" in record && record.reaskPrompt === true) {
353
+ const sourceRunPath = admittedSourceRunPath(admitted);
354
+ const reask = env.reviewReask ?? env.gateReviewInstruction;
355
+ const body = await resolveReviewSeatDialogueBody({
356
+ ...(reask === undefined ? {} : { reask }),
357
+ callerInstruction: admitted.instruction,
358
+ ...(sourceRunPath === "" ? {} : { sourceRunPath }),
359
+ projectRoot: admitted.projectRoot,
360
+ home: env.home,
361
+ });
362
+ // #1166 J11: merge existing file-flag delivery — never early-return body alone
363
+ // when caller attach paths were admitted. Gate with no file flags stays body-only.
364
+ if (body !== undefined) {
365
+ return appendCallerFileFlagPaths(body, admitted.attachments);
366
+ }
302
367
  }
303
368
  return buildInstructionTransportPrompt(admitted);
304
369
  }
@@ -417,6 +482,7 @@ async function dispatchAdmitted(
417
482
  if (activeEnv.correlationId !== undefined && activeEnv.correlationId.trim() !== "") {
418
483
  await recordAdmittedCorrelation(admitted, activeEnv.correlationId);
419
484
  }
485
+ const initialPromptText = await resolveInitialPrompt(admitted, activeEnv);
420
486
  const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
421
487
  if (auto) {
422
488
  return await runPostAdmissionResumable({
@@ -425,7 +491,10 @@ async function dispatchAdmitted(
425
491
  io: turnIo,
426
492
  buildInitialRequest: () => buildInstructionSeatTurnRequest(
427
493
  admitted,
428
- roleTurnOptions(activeEnv, admitted, { kind: "initial", prompt: initialPrompt(admitted, activeEnv) }),
494
+ roleTurnOptions(activeEnv, admitted, {
495
+ kind: "initial",
496
+ prompt: initialPromptText,
497
+ }),
429
498
  ),
430
499
  buildResumeRequest: () => buildInstructionSeatTurnRequest(
431
500
  admitted,
@@ -444,7 +513,10 @@ async function dispatchAdmitted(
444
513
  io: turnIo,
445
514
  request: buildInstructionSeatTurnRequest(
446
515
  admitted,
447
- roleTurnOptions(activeEnv, admitted, { kind: "initial", prompt: initialPrompt(admitted, activeEnv) }),
516
+ roleTurnOptions(activeEnv, admitted, {
517
+ kind: "initial",
518
+ prompt: initialPromptText,
519
+ }),
448
520
  ),
449
521
  adapters,
450
522
  ...(activeEnv.engine === undefined ? {} : { effectiveEngine: activeEnv.engine }),
@@ -580,14 +652,28 @@ function resumeSameParentInstructionSeat(input: {
580
652
  });
581
653
  }
582
654
 
583
- /** Re-ask words when the parent supplied them; otherwise the caller's own instruction. */
584
- function sameParentInstruction(
655
+ /**
656
+ * Same delivery rule as resolveInitialPrompt for same-parent resume summons.
657
+ * Reask / explicit caller text / source-only ledger peer — one authority.
658
+ */
659
+ async function sameParentDialogue(
585
660
  env: InstructionSeatRunEnv,
586
661
  fallback: { readonly instruction: string; readonly instructionEmpty: boolean },
587
- ): { readonly instruction: string; readonly instructionEmpty: boolean } {
588
- const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
589
- if (resumeInstruction === undefined) return fallback;
590
- return { instruction: resumeInstruction, instructionEmpty: false };
662
+ sourceRunPath: string,
663
+ projectRoot: string,
664
+ ): Promise<{ readonly instruction: string; readonly instructionEmpty: boolean }> {
665
+ const reask = env.reviewReask ?? env.gateReviewInstruction;
666
+ const body = await resolveReviewSeatDialogueBody({
667
+ ...(reask === undefined ? {} : { reask }),
668
+ callerInstruction: fallback.instruction,
669
+ sourceRunPath,
670
+ projectRoot,
671
+ home: env.home,
672
+ });
673
+ if (body !== undefined) {
674
+ return { instruction: body, instructionEmpty: false };
675
+ }
676
+ return fallback;
591
677
  }
592
678
 
593
679
  /**
@@ -618,6 +704,9 @@ async function runCountersignBody(
618
704
  sourceRunPath: gateParentRunPath,
619
705
  instruction: resumeInstruction,
620
706
  instructionEmpty: resumeInstruction.length === 0,
707
+ // #1166 J11: same-parent resume delivers this call's file-flag paths
708
+ // through the shared summons → appendCallerFileFlagPaths rule.
709
+ attachmentPaths: parsed.attachmentPaths ?? [],
621
710
  },
622
711
  });
623
712
  if (resumed != null) return resumed;
@@ -652,10 +741,14 @@ async function runCountersignBody(
652
741
  await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
653
742
  admitted = { ...admitted, sourceRunPath: gateParentRunPath };
654
743
  }
744
+ // #1166 J11: review-body branch must merge the same file-flag delivery as
745
+ // resolveInitialPrompt / resumeTurnRequestProjectionOptions — never body alone.
746
+ const reviewBody = env.reviewReask ?? env.gateReviewInstruction;
655
747
  const turnProjection = roleTurnOptions(env, admitted, {
656
748
  kind: "initial",
657
- prompt: (env.reviewReask ?? env.gateReviewInstruction)
658
- ?? buildInstructionTransportPrompt(admitted),
749
+ prompt: reviewBody !== undefined
750
+ ? appendCallerFileFlagPaths(reviewBody, admitted.attachments)
751
+ : buildInstructionTransportPrompt(admitted),
659
752
  });
660
753
  const turnRequest = buildInstructionSeatTurnRequest(admitted, turnProjection);
661
754
  const result = await runPostAdmissionOneShot({
@@ -746,10 +839,15 @@ export async function runPublicInstructionSeat(
746
839
  if (auditorSource === undefined) return { exitCode: 2 };
747
840
  const sourceDirectory = auditorSource;
748
841
  const summons: SameTicketSummonsMaterials = {
749
- ...sameParentInstruction(env, {
750
- instruction: parsed.instruction ?? "",
751
- instructionEmpty: (parsed.instruction ?? "").length === 0,
752
- }),
842
+ ...(await sameParentDialogue(
843
+ env,
844
+ {
845
+ instruction: parsed.instruction ?? "",
846
+ instructionEmpty: (parsed.instruction ?? "").length === 0,
847
+ },
848
+ sourceDirectory,
849
+ projectRoot,
850
+ )),
753
851
  attachmentPaths: parsed.attachmentPaths ?? [],
754
852
  };
755
853
  const resumed = await withAuditorSoulEnv({
@@ -767,15 +865,34 @@ export async function runPublicInstructionSeat(
767
865
  if (resumed != null) return resumed;
768
866
  }
769
867
 
770
- if (record.sameParent === "gate-pointer") {
771
- const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction ?? "");
772
- if (parentRunPath !== undefined) {
868
+ if (record.sameParent === "source-run") {
869
+ // #1166: structured --source-run only. Instruction bytes stay opaque caller/peer text.
870
+ if (parsed.sourceRun !== undefined) {
871
+ let parentRunPath: string;
872
+ try {
873
+ parentRunPath = (await resolveNotarySourceRunLocator({
874
+ projectRoot,
875
+ sourceRun: parsed.sourceRun,
876
+ home: env.home,
877
+ })).runDirectory;
878
+ } catch (error) {
879
+ if (error instanceof NotarySourceRunError) {
880
+ presentStructuralRejection(new CliUsageError(error.message, { cause: error }), io);
881
+ return { exitCode: 2 };
882
+ }
883
+ throw error;
884
+ }
773
885
  const summons: SameTicketSummonsMaterials = {
774
886
  sourceRunPath: parentRunPath,
775
- ...sameParentInstruction(env, {
776
- instruction: parsed.instruction ?? "",
777
- instructionEmpty: (parsed.instruction ?? "").length === 0,
778
- }),
887
+ ...(await sameParentDialogue(
888
+ env,
889
+ {
890
+ instruction: parsed.instruction ?? "",
891
+ instructionEmpty: (parsed.instruction ?? "").length === 0,
892
+ },
893
+ parentRunPath,
894
+ projectRoot,
895
+ )),
779
896
  attachmentPaths: parsed.attachmentPaths ?? [],
780
897
  };
781
898
  const resumed = await resumeSameParentInstructionSeat({
@@ -805,13 +922,15 @@ export async function runPublicInstructionSeat(
805
922
  }
806
923
  throw error;
807
924
  }
808
- const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
809
925
  const summons: SameTicketSummonsMaterials = {
810
926
  sourceRunPath: source.runDirectory,
811
927
  sourceRun: source,
812
- ...(resumeInstruction === undefined
813
- ? {}
814
- : { instruction: resumeInstruction, instructionEmpty: false }),
928
+ ...(await sameParentDialogue(
929
+ env,
930
+ { instruction: "", instructionEmpty: true },
931
+ source.runDirectory,
932
+ projectRoot,
933
+ )),
815
934
  };
816
935
  resolvedNotarySource = source;
817
936
  const resumed = await resumeSameParentInstructionSeat({
@@ -843,16 +962,18 @@ export async function runPublicInstructionSeat(
843
962
  throw error;
844
963
  }
845
964
 
846
- if (record.sameParent === "gate-pointer") {
847
- const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction ?? "");
848
- if (parentRunPath !== undefined) {
849
- await persistAdmittedSourceRunPath(admitted, parentRunPath);
850
- const sourcePatch = { sourceRunPath: parentRunPath };
851
- admitted = { ...admitted, ...sourcePatch };
852
- }
965
+ if (record.sameParent === "source-run" && parsed.sourceRun !== undefined) {
966
+ const parentRunPath = (await resolveNotarySourceRunLocator({
967
+ projectRoot,
968
+ sourceRun: parsed.sourceRun,
969
+ home: env.home,
970
+ })).runDirectory;
971
+ await persistAdmittedSourceRunPath(admitted, parentRunPath);
972
+ admitted = { ...admitted, sourceRunPath: parentRunPath } as AdmittedRoleInvocation;
853
973
  }
854
974
  if (record.sameParent === "subject-source" && auditorSource !== undefined) {
855
975
  await persistAdmittedSourceRunPath(admitted, auditorSource, auditorSubject);
976
+ admitted = { ...admitted, sourceRunPath: auditorSource } as AdmittedRoleInvocation;
856
977
  }
857
978
 
858
979
  const runAdmitted = async (): Promise<SeatRunResult> => {
@@ -1247,8 +1368,7 @@ async function auditSubmittedRole(
1247
1368
  summonAuditor: async (subject, sourceRunDirectory, signal, reask, submission) => {
1248
1369
  const summoned = await summonPublicRole({
1249
1370
  role: "auditor",
1250
- argv: ["--subject", subject, "--source-run", sourceRunDirectory,
1251
- `${AUDITOR_DOSSIER_PROMPT}${sourceRunDirectory}`],
1371
+ argv: ["--subject", subject, "--source-run", sourceRunDirectory],
1252
1372
  cwd: admitted.projectRoot, home: env.home, packageRoot: env.packageRoot,
1253
1373
  principalAuthority: env.principalAuthority,
1254
1374
  correlationId: admitted.runId,
@@ -183,6 +183,8 @@ export type AdmittedNavigatorInvocation = AdmittedRoleInvocationBase & {
183
183
 
184
184
  export type AdmittedAuditorInvocation = AdmittedRoleInvocationBase & {
185
185
  readonly role: "auditor";
186
+ /** Audited source-run directory (#1166 identity / ADR 0085 peer-body load). */
187
+ readonly sourceRunPath?: string;
186
188
  };
187
189
 
188
190
  export type AdmittedDiaristInvocation = AdmittedRoleInvocationBase & {
@@ -1817,10 +1819,32 @@ function admittedTransportPromptKind(
1817
1819
  return "instruction";
1818
1820
  }
1819
1821
 
1822
+ /** Append opaque caller file-flag paths after dialogue (#1165 / #1166 J11 / #1168). */
1823
+ export function appendCallerFileFlagPaths(
1824
+ body: string,
1825
+ attachments: readonly { readonly path: string }[],
1826
+ requestManifestPath?: string,
1827
+ prerequisitesPath?: string,
1828
+ ): string {
1829
+ const flaggedPaths = [
1830
+ ...attachments.map((attachment) => `--attach ${attachment.path}`),
1831
+ ...(requestManifestPath === undefined
1832
+ ? []
1833
+ : [`--request-manifest ${requestManifestPath}`]),
1834
+ ...(prerequisitesPath === undefined
1835
+ ? []
1836
+ : [`--prerequisites ${prerequisitesPath}`]),
1837
+ ];
1838
+ if (flaggedPaths.length === 0) return body;
1839
+ // ADR 0087 / #1165 / #1166 J13: after caller words, only necessary file flags
1840
+ // and original paths — no package-authored title or list wrapper.
1841
+ return [body, "", ...flaggedPaths].join("\n");
1842
+ }
1843
+
1820
1844
  /**
1821
1845
  * One initial prompt transport. The registry `transportPrompt` leaf selects
1822
- * a fixed kickoff, a bound baseline, or frozen skill args. Absent means the
1823
- * caller instruction plus caller attachment paths as-is (#1165).
1846
+ * a fixed kickoff, a bound baseline, or skill args. Absent means the caller
1847
+ * instruction plus caller file-flag paths (ADR 0087).
1824
1848
  * Engine / outsourcing material rides startup readingMaterial (#1167), not here.
1825
1849
  */
1826
1850
  export function buildInstructionTransportPrompt(
@@ -1828,10 +1852,9 @@ export function buildInstructionTransportPrompt(
1828
1852
  ): string {
1829
1853
  const kind = admittedTransportPromptKind(admitted);
1830
1854
  if (kind === "fixed-kickoff") {
1831
- if (admitted.sourceRunPath === undefined || admitted.sourceRunPath.trim() === "") {
1832
- throw new Error("fixed-kickoff transport prompt is missing the source run pointer");
1833
- }
1834
- return admitted.sourceRunPath;
1855
+ // #1166 / ADR 0087: audited-run identity rides startup materials; dialogue
1856
+ // prompt is peer body / reask only — never a package-computed directory path.
1857
+ return "";
1835
1858
  }
1836
1859
  if (kind === "baseline") {
1837
1860
  if (admitted.baseRevision === undefined) {
@@ -1863,26 +1886,12 @@ export function buildInstructionTransportPrompt(
1863
1886
  }
1864
1887
  return lines.join("\n");
1865
1888
  }
1866
- // #1165 J3 / #1168: assemble from the admitted instruction bytes themselves.
1867
- const lines: string[] = [admitted.instruction];
1868
- // #1164/#1165/#1168: each caller path keeps its file-flag provenance.
1869
- const flaggedPaths: string[] = [
1870
- ...admitted.attachments.map((attachment) => `--attach ${attachment.path}`),
1871
- ...(admitted.requestManifestPath === undefined
1872
- ? []
1873
- : [`--request-manifest ${admitted.requestManifestPath}`]),
1874
- ...(admitted.prerequisitesPath === undefined
1875
- ? []
1876
- : [`--prerequisites ${admitted.prerequisitesPath}`]),
1877
- ];
1878
- if (flaggedPaths.length > 0) {
1879
- lines.push("");
1880
- lines.push("已受理附件:");
1881
- for (const entry of flaggedPaths) {
1882
- lines.push(`- ${entry}`);
1883
- }
1884
- }
1885
- return lines.join("\n");
1889
+ return appendCallerFileFlagPaths(
1890
+ admitted.instruction,
1891
+ admitted.attachments,
1892
+ admitted.requestManifestPath,
1893
+ admitted.prerequisitesPath,
1894
+ );
1886
1895
  }
1887
1896
 
1888
1897
  export type AdmitCountersignInvocationOptions = {
@@ -612,6 +612,25 @@ const NOTARY_OPTIONS = [
612
612
  },
613
613
  ] as const satisfies readonly PublicOptionDefinition[];
614
614
 
615
+ /** 台院: optional --source-run binds the audited leg; gate summons always pass it (#1166). */
616
+ const INSPECTOR_OPTIONS = [
617
+ ...projectAndAttach("inspector"),
618
+ {
619
+ id: "source-run",
620
+ owner: "inspector",
621
+ canonical: "--source-run",
622
+ aliases: [],
623
+ valueMetavar: "runId@role|path",
624
+ required: false,
625
+ repeatable: false,
626
+ form: "option",
627
+ description: {
628
+ en: "Optional audited source-run locator (runId@role or path). Gate summons always supply it; identity rides startup materials.",
629
+ zh: "可选被审腿定位符(runId@role 或路径)。闸传召必带;身份在启动材料里。",
630
+ },
631
+ },
632
+ ] as const satisfies readonly PublicOptionDefinition[];
633
+
615
634
  const MERGER_OPTIONS = [
616
635
  // Merger project face: worktree whose Git materials are handed to the role.
617
636
  {
@@ -779,6 +798,7 @@ const SEAT_OPTION_ROWS: Partial<
779
798
  merger: MERGER_OPTIONS,
780
799
  notary: NOTARY_OPTIONS,
781
800
  auditor: AUDITOR_OPTIONS,
801
+ inspector: INSPECTOR_OPTIONS,
782
802
  };
783
803
 
784
804
  function seatOptionRows(role: PublicCallableRole): readonly PublicOptionDefinition[] {
@@ -19,8 +19,8 @@ import {
19
19
  import { CliUsageError } from "./cli-errors.ts";
20
20
  import { projectPublicTurnAxes, type RoleTurnRequestProjectionOptions } from "./turn-request.ts";
21
21
  import {
22
+ appendCallerFileFlagPaths,
22
23
  bindAdmittedTicketNumber,
23
- buildInstructionTransportPrompt,
24
24
  relocateAdmittedRunToTicket,
25
25
  } from "./invocation.ts";
26
26
  import { readRecordedSubmissionRows } from "../submission-ledger.ts";
@@ -326,6 +326,11 @@ export type PostAdmissionEnv = {
326
326
  boundTicketNumber?: number;
327
327
  /** Station child role run (#840): omit automatic navigator attendance. */
328
328
  stationChild?: boolean;
329
+ /**
330
+ * Navigator nest work-context locator (#1166). Request-scoped; never folded
331
+ * into dialogue. Child spawn may mirror onto child env only.
332
+ */
333
+ navigatorWorkContextPath?: string;
329
334
  };
330
335
 
331
336
  /**
@@ -1458,8 +1463,8 @@ export async function dispatchPostAdmissionTurn<
1458
1463
  * Shared resume continuation projection (#471 / #600 / #633 / #637 / #755 / #879):
1459
1464
  * seat-table model/engine/timeout axes, restored correlation, and either
1460
1465
  * - manual resume (no same-ticket summons): caller message bytes, or
1461
- * - same-ticket summons (审核循环续话): caller/peer words + optional frozen
1462
- * attachment paths only — no「请重读」、no code-authored content substitute,
1466
+ * - same-ticket summons (审核循环续话): caller/peer words + optional caller file-flag
1467
+ * paths only — no「请重读」、no code-authored content substitute,
1463
1468
  * no engine handbook packaging (#750/#755/#879).
1464
1469
  * Caller message wins as prompt base when supplied (bytes unchanged, including
1465
1470
  * blank/whitespace); else summons instruction (parent payload or reask).
@@ -1477,22 +1482,17 @@ export function resumeTurnRequestProjectionOptions(
1477
1482
  readonly attachments: readonly { path: string }[];
1478
1483
  },
1479
1484
  ): RoleTurnRequestProjectionOptions {
1480
- // #879: officer dialogue content is caller/peer words only — no「请重读」,
1481
- // no code-authored constant substitute, no attachment-list wrap of peer body.
1482
- // Binding pointer stays on summons sourceRunPath / activation / attachments.
1485
+ // #879: officer dialogue is caller/peer words; #1166 J11 merges caller file-flag
1486
+ // paths when present. Gate with no file flags stays body-only (ADR 0085).
1483
1487
  const officerDialogue = isStationChildOfficerDialogue(admitted.role, env);
1488
+ const fileFlags = summonsPrepared?.attachments ?? [];
1484
1489
  let prompt: string;
1485
1490
  if (request.message !== undefined) {
1486
1491
  if (summonsPrepared !== undefined) {
1487
- // #755: same-ticket review / open-court — caller words.
1488
- // #879 station-child officer: words only (paths remain on summons materials).
1489
- prompt = officerDialogue
1492
+ // #755: same-ticket review / open-court — caller words + caller file flags.
1493
+ prompt = officerDialogue && fileFlags.length === 0
1490
1494
  ? request.message
1491
- : buildInstructionTransportPrompt({
1492
- instruction: request.message,
1493
- instructionEmpty: false,
1494
- attachments: summonsPrepared.attachments,
1495
- });
1495
+ : appendCallerFileFlagPaths(request.message, fileFlags);
1496
1496
  } else if (request.summons !== undefined) {
1497
1497
  // #755: same-ticket summons without prepared materials — caller words only.
1498
1498
  prompt = request.message;
@@ -1500,13 +1500,11 @@ export function resumeTurnRequestProjectionOptions(
1500
1500
  prompt = request.message;
1501
1501
  }
1502
1502
  } else if (summonsPrepared !== undefined) {
1503
- // #879 station-child officer: instruction bytes === peer body/reask (no wrap).
1504
- // Other seats keep #755 instruction + optional attachment path listing.
1505
- // #1165 J3: officer dialogue is raw instruction bytes; do not gate on
1506
- // instructionEmpty metadata (resume of older admitted pages).
1507
- prompt = officerDialogue
1508
- ? summonsPrepared.instruction
1509
- : buildInstructionTransportPrompt(summonsPrepared);
1503
+ const body = summonsPrepared.instruction;
1504
+ // No file flags: body alone (gate peer words). With flags: merge delivery.
1505
+ prompt = officerDialogue && fileFlags.length === 0
1506
+ ? body
1507
+ : appendCallerFileFlagPaths(body, fileFlags);
1510
1508
  } else if (request.summons !== undefined) {
1511
1509
  // #879: same-ticket summons with no instruction (e.g. notary source-run binding
1512
1510
  // only). Pointer is activation/sourceRun material — not dialogue content.
@@ -1527,6 +1525,10 @@ export function resumeTurnRequestProjectionOptions(
1527
1525
  ...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
1528
1526
  // #1132: one configured ceiling, already resolved by the caller (#422).
1529
1527
  deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
1528
+ ...(env.navigatorWorkContextPath === undefined
1529
+ || env.navigatorWorkContextPath.trim() === ""
1530
+ ? {}
1531
+ : { navigatorWorkContextPath: env.navigatorWorkContextPath }),
1530
1532
  };
1531
1533
  }
1532
1534
 
@@ -1547,6 +1549,10 @@ export function roleTurnOptions(
1547
1549
  ...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
1548
1550
  // #1132: one configured ceiling, already resolved by the caller (#422).
1549
1551
  deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
1552
+ ...(env.navigatorWorkContextPath === undefined
1553
+ || env.navigatorWorkContextPath.trim() === ""
1554
+ ? {}
1555
+ : { navigatorWorkContextPath: env.navigatorWorkContextPath }),
1550
1556
  };
1551
1557
  }
1552
1558
 
@@ -1668,7 +1674,7 @@ export async function prepareSummonsResumeMaterials(
1668
1674
  * validation, turn builder, and adapters stay on the seat.
1669
1675
  *
1670
1676
  * Court handling (#637): public manual resume reads only the open court's
1671
- * settlement identity; internal re-summons may freeze and record its materials.
1677
+ * settlement identity; internal re-summons passes caller file-flag paths as-is.
1672
1678
  */
1673
1679
  export async function runPostAdmissionSeatResume<
1674
1680
  A extends AdmittedRoleInvocation,
@@ -885,21 +885,9 @@ export async function findRunDirectoryById(
885
885
  );
886
886
  }
887
887
 
888
- /** Code-owned gate inspector summons prefix (public-role-summons / #747). */
889
- export const GATE_DOSSIER_POINTER_PREFIX = "卷宗指针:" as const;
890
-
891
- /** Parent path from a code-owned inspector 卷宗指针 instruction; else undefined. */
892
- export function parentRunPathFromGatePointerInstruction(
893
- instruction: string,
894
- ): string | undefined {
895
- if (!instruction.startsWith(GATE_DOSSIER_POINTER_PREFIX)) return undefined;
896
- const path = instruction.slice(GATE_DOSSIER_POINTER_PREFIX.length).trim();
897
- return path === "" ? undefined : path;
898
- }
899
-
900
888
  /**
901
- * Parent-run binding on a retained officer run (#747).
902
- * Notary/auditor: typed sourceRunPath. Inspector: exact code-owned 卷宗指针 instruction.
889
+ * Parent-run binding on a retained officer run (#747 / #1166).
890
+ * Typed sourceRunPath only — instruction bytes are never a path protocol.
903
891
  * Missing page → undefined; damage / non-ENOENT IO propagates.
904
892
  */
905
893
  export async function readRunParentPath(
@@ -910,9 +898,6 @@ export async function readRunParentPath(
910
898
  if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
911
899
  return record.sourceRunPath;
912
900
  }
913
- if (typeof record.instruction === "string") {
914
- return parentRunPathFromGatePointerInstruction(record.instruction);
915
- }
916
901
  return undefined;
917
902
  }
918
903
 
@@ -17,7 +17,6 @@ import {
17
17
  import type { SeatModelConfig } from "./config.ts";
18
18
  import type { AdmittedRoleInvocation } from "./invocation.ts";
19
19
  import { installedMethodSkillPath } from "./machine-method-skills.ts";
20
- import { parentRunPathFromGatePointerInstruction } from "./run-lifecycle.ts";
21
20
  import type { PublicThinkingLevel } from "./registry.ts";
22
21
 
23
22
  /** Structural model shape shared by the seam so seat/env sources fit. */
@@ -77,6 +76,11 @@ export type RoleTurnRequestProjectionOptions = {
77
76
  * execution seam count with the same configured number.
78
77
  */
79
78
  readonly deliveryRequestLimit?: number;
79
+ /**
80
+ * Navigator nest work-context locator (#1166). Request-scoped internal path —
81
+ * not dialogue content.
82
+ */
83
+ readonly navigatorWorkContextPath?: string;
80
84
  };
81
85
 
82
86
  export type AdmittedTurnInvocation = {
@@ -127,6 +131,10 @@ export function projectRoleTurnRequest(
127
131
  ...(options.deliveryRequestLimit === undefined
128
132
  ? {}
129
133
  : { deliveryRequestLimit: options.deliveryRequestLimit }),
134
+ ...(options.navigatorWorkContextPath === undefined
135
+ || options.navigatorWorkContextPath.trim() === ""
136
+ ? {}
137
+ : { navigatorWorkContextPath: options.navigatorWorkContextPath }),
130
138
  };
131
139
  }
132
140
 
@@ -163,13 +171,6 @@ function activationForAdmitted(admitted: AdmittedRoleInvocation): RoleTurnActiva
163
171
  const activation: Record<string, unknown> = { role: admitted.role };
164
172
  for (const spec of packagedRoleActivationFlags(admitted.role)) {
165
173
  let value = readAdmittedPath(admitted, spec.from ?? spec.field);
166
- if (spec.fallback === "gate-pointer") {
167
- const trimmed = typeof value === "string" ? value.trim() : "";
168
- value = trimmed !== ""
169
- ? trimmed
170
- : parentRunPathFromGatePointerInstruction(admitted.instruction);
171
- if (typeof value !== "string" || value === "") continue;
172
- }
173
174
  if (value === undefined) continue;
174
175
  if (spec.text === true) {
175
176
  if (typeof value !== "number") continue;