@akagilnc/pi-workflow-roles 0.1.3771 → 0.1.3783

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.
@@ -65,6 +65,12 @@ export type PublicSummonRequest = {
65
65
  * Never folded into argv / parent-run lookup keys (ADR 0079 卷宗指针 stays pure).
66
66
  */
67
67
  readonly gateReviewInstruction?: string;
68
+ /**
69
+ * Parent-invocation host axis (#617 / #645). Nested institutional legs inherit
70
+ * the live parent `--host` so a single public entry does not require mutating
71
+ * persistent seat host. Seat model/thinking stay on the officer seat table.
72
+ */
73
+ readonly host?: string;
68
74
  /**
69
75
  * Offline test inject — same face as public CLI env.roleTurnHost. Production
70
76
  * summons leave this unset and use the seat-resolved host.
@@ -211,14 +217,13 @@ async function createSummonEnv(options: {
211
217
  const hostName = options.seat.host ?? "pi";
212
218
  let roleTurnHost = piHost;
213
219
  if (hostName !== "pi") {
214
- const { lookupHostDescription } = await import("./host-descriptions.ts");
215
- const { loadProductionAcpHostFactory } = await import(
216
- "./public-cli/load-production-acp-host.ts"
220
+ const { lookupHostFamily } = await import("./host-descriptions.ts");
221
+ const { loadProductionExternalHostFactory } = await import(
222
+ "./public-cli/load-production-external-host.ts"
217
223
  );
218
- // #729: the description table is the sole host authority — every registered
219
- // key loads the same generic ACP factory (no per-host fork). Unregistered
220
- // keys fail closed before any turn (#510), as the pre-merge fork did.
221
- if (lookupHostDescription(hostName) === undefined) {
224
+ // #729 / #645: description tables are the sole host authority — family
225
+ // dispatch (ACP vs headless) is data-driven. Unregistered keys fail closed.
226
+ if (lookupHostFamily(hostName) === undefined) {
222
227
  throw new Error(
223
228
  `public role summons host unregistered: host=${hostName} seat=${options.role}`,
224
229
  );
@@ -226,7 +231,7 @@ async function createSummonEnv(options: {
226
231
  let hostPromise: Promise<RoleTurnHost> | undefined;
227
232
  roleTurnHost = {
228
233
  executeTurn: async (request) => {
229
- hostPromise ??= loadProductionAcpHostFactory(options.packageRoot, hostName).then(
234
+ hostPromise ??= loadProductionExternalHostFactory(options.packageRoot, hostName).then(
230
235
  (create) =>
231
236
  create({
232
237
  packageRoot: options.packageRoot,
@@ -286,7 +291,10 @@ export async function summonPublicRole(
286
291
  const credentials =
287
292
  options.credentials ?? (await loadCredentialProviders(agentDir));
288
293
  const config = await loadPublicCliConfig(home);
289
- const seat = resolveEffectiveSeat(config, options.role, credentials);
294
+ // Nested legs inherit parent invocation host when provided (#645 review path).
295
+ // Model/thinking stay on the officer seat — host-only inherit (seat-table law).
296
+ const invocation = options.host === undefined ? undefined : { host: options.host };
297
+ const seat = resolveEffectiveSeat(config, options.role, credentials, invocation);
290
298
  const env = {
291
299
  ...(await createSummonEnv({
292
300
  role: options.role,
@@ -412,6 +420,47 @@ export async function summonPublicRole(
412
420
  }
413
421
 
414
422
  /** Gate officer summons: notary/auditor via --source-run; inspector via pointer instruction. */
423
+ /**
424
+ * Read parent invocation host so nested officers inherit live --host only (#645).
425
+ * Gate source runs necessarily own invocation.json — missing page, bad JSON, or
426
+ * bad shape stay loud (failure-honesty). Absent host field is lawful (seat default).
427
+ */
428
+ async function parentInvocationHost(sourceRunDirectory: string): Promise<string | undefined> {
429
+ const { readFile } = await import("node:fs/promises");
430
+ const path = join(sourceRunDirectory, "invocation.json");
431
+ let text: string;
432
+ try {
433
+ text = await readFile(path, "utf8");
434
+ } catch (error) {
435
+ const code =
436
+ error instanceof Error && "code" in error
437
+ ? (error as NodeJS.ErrnoException).code
438
+ : undefined;
439
+ throw new Error(
440
+ `parent invocation.json required for gate source run at ${path}: ${code ?? (error instanceof Error ? error.message : String(error))}`,
441
+ { cause: error },
442
+ );
443
+ }
444
+ let raw: unknown;
445
+ try {
446
+ raw = JSON.parse(text) as unknown;
447
+ } catch (error) {
448
+ throw new Error(
449
+ `parent invocation.json unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`,
450
+ { cause: error },
451
+ );
452
+ }
453
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
454
+ throw new Error(`parent invocation.json has non-object shape at ${path}`);
455
+ }
456
+ const host = (raw as Record<string, unknown>).host;
457
+ if (host === undefined) return undefined;
458
+ if (typeof host !== "string" || host.trim() === "") {
459
+ throw new Error(`parent invocation.json host must be a non-empty string at ${path}`);
460
+ }
461
+ return host;
462
+ }
463
+
415
464
  export async function summonGateOfficer(options: {
416
465
  readonly officer: "inspector" | "notary" | "auditor";
417
466
  readonly sourceRunDirectory: string;
@@ -452,6 +501,9 @@ export async function summonGateOfficer(options: {
452
501
  submission: options.submission,
453
502
  });
454
503
  }
504
+ // Inherit parent live --host only so review roundtrip stays on one host without
505
+ // mutating persistent seat tables (#645). Officer model/thinking stay seat-owned.
506
+ const parentHost = await parentInvocationHost(options.sourceRunDirectory);
455
507
  const common = {
456
508
  cwd: options.cwd,
457
509
  ...(home === undefined ? {} : { home }),
@@ -462,6 +514,7 @@ export async function summonGateOfficer(options: {
462
514
  ...(gateReviewInstruction === undefined
463
515
  ? {}
464
516
  : { gateReviewInstruction }),
517
+ ...(parentHost === undefined ? {} : { host: parentHost }),
465
518
  ...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
466
519
  ...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
467
520
  } as const;