@akagilnc/pi-workflow-roles 0.1.4035 → 0.1.4193

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 (108) hide show
  1. package/dist/acp-host/production-host.js +2368 -1155
  2. package/dist/archivist-record-entry.js +204 -20
  3. package/dist/archivist-record-topology.js +36 -16
  4. package/dist/auditor-dossier-tool.js +2 -1
  5. package/dist/diarist-contracts.js +96 -0
  6. package/dist/doctor-auditor.js +1 -1
  7. package/dist/doctor-evidence.js +3 -2
  8. package/dist/dossier-resolution.js +21 -8
  9. package/dist/gatekeeper-role.js +34 -27
  10. package/dist/headless-host/description.js +7 -8
  11. package/dist/headless-host/production-host.js +2375 -1149
  12. package/dist/host-contracts.js +16 -0
  13. package/dist/inspector-contracts.js +8 -0
  14. package/dist/migrate-book-topology.js +278917 -0
  15. package/dist/navigator-public-session.js +22 -25
  16. package/dist/notary-source-run.js +7 -4
  17. package/dist/packaged-role-registry.js +1 -1
  18. package/dist/pi/durable-principal.js +9 -6
  19. package/dist/pi/role-turn-host.js +43 -13
  20. package/dist/public-cli/auto-resume.js +2 -5
  21. package/dist/public-cli/case-dossier-delivery.js +60 -1
  22. package/dist/public-cli/diarist-run.js +27 -25
  23. package/dist/public-cli/inspector-run.js +17 -3
  24. package/dist/public-cli/instruction-seat-run.js +17 -17
  25. package/dist/public-cli/invocation.js +127 -63
  26. package/dist/public-cli/main.js +1685 -696
  27. package/dist/public-cli/notary-run.js +7 -4
  28. package/dist/public-cli/option-definitions.js +2 -2
  29. package/dist/public-cli/post-admission.js +113 -71
  30. package/dist/public-cli/run-lifecycle.js +209 -58
  31. package/dist/public-cli/seat-ticket-binding.js +3 -6
  32. package/dist/public-cli/settlement.js +29 -10
  33. package/dist/public-role-summons.js +8 -0
  34. package/dist/role-run-placement.js +84 -0
  35. package/dist/role-run-relocation.js +358 -0
  36. package/dist/run-terminal-artifacts.js +2 -1
  37. package/dist/run-ticket-number.js +51 -32
  38. package/dist/sitian-appender.js +11 -26
  39. package/dist/submission-ledger.js +106 -25
  40. package/dist/ticket-provenance.js +3 -1
  41. package/dist/user-dialogue-stdin.js +33 -0
  42. package/extensions/role-runtime.ts +1 -0
  43. package/package.json +3 -2
  44. package/resources/836-deleted-machine-instruction-inventory.md +1 -1
  45. package/scripts/build-package.mjs +34 -2
  46. package/souls/fixer.md +3 -2
  47. package/src/analyst-ledger.ts +14 -15
  48. package/src/archivist-record-entry.ts +285 -38
  49. package/src/archivist-record-topology.ts +43 -18
  50. package/src/auditor-dossier-tool.ts +2 -1
  51. package/src/book-topology-migration-placement.ts +493 -0
  52. package/src/book-topology-migration.ts +220 -0
  53. package/src/book-topology-migrators-t11.ts +856 -0
  54. package/src/book-topology-mixed-volume-migrators.ts +470 -0
  55. package/src/book-topology-partition-migrators.ts +25 -0
  56. package/src/book-topology-record-class-migrators.ts +779 -0
  57. package/src/book-topology-runs-migrator.ts +244 -0
  58. package/src/diarist-contracts.ts +116 -0
  59. package/src/diarist-role.ts +6 -0
  60. package/src/diarist.ts +2 -0
  61. package/src/doctor-auditor.ts +1 -1
  62. package/src/doctor-evidence.ts +4 -1
  63. package/src/doctor-role.ts +1 -1
  64. package/src/dossier-resolution.ts +26 -8
  65. package/src/external-host-turn-loop.ts +9 -0
  66. package/src/gatekeeper-pass-envelope.ts +6 -0
  67. package/src/gatekeeper-role.ts +47 -28
  68. package/src/headless-host/description.ts +8 -11
  69. package/src/headless-host/role-turn-host.ts +20 -5
  70. package/src/host-contracts.ts +22 -3
  71. package/src/inspector-contracts.ts +7 -0
  72. package/src/judge-role.ts +4 -0
  73. package/src/navigator-public-session.ts +33 -30
  74. package/src/navigator-work-context.ts +3 -4
  75. package/src/notary-role.ts +1 -0
  76. package/src/notary-source-run.ts +9 -4
  77. package/src/packaged-role-registry.ts +1 -1
  78. package/src/pi/adapter.ts +19 -4
  79. package/src/pi/durable-principal.ts +9 -13
  80. package/src/pi/role-turn-host.ts +48 -14
  81. package/src/public-cli/auto-resume.ts +2 -6
  82. package/src/public-cli/case-dossier-delivery.ts +86 -1
  83. package/src/public-cli/countersign-run.ts +182 -32
  84. package/src/public-cli/diarist-run.ts +34 -26
  85. package/src/public-cli/inspector-run.ts +26 -2
  86. package/src/public-cli/instruction-seat-run.ts +24 -20
  87. package/src/public-cli/invocation.ts +194 -58
  88. package/src/public-cli/notary-run.ts +13 -4
  89. package/src/public-cli/option-definitions.ts +2 -2
  90. package/src/public-cli/post-admission.ts +147 -78
  91. package/src/public-cli/run-lifecycle.ts +258 -65
  92. package/src/public-cli/seat-ticket-binding.ts +3 -6
  93. package/src/public-cli/settlement.ts +38 -10
  94. package/src/public-role-summons.ts +31 -7
  95. package/src/role-envelope.ts +9 -30
  96. package/src/role-run-placement.ts +128 -0
  97. package/src/role-run-relocation.ts +491 -0
  98. package/src/role-runtime-dependencies.ts +1 -0
  99. package/src/role-runtime.ts +106 -24
  100. package/src/run-terminal-artifacts.ts +3 -1
  101. package/src/run-ticket-number.ts +80 -20
  102. package/src/sitian-appender.ts +25 -28
  103. package/src/submission-ledger.ts +148 -30
  104. package/src/ticket-provenance.ts +4 -1
  105. package/src/ticket-trajectory.ts +22 -35
  106. package/src/user-dialogue-stdin.ts +37 -0
  107. package/src/worker-role.ts +8 -4
  108. package/src/worker-submission-gates.ts +6 -6
@@ -7,41 +7,38 @@ import {
7
7
  parseNavigatorModelSetting,
8
8
  resolveNavigatorSeatSelection
9
9
  } from "./navigator-session-contracts.js";
10
+ import { runDirectoryFromHostContext } from "./host-contracts.js";
11
+ async function resolveNavigatorLedgerHome(context) {
12
+ const { tryHomeFromAkRolesPath } = await import("./activation-ledger-topology.js");
13
+ const runDirectory = runDirectoryFromHostContext(context);
14
+ if (runDirectory !== void 0) {
15
+ const fromRun = tryHomeFromAkRolesPath(runDirectory);
16
+ if (fromRun !== void 0 && fromRun.length > 0) return fromRun;
17
+ }
18
+ const parentFile = context.sessionManager?.getSessionFile?.();
19
+ if (typeof parentFile === "string" && parentFile.trim() !== "") {
20
+ const fromParent = tryHomeFromAkRolesPath(parentFile);
21
+ if (fromParent !== void 0 && fromParent.length > 0) return fromParent;
22
+ }
23
+ return void 0;
24
+ }
10
25
  function createNativeNavigatorSessionFactory() {
11
26
  return async ({ context, subject, tool }) => {
12
27
  const resolved = await resolveNavigatorSeatSelection(context);
13
28
  let thinkingLevel = resolved.thinkingLevel;
14
- const { createRecordSession } = await import("./archivist-record-entry.js");
29
+ const { createRecordSession, NAVIGATOR_RECORD_KIND } = await import("./archivist-record-entry.js");
30
+ const home = await resolveNavigatorLedgerHome(context);
15
31
  const sessionManager = createRecordSession({
16
32
  cwd: context.cwd,
17
- kind: "navigator",
33
+ kind: NAVIGATOR_RECORD_KIND,
18
34
  subject,
19
- parent: context.sessionManager
35
+ ...context.sessionManager !== void 0 ? { parent: context.sessionManager } : {},
36
+ ...home !== void 0 ? { home } : {}
20
37
  });
21
38
  let providerFailure;
22
39
  let noReceipt;
23
40
  let disposed = false;
24
41
  let inFlightPrompt;
25
- const resolveHome = async () => {
26
- const parentFile = context.sessionManager?.getSessionFile?.();
27
- if (typeof parentFile === "string" && parentFile.trim() !== "") {
28
- try {
29
- const { tryHomeFromAkRolesPath, homeFromRunDirectory } = await import("./activation-ledger-topology.js");
30
- const fromParent = tryHomeFromAkRolesPath(parentFile);
31
- if (fromParent !== void 0 && fromParent.length > 0) return fromParent;
32
- const runDir = parentFile.replace(/\/session\/session\.jsonl$/, "");
33
- if (runDir !== parentFile) {
34
- try {
35
- return homeFromRunDirectory(runDir);
36
- } catch {
37
- }
38
- }
39
- } catch {
40
- }
41
- }
42
- const envHome = process.env.HOME;
43
- return typeof envHome === "string" && envHome.trim() !== "" ? envHome : void 0;
44
- };
45
42
  return {
46
43
  prompt: async (text) => {
47
44
  if (disposed) {
@@ -52,12 +49,12 @@ function createNativeNavigatorSessionFactory() {
52
49
  const run = (async () => {
53
50
  try {
54
51
  const { summonPublicRole } = await import("./public-role-summons.js");
55
- const home = await resolveHome();
52
+ const summonHome = await resolveNavigatorLedgerHome(context);
56
53
  const summoned = await summonPublicRole({
57
54
  role: "navigator",
58
55
  argv: [text],
59
56
  cwd: context.cwd,
60
- ...home === void 0 ? {} : { home }
57
+ ...summonHome === void 0 ? {} : { home: summonHome }
61
58
  });
62
59
  const outcome = summoned.terminal?.roleOutcome;
63
60
  if (outcome === void 0) {
@@ -7,7 +7,7 @@ import { dirname, isAbsolute, join, resolve, basename } from "node:path";
7
7
  import { lstat, realpath } from "node:fs/promises";
8
8
  import { resolveBookKeyFromGit } from "./activation-ledger-git.js";
9
9
  import { activationBookDirectory, physicalPathIdentity, resolveActivationLedgerHome, } from "./activation-ledger-topology.js";
10
- import { readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
10
+ import { findRunDirectoryById, readRoleRunIdentity } from "./public-cli/run-lifecycle.js";
11
11
  // Run directory leaf: <runId>@<role>. Production uses uuidv7 runIds; offline tracers
12
12
  // may use deterministic non-uuid leaves. Role token stays identifier-shaped.
13
13
  const RUN_DIR_NAME = /^([A-Za-z0-9][A-Za-z0-9._-]*)@([A-Za-z][A-Za-z0-9_-]*)$/;
@@ -67,16 +67,19 @@ export async function resolveNotarySourceRunLocator(options) {
67
67
  let candidate;
68
68
  const bare = parseRunDirectoryName(raw);
69
69
  if (bare !== undefined && !raw.includes("/") && !raw.includes("\\")) {
70
- candidate = join(bookRunsRoot, `${bare.runId}@${bare.role}`);
70
+ candidate = (await findRunDirectoryById(options.home, bare.runId, bookKey, bare.role))
71
+ ?? join(bookRunsRoot, `${bare.runId}@${bare.role}`);
71
72
  }
72
73
  else {
73
74
  candidate = isAbsolute(raw) ? raw : resolve(options.projectRoot, raw);
74
75
  }
75
76
  const real = await requireRunDirectory(candidate, raw);
76
77
  const identity = parseRunDirectoryName(basename(real));
77
- const runsRootIdentity = physicalPathIdentity(bookRunsRoot);
78
+ const bookIdentity = physicalPathIdentity(activationBookDirectory(ledgerHome, bookKey));
78
79
  const parentIdentity = physicalPathIdentity(dirname(real));
79
- if (parentIdentity !== runsRootIdentity) {
80
+ const subjectBookIdentity = physicalPathIdentity(dirname(dirname(dirname(real))));
81
+ if (parentIdentity !== physicalPathIdentity(bookRunsRoot) &&
82
+ !(basename(dirname(real)) === "runs" && subjectBookIdentity === bookIdentity)) {
80
83
  throw new NotarySourceRunError("notary --source-run must resolve to a retained run under the project machine-ledger book");
81
84
  }
82
85
  // Authoritative retained record only — legal role/state via shared reader (DRY #14).
@@ -158,7 +158,7 @@ const PUBLIC_ROLE_RECORDS = [
158
158
  role: "inspector",
159
159
  phases: [null],
160
160
  outputTool: INSPECTOR_OUTPUT_TOOL_NAME,
161
- inputFlag: void 0,
161
+ inputFlag: "ak-inspector-source-run",
162
162
  phaseFlag: void 0,
163
163
  activationStage: "load-and-install",
164
164
  sessionMaterials: INSPECTOR_SESSION_MATERIALS
@@ -1,21 +1,24 @@
1
1
  import { lstat } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
4
- import { activationBookDirectory, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
4
+ import { resolveActivationLedgerHome } from "../activation-ledger-topology.js";
5
+ import { roleRunPlacement } from "../role-run-placement.js";
5
6
  function encode(coordinates) {
6
7
  return coordinates;
7
8
  }
8
9
  export function issuePiDurablePrincipalCoordinates(request) {
9
10
  const ledgerHome = resolveActivationLedgerHome(request.home);
10
11
  const bookKey = resolveBookKeyFromGit(request.cwd);
11
- const runDirectory = join(activationBookDirectory(ledgerHome, bookKey), "runs", `${request.runId}@${request.role}`);
12
- const sessionDirectory = join(runDirectory, "session");
12
+ const placement = roleRunPlacement(ledgerHome, {
13
+ bookKey,
14
+ subject: { unbound: true },
15
+ runId: request.runId,
16
+ role: request.role,
17
+ });
13
18
  return {
14
19
  ledgerHome,
15
20
  bookKey,
16
- runDirectory,
17
- sessionDirectory,
18
- sessionFile: join(sessionDirectory, "session.jsonl"),
21
+ ...placement,
19
22
  };
20
23
  }
21
24
  /** Pi's durable-principal codec and availability authority. */
@@ -10,9 +10,10 @@ import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:pa
10
10
  import { platform } from "node:process";
11
11
  import { promisify } from "node:util";
12
12
  import { randomUUID } from "node:crypto";
13
- import { ExplicitInternalActivationError } from "../host-contracts.js";
14
- import { applyEngineChildEnv } from "../engine-detour.js";
13
+ import { ExplicitInternalActivationError, isOfficerReviewSeat } from "../host-contracts.js";
14
+ import { applyEngineChildEnv, ENGINE_MODEL_FLAG_NAME, normalizeEngineName } from "../engine-detour.js";
15
15
  import { projectActivationFlags } from "../role-activation-flags.js";
16
+ import { encodeUserDialogueStdin } from "../user-dialogue-stdin.js";
16
17
  /** Package-relative Internal role entrypoint (ADR 0052; same path as public-cli registry). */
17
18
  const INTERNAL_ROLE_ENTRYPOINT_RELATIVE = "extensions/role-runtime.ts";
18
19
  export function resolveInternalRoleEntrypoint(packageRoot) {
@@ -95,17 +96,10 @@ export function applyPiNativeSkillInvocation(methods, prompt) {
95
96
  * Pi last-hop argv after `--no-extensions -e entry` (#819).
96
97
  * Activation flag membership comes from middle-layer projectActivationFlags;
97
98
  * this function only renders session coords, controlled constants, and pairs.
98
- * Single forced method → Pi-native `/skill:` on the argv prompt (#822).
99
+ * User dialogue is not an argv element (#879): it rides spawn stdin.
99
100
  */
100
101
  export function buildPiTurnExtraArgs(request, authority, extraPiArgs = []) {
101
102
  const { sessionFile, sessionDirectory } = authority.decode(request.principal);
102
- const rawPrompt = request.continuation.kind === "initial" || request.continuation.kind === "resume"
103
- ? request.continuation.prompt
104
- : (() => {
105
- const _exhaustive = request.continuation;
106
- return _exhaustive;
107
- })();
108
- const prompt = applyPiNativeSkillInvocation(request.methods, rawPrompt);
109
103
  return [
110
104
  "--no-skills",
111
105
  ...buildMethodArgs(request.methods),
@@ -119,12 +113,28 @@ export function buildPiTurnExtraArgs(request, authority, extraPiArgs = []) {
119
113
  ...extraPiArgs,
120
114
  // Envelope assembly = projectActivationFlags; pi only renders argv pairs.
121
115
  ...activationFlagsToPiArgv(projectActivationFlags(request)),
116
+ ...piEngineModelArgs(request),
122
117
  "--mode",
123
118
  "json",
124
119
  ...buildSeatModelCliArgs(request.model),
125
- prompt,
126
120
  ];
127
121
  }
122
+ /** Engine name stays on child env; model has no env fallback (#883 / #879). */
123
+ function piEngineModelArgs(request) {
124
+ const model = normalizeEngineName(request.engineModel);
125
+ if (model === undefined)
126
+ return [];
127
+ return [`--${ENGINE_MODEL_FLAG_NAME}`, model];
128
+ }
129
+ function piUserDialogueBody(request) {
130
+ const rawPrompt = request.continuation.kind === "initial" || request.continuation.kind === "resume"
131
+ ? request.continuation.prompt
132
+ : (() => {
133
+ const _exhaustive = request.continuation;
134
+ return _exhaustive;
135
+ })();
136
+ return applyPiNativeSkillInvocation(request.methods, rawPrompt);
137
+ }
128
138
  const execFileAsync = promisify(execFile);
129
139
  async function resolveSelectedPi(command, cwd, env) {
130
140
  const searchPath = env.PATH ?? (platform === "win32" ? (process.env.PATH ?? "") : "/usr/bin:/bin");
@@ -173,11 +183,22 @@ export function createDefaultPiSpawnRunner(options) {
173
183
  const child = spawn(piIdentity.executable, [...args], {
174
184
  cwd: spawnOptions.cwd,
175
185
  env: spawnOptions.env,
176
- stdio: ["ignore", "ignore", "pipe"],
186
+ stdio: ["pipe", "ignore", "pipe"],
177
187
  });
188
+ if (child.stdin === null) {
189
+ throw new Error("Pi child stdin pipe was not created");
190
+ }
178
191
  if (child.stderr === null) {
179
192
  throw new Error("Pi child stderr pipe was not created");
180
193
  }
194
+ let stdinDeliveryError;
195
+ child.stdin.on("error", (error) => {
196
+ stdinDeliveryError ??= error;
197
+ });
198
+ if (spawnOptions.stdin !== undefined) {
199
+ child.stdin.write(spawnOptions.stdin);
200
+ }
201
+ child.stdin.end();
181
202
  let stderr = "";
182
203
  let timedOut = false;
183
204
  // No default wall clock. Only an explicit caller budget arms a timer (ADR 0010).
@@ -248,6 +269,10 @@ export function createDefaultPiSpawnRunner(options) {
248
269
  reject(executionError);
249
270
  return;
250
271
  }
272
+ if (stdinDeliveryError !== undefined) {
273
+ reject(stdinDeliveryError);
274
+ return;
275
+ }
251
276
  resolveResult({
252
277
  code,
253
278
  stderr,
@@ -275,8 +300,11 @@ export function createPiRoleTurnHost(config) {
275
300
  async executeTurn(request) {
276
301
  // #617 DK-7: Pi argv gets projected native paths once; never record bytes.
277
302
  // Pi already owns its own session file, so only sitian prior volume rides in.
303
+ // #879: station-child officer dialogue keeps peer words — do not splice
304
+ // host-transition priorNativePaths into the review prompt body.
278
305
  let turnRequest = request;
279
- const paths = request.hostTransition?.priorNativeKind === "sitian"
306
+ const officerStationChild = request.stationChild === true && isOfficerReviewSeat(request.activation.role);
307
+ const paths = !officerStationChild && request.hostTransition?.priorNativeKind === "sitian"
280
308
  ? request.hostTransition.priorNativePaths
281
309
  : undefined;
282
310
  if (request.continuation.kind === "resume"
@@ -293,6 +321,7 @@ export function createPiRoleTurnHost(config) {
293
321
  const roleEntry = await realpath(resolveInternalRoleEntrypoint(config.packageRoot));
294
322
  const extraArgs = buildPiTurnExtraArgs(turnRequest, config.principalAuthority, config.extraPiArgs ?? []);
295
323
  const args = buildExplicitInternalActivationArgs(roleEntry, extraArgs);
324
+ const stdin = encodeUserDialogueStdin(piUserDialogueBody(turnRequest));
296
325
  // Shared envelope isolates this call's court identity: omitting courtAttemptId
297
326
  // must not inherit a parent process.env.AK_ROLE_COURT_ATTEMPT (#637).
298
327
  const env = {
@@ -330,6 +359,7 @@ export function createPiRoleTurnHost(config) {
330
359
  return await spawnRunner(args, {
331
360
  cwd: request.cwd,
332
361
  env,
362
+ stdin,
333
363
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
334
364
  ...(request.signal === undefined ? {} : { signal: request.signal }),
335
365
  });
@@ -15,6 +15,7 @@ import { constants as fsConstants } from "node:fs";
15
15
  import { randomUUID } from "node:crypto";
16
16
  import { lstat, mkdir, open } from "node:fs/promises";
17
17
  import { join } from "node:path";
18
+ import { roleRunArtifactsDirectory } from "../role-run-placement.js";
18
19
  import { AUTO_RESUME_LIMIT, describeErrorIdentity, acquireRunWriterLease, markRunResumable, markRunTerminal, RunWriterLeaseHeldError, } from "./run-lifecycle.js";
19
20
  import { parseAutoResumeLimit } from "./config.js";
20
21
  import { isLawfulTypedTerminalOutcome, formatTerminalResult } from "./terminal.js";
@@ -87,10 +88,6 @@ export class TurnDispatchedFailure extends Error {
87
88
  }
88
89
  /** Session custom-entry type carrying the pointer to one dispatch error file. */
89
90
  export const DISPATCH_ERROR_RETENTION_ENTRY_TYPE = "ak_run_dispatch_error_retention";
90
- /** Artifacts subdirectory of a run directory (established run-artifacts location). */
91
- function runArtifactsDirectory(runDirectory) {
92
- return join(runDirectory, "artifacts");
93
- }
94
91
  /**
95
92
  * #182-A hardened path identity, mirrored from settlement.ts's
96
93
  * ensureAuditEvidenceDirectory: a planted symlink at the run directory or the
@@ -106,7 +103,7 @@ export async function ensureRealArtifactsDirectory(runDirectory) {
106
103
  if (runStat.isSymbolicLink() || !runStat.isDirectory()) {
107
104
  throw new Error("run artifact retention: run directory is not a real directory");
108
105
  }
109
- const artifactsDir = runArtifactsDirectory(runDirectory);
106
+ const artifactsDir = roleRunArtifactsDirectory(runDirectory);
110
107
  try {
111
108
  const existing = await lstat(artifactsDir);
112
109
  if (existing.isSymbolicLink() || !existing.isDirectory()) {
@@ -5,11 +5,23 @@
5
5
  * 只读已有案卷:不刷新、不生成、不校验内容、不新增拒收或停工条件。
6
6
  * 机器文本仅中立标识材料(ADR 0073),用途说明归角色材料所有。
7
7
  *
8
- * 递送挂载点唯一:`post-admission` 在 beforeDispatch 之后为每个公共入口追加本段。
8
+ * 递送挂载点唯一:`post-admission` 在 beforeDispatch 之后为每个公共入口挂载。
9
+ * 普通入口把本段追加进 continuation;station-child 审核轮次走 attachments
10
+ * 冻结 + role-runtime `loadCaseDossierReadingMaterial` → readingMaterial →
11
+ * systemPrompt.materials fold(#879:对话 instruction 保持父腿 payload 原文;
12
+ * 起居录作独立附件面,不新造 RoleTurnRequest.materials)。
9
13
  */
14
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
15
+ import { tmpdir } from "node:os";
16
+ import { join } from "node:path";
10
17
  import { resolveTicketProvenanceVolume } from "../ticket-provenance.js";
18
+ import { freezeAttachmentsIntoRun, } from "./invocation.js";
11
19
  /** Section heading of the system-delivered dossier pointer (presentation only). */
12
20
  const CASE_DOSSIER_SECTION_HEADING = "## 本票起居录(系统随案提供)";
21
+ /** Stable freeze key under run/attachments/ for station-child 0081 delivery. */
22
+ const CASE_DOSSIER_ATTACH_KEY = "case-dossier";
23
+ /** Leaf name of the frozen pointer section (content = purpose + paths). */
24
+ const CASE_DOSSIER_ATTACH_FILE = "case-dossier-pointer.md";
13
25
  /** Pointer only — presence/absence is for the role to observe at the path. */
14
26
  function describeDossierFile(path) {
15
27
  return path;
@@ -32,3 +44,50 @@ export async function projectCaseDossierPointerSection(input) {
32
44
  `记录卷宗:${describeDossierFile(volume.recordFile)}`,
33
45
  ].join("\n");
34
46
  }
47
+ /**
48
+ * ADR 0081 delivery for station-child officer turns (#879): freeze the same
49
+ * pointer section through the existing attachments seam. Role-runtime loads
50
+ * that freeze via loadCaseDossierReadingMaterial onto readingMaterial; the
51
+ * envelope then folds it into systemPrompt.materials. Peer dialogue instruction
52
+ * stays the parent payload; never RoleTurnRequest.materials.
53
+ * Returns frozen attachments, or undefined when unbound (no dossier).
54
+ */
55
+ export async function deliverCaseDossierAsAttachment(input) {
56
+ const section = await projectCaseDossierPointerSection({
57
+ ticketNumber: input.ticketNumber,
58
+ projectRoot: input.projectRoot,
59
+ home: input.home,
60
+ });
61
+ if (section === undefined)
62
+ return undefined;
63
+ // Stage in OS temp only — never leave a run-local .case-dossier-stage copy.
64
+ const stagingDir = await mkdtemp(join(tmpdir(), "ak-case-dossier-"));
65
+ try {
66
+ const stagingPath = join(stagingDir, CASE_DOSSIER_ATTACH_FILE);
67
+ await writeFile(stagingPath, `${section}\n`, "utf8");
68
+ return await freezeAttachmentsIntoRun([stagingPath], input.runDirectory, CASE_DOSSIER_ATTACH_KEY);
69
+ }
70
+ finally {
71
+ await rm(stagingDir, { recursive: true, force: true });
72
+ }
73
+ }
74
+ /**
75
+ * Load a previously frozen case-dossier attachment as reading material
76
+ * (existing agent-start / systemPrompt.materials fold — not dialogue prompt,
77
+ * not RoleTurnRequest.materials). Undefined when the run has no such freeze.
78
+ */
79
+ export async function loadCaseDossierReadingMaterial(runDirectory) {
80
+ const frozenPath = join(runDirectory, "attachments", CASE_DOSSIER_ATTACH_KEY, `00-${CASE_DOSSIER_ATTACH_FILE}`);
81
+ let section;
82
+ try {
83
+ section = await readFile(frozenPath, "utf8");
84
+ }
85
+ catch (error) {
86
+ if (error.code === "ENOENT")
87
+ return undefined;
88
+ throw error;
89
+ }
90
+ if (section.trim() === "")
91
+ return undefined;
92
+ return { kind: "case-dossier-pointer", frozenPath, section };
93
+ }
@@ -1,20 +1,41 @@
1
1
  import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.js";
2
2
  import { CliUsageError } from "./cli-errors.js";
3
- import { admitDiaristInvocation, bindAdmittedTicketNumber, buildInstructionTransportPrompt, } from "./invocation.js";
3
+ import { admitDiaristInvocation, bindAdmittedTicketNumber, buildInstructionTransportPrompt, relocateAdmittedRunToTicket, } from "./invocation.js";
4
4
  import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
5
5
  import { loadResumableDiaristRun, markRunAdmitted, } from "./run-lifecycle.js";
6
6
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
7
7
  import { presentStructuralRejection, trySettleDiaristTerminalResult, } from "./settlement.js";
8
8
  import { projectRoleTurnRequest, } from "./turn-request.js";
9
- import { readRunTicketNumber } from "../run-ticket-number.js";
9
+ import { readBoardTicketNumber } from "../run-ticket-number.js";
10
10
  /** Project admitted invocation onto the host-neutral turn request. */
11
11
  export function buildDiaristTurnRequest(admitted, options) {
12
12
  return projectRoleTurnRequest(admitted, { activation: { role: "diarist" } }, options);
13
13
  }
14
- function diaristAdapters() {
14
+ /**
15
+ * Board-bound ticket on pages → memory bind + unbound→ticket relocate under lease.
16
+ * Shared by resume (interrupted board-bound unbound) and post-turn first assert.
17
+ */
18
+ async function bindAndRelocateDiaristIfBoardBound(admitted, authority, lease) {
19
+ const boardTicket = await readBoardTicketNumber(admitted.runDirectory);
20
+ if (boardTicket === undefined)
21
+ return;
22
+ if (admitted.ticketNumber === undefined) {
23
+ await bindAdmittedTicketNumber(admitted, boardTicket);
24
+ }
25
+ await relocateAdmittedRunToTicket(admitted, authority, lease);
26
+ }
27
+ function diaristAdapters(env) {
15
28
  return {
16
29
  trySettle: (admitted, authority, scope) => trySettleDiaristTerminalResult(admitted, authority, scope),
17
30
  shouldPresentSettled: () => true,
31
+ // Resume interrupted state: board ticket already on pages, run still under unbound/.
32
+ beforeDispatch: async (admitted, lease) => {
33
+ await bindAndRelocateDiaristIfBoardBound(admitted, env.principalAuthority, lease);
34
+ },
35
+ // First post-turn assert: accept hook wrote board pages; relocate before lease release.
36
+ afterDispatch: async (admitted, lease) => {
37
+ await bindAndRelocateDiaristIfBoardBound(admitted, env.principalAuthority, lease);
38
+ },
18
39
  };
19
40
  }
20
41
  export async function runPublicDiarist(argv, env, io, parseDiaristArgv) {
@@ -67,6 +88,7 @@ export async function runPublicDiarist(argv, env, io, parseDiaristArgv) {
67
88
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
68
89
  ...(env.model === undefined ? {} : { model: env.model }),
69
90
  ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
91
+ ...(handoffTicket === undefined ? {} : { assertedTicketNumber: handoffTicket }),
70
92
  });
71
93
  }
72
94
  catch (error) {
@@ -77,12 +99,6 @@ export async function runPublicDiarist(argv, env, io, parseDiaristArgv) {
77
99
  throw error;
78
100
  }
79
101
  await markRunAdmitted(admitted, env.principalAuthority);
80
- // Typed handoff: bind before the turn so identity is on durable pages.
81
- if (typeof handoffTicket === "number" &&
82
- Number.isSafeInteger(handoffTicket) &&
83
- handoffTicket >= 1) {
84
- await bindAdmittedTicketNumber(admitted, handoffTicket);
85
- }
86
102
  const turnProjection = {
87
103
  packageRoot: env.packageRoot,
88
104
  home: env.home,
@@ -107,22 +123,8 @@ export async function runPublicDiarist(argv, env, io, parseDiaristArgv) {
107
123
  env,
108
124
  io,
109
125
  request: turnRequest,
110
- adapters: diaristAdapters(),
126
+ adapters: diaristAdapters(env),
111
127
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
112
- }).then(async (result) => {
113
- // Accept hook binds ticket onto durable pages (#771). Mirror that page fact
114
- // onto the caller-visible admitted object — never pick a ticketNumber out of
115
- // the payload sequence (#881 sole-collapse ban on ticket/escalate).
116
- if (admitted.ticketNumber === undefined && result.admitted !== undefined) {
117
- const fromPages = await readRunTicketNumber(admitted.runDirectory);
118
- if (fromPages !== undefined) {
119
- await bindAdmittedTicketNumber(admitted, fromPages);
120
- if (result.admitted.ticketNumber === undefined) {
121
- result.admitted.ticketNumber = fromPages;
122
- }
123
- }
124
- }
125
- return result;
126
128
  });
127
129
  }
128
130
  /**
@@ -139,7 +141,7 @@ export async function runPublicDiaristResume(request, env, io) {
139
141
  const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
140
142
  return buildDiaristTurnRequest(admitted, resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared));
141
143
  },
142
- adapters: diaristAdapters(),
144
+ adapters: diaristAdapters(env),
143
145
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
144
146
  });
145
147
  }
@@ -1,16 +1,24 @@
1
1
  import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.js";
2
2
  import { CliUsageError } from "./cli-errors.js";
3
3
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
4
- import { admitInspectorInvocation, buildInspectorTransportPrompt, } from "./invocation.js";
4
+ import { admitInspectorInvocation, buildInspectorTransportPrompt, persistAdmittedSourceRunPath, } from "./invocation.js";
5
5
  import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
6
6
  import { loadResumableInspectorRun, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
7
7
  import { presentStructuralRejection, trySettleInspectorTerminalResult, } from "./settlement.js";
8
8
  import { projectRoleTurnRequest, } from "./turn-request.js";
9
+ function inspectorParentRunPath(admitted) {
10
+ if (typeof admitted.sourceRunPath === "string" && admitted.sourceRunPath.trim() !== "") {
11
+ return admitted.sourceRunPath;
12
+ }
13
+ return parentRunPathFromGatePointerInstruction(admitted.instruction);
14
+ }
9
15
  /** Project admitted invocation onto the host-neutral turn request. */
10
16
  export function buildInspectorTurnRequest(admitted, options) {
17
+ const sourceRun = inspectorParentRunPath(admitted);
11
18
  return projectRoleTurnRequest(admitted, {
12
19
  activation: {
13
20
  role: "inspector",
21
+ ...(sourceRun === undefined ? {} : { sourceRun }),
14
22
  },
15
23
  }, options);
16
24
  }
@@ -34,7 +42,7 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
34
42
  // #753/#786: gate re-ask / verbatim submission body ride summons.instruction on resume.
35
43
  const parentRunPath = parentRunPathFromGatePointerInstruction(parsed.instruction);
36
44
  if (parentRunPath !== undefined) {
37
- const resumeInstruction = env.reviewReask;
45
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
38
46
  const summons = {
39
47
  sourceRunPath: parentRunPath,
40
48
  ...(resumeInstruction === undefined
@@ -79,6 +87,10 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
79
87
  throw error;
80
88
  }
81
89
  await markRunAdmitted(admitted, env.principalAuthority);
90
+ if (parentRunPath !== undefined) {
91
+ await persistAdmittedSourceRunPath(admitted, parentRunPath);
92
+ admitted = { ...admitted, sourceRunPath: parentRunPath };
93
+ }
82
94
  const engineMaterial = engineSessionMaterialFromOptions({
83
95
  ...pickEngineAxis(env),
84
96
  packageRoot: env.packageRoot,
@@ -95,7 +107,9 @@ export async function runPublicInspector(argv, env, io, parseInspectorArgv) {
95
107
  : { correlationId: env.correlationId }),
96
108
  continuation: {
97
109
  kind: "initial",
98
- prompt: buildInspectorTransportPrompt(admitted, engineMaterial),
110
+ // #879: gate first mint uses parent payload as content; binding stays typed activation.
111
+ prompt: (env.reviewReask ?? env.gateReviewInstruction)
112
+ ?? buildInspectorTransportPrompt(admitted, engineMaterial),
99
113
  },
100
114
  });
101
115
  return await runPostAdmissionOneShot({
@@ -1,7 +1,7 @@
1
1
  import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.js";
2
- import { readRunTicketNumber } from "../run-ticket-number.js";
2
+ import { readBoardTicketNumber } from "../run-ticket-number.js";
3
3
  import { CliUsageError } from "./cli-errors.js";
4
- import { admitAuditorInvocation, admitGatekeeperInvocation, admitNavigatorInvocation, bindAdmittedTicketNumber, buildInstructionTransportPrompt, persistAdmittedSourceRunPath, } from "./invocation.js";
4
+ import { admitAuditorInvocation, admitGatekeeperInvocation, admitNavigatorInvocation, buildInstructionTransportPrompt, persistAdmittedSourceRunPath, } from "./invocation.js";
5
5
  import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
6
6
  import { loadResumableInstructionSeatRun, markRunAdmitted, } from "./run-lifecycle.js";
7
7
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
@@ -145,15 +145,17 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
145
145
  home: env.home,
146
146
  });
147
147
  auditorSourceRun = resolved.runDirectory;
148
- auditorSourceTicket = await readRunTicketNumber(resolved.runDirectory);
148
+ // Auditor inherits board identity only — migration-derived is display, not admission.
149
+ auditorSourceTicket = await readBoardTicketNumber(resolved.runDirectory);
149
150
  }
150
151
  catch (error) {
151
152
  presentStructuralRejection(new CliUsageError(error instanceof Error ? error.message : String(error)), io);
152
153
  return { exitCode: 2 };
153
154
  }
154
155
  }
155
- // #756/#786: auditor reask / verbatim submission body ride summons.instruction on resume.
156
- const resumeInstruction = env.reviewReask;
156
+ // #756/#879: auditor reask / verbatim submission body ride summons.instruction.
157
+ // Binding pointer stays --source-run / argv 卷宗指针; content is body or reask.
158
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
157
159
  const summons = {
158
160
  ...(resumeInstruction === undefined
159
161
  ? {
@@ -194,6 +196,9 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
194
196
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
195
197
  ...(env.model === undefined ? {} : { model: env.model }),
196
198
  ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
199
+ ...(auditorSourceTicket === undefined
200
+ ? {}
201
+ : { assertedTicketNumber: auditorSourceTicket }),
197
202
  });
198
203
  }
199
204
  catch (error) {
@@ -225,10 +230,12 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
225
230
  : { correlationId: env.correlationId }),
226
231
  continuation: {
227
232
  kind: "initial",
228
- prompt: buildInstructionTransportPrompt(admitted, engineSessionMaterialFromOptions({
229
- ...pickEngineAxis(env),
230
- packageRoot: env.packageRoot,
231
- })),
233
+ // #879: gate first mint uses parent payload as content when present.
234
+ prompt: (env.reviewReask ?? env.gateReviewInstruction)
235
+ ?? buildInstructionTransportPrompt(admitted, engineSessionMaterialFromOptions({
236
+ ...pickEngineAxis(env),
237
+ packageRoot: env.packageRoot,
238
+ })),
232
239
  },
233
240
  });
234
241
  return await runPostAdmissionOneShot({
@@ -236,14 +243,7 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
236
243
  env,
237
244
  io,
238
245
  request: turnRequest,
239
- adapters: instructionSeatAdapters({
240
- beforeDispatch: async (admittedSeat) => {
241
- // Auditor inherits source-run ticket (notary face) — typed, not prose match.
242
- if (auditorSourceTicket !== undefined) {
243
- await bindAdmittedTicketNumber(admittedSeat, auditorSourceTicket);
244
- }
245
- },
246
- }),
246
+ adapters: instructionSeatAdapters(),
247
247
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
248
248
  });
249
249
  },