@akagilnc/pi-workflow-roles 0.1.3572 → 0.1.3621

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 (129) hide show
  1. package/dist/acp-host/production-host.js +20239 -10556
  2. package/dist/analyst-gate-cycles-read.js +387 -0
  3. package/dist/archivist-record-entry.js +22 -1
  4. package/dist/audit-escalation.js +6 -0
  5. package/dist/auditor-soul.js +74 -0
  6. package/dist/collector-config.js +86 -0
  7. package/dist/collector-evidence.js +316 -0
  8. package/dist/collector-github.js +527 -0
  9. package/dist/collector-ledger.js +853 -0
  10. package/dist/collector-tool-schemas.js +33 -0
  11. package/dist/compliance-transport.js +104 -52
  12. package/dist/diarist-mechanical.js +411 -0
  13. package/dist/diarist-ticket-resolution.js +177 -0
  14. package/dist/diarist.js +311 -0
  15. package/dist/doctor-auditor.js +25 -0
  16. package/dist/doctor-contracts.js +2 -0
  17. package/dist/doctor-evidence.js +142 -0
  18. package/dist/gatekeeper-role.js +104 -81
  19. package/dist/host-transition-prior-native.js +78 -0
  20. package/dist/institutional-resolution.js +1 -95
  21. package/dist/judge-auditor.js +26 -0
  22. package/dist/ledger-session-read.js +227 -0
  23. package/dist/merger-git-state.js +78 -0
  24. package/dist/navigator-attendance.js +15 -7
  25. package/dist/navigator-public-session.js +170 -0
  26. package/dist/navigator-session-contracts.js +36 -40
  27. package/dist/notary-source-run.js +122 -0
  28. package/dist/package-contracts/auditor-output.js +66 -0
  29. package/dist/package-contracts/evidence-child-output.js +34 -0
  30. package/dist/package-contracts/judge-output.js +2 -0
  31. package/dist/package-contracts/terminating-tools.js +25 -2
  32. package/dist/package-resources/method-skill.js +254 -0
  33. package/dist/packaged-role-registry.js +36 -0
  34. package/dist/pi/durable-principal.js +61 -0
  35. package/dist/pi/in-process-session.js +25 -6
  36. package/dist/pi/known-failure.js +52 -0
  37. package/dist/pi/role-turn-host.js +430 -0
  38. package/dist/public-cli/auto-resume.js +414 -0
  39. package/dist/public-cli/cli-errors.js +8 -0
  40. package/dist/public-cli/cli-io.js +1 -0
  41. package/dist/public-cli/command-renderer.js +5 -0
  42. package/dist/public-cli/doctor-run.js +84 -0
  43. package/dist/public-cli/inspector-run.js +136 -0
  44. package/dist/public-cli/instruction-seat-run.js +256 -0
  45. package/dist/public-cli/invocation.js +2225 -0
  46. package/dist/public-cli/judge-run.js +118 -0
  47. package/dist/public-cli/load-production-acp-host.js +40 -0
  48. package/dist/public-cli/main.js +1140 -652
  49. package/dist/public-cli/notary-run.js +161 -0
  50. package/dist/public-cli/option-definitions.js +1192 -0
  51. package/dist/public-cli/post-admission.js +667 -0
  52. package/dist/public-cli/public-run-credentials.js +49 -0
  53. package/dist/public-cli/registry.js +4 -1
  54. package/dist/public-cli/reviewer-dispatch-rejection.js +77 -0
  55. package/dist/public-cli/run-lifecycle.js +1344 -0
  56. package/dist/public-cli/seat-ticket-binding.js +113 -0
  57. package/dist/public-cli/settlement.js +3441 -0
  58. package/dist/public-cli/terminal.js +135 -0
  59. package/dist/public-cli/turn-request.js +25 -0
  60. package/dist/public-role-summons.js +301 -0
  61. package/dist/receipt-delivery-policy.js +10 -0
  62. package/dist/reviewer-child-executor.js +80 -8
  63. package/dist/reviewer-execution-ledger.js +2 -1
  64. package/dist/run-terminal-artifacts.js +195 -0
  65. package/dist/run-ticket-number.js +40 -0
  66. package/dist/session-assistant-usage.js +107 -0
  67. package/dist/shape-unreadable-failure.js +34 -0
  68. package/dist/submission-errors.js +3 -0
  69. package/dist/submission-ledger.js +388 -0
  70. package/dist/ticket-provenance-contracts.js +115 -0
  71. package/dist/ticket-provenance.js +340 -0
  72. package/extensions/role-runtime.ts +7 -0
  73. package/package.json +1 -1
  74. package/scripts/build-package.mjs +2 -1
  75. package/souls/doctor-auditor.md +1 -0
  76. package/souls/evidence-child.md +1 -0
  77. package/src/acp-host/production-host.ts +3 -0
  78. package/src/acp-host/role-envelope.ts +2 -2
  79. package/src/acp-host/role-turn-host.ts +9 -1
  80. package/src/analyst-gate-cycles-read.ts +37 -2
  81. package/src/archivist-record-entry.ts +45 -1
  82. package/src/audit-escalation.ts +17 -0
  83. package/src/auditor-role.ts +22 -0
  84. package/src/auditor-soul.ts +42 -0
  85. package/src/compliance-transport.ts +177 -66
  86. package/src/doctor-auditor.ts +10 -14
  87. package/src/doctor-contracts.ts +1 -0
  88. package/src/doctor-role.ts +2 -2
  89. package/src/evidence-child-role.ts +22 -0
  90. package/src/gatekeeper-pass-envelope.ts +109 -0
  91. package/src/gatekeeper-role.ts +145 -104
  92. package/src/host-contracts.ts +9 -1
  93. package/src/institutional-resolution.ts +8 -163
  94. package/src/judge-auditor.ts +10 -15
  95. package/src/judge-role.ts +8 -0
  96. package/src/navigator-attendance.ts +25 -9
  97. package/src/navigator-public-session.ts +217 -0
  98. package/src/navigator-session-contracts.ts +61 -54
  99. package/src/notary-source-run.ts +3 -1
  100. package/src/package-contracts/auditor-output.ts +82 -0
  101. package/src/package-contracts/evidence-child-output.ts +51 -0
  102. package/src/package-contracts/judge-output.ts +1 -0
  103. package/src/package-contracts/reviewer-output.ts +2 -2
  104. package/src/package-contracts/terminating-tools.ts +31 -1
  105. package/src/packaged-role-registry.ts +43 -0
  106. package/src/pi/adapter.ts +2 -1
  107. package/src/pi/in-process-session.ts +38 -12
  108. package/src/pi/role-turn-host.ts +34 -0
  109. package/src/public-cli/cli.ts +14 -6
  110. package/src/public-cli/instruction-seat-run.ts +289 -50
  111. package/src/public-cli/invocation.ts +66 -25
  112. package/src/public-cli/option-definitions.ts +61 -0
  113. package/src/public-cli/post-admission.ts +7 -1
  114. package/src/public-cli/registry.ts +4 -1
  115. package/src/public-cli/run-lifecycle.ts +22 -3
  116. package/src/public-cli/settlement.ts +120 -5
  117. package/src/public-cli/terminal.ts +2 -0
  118. package/src/public-role-summons.ts +431 -0
  119. package/src/receipt-delivery-policy.ts +10 -0
  120. package/src/reviewer-agent.ts +1 -1
  121. package/src/reviewer-child-executor.ts +114 -12
  122. package/src/reviewer-execution-ledger.ts +3 -2
  123. package/src/role-runtime.ts +158 -17
  124. package/src/session-assistant-usage.ts +119 -0
  125. package/src/session-opening-materials.ts +1 -1
  126. package/src/shape-unreadable-failure.ts +49 -0
  127. package/src/submission-errors.ts +3 -0
  128. package/dist/evidence-child-executor.js +0 -829
  129. package/src/evidence-child-executor.ts +0 -1140
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Role-inside-role summons via the single public activation path (#675).
3
+ * Gate / compliance / evidence callers invoke the same post-admission face a
4
+ * human uses; no second institutional session open, no model-only seat page.
5
+ *
6
+ * Lazy-loads every local value dependency. Pi loads extensions through jiti
7
+ * (CJS transform, moduleCache:false); a static import graph that re-enters this
8
+ * module via settlement → compliance leaves binding slots undefined
9
+ * (`reading 'dirname'`, `reading 'tryHomeFromAkRolesPath'`). Dynamic import
10
+ * starts after the caller module has finished init, so those slots stay intact.
11
+ */
12
+ import { existsSync } from "node:fs";
13
+ import { join } from "node:path";
14
+
15
+ import type { CliIo } from "./public-cli/cli-io.ts";
16
+ import type { CredentialProviders, EffectiveSeat } from "./public-cli/config.ts";
17
+ import type { PublicCallableRole } from "./public-cli/registry.ts";
18
+ import type { RoleTurnHost } from "./host-contracts.ts";
19
+ import type { TerminalResult } from "./public-cli/terminal.ts";
20
+
21
+ /** Env published by the parent activation so nested summons never re-derive root. */
22
+ export const AK_ROLE_PACKAGE_ROOT_ENV = "AK_ROLE_PACKAGE_ROOT" as const;
23
+
24
+
25
+ export type PublicSummonRole =
26
+ | "inspector"
27
+ | "notary"
28
+ | "auditor"
29
+ | "evidence-child"
30
+ | "navigator"
31
+ | "gatekeeper"
32
+ | "judge"
33
+ | "doctor";
34
+
35
+ export type PublicSummonRequest = {
36
+ readonly role: PublicSummonRole;
37
+ /** Argv after the role token (same shape as `ak-role <role> …`). */
38
+ readonly argv: readonly string[];
39
+ readonly cwd: string;
40
+ /** Package home; derived from source run when omitted. */
41
+ readonly home?: string;
42
+ readonly packageRoot?: string;
43
+ readonly io?: CliIo;
44
+ readonly credentials?: CredentialProviders;
45
+ readonly agentDir?: string;
46
+ /**
47
+ * Optional Pi argv forwarded to the role-turn host (same face as public CLI
48
+ * seat extraPiArgs). Callers pass explicitly — no process.env test protocol.
49
+ */
50
+ readonly extraPiArgs?: readonly string[];
51
+ /**
52
+ * Parent cancellation. A nested summon is an in-process await over a child
53
+ * activation; without this the child keeps running (and spending) after the
54
+ * parent tool call is cancelled (#675).
55
+ */
56
+ readonly signal?: AbortSignal;
57
+ };
58
+
59
+ export type PublicSummonResult = {
60
+ readonly exitCode: number;
61
+ readonly terminal?: TerminalResult;
62
+ /** Independent officer/role run directory (正本); parent books pointer only. */
63
+ readonly runDirectory?: string;
64
+ /** Offline diagnostics from nested CLI (structural rejection text). */
65
+ readonly stderr?: string;
66
+ };
67
+
68
+ function createCapturingIo(): { io: CliIo; stderrText(): string } {
69
+ const chunks: string[] = [];
70
+ return {
71
+ io: {
72
+ stdout() {},
73
+ stderr(text: string) {
74
+ chunks.push(text);
75
+ },
76
+ },
77
+ stderrText: () => chunks.join(""),
78
+ };
79
+ }
80
+
81
+ /** Path parent without depending on a jiti-bound `path.dirname` closure. */
82
+ function parentDir(path: string): string {
83
+ const end = path.endsWith("/") || path.endsWith("\\") ? path.slice(0, -1) : path;
84
+ const idx = Math.max(end.lastIndexOf("/"), end.lastIndexOf("\\"));
85
+ if (idx <= 0) return end;
86
+ return end.slice(0, idx);
87
+ }
88
+
89
+ function walkPackageRoot(start: string): string | undefined {
90
+ let dir = start;
91
+ for (let i = 0; i < 12; i += 1) {
92
+ if (existsSync(join(dir, "package.json")) && existsSync(join(dir, "souls"))) {
93
+ return dir;
94
+ }
95
+ const parent = parentDir(dir);
96
+ if (parent === dir) break;
97
+ dir = parent;
98
+ }
99
+ return undefined;
100
+ }
101
+
102
+ /**
103
+ * Resolve install package root for nested public summons.
104
+ * Prefer explicit / env coordinates; never require import.meta under jiti.
105
+ */
106
+ export function resolveSummonsPackageRoot(explicit?: string): string {
107
+ if (typeof explicit === "string" && explicit.trim() !== "") {
108
+ return explicit;
109
+ }
110
+ const fromEnv = process.env[AK_ROLE_PACKAGE_ROOT_ENV];
111
+ if (typeof fromEnv === "string" && fromEnv.trim() !== "") {
112
+ return fromEnv;
113
+ }
114
+ const fromCwd = walkPackageRoot(process.cwd());
115
+ if (fromCwd !== undefined) return fromCwd;
116
+ try {
117
+ const metaUrl = import.meta.url;
118
+ if (typeof metaUrl === "string" && metaUrl.startsWith("file:")) {
119
+ const filePath = decodeURIComponent(metaUrl.slice("file://".length));
120
+ const fromMeta = walkPackageRoot(parentDir(filePath));
121
+ if (fromMeta !== undefined) return fromMeta;
122
+ }
123
+ } catch {
124
+ // import.meta unavailable — fall through.
125
+ }
126
+ throw new Error(
127
+ "public role summons cannot resolve package root (pass packageRoot or set AK_ROLE_PACKAGE_ROOT)",
128
+ );
129
+ }
130
+
131
+ async function resolveSummonHome(options: PublicSummonRequest): Promise<string> {
132
+ if (options.home !== undefined && options.home.trim() !== "") {
133
+ return options.home;
134
+ }
135
+ const { tryHomeFromAkRolesPath, packageMachineHome } = await import(
136
+ "./activation-ledger-topology.ts"
137
+ );
138
+ const fromCwd = tryHomeFromAkRolesPath(options.cwd);
139
+ if (fromCwd !== undefined && fromCwd.length > 0) return fromCwd;
140
+ return packageMachineHome();
141
+ }
142
+
143
+ /** Seat axes only — no parent-env fallback (#675 / #617 DK-3). */
144
+ function projectSeatEngine(seat: EffectiveSeat): { engine?: string } {
145
+ return seat.engine === undefined ? {} : { engine: seat.engine };
146
+ }
147
+
148
+ function projectSeatHost(seat: EffectiveSeat): { host?: string } {
149
+ return seat.host === undefined ? {} : { host: seat.host };
150
+ }
151
+
152
+ function projectSeatModel(
153
+ seat: EffectiveSeat,
154
+ ): { model?: import("./public-cli/config.ts").SeatModelConfig } {
155
+ return seat.selection === undefined ? {} : { model: seat.selection };
156
+ }
157
+
158
+ async function createSummonEnv(options: {
159
+ readonly role: PublicCallableRole;
160
+ readonly home: string;
161
+ readonly agentDir: string;
162
+ readonly cwd: string;
163
+ readonly packageRoot: string;
164
+ readonly credentials: CredentialProviders;
165
+ readonly seat: EffectiveSeat;
166
+ readonly extraPiArgs?: readonly string[];
167
+ }) {
168
+ const [{ piDurablePrincipalAuthority }, { appendPiSessionCustomEntry, createPiRoleTurnHost }] =
169
+ await Promise.all([
170
+ import("./pi/durable-principal.ts"),
171
+ import("./pi/role-turn-host.ts"),
172
+ ]);
173
+ const principalAuthority = piDurablePrincipalAuthority;
174
+ // package root is request-scoped only — never leave process.env residue (#675).
175
+ const piRecords = {
176
+ async recordLaunchedPiIdentity(runDirectory: string, identity: unknown) {
177
+ const { recordLaunchedPiIdentity } = await import("./public-cli/invocation.ts");
178
+ return recordLaunchedPiIdentity(runDirectory, identity as never);
179
+ },
180
+ async recordLaunchedRolePackageIdentity(runDirectory: string, identity: unknown) {
181
+ const { recordLaunchedRolePackageIdentity } = await import("./public-cli/invocation.ts");
182
+ return recordLaunchedRolePackageIdentity(runDirectory, identity as never);
183
+ },
184
+ async observeLaunchedRolePackageIdentity(root: string, roleEntry: string) {
185
+ const { observeLaunchedRolePackageIdentity } = await import("./public-cli/invocation.ts");
186
+ return observeLaunchedRolePackageIdentity(root, roleEntry);
187
+ },
188
+ } as const;
189
+ const piHost = createPiRoleTurnHost({
190
+ packageRoot: options.packageRoot,
191
+ principalAuthority,
192
+ ...(options.extraPiArgs === undefined || options.extraPiArgs.length === 0
193
+ ? {}
194
+ : { extraPiArgs: options.extraPiArgs }),
195
+ ...piRecords,
196
+ });
197
+ // Same host axis table as public CLI (#617 DK-3 / #675 / #729): seat.host is
198
+ // a description-table key; pi stays the in-process default adapter.
199
+ const hostName = options.seat.host ?? "pi";
200
+ let roleTurnHost = piHost;
201
+ if (hostName !== "pi") {
202
+ const { lookupHostDescription } = await import("./host-descriptions.ts");
203
+ const { loadProductionAcpHostFactory } = await import(
204
+ "./public-cli/load-production-acp-host.ts"
205
+ );
206
+ // #729: the description table is the sole host authority — every registered
207
+ // key loads the same generic ACP factory (no per-host fork). Unregistered
208
+ // keys fail closed before any turn (#510), as the pre-merge fork did.
209
+ if (lookupHostDescription(hostName) === undefined) {
210
+ throw new Error(
211
+ `public role summons host unregistered: host=${hostName} seat=${options.role}`,
212
+ );
213
+ }
214
+ let hostPromise: Promise<RoleTurnHost> | undefined;
215
+ roleTurnHost = {
216
+ executeTurn: async (request) => {
217
+ hostPromise ??= loadProductionAcpHostFactory(options.packageRoot, hostName).then(
218
+ (create) =>
219
+ create({
220
+ packageRoot: options.packageRoot,
221
+ principalAuthority,
222
+ }),
223
+ );
224
+ return (await hostPromise).executeTurn(request);
225
+ },
226
+ };
227
+ }
228
+ return {
229
+ home: options.home,
230
+ principalAuthority,
231
+ agentDir: options.agentDir,
232
+ sessionAppender: appendPiSessionCustomEntry,
233
+ packageRoot: options.packageRoot,
234
+ roleTurnHost,
235
+ cwd: options.cwd,
236
+ credentials: options.credentials,
237
+ ...projectSeatModel(options.seat),
238
+ ...projectSeatEngine(options.seat),
239
+ ...projectSeatHost(options.seat),
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Summon one public callable role through the same runners the CLI uses
245
+ * (ADR 0052 / #675). Seat axes come from the live table.
246
+ */
247
+ export async function summonPublicRole(
248
+ options: PublicSummonRequest,
249
+ ): Promise<PublicSummonResult> {
250
+ const packageRoot = resolveSummonsPackageRoot(options.packageRoot);
251
+ const home = await resolveSummonHome(options);
252
+ const agentDir =
253
+ options.agentDir
254
+ ?? process.env.PI_CODING_AGENT_DIR
255
+ ?? join(home, ".pi", "agent");
256
+ const {
257
+ loadCredentialProviders,
258
+ loadPublicCliConfig,
259
+ resolveEffectiveSeat,
260
+ } = await import("./public-cli/config.ts");
261
+ const credentials =
262
+ options.credentials ?? (await loadCredentialProviders(agentDir));
263
+ const config = await loadPublicCliConfig(home);
264
+ const seat = resolveEffectiveSeat(config, options.role, credentials);
265
+ const env = {
266
+ ...(await createSummonEnv({
267
+ role: options.role,
268
+ home,
269
+ agentDir,
270
+ cwd: options.cwd,
271
+ packageRoot,
272
+ credentials,
273
+ seat,
274
+ ...(options.extraPiArgs === undefined ? {} : { extraPiArgs: options.extraPiArgs }),
275
+ })),
276
+ // Host config passthrough only — same face as public CLI (#422 / #675).
277
+ ...(config.autoResumeLimit === undefined
278
+ ? {}
279
+ : { autoResumeLimit: config.autoResumeLimit }),
280
+ // Parent cancellation reaches the nested activation's own turn dispatch.
281
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
282
+ };
283
+ const captured = options.io === undefined ? createCapturingIo() : undefined;
284
+ const io = options.io ?? captured!.io;
285
+
286
+ let result: {
287
+ exitCode: number;
288
+ terminal?: TerminalResult;
289
+ admitted?: { readonly runDirectory?: string };
290
+ };
291
+ switch (options.role) {
292
+ case "notary": {
293
+ const [{ runPublicNotary }, { parseNotaryArgv }] = await Promise.all([
294
+ import("./public-cli/notary-run.ts"),
295
+ import("./public-cli/invocation.ts"),
296
+ ]);
297
+ result = await runPublicNotary(options.argv, env, io, parseNotaryArgv);
298
+ break;
299
+ }
300
+ case "inspector": {
301
+ const [{ runPublicInspector }, { parseInspectorArgv }] = await Promise.all([
302
+ import("./public-cli/inspector-run.ts"),
303
+ import("./public-cli/invocation.ts"),
304
+ ]);
305
+ result = await runPublicInspector(options.argv, env, io, parseInspectorArgv);
306
+ break;
307
+ }
308
+ case "auditor": {
309
+ const [{ runPublicInstructionSeat }, { parseAuditorArgv }] = await Promise.all([
310
+ import("./public-cli/instruction-seat-run.ts"),
311
+ import("./public-cli/invocation.ts"),
312
+ ]);
313
+ result = await runPublicInstructionSeat(
314
+ options.argv,
315
+ env,
316
+ io,
317
+ "auditor",
318
+ parseAuditorArgv,
319
+ );
320
+ break;
321
+ }
322
+ case "evidence-child": {
323
+ const [{ runPublicInstructionSeat }, { parseEvidenceChildArgv }] = await Promise.all([
324
+ import("./public-cli/instruction-seat-run.ts"),
325
+ import("./public-cli/invocation.ts"),
326
+ ]);
327
+ result = await runPublicInstructionSeat(
328
+ options.argv,
329
+ env,
330
+ io,
331
+ "evidence-child",
332
+ parseEvidenceChildArgv,
333
+ );
334
+ break;
335
+ }
336
+ case "navigator": {
337
+ const [{ runPublicInstructionSeat }, { parseNavigatorArgv }] = await Promise.all([
338
+ import("./public-cli/instruction-seat-run.ts"),
339
+ import("./public-cli/invocation.ts"),
340
+ ]);
341
+ result = await runPublicInstructionSeat(
342
+ options.argv,
343
+ env,
344
+ io,
345
+ "navigator",
346
+ parseNavigatorArgv,
347
+ );
348
+ break;
349
+ }
350
+ case "gatekeeper": {
351
+ const [{ runPublicInstructionSeat }, { parseGatekeeperArgv }] = await Promise.all([
352
+ import("./public-cli/instruction-seat-run.ts"),
353
+ import("./public-cli/invocation.ts"),
354
+ ]);
355
+ result = await runPublicInstructionSeat(
356
+ options.argv,
357
+ env,
358
+ io,
359
+ "gatekeeper",
360
+ parseGatekeeperArgv,
361
+ );
362
+ break;
363
+ }
364
+ case "judge": {
365
+ const [{ runPublicJudge }, { parseJudgeArgv }] = await Promise.all([
366
+ import("./public-cli/judge-run.ts"),
367
+ import("./public-cli/invocation.ts"),
368
+ ]);
369
+ result = await runPublicJudge(options.argv, env, io, parseJudgeArgv);
370
+ break;
371
+ }
372
+ case "doctor": {
373
+ const [{ runPublicDoctor }, { parseDoctorArgv }] = await Promise.all([
374
+ import("./public-cli/doctor-run.ts"),
375
+ import("./public-cli/invocation.ts"),
376
+ ]);
377
+ result = await runPublicDoctor(options.argv, env, io, parseDoctorArgv);
378
+ break;
379
+ }
380
+ }
381
+
382
+ const stderr = captured?.stderrText();
383
+ const runDirectory = result.admitted?.runDirectory;
384
+ return {
385
+ exitCode: result.exitCode,
386
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
387
+ ...(typeof runDirectory === "string" && runDirectory.trim() !== ""
388
+ ? { runDirectory }
389
+ : {}),
390
+ ...(stderr === undefined || stderr === "" ? {} : { stderr }),
391
+ };
392
+ }
393
+
394
+ /** Gate officer summons: notary via --source-run; inspector via pointer instruction. */
395
+ export async function summonGateOfficer(options: {
396
+ readonly officer: "inspector" | "notary";
397
+ readonly sourceRunDirectory: string;
398
+ readonly cwd: string;
399
+ readonly home?: string;
400
+ readonly packageRoot?: string;
401
+ readonly io?: CliIo;
402
+ /** Parent cancellation for the nested officer activation (#675). */
403
+ readonly signal?: AbortSignal;
404
+ }): Promise<PublicSummonResult> {
405
+ let home = options.home;
406
+ if (home === undefined) {
407
+ const { homeFromRunDirectory } = await import("./activation-ledger-topology.ts");
408
+ // Hard path resolve: fail loud — never fall through to packageMachineHome (#604 / #675).
409
+ home = homeFromRunDirectory(options.sourceRunDirectory);
410
+ }
411
+ if (options.officer === "notary") {
412
+ return summonPublicRole({
413
+ role: "notary",
414
+ argv: ["--source-run", options.sourceRunDirectory, "--project", options.cwd],
415
+ cwd: options.cwd,
416
+ ...(home === undefined ? {} : { home }),
417
+ ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
418
+ ...(options.io === undefined ? {} : { io: options.io }),
419
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
420
+ });
421
+ }
422
+ return summonPublicRole({
423
+ role: "inspector",
424
+ argv: [`卷宗指针:${options.sourceRunDirectory}`],
425
+ cwd: options.cwd,
426
+ ...(home === undefined ? {} : { home }),
427
+ ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
428
+ ...(options.io === undefined ? {} : { io: options.io }),
429
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
430
+ });
431
+ }
@@ -90,6 +90,16 @@ export function createReceiptDeliveryPolicy() {
90
90
  recordDeliveryRequest() {
91
91
  deliveryTurns = Math.min(RECEIPT_DELIVERY_TURN_LIMIT, deliveryTurns + 1);
92
92
  },
93
+ /**
94
+ * A nested session settled without an accepted receipt after spending its own
95
+ * delivery budget. Adopt its facts and close this budget: another prompt would
96
+ * open an independent session, not a delivery request on the settled one.
97
+ */
98
+ recordNestedNoReceipt(facts: NoReceiptLifecycleFacts) {
99
+ terminalToolCalled = terminalToolCalled || facts.terminalToolCalled;
100
+ rejectedReceipts.push(...facts.rejectedReceipts);
101
+ deliveryTurns = RECEIPT_DELIVERY_TURN_LIMIT;
102
+ },
93
103
  nextAction(): "accepted" | "request-delivery" | "no-receipt" {
94
104
  if (accepted) return "accepted";
95
105
  return deliveryTurns < RECEIPT_DELIVERY_TURN_LIMIT ? "request-delivery" : "no-receipt";
@@ -7,7 +7,7 @@ import { createReviewerWorkspaceOwner, type ReviewerWorkspaceFaultPoint } from "
7
7
  import { normalizeReviewerFailureDiagnostic } from "./reviewer-failure-diagnostic.ts";
8
8
 
9
9
  type ReviewerLegRunResultCommon = Readonly<{ target: ReviewerTargetSnapshot; prompt: ReviewerPromptText; workspaceDisposition: ReviewerWorkspaceDisposition }>;
10
- export type ReviewerSuccessfulLegRunResult = ReviewerLegRunResultCommon & Readonly<{ status: "successful"; report: string; usage: ReviewerUsage; failure?: never }>;
10
+ export type ReviewerSuccessfulLegRunResult = ReviewerLegRunResultCommon & Readonly<{ status: "successful"; report: unknown; usage: ReviewerUsage; failure?: never }>;
11
11
  export type ReviewerFailedLegRunResult = ReviewerLegRunResultCommon & Readonly<{ status: "failed"; failure: ReviewerFailureClassification; diagnostic: string; cause?: unknown; report?: never; usage?: never }>;
12
12
  export type ReviewerLegRunResult = ReviewerSuccessfulLegRunResult | ReviewerFailedLegRunResult;
13
13
  type Envelope<L> = Readonly<{ identity: string; target: ReviewerTargetSnapshot; legs: Readonly<L> }>;
@@ -1,15 +1,28 @@
1
- import { executeEvidenceChild } from "./evidence-child-executor.ts";
1
+ import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
2
2
  import type { HostContext } from "./host-contracts.ts";
3
3
  import type { AcceptedReviewerLeg } from "./reviewer-dispatch.ts";
4
+ import type { PublicSummonResult } from "./public-role-summons.ts";
5
+ import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
6
+ import type { Usage } from "@earendil-works/pi-ai";
7
+
8
+ export type EvidenceChildSummon = (
9
+ argv: readonly string[],
10
+ /** Parent cancellation forwarded to the nested activation (#675). */
11
+ signal?: AbortSignal,
12
+ ) => Promise<PublicSummonResult>;
4
13
 
5
14
  export type ReviewerChildExecuteOptions = Readonly<{
6
15
  signal?: AbortSignal;
7
16
  credentialScratchParent?: string;
8
- /** Run directory carrying the institutional resolution page (#518). Derives
9
- * from context when absent. */
17
+ /** Parent run directory pointer (ADR 0079). */
10
18
  runDirectory?: string;
11
- /** Package root for optional engine method-material on legs (#378). */
19
+ /** @deprecated engine rides the public seat table (#675); retained for call-site compatibility. */
12
20
  packageRoot?: string;
21
+ /**
22
+ * Test seam for public-role summons. Production calls the shared public
23
+ * activation path (#675).
24
+ */
25
+ summonEvidenceChild?: EvidenceChildSummon;
13
26
  }>;
14
27
 
15
28
  /**
@@ -26,15 +39,104 @@ export function projectSharedChildFailure(error: unknown): unknown {
26
39
  return error;
27
40
  }
28
41
 
29
- /** Reviewer policy adapter over the shared evidence-child lifecycle seam. */
30
- export async function executeReviewerChild(workspace: string, leg: AcceptedReviewerLeg, context: HostContext, options: ReviewerChildExecuteOptions = {}) {
31
- try {
32
- return await executeEvidenceChild(workspace, leg.prompt, context, {
33
- ...(options.signal === undefined ? {} : { signal: options.signal }),
34
- ...(options.credentialScratchParent === undefined ? {} : { credentialScratchParent: options.credentialScratchParent }),
35
- ...(options.runDirectory === undefined ? {} : { runDirectory: options.runDirectory }),
36
- ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
42
+ function failureClassification(
43
+ cause: string | undefined,
44
+ ): "provider" | "child" | "unknown" {
45
+ if (cause === "provider") return "provider";
46
+ if (
47
+ cause === "output"
48
+ || cause === "timeout"
49
+ || cause === "activation"
50
+ || cause === "session"
51
+ || cause === "unrecognized"
52
+ ) {
53
+ return "child";
54
+ }
55
+ return "unknown";
56
+ }
57
+
58
+ function reportFromSummon(summoned: PublicSummonResult): unknown {
59
+ const outcome = summoned.terminal?.roleOutcome;
60
+ if (outcome === undefined) {
61
+ throw Object.assign(
62
+ new Error(`Evidence-child public summon produced no terminal (exit ${summoned.exitCode})`),
63
+ { evidenceChildFailure: "child" as const },
64
+ );
65
+ }
66
+ if (outcome.kind === "failure") {
67
+ throw Object.assign(new Error(outcome.diagnostic), {
68
+ evidenceChildFailure: failureClassification(outcome.cause),
37
69
  });
70
+ }
71
+ if (outcome.kind === "accepted") {
72
+ // No field-presence reject (ADR 0055 / #675). Keep original accepted bytes:
73
+ // report key when present, otherwise the whole decisiveFacts body.
74
+ const facts = outcome.decisiveFacts;
75
+ if (Object.hasOwn(facts, "report")) return facts.report;
76
+ return facts;
77
+ }
78
+ throw Object.assign(
79
+ new Error("Evidence-child public summon returned no accepted report body"),
80
+ { evidenceChildFailure: "child" as const },
81
+ );
82
+ }
83
+
84
+ /** Reviewer evidence leg via the public evidence-child activation path (#675). */
85
+ export async function executeReviewerChild(
86
+ workspace: string,
87
+ leg: AcceptedReviewerLeg,
88
+ context: HostContext,
89
+ options: ReviewerChildExecuteOptions = {},
90
+ ): Promise<{ report: unknown; usage: Usage; prompt: ReviewerPromptText }> {
91
+ try {
92
+ if (options.signal?.aborted) {
93
+ throw Object.assign(new DOMException("The operation was aborted.", "AbortError"), {
94
+ evidenceChildFailure: "child" as const,
95
+ });
96
+ }
97
+ const runDirectory = options.runDirectory ?? auditorRunDirectory(context);
98
+ const pointer =
99
+ runDirectory === undefined
100
+ ? ""
101
+ : `\n卷宗指针:${runDirectory}`;
102
+ const argv = [`${String(leg.prompt)}${pointer}`];
103
+ const summon =
104
+ options.summonEvidenceChild
105
+ ?? (async (nextArgv: readonly string[], childSignal?: AbortSignal) => {
106
+ const { summonPublicRole } = await import("./public-role-summons.ts");
107
+ // Parent run home owns nested public seats; leg workspace cwd is a bare worktree.
108
+ let home: string | undefined;
109
+ if (runDirectory !== undefined) {
110
+ const { homeFromRunDirectory } = await import("./activation-ledger-topology.ts");
111
+ // Hard path resolve: fail loud — no packageMachineHome fallback (#604 / #675).
112
+ home = homeFromRunDirectory(runDirectory);
113
+ }
114
+ return summonPublicRole({
115
+ role: "evidence-child",
116
+ argv: nextArgv,
117
+ cwd: workspace,
118
+ ...(home === undefined ? {} : { home }),
119
+ ...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
120
+ ...(childSignal === undefined ? {} : { signal: childSignal }),
121
+ });
122
+ });
123
+ const summoned = await summon(argv, options.signal);
124
+ if (options.signal?.aborted) {
125
+ throw Object.assign(new DOMException("The operation was aborted.", "AbortError"), {
126
+ evidenceChildFailure: "child" as const,
127
+ });
128
+ }
129
+ const report = reportFromSummon(summoned);
130
+ const { usageFromPublicSummon } = await import("./session-assistant-usage.ts");
131
+ // Real session usage when present; no session → token-zero without inventing cost.
132
+ const usage = (await usageFromPublicSummon(summoned)) ?? ({
133
+ input: 0,
134
+ output: 0,
135
+ cacheRead: 0,
136
+ cacheWrite: 0,
137
+ totalTokens: 0,
138
+ } as Usage);
139
+ return { report, usage, prompt: leg.prompt };
38
140
  } catch (error) {
39
141
  throw projectSharedChildFailure(error);
40
142
  }
@@ -44,7 +44,7 @@ type ReviewerLegResultEvidenceCommon = Readonly<{
44
44
  workspaceDisposition: ReviewerWorkspaceDisposition;
45
45
  }>;
46
46
  export type ReviewerLegResultEvidence = ReviewerLegResultEvidenceCommon & (
47
- | Readonly<{ status: "successful"; report: string; usage: ReviewerUsage; failure?: never }>
47
+ | Readonly<{ status: "successful"; report: unknown; usage: ReviewerUsage; failure?: never }>
48
48
  | Readonly<{ status: "failed"; failure: ReviewerFailureClassification; diagnostic: string; report?: never; usage?: never }>
49
49
  );
50
50
  export type ReviewerEvidenceEvent =
@@ -179,7 +179,8 @@ export function createReviewerExecutionLedger(): ReviewerExecutionLedger {
179
179
  throw new Error("Actual runner prompt does not exactly match compiled prompt text");
180
180
  if (!sameReviewerPinnedTarget(event.target, accepted.target)) throw new Error("Runner target does not match shared pinned target");
181
181
  if (event.status === "successful") {
182
- if (typeof event.report !== "string" || event.report.length === 0 || event.failure !== undefined) throw new Error("Successful settlement requires a report");
182
+ // Report field required; no type/blank reshape gate (ADR 0055 / #675).
183
+ if (event.report === undefined || event.failure !== undefined) throw new Error("Successful settlement requires a report");
183
184
  } else if (event.failure === undefined || event.report !== undefined) {
184
185
  throw new Error("Failed settlement requires a bounded failure classification and no report");
185
186
  }