@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
@@ -514,6 +514,24 @@ const NOTARY_OPTIONS = [
514
514
  },
515
515
  },
516
516
  ];
517
+ /** 台院: optional --source-run binds the audited leg; gate summons always pass it (#1166). */
518
+ const INSPECTOR_OPTIONS = [
519
+ ...projectAndAttach("inspector"),
520
+ {
521
+ id: "source-run",
522
+ owner: "inspector",
523
+ canonical: "--source-run",
524
+ aliases: [],
525
+ valueMetavar: "runId@role|path",
526
+ required: false,
527
+ repeatable: false,
528
+ form: "option",
529
+ description: {
530
+ en: "Optional audited source-run locator (runId@role or path). Gate summons always supply it; identity rides startup materials.",
531
+ zh: "可选被审腿定位符(runId@role 或路径)。闸传召必带;身份在启动材料里。",
532
+ },
533
+ },
534
+ ];
517
535
  const MERGER_OPTIONS = [
518
536
  // Merger project face: worktree whose Git materials are handed to the role.
519
537
  {
@@ -677,6 +695,7 @@ const SEAT_OPTION_ROWS = {
677
695
  merger: MERGER_OPTIONS,
678
696
  notary: NOTARY_OPTIONS,
679
697
  auditor: AUDITOR_OPTIONS,
698
+ inspector: INSPECTOR_OPTIONS,
680
699
  };
681
700
  function seatOptionRows(role) {
682
701
  return SEAT_OPTION_ROWS[role] ?? projectAndAttach(role);
@@ -10,7 +10,7 @@ import { writeFile } from "node:fs/promises";
10
10
  import { buildAutoResumeContinuationPrompt, findRunDirectoryById, readRoleRunState, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
11
11
  import { CliUsageError } from "./cli-errors.js";
12
12
  import { projectPublicTurnAxes } from "./turn-request.js";
13
- import { bindAdmittedTicketNumber, buildInstructionTransportPrompt, relocateAdmittedRunToTicket, } from "./invocation.js";
13
+ import { appendCallerFileFlagPaths, bindAdmittedTicketNumber, relocateAdmittedRunToTicket, } from "./invocation.js";
14
14
  import { readRecordedSubmissionRows } from "../submission-ledger.js";
15
15
  import { parseTicketNumber, readDeclaredTicketNumber } from "../run-ticket-number.js";
16
16
  import { readStoredHostSessionId } from "../session-identity.js";
@@ -906,8 +906,8 @@ export async function dispatchPostAdmissionTurn(input) {
906
906
  * Shared resume continuation projection (#471 / #600 / #633 / #637 / #755 / #879):
907
907
  * seat-table model/engine/timeout axes, restored correlation, and either
908
908
  * - manual resume (no same-ticket summons): caller message bytes, or
909
- * - same-ticket summons (审核循环续话): caller/peer words + optional frozen
910
- * attachment paths only — no「请重读」、no code-authored content substitute,
909
+ * - same-ticket summons (审核循环续话): caller/peer words + optional caller file-flag
910
+ * paths only — no「请重读」、no code-authored content substitute,
911
911
  * no engine handbook packaging (#750/#755/#879).
912
912
  * Caller message wins as prompt base when supplied (bytes unchanged, including
913
913
  * blank/whitespace); else summons instruction (parent payload or reask).
@@ -916,22 +916,17 @@ export async function dispatchPostAdmissionTurn(input) {
916
916
  * first when request.summons carries instruction or attachment paths.
917
917
  */
918
918
  export function resumeTurnRequestProjectionOptions(admitted, request, env, summonsPrepared) {
919
- // #879: officer dialogue content is caller/peer words only — no「请重读」,
920
- // no code-authored constant substitute, no attachment-list wrap of peer body.
921
- // Binding pointer stays on summons sourceRunPath / activation / attachments.
919
+ // #879: officer dialogue is caller/peer words; #1166 J11 merges caller file-flag
920
+ // paths when present. Gate with no file flags stays body-only (ADR 0085).
922
921
  const officerDialogue = isStationChildOfficerDialogue(admitted.role, env);
922
+ const fileFlags = summonsPrepared?.attachments ?? [];
923
923
  let prompt;
924
924
  if (request.message !== undefined) {
925
925
  if (summonsPrepared !== undefined) {
926
- // #755: same-ticket review / open-court — caller words.
927
- // #879 station-child officer: words only (paths remain on summons materials).
928
- prompt = officerDialogue
926
+ // #755: same-ticket review / open-court — caller words + caller file flags.
927
+ prompt = officerDialogue && fileFlags.length === 0
929
928
  ? request.message
930
- : buildInstructionTransportPrompt({
931
- instruction: request.message,
932
- instructionEmpty: false,
933
- attachments: summonsPrepared.attachments,
934
- });
929
+ : appendCallerFileFlagPaths(request.message, fileFlags);
935
930
  }
936
931
  else if (request.summons !== undefined) {
937
932
  // #755: same-ticket summons without prepared materials — caller words only.
@@ -942,13 +937,11 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
942
937
  }
943
938
  }
944
939
  else if (summonsPrepared !== undefined) {
945
- // #879 station-child officer: instruction bytes === peer body/reask (no wrap).
946
- // Other seats keep #755 instruction + optional attachment path listing.
947
- // #1165 J3: officer dialogue is raw instruction bytes; do not gate on
948
- // instructionEmpty metadata (resume of older admitted pages).
949
- prompt = officerDialogue
950
- ? summonsPrepared.instruction
951
- : buildInstructionTransportPrompt(summonsPrepared);
940
+ const body = summonsPrepared.instruction;
941
+ // No file flags: body alone (gate peer words). With flags: merge delivery.
942
+ prompt = officerDialogue && fileFlags.length === 0
943
+ ? body
944
+ : appendCallerFileFlagPaths(body, fileFlags);
952
945
  }
953
946
  else if (request.summons !== undefined) {
954
947
  // #879: same-ticket summons with no instruction (e.g. notary source-run binding
@@ -971,6 +964,10 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
971
964
  ...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
972
965
  // #1132: one configured ceiling, already resolved by the caller (#422).
973
966
  deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
967
+ ...(env.navigatorWorkContextPath === undefined
968
+ || env.navigatorWorkContextPath.trim() === ""
969
+ ? {}
970
+ : { navigatorWorkContextPath: env.navigatorWorkContextPath }),
974
971
  };
975
972
  }
976
973
  /** Shared new-turn and in-call auto-resume projection. Seat code supplies prompt and activation. */
@@ -986,6 +983,10 @@ export function roleTurnOptions(env, admitted, continuation) {
986
983
  ...(env.stationChild === undefined ? {} : { stationChild: env.stationChild }),
987
984
  // #1132: one configured ceiling, already resolved by the caller (#422).
988
985
  deliveryRequestLimit: deliveryLimitFromConfig(env.autoResumeLimit),
986
+ ...(env.navigatorWorkContextPath === undefined
987
+ || env.navigatorWorkContextPath.trim() === ""
988
+ ? {}
989
+ : { navigatorWorkContextPath: env.navigatorWorkContextPath }),
989
990
  };
990
991
  }
991
992
  /**
@@ -1070,7 +1071,7 @@ export async function prepareSummonsResumeMaterials(_runDirectory, summons) {
1070
1071
  * validation, turn builder, and adapters stay on the seat.
1071
1072
  *
1072
1073
  * Court handling (#637): public manual resume reads only the open court's
1073
- * settlement identity; internal re-summons may freeze and record its materials.
1074
+ * settlement identity; internal re-summons passes caller file-flag paths as-is.
1074
1075
  */
1075
1076
  export async function runPostAdmissionSeatResume(input) {
1076
1077
  let request = input.request;
@@ -631,18 +631,9 @@ export async function findRunDirectoryById(home, runId, onlyBookKey, onlyRole) {
631
631
  .filter((bookKey) => onlyBookKey === undefined || bookKey === onlyBookKey)
632
632
  .map((bookKey) => activationBookDirectory(ledgerHome, bookKey)), runId, onlyRole);
633
633
  }
634
- /** Code-owned gate inspector summons prefix (public-role-summons / #747). */
635
- export const GATE_DOSSIER_POINTER_PREFIX = "卷宗指针:";
636
- /** Parent path from a code-owned inspector 卷宗指针 instruction; else undefined. */
637
- export function parentRunPathFromGatePointerInstruction(instruction) {
638
- if (!instruction.startsWith(GATE_DOSSIER_POINTER_PREFIX))
639
- return undefined;
640
- const path = instruction.slice(GATE_DOSSIER_POINTER_PREFIX.length).trim();
641
- return path === "" ? undefined : path;
642
- }
643
634
  /**
644
- * Parent-run binding on a retained officer run (#747).
645
- * Notary/auditor: typed sourceRunPath. Inspector: exact code-owned 卷宗指针 instruction.
635
+ * Parent-run binding on a retained officer run (#747 / #1166).
636
+ * Typed sourceRunPath only — instruction bytes are never a path protocol.
646
637
  * Missing page → undefined; damage / non-ENOENT IO propagates.
647
638
  */
648
639
  export async function readRunParentPath(runDirectory) {
@@ -652,9 +643,6 @@ export async function readRunParentPath(runDirectory) {
652
643
  if (typeof record.sourceRunPath === "string" && record.sourceRunPath.trim() !== "") {
653
644
  return record.sourceRunPath;
654
645
  }
655
- if (typeof record.instruction === "string") {
656
- return parentRunPathFromGatePointerInstruction(record.instruction);
657
- }
658
646
  return undefined;
659
647
  }
660
648
  /**
@@ -1,7 +1,6 @@
1
1
  import { pickEngineAxis } from "../package-resources/engine-material.js";
2
2
  import { packagedRoleActivationFlags, packagedRoleMetadata, } from "../packaged-role-registry.js";
3
3
  import { installedMethodSkillPath } from "./machine-method-skills.js";
4
- import { parentRunPathFromGatePointerInstruction } from "./run-lifecycle.js";
5
4
  /**
6
5
  * Env axes shared by every public turn projection.
7
6
  * Callers add correlation, continuation, host, and station-only fields.
@@ -49,6 +48,10 @@ export function projectRoleTurnRequest(admitted, roleDetails, options) {
49
48
  ...(options.deliveryRequestLimit === undefined
50
49
  ? {}
51
50
  : { deliveryRequestLimit: options.deliveryRequestLimit }),
51
+ ...(options.navigatorWorkContextPath === undefined
52
+ || options.navigatorWorkContextPath.trim() === ""
53
+ ? {}
54
+ : { navigatorWorkContextPath: options.navigatorWorkContextPath }),
52
55
  };
53
56
  }
54
57
  /**
@@ -83,14 +86,6 @@ function activationForAdmitted(admitted) {
83
86
  const activation = { role: admitted.role };
84
87
  for (const spec of packagedRoleActivationFlags(admitted.role)) {
85
88
  let value = readAdmittedPath(admitted, spec.from ?? spec.field);
86
- if (spec.fallback === "gate-pointer") {
87
- const trimmed = typeof value === "string" ? value.trim() : "";
88
- value = trimmed !== ""
89
- ? trimmed
90
- : parentRunPathFromGatePointerInstruction(admitted.instruction);
91
- if (typeof value !== "string" || value === "")
92
- continue;
93
- }
94
89
  if (value === undefined)
95
90
  continue;
96
91
  if (spec.text === true) {
@@ -197,7 +197,8 @@ async function summonPublicRole(options) {
197
197
  ...options.boundTicketNumber === void 0 ? {} : { boundTicketNumber: options.boundTicketNumber },
198
198
  ...options.parentRunPath === void 0 || options.parentRunPath.trim() === "" ? {} : { parentRunPath: options.parentRunPath },
199
199
  ...options.correlationId === void 0 || options.correlationId.trim() === "" ? {} : { correlationId: options.correlationId },
200
- ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId }
200
+ ...options.createRunId === void 0 ? {} : { createRunId: options.createRunId },
201
+ ...options.navigatorWorkContextPath === void 0 || options.navigatorWorkContextPath.trim() === "" ? {} : { navigatorWorkContextPath: options.navigatorWorkContextPath }
201
202
  }
202
203
  };
203
204
  } catch (error) {
@@ -454,7 +455,8 @@ async function summonGateOfficer(options) {
454
455
  "judge",
455
456
  "--source-run",
456
457
  options.sourceRunDirectory,
457
- `\u5377\u5B97\u6307\u9488\uFF1A${options.sourceRunDirectory}`
458
+ "--project",
459
+ options.cwd
458
460
  ],
459
461
  ...common
460
462
  });
@@ -482,7 +484,7 @@ async function summonGateOfficer(options) {
482
484
  }
483
485
  return summonPublicRole({
484
486
  role: options.officer,
485
- argv: [`\u5377\u5B97\u6307\u9488\uFF1A${options.sourceRunDirectory}`],
487
+ argv: ["--source-run", options.sourceRunDirectory, "--project", options.cwd],
486
488
  ...common
487
489
  });
488
490
  }
@@ -1,5 +1,5 @@
1
1
  import { readdir } from "node:fs/promises";
2
- import { basename, dirname, join } from "node:path";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
3
  import {
4
4
  activationBookDirectory,
5
5
  ensureRealDirectoryTree
@@ -27,6 +27,11 @@ function sessionFileOf(runDirectory) {
27
27
  function runDirectoryOfSessionFile(sessionFile) {
28
28
  return dirname(dirname(sessionFile));
29
29
  }
30
+ function auditorRunDirectory(context) {
31
+ const sessionFile = context.sessionManager?.getSessionFile?.();
32
+ if (sessionFile === void 0) return void 0;
33
+ return resolve(runDirectoryOfSessionFile(sessionFile));
34
+ }
30
35
  function runDirectoryFromSessionDirectory(sessionDirectory) {
31
36
  return dirname(sessionDirectory);
32
37
  }
@@ -107,6 +112,7 @@ function ensureRoleRunPlacement(ledgerHome, placement) {
107
112
  }
108
113
  export {
109
114
  ROLE_RUN_SESSION_FILENAME,
115
+ auditorRunDirectory,
110
116
  ensureRoleRunDirectory,
111
117
  ensureRoleRunPlacement,
112
118
  findRoleRunDirectory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5598",
3
+ "version": "0.1.5614",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -32,9 +32,8 @@ const entries = [
32
32
  "archivist-record-topology",
33
33
  // Session material loaders used by published non-bundle roots.
34
34
  "session-opening-materials",
35
- // Value-import closure of published non-bundle roots (build-package-only loadable).
36
- "auditor-dossier-tool",
37
- // Static import of auditor-dossier-tool / placement path authority (#859).
35
+ // Value-import closure of published non-bundle roots (build-package-only loadable);
36
+ // placement path authority (#859).
38
37
  "role-run-placement",
39
38
  // Static import of role-run-placement (safe-positive ticket invariant).
40
39
  "run-ticket-number",
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Shared audited-run identity for review-officer startup materials (#1166 / ADR 0087).
3
+ * Value form is `<runId>@<席>` only — no directory path.
4
+ */
5
+ import { basename } from "node:path";
6
+
7
+ import { formatRunLeaf, parseRunLeaf } from "./role-run-placement.ts";
8
+
9
+ export const AUDITED_RUN_IDENTITY_KIND = "audited-run-identity" as const;
10
+
11
+ export type AuditedRunIdentityMaterial = {
12
+ readonly kind: typeof AUDITED_RUN_IDENTITY_KIND;
13
+ readonly identity: string;
14
+ };
15
+
16
+ /** Identity leaf from a run directory basename; undefined when the leaf is not `runId@role`. */
17
+ export function auditedRunIdentityFromDirectory(runDirectory: string): string | undefined {
18
+ const parsed = parseRunLeaf(basename(runDirectory));
19
+ if (parsed === undefined) return undefined;
20
+ return formatRunLeaf(parsed.runId, parsed.role);
21
+ }
22
+
23
+ export function auditedRunIdentityMaterial(identity: string): AuditedRunIdentityMaterial {
24
+ return { kind: AUDITED_RUN_IDENTITY_KIND, identity };
25
+ }
26
+
27
+ /** Identity from locator fields already in hand (notary source-run). */
28
+ export function auditedRunIdentityFromParts(runId: string, role: string): string {
29
+ return formatRunLeaf(runId, role);
30
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Usage } from "@earendil-works/pi-ai";
2
2
  import type { AuditorSoulRole } from "./auditor-soul.ts";
3
- import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
3
+ import { auditorRunDirectory } from "./role-run-placement.ts";
4
4
  import type { HostContext } from "./host-contracts.ts";
5
5
  import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
6
6
  import type { PublicSummonResult } from "./public-role-summons.ts";
@@ -40,8 +40,6 @@ export type ComplianceDecision =
40
40
  | ComplianceReceived
41
41
  | ComplianceTransportFailure;
42
42
  /** Source-run pointer remains the binding locator; parent body rides the separate instruction. */
43
- export const AUDITOR_DOSSIER_PROMPT = "卷宗指针:" as const;
44
-
45
43
  export const COMPLIANCE_RESPONSE_ENTRY_TYPE = "ak_compliance_response" as const;
46
44
  export const AUDITOR_PARENT_ATTEMPT_BINDING_ENTRY_TYPE = "ak_auditor_parent_attempt_binding" as const;
47
45
  export const AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE = "ak_auditor_compliance_failure" as const;
@@ -215,7 +213,6 @@ export async function runComplianceAudit(options: RunComplianceAuditOptions): Pr
215
213
  auditSubject,
216
214
  "--source-run",
217
215
  sourceRunDirectory,
218
- `${AUDITOR_DOSSIER_PROMPT}${sourceRunDirectory}`,
219
216
  ],
220
217
  cwd: options.context.cwd ?? process.cwd(),
221
218
  home,
@@ -1,4 +1,4 @@
1
- import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
1
+ import { auditorRunDirectory } from "./role-run-placement.ts";
2
2
  import {
3
3
  runComplianceAudit,
4
4
  type AuditorSummon,
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { HostContext } from "./host-contracts.ts";
3
3
 
4
- import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
4
+ import { auditorRunDirectory } from "./role-run-placement.ts";
5
5
  import { packagedGateStageLabel } from "./packaged-role-registry.ts";
6
6
  import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
7
7
  import { GatekeeperDecisionError, receivedDiscriminator, unreadableDiscriminatorNotice } from "./submission-errors.ts";
@@ -200,6 +200,11 @@ export type RoleTurnRequest = {
200
200
  * number. Absent = package default. Never re-read from disk downstream.
201
201
  */
202
202
  readonly deliveryRequestLimit?: number;
203
+ /**
204
+ * Navigator nest work-context locator for startup materials (#1166).
205
+ * Request-scoped — never parent process.env across await.
206
+ */
207
+ readonly navigatorWorkContextPath?: string;
203
208
  };
204
209
 
205
210
  /** Turn result — only fields upper layers currently consume. */
@@ -258,7 +263,7 @@ export interface DurablePrincipalAuthority {
258
263
  type HostSessionManager = { getLeafEntry(): HostSessionEntry | undefined; getLeafId(): string | null | undefined; getEntries(): Iterable<HostSessionEntry>; getSessionDir(): string; getSessionFile(): string | undefined; getHeader?(): { readonly type: string; readonly id?: string } | null; setSessionFile?(path: string): void; appendCustomEntry?(customType: string, data?: unknown): unknown; };
259
264
 
260
265
  /** Context supplied by a host for one activation and its interceptable events. */
261
- export type HostContext = { cwd: string; mode: string; model: { readonly provider: string } | undefined; sessionManager: HostSessionManager; /** Per-turn admitted run directory (#879); never process-global env. */ runDirectory?: string; /** Per-turn court attempt (#879); never process-global env. */ courtAttemptId?: string; /** Public-invocation scope (#537); never process-global env. */ invocationScopeId?: string; /** Selected host axis (#537 / ADR 0082); never process-global env invent. */ host?: string; signal?: AbortSignal | undefined; ui?: { notify?(message: string, type?: "info" | "warning" | "error"): void }; transcript?(): string; abort(): void; };
266
+ export type HostContext = { cwd: string; mode: string; model: { readonly provider: string } | undefined; sessionManager: HostSessionManager; /** Per-turn admitted run directory (#879); never process-global env. */ runDirectory?: string; /** Per-turn court attempt (#879); never process-global env. */ courtAttemptId?: string; /** Public-invocation scope (#537); never process-global env. */ invocationScopeId?: string; /** Selected host axis (#537 / ADR 0082); never process-global env invent. */ host?: string; /** Navigator nest work-context locator (#1166); never process-global env. */ navigatorWorkContextPath?: string; signal?: AbortSignal | undefined; ui?: { notify?(message: string, type?: "info" | "warning" | "error"): void }; transcript?(): string; abort(): void; };
262
267
 
263
268
  /** Per-turn run directory; adapters must project any child-process identity. */
264
269
  export function runDirectoryFromHostContext(context: HostContext): string | undefined {
@@ -26,7 +26,6 @@ import type { PublicSummonResult } from "./public-role-summons.ts";
26
26
  import { CliUsageError } from "./public-cli/cli-errors.ts";
27
27
  import { isNavigatorSeat } from "./packaged-role-registry.ts";
28
28
  import {
29
- attachWorkContextPointer,
30
29
  navigatorWorkContextFile,
31
30
  } from "./navigator-work-base.ts";
32
31
 
@@ -157,19 +156,24 @@ export function createNativeNavigatorSessionFactory(deps?: {
157
156
  // (ADR 0018 / #959 — legal HostContext may omit signal).
158
157
  if (disposed) return;
159
158
  const resumeRunId = hostRunId;
159
+ // Prompt stays caller/summoner bytes (#1166). Nest work-context path is an
160
+ // internal locator for startup materials only — never folded into argv,
161
+ // never written onto shared parent process.env across await.
160
162
  const workContextPath = navigatorWorkContextFile(sessionManager.getSessionDir());
161
- const argvText = existsSync(workContextPath)
162
- ? attachWorkContextPointer(text, workContextPath)
163
- : text;
163
+ const workContextLocator =
164
+ existsSync(workContextPath) ? workContextPath : undefined;
164
165
 
165
166
  // Call contract only: forward shared-lifecycle signal; do not own AbortController here
166
167
  // (ADR 0018 / #959 — cancel ownership stays on the attendance/envelope seam).
167
168
  const baseSummon = {
168
169
  role: "navigator" as const,
169
- argv: [argvText] as const,
170
+ argv: [text] as const,
170
171
  cwd: context.cwd,
171
172
  ...(summonHome === undefined ? {} : { home: summonHome }),
172
173
  ...(context.signal === undefined ? {} : { signal: context.signal }),
174
+ ...(workContextLocator === undefined
175
+ ? {}
176
+ : { navigatorWorkContextPath: workContextLocator }),
173
177
  };
174
178
 
175
179
  // Prefer host CLI resume so prior advice stays on the host session.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Durable work subject and controlling authority for one navigator nest.
3
- * Settlement user turns keep a path pointer; the navigator system prompt
4
- * loads the bytes once per agent start from this file (base material).
3
+ * Startup materials load these bytes from the nest session directory; the
4
+ * caller/summon prompt must not receive a package-injected path (#1166).
5
5
  */
6
6
  import { existsSync, realpathSync } from "node:fs";
7
7
  import { readFile } from "node:fs/promises";
@@ -12,6 +12,9 @@ import { isEnoent, isRecord } from "./unknown-value.ts";
12
12
 
13
13
  export const NAVIGATOR_WORK_CONTEXT_BASENAME = "work-context.json";
14
14
 
15
+ /** Child-process / in-process locator for nest work-context bytes — not prompt material (#1166). */
16
+ export const AK_ROLE_NAVIGATOR_WORK_CONTEXT_ENV = "AK_ROLE_NAVIGATOR_WORK_CONTEXT" as const;
17
+
15
18
  export type NavigatorWorkBase = {
16
19
  readonly subject: string;
17
20
  readonly authority: string;
@@ -81,38 +84,20 @@ export async function readNavigatorWorkBase(path: string): Promise<NavigatorWork
81
84
  return { subject: record.subject, authority: record.authority };
82
85
  }
83
86
 
84
- function jsonObjectFromPrompt(prompt: string): Record<string, unknown> | undefined {
85
- const head = prompt.trimStart().split("\n\n", 1)[0] ?? "";
86
- if (!head.startsWith("{")) return undefined;
87
- try {
88
- const parsed: unknown = JSON.parse(head);
89
- if (!isRecord(parsed)) return undefined;
90
- return parsed as Record<string, unknown>;
91
- } catch {
92
- return undefined;
93
- }
94
- }
95
-
96
- export function workContextPathFromPrompt(prompt: string): string | undefined {
97
- const record = jsonObjectFromPrompt(prompt);
98
- const path = record?.workContextPath;
99
- if (typeof path !== "string" || path.trim() === "") return undefined;
100
- return isNavigatorWorkContextFile(path) ? path : undefined;
101
- }
102
-
103
- /** Settlement JSON gains the nest pointer. Non-JSON prompts stay unchanged. */
104
- export function attachWorkContextPointer(text: string, workContextPath: string): string {
105
- const record = jsonObjectFromPrompt(text);
106
- if (record === undefined) return text;
107
- if (typeof record.workContextPath === "string" && record.workContextPath.trim() !== "") return text;
108
- const head = text.trimStart().split("\n\n", 1)[0] ?? "";
109
- const rest = text.trimStart().slice(head.length);
110
- return `${JSON.stringify({ ...record, workContextPath })}${rest}`;
111
- }
112
-
113
- /** System-prompt base for one agent start. Absent or unreadable file yields undefined. */
114
- export async function loadNavigatorWorkBaseSuffix(prompt: string): Promise<string | undefined> {
115
- const path = workContextPathFromPrompt(prompt);
87
+ /** System-prompt base for one agent start. Prefer request locator, else nest session dir. */
88
+ export async function loadNavigatorWorkBaseSuffix(
89
+ sessionDir: string | undefined,
90
+ locatorPath?: string,
91
+ ): Promise<string | undefined> {
92
+ const path =
93
+ typeof locatorPath === "string" && locatorPath.trim() !== ""
94
+ ? locatorPath.trim()
95
+ : typeof process.env[AK_ROLE_NAVIGATOR_WORK_CONTEXT_ENV] === "string"
96
+ && process.env[AK_ROLE_NAVIGATOR_WORK_CONTEXT_ENV].trim() !== ""
97
+ ? process.env[AK_ROLE_NAVIGATOR_WORK_CONTEXT_ENV].trim()
98
+ : sessionDir !== undefined && sessionDir.trim() !== ""
99
+ ? navigatorWorkContextFile(sessionDir)
100
+ : undefined;
116
101
  if (path === undefined) return undefined;
117
102
  const body = await readNavigatorWorkBase(path);
118
103
  if (body === undefined) return undefined;
@@ -9,6 +9,10 @@ import { isSafePositiveTicketNumber } from "./run-ticket-number.ts";
9
9
  * This module keeps evidence assembly + projection only.
10
10
  */
11
11
 
12
+ import {
13
+ auditedRunIdentityFromParts,
14
+ auditedRunIdentityMaterial,
15
+ } from "./audited-run-identity.ts";
12
16
  import {
13
17
  NOTARY_OUTPUT_TOOL_NAME,
14
18
  NOTARY_SOURCE_RUN_FLAG,
@@ -53,25 +57,15 @@ export function readNotaryTicketFlag(flag: unknown): number | undefined {
53
57
  return n;
54
58
  }
55
59
 
56
- /**
57
- * Typed session bound (locator + optional ticket).
58
- * Sole production projection for agent-start material + session custom entry.
59
- */
60
- export function projectNotarySessionBound(input: {
61
- readonly sourceRun: NotarySourceRunLocator;
62
- readonly ticketNumber?: number;
63
- }): {
60
+ /** Agent-start reading material: identity `<runId>@<席>` only — no directory path. */
61
+ export function projectNotaryAuditedRunIdentity(input: {
64
62
  readonly sourceRun: NotarySourceRunLocator;
65
- readonly ticketNumber?: number;
66
- } {
67
- return {
68
- sourceRun: input.sourceRun,
69
- ...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
70
- };
63
+ }) {
64
+ return auditedRunIdentityMaterial(
65
+ auditedRunIdentityFromParts(input.sourceRun.runId, input.sourceRun.role),
66
+ );
71
67
  }
72
68
 
73
- export type NotarySessionBound = ReturnType<typeof projectNotarySessionBound>;
74
-
75
69
  /**
76
70
  * Session custom-entry type for typed notary bound (written by shared envelope).
77
71
  * Role module only projects; lifecycle write is envelope-owned (ADR 0018).
@@ -102,7 +96,7 @@ export function projectNotaryBoundFromFlags(
102
96
  }
103
97
 
104
98
  /** Assemble systemPrompt body from base + soul only (NO embedded bound JSON).
105
- * The typed session bound travels separately as `readingMaterial`; adapters fold
99
+ * Audited-run identity travels separately as `readingMaterial`; adapters fold
106
100
  * it into the provider-visible prompt at the send boundary. */
107
101
  export function assembleNotaryAgentStartPrompt(input: {
108
102
  readonly baseSystemPrompt: string;
@@ -172,11 +166,8 @@ export function createNotaryRoleRuntime(
172
166
  baseSystemPrompt: event.systemPrompt,
173
167
  soul: activation.soul,
174
168
  }),
175
- readingMaterial: projectNotarySessionBound({
169
+ readingMaterial: projectNotaryAuditedRunIdentity({
176
170
  sourceRun: activation.sourceRun,
177
- ...(activation.ticketNumber === undefined
178
- ? {}
179
- : { ticketNumber: activation.ticketNumber }),
180
171
  }),
181
172
  };
182
173
  });
@@ -390,7 +390,8 @@ export const PUBLIC_ROLE_RECORDS = [
390
390
  presentSettled: "always",
391
391
  summonResume: false,
392
392
  admission: "instruction",
393
- sameParent: "gate-pointer",
393
+ /** Structured --source-run only; instruction stays opaque caller/peer bytes (#1166). */
394
+ sameParent: "source-run",
394
395
  /** Resume restores an optional source-run path onto the admitted face. */
395
396
  resumeSourcePath: true,
396
397
  /** 台院 is a review officer and inherits the gatekeeper model. */
@@ -403,11 +404,12 @@ export const PUBLIC_ROLE_RECORDS = [
403
404
  /** Court reask replaces the initial prompt. */
404
405
  reaskPrompt: true,
405
406
  gateStageLabel: "台院",
407
+ /** Gate summon binds --source-run; identity is startup material (#1166). */
408
+ gateSummon: "source-run",
406
409
  activationFlags: [
407
410
  {
408
411
  field: "sourceRun",
409
412
  from: "sourceRunPath",
410
- fallback: "gate-pointer",
411
413
  flag: "ak-inspector-source-run",
412
414
  binds: "input",
413
415
  },
@@ -554,7 +556,6 @@ export function packagedModelParent(role: string): PackagedRole | undefined {
554
556
  * One seat's admitted → activation copy.
555
557
  * `field` is the activation key. `from` is the admitted path when it differs.
556
558
  * `text` writes a number as decimal text on the activation object.
557
- * `fallback: "gate-pointer"` is the inspector instruction pointer when sourceRunPath is blank.
558
559
  * `flag` omitted: the value stays on the activation object and is not a host flag
559
560
  * (secretariat and countersign ticketNumber).
560
561
  */
@@ -562,7 +563,6 @@ export type PackagedActivationFlag = {
562
563
  readonly field: string;
563
564
  readonly from?: string;
564
565
  readonly text?: true;
565
- readonly fallback?: "gate-pointer";
566
566
  readonly flag?: string;
567
567
  readonly binds?: "input" | "phase";
568
568
  };
@@ -429,7 +429,7 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
429
429
  if (request.deliveryRequestLimit === undefined) delete env[RECEIPT_DELIVERY_LIMIT_ENV];
430
430
  else env[RECEIPT_DELIVERY_LIMIT_ENV] = String(request.deliveryRequestLimit);
431
431
  applyEngineChildEnv(env, request.engine);
432
- // Nested auditor dossier tool binds the parent run pointer when published.
432
+ // Nested auditor binds the audited source-run for identity / soul selection.
433
433
  if (
434
434
  typeof process.env.AK_ROLE_AUDITOR_SOURCE_RUN === "string"
435
435
  && process.env.AK_ROLE_AUDITOR_SOURCE_RUN.trim() !== ""
@@ -443,6 +443,16 @@ export function createPiRoleTurnHost(config: PiRoleTurnHostConfig): RoleTurnHost
443
443
  ) {
444
444
  env.AK_ROLE_AUDITOR_SUBJECT = process.env.AK_ROLE_AUDITOR_SUBJECT;
445
445
  }
446
+ // Navigator nest work-context locator for startup materials (#1166) —
447
+ // child-process scoped only from this turn request; never parent process.env.
448
+ if (
449
+ typeof request.navigatorWorkContextPath === "string"
450
+ && request.navigatorWorkContextPath.trim() !== ""
451
+ ) {
452
+ env.AK_ROLE_NAVIGATOR_WORK_CONTEXT = request.navigatorWorkContextPath.trim();
453
+ } else {
454
+ delete env.AK_ROLE_NAVIGATOR_WORK_CONTEXT;
455
+ }
446
456
  if (
447
457
  config.recordLaunchedRolePackageIdentity !== undefined &&
448
458
  config.observeLaunchedRolePackageIdentity !== undefined