@akagilnc/pi-workflow-roles 0.1.4800 → 0.1.4870

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 (143) hide show
  1. package/dist/acp-host/production-host.js +19434 -18218
  2. package/dist/analyst-book-key.js +63 -0
  3. package/dist/analyst-cohort.js +167 -0
  4. package/dist/analyst-entry.js +320 -0
  5. package/dist/analyst-index.js +247 -0
  6. package/dist/analyst-ledger.js +561 -0
  7. package/dist/analyst-median.js +16 -0
  8. package/dist/analyst-metric-families/acceptance-success-rework.js +249 -0
  9. package/dist/analyst-metric-families/b2-frame-buckets-actions.js +189 -0
  10. package/dist/analyst-metric-families/gate-cycles.js +77 -0
  11. package/dist/analyst-metric-families/leg-wall-clock.js +56 -0
  12. package/dist/analyst-metric-families/round-timeline.js +126 -0
  13. package/dist/analyst-metric-families.js +23 -0
  14. package/dist/analyst-metric-family.js +11 -0
  15. package/dist/analyst-model-groups.js +135 -0
  16. package/dist/analyst-page.js +182 -0
  17. package/dist/atomic-write.js +23 -0
  18. package/dist/auditor-soul.js +1 -1
  19. package/dist/countersign-contracts.js +0 -2
  20. package/dist/diarist-contracts.js +0 -1
  21. package/dist/doctor-contracts.js +2 -2
  22. package/dist/gatekeeper-role.js +15 -16
  23. package/dist/gleaner-left-contracts.js +0 -2
  24. package/dist/headless-host/production-host.js +16967 -15749
  25. package/dist/host-contracts.js +0 -4
  26. package/dist/inspector-contracts.js +0 -2
  27. package/dist/institutional-resolution.js +4 -2
  28. package/dist/merger-contracts.js +0 -2
  29. package/dist/migrate-book-topology.js +3908 -3089
  30. package/dist/navigator-public-session.js +4 -3
  31. package/dist/notary-contracts.js +0 -2
  32. package/dist/package-contracts/auditor-output.js +0 -1
  33. package/dist/package-contracts/collector-output.js +0 -2
  34. package/dist/package-contracts/fixer-output.js +0 -2
  35. package/dist/package-contracts/gatekeeper-output.js +0 -2
  36. package/dist/package-contracts/judge-output.js +0 -2
  37. package/dist/package-contracts/navigator-output.js +0 -2
  38. package/dist/package-contracts/reviewer-output.js +0 -2
  39. package/dist/package-contracts/terminating-tools.js +32 -92
  40. package/dist/package-contracts/worker-output.js +0 -4
  41. package/dist/packaged-role-registry.js +518 -36
  42. package/dist/pi/role-turn-host.js +2 -1
  43. package/dist/public-cli/analyst-run.js +185 -0
  44. package/dist/public-cli/cli.js +885 -0
  45. package/dist/public-cli/config.js +12 -9
  46. package/dist/public-cli/countersign-run.js +4 -311
  47. package/dist/public-cli/instruction-seat-run.js +628 -158
  48. package/dist/public-cli/invocation.js +806 -1550
  49. package/dist/public-cli/main.js +6962 -9882
  50. package/dist/public-cli/option-definitions.js +51 -99
  51. package/dist/public-cli/post-admission.js +30 -2
  52. package/dist/public-cli/run-lifecycle.js +234 -353
  53. package/dist/public-cli/seat-ticket-binding.js +6 -0
  54. package/dist/public-cli/settlement.js +261 -675
  55. package/dist/public-cli/turn-request.js +76 -0
  56. package/dist/public-role-summons.js +38 -112
  57. package/dist/role-activation-flags.js +23 -45
  58. package/dist/secretariat-contracts.js +0 -2
  59. package/dist/session-identity.js +17 -8
  60. package/dist/session-opening-materials.js +3 -2
  61. package/extensions/role-runtime.ts +6 -80
  62. package/package.json +1 -1
  63. package/souls/coder.md +1 -2
  64. package/src/acp-host/role-turn-host.ts +4 -1
  65. package/src/analyst-metric-families/acceptance-success-rework.ts +3 -2
  66. package/src/auditor-role.ts +1 -2
  67. package/src/auditor-soul.ts +1 -1
  68. package/src/collector-role.ts +2 -2
  69. package/src/countersign-contracts.ts +0 -1
  70. package/src/countersign-role.ts +1 -1
  71. package/src/diarist-contracts.ts +0 -1
  72. package/src/diarist-role.ts +0 -1
  73. package/src/doctor-auditor.ts +1 -2
  74. package/src/doctor-contracts.ts +1 -1
  75. package/src/doctor-role.ts +3 -2
  76. package/src/external-host-turn-loop.ts +1 -1
  77. package/src/factory-board.ts +10 -7
  78. package/src/gatekeeper-role.ts +16 -10
  79. package/src/gleaner-left-contracts.ts +0 -1
  80. package/src/gleaner-left-role.ts +0 -1
  81. package/src/headless-host/role-turn-host.ts +7 -1
  82. package/src/host-contracts.ts +10 -6
  83. package/src/inspector-contracts.ts +0 -1
  84. package/src/inspector-role.ts +0 -2
  85. package/src/institutional-resolution.ts +6 -4
  86. package/src/judge-role.ts +3 -3
  87. package/src/merger-contracts.ts +0 -1
  88. package/src/merger-role.ts +2 -2
  89. package/src/navigator-public-session.ts +7 -6
  90. package/src/navigator-work-context.ts +6 -8
  91. package/src/notary-contracts.ts +0 -1
  92. package/src/notary-role.ts +2 -3
  93. package/src/package-contracts/auditor-output.ts +0 -1
  94. package/src/package-contracts/collector-output.ts +0 -1
  95. package/src/package-contracts/fixer-output.ts +0 -1
  96. package/src/package-contracts/gatekeeper-output.ts +0 -1
  97. package/src/package-contracts/judge-output.ts +0 -1
  98. package/src/package-contracts/navigator-output.ts +0 -1
  99. package/src/package-contracts/reviewer-output.ts +0 -1
  100. package/src/package-contracts/terminating-tools.ts +27 -99
  101. package/src/package-contracts/worker-output.ts +0 -2
  102. package/src/packaged-role-registry.ts +604 -35
  103. package/src/pi/role-turn-host.ts +2 -1
  104. package/src/public-cli/cli.ts +64 -259
  105. package/src/public-cli/config.ts +18 -10
  106. package/src/public-cli/countersign-run.ts +14 -449
  107. package/src/public-cli/instruction-seat-run.ts +758 -277
  108. package/src/public-cli/invocation.ts +1097 -2051
  109. package/src/public-cli/option-definitions.ts +62 -127
  110. package/src/public-cli/post-admission.ts +47 -2
  111. package/src/public-cli/run-lifecycle.ts +303 -613
  112. package/src/public-cli/seat-ticket-binding.ts +6 -0
  113. package/src/public-cli/settlement.ts +399 -1185
  114. package/src/public-cli/turn-request.ts +92 -1
  115. package/src/public-role-summons.ts +44 -128
  116. package/src/reviewer-role.ts +3 -2
  117. package/src/role-activation-flags.ts +20 -41
  118. package/src/role-runtime-dependencies.ts +25 -26
  119. package/src/role-runtime.ts +121 -232
  120. package/src/secretariat-contracts.ts +0 -1
  121. package/src/secretariat-role.ts +0 -2
  122. package/src/session-identity.ts +23 -8
  123. package/src/session-opening-materials.ts +2 -1
  124. package/src/worker-role.ts +3 -4
  125. package/dist/doctor-auditor.js +0 -25
  126. package/dist/public-cli/diarist-run.js +0 -130
  127. package/dist/public-cli/doctor-run.js +0 -87
  128. package/dist/public-cli/inspector-run.js +0 -151
  129. package/dist/public-cli/judge-run.js +0 -114
  130. package/dist/public-cli/notary-run.js +0 -174
  131. package/dist/public-cli/reviewer-run.js +0 -339
  132. package/src/public-cli/coder-run.ts +0 -255
  133. package/src/public-cli/collector-run.ts +0 -189
  134. package/src/public-cli/diarist-run.ts +0 -233
  135. package/src/public-cli/doctor-run.ts +0 -167
  136. package/src/public-cli/fixer-run.ts +0 -265
  137. package/src/public-cli/gleaner-left-run.ts +0 -167
  138. package/src/public-cli/inspector-run.ts +0 -261
  139. package/src/public-cli/judge-run.ts +0 -196
  140. package/src/public-cli/merger-run.ts +0 -294
  141. package/src/public-cli/notary-run.ts +0 -279
  142. package/src/public-cli/reviewer-run.ts +0 -480
  143. package/src/public-cli/secretariat-run.ts +0 -265
@@ -3,7 +3,7 @@ import { readFileSync, writeSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import {
5
5
  ExplicitInternalActivationError,
6
- isOfficerReviewSeat,
6
+
7
7
  runDirectoryFromHostContext,
8
8
  type HostContext,
9
9
  type HostToolResult,
@@ -57,22 +57,21 @@ import {
57
57
  projectNotaryBoundFromFlags,
58
58
  readNotaryTicketFlag,
59
59
  } from "./notary-role.ts";
60
- import { NOTARY_TICKET_FLAG } from "./notary-contracts.ts";
60
+ import { NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG } from "./notary-contracts.ts";
61
61
  import {
62
62
  COUNTERSIGN_TOOL_SPEC,
63
63
  type CountersignRuntimeDependencies,
64
64
  } from "./countersign-role.ts";
65
- import { COUNTERSIGN_ACCEPTED_TEXT } from "./countersign-contracts.ts";
66
65
  import {
67
66
  GLEANER_LEFT_TOOL_SPEC,
68
67
  type GleanerLeftRuntimeDependencies,
69
68
  } from "./gleaner-left-role.ts";
70
- import { GLEANER_LEFT_ACCEPTED_TEXT, GLEANER_LEFT_BASE_FLAG } from "./gleaner-left-contracts.ts";
69
+ import { GLEANER_LEFT_BASE_FLAG } from "./gleaner-left-contracts.ts";
71
70
  import {
72
71
  INSPECTOR_TOOL_SPEC,
73
72
  type InspectorRuntimeDependencies,
74
73
  } from "./inspector-role.ts";
75
- import { INSPECTOR_ACCEPTED_TEXT, INSPECTOR_SOURCE_RUN_FLAG } from "./inspector-contracts.ts";
74
+ import { INSPECTOR_SOURCE_RUN_FLAG } from "./inspector-contracts.ts";
76
75
  import {
77
76
  DIARIST_TOOL_SPEC,
78
77
  type DiaristRuntimeDependencies,
@@ -85,12 +84,10 @@ import {
85
84
  type SecretariatSummonCountersignParameters,
86
85
  } from "./secretariat-role.ts";
87
86
  import {
88
- SECRETARIAT_ACCEPTED_TEXT,
89
87
  SECRETARIAT_OUTPUT_TOOL_NAME,
90
88
  SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME,
91
89
  } from "./secretariat-contracts.ts";
92
90
  import {
93
- DIARIST_ACCEPTED_TEXT,
94
91
  projectDiaristSessions,
95
92
  } from "./diarist-contracts.ts";
96
93
  import { commitDiaristProjection } from "./diarist.ts";
@@ -108,9 +105,7 @@ import {
108
105
  AUDITOR_TOOL_SPEC,
109
106
  type AuditorRuntimeDependencies,
110
107
  } from "./auditor-role.ts";
111
- import { AUDITOR_ACCEPTED_TEXT } from "./package-contracts/auditor-output.ts";
112
- import { GATEKEEPER_ACCEPTED_TEXT } from "./package-contracts/gatekeeper-output.ts";
113
- import { NAVIGATOR_ACCEPTED_TEXT } from "./package-contracts/navigator-output.ts";
108
+
114
109
  import { formatNavigatorReport, NAVIGATOR_EVENT_TYPE, navigatorSubjectKey, navigatorUnavailableError, subjectPath, type NavigatorAttendance, type NavigatorAttendanceOptions, type NavigatorEvent, type NavigatorPhase, type NavigatorReport, type NavigatorSettlement, type NavigatorSubjectProvenance, type NavigatorTargetRole, type NavigatorWorkContext } from "./navigator-attendance.ts";
115
110
  import {
116
111
  buildNavigatorInfrastructureFailureFact,
@@ -121,7 +116,7 @@ import {
121
116
  } from "./navigator-invocation-identity.ts";
122
117
  import { recordTypedProviderHttpStatus } from "./typed-provider-http.ts";
123
118
  import { NAVIGATOR_POST_ROLE_GRACE_MS, raceNavigatorGrace } from "./public-cli/settlement.ts";
124
- import { PACKAGED_ROLE_REGISTRY, packagedRoleMetadata, packagedRoleOutputTool, packagedRolePhaseFlag, type PackagedRole } from "./packaged-role-registry.ts";
119
+ import { PACKAGED_ROLE_REGISTRY, isOfficerReviewSeat, packagedRoleAcceptedText, packagedRoleActivationFlags, packagedRoleInputFlag, packagedRoleMetadata, packagedRoleOutputTool, packagedRolePhaseFlag, type PackagedRole } from "./packaged-role-registry.ts";
125
120
  import { isAuditEscalationProjection } from "./audit-escalation.ts";
126
121
  import {
127
122
  createJudgeRoleRuntime,
@@ -397,76 +392,18 @@ export { createProductionMergerGitState } from "./merger-git-state.ts";
397
392
  export type { MergerGitState, ActiveMergerGitState } from "./merger-git-state.ts";
398
393
  export type { MergerRoleDependencies } from "./merger-role.ts";
399
394
 
400
- type WorkerArmable = {
401
- activate(context?: HostContext): Promise<void>;
402
- armSubmissionGate(cwd: string, parent: { getSessionFile(): string | undefined }): void;
403
- };
404
-
405
- type ActivationRuntime = {
406
- event: { reason: string };
407
- context: HostContext;
408
- judge: { activate(): Promise<void> };
409
- fixer: WorkerArmable;
410
- coder: WorkerArmable;
411
- reviewer: {
412
- activate(
413
- context: HostContext,
414
- admitted: ReviewerAdmittedInputs,
415
- ): Promise<ReviewerActivation>;
416
- };
417
- /** Envelope decodes Reviewer transport flags inside the activation stage. */
418
- decodeReviewerAdmitted(): ReviewerAdmittedInputs;
419
- /** Envelope stores live Reviewer activation for agent_start prompt assembly. */
420
- bindReviewerParent(activation: ReviewerActivation): void;
421
- collector: {
422
- activate(context: HostContext, event: { reason: string }): Promise<void>;
423
- };
424
- doctor: { activate(): Promise<void> };
425
- notary: {
426
- activate(admitted?: import("./notary-role.ts").NotaryAdmittedTicket): Promise<void>;
427
- };
428
- /** Envelope decodes Notary ticket flag inside the activation stage (ADR 0018). */
429
- decodeNotaryAdmitted(): import("./notary-role.ts").NotaryAdmittedTicket | undefined;
430
- countersign: { activate(): Promise<void> };
431
- gleanerLeft: { activate(): Promise<void> };
432
- inspector: { activate(): Promise<void> };
433
- gatekeeper: { activate(): Promise<void> };
434
- navigator: { activate(): Promise<void> };
435
- auditor: { activate(): Promise<void> };
436
- diarist: { activate(): Promise<void> };
437
- secretariat: { activate(): Promise<void> };
438
- merger(): Promise<void>;
439
- };
440
-
441
- function activationStage(role: PackagedRole, runtime: ActivationRuntime): { id: string; run(): Promise<void> } {
442
- switch (role) {
443
- case "judge": return { id: "load-and-install", run: async () => runtime.judge.activate() };
444
- case "fixer": return { id: "load-and-install", run: async () => runtime.fixer.activate() };
445
- case "coder": return { id: "load-and-install", run: async () => runtime.coder.activate(runtime.context) };
446
- case "reviewer": return { id: "load-and-install", run: async () => {
447
- const admitted = runtime.decodeReviewerAdmitted();
448
- const activation = await runtime.reviewer.activate(runtime.context, admitted);
449
- runtime.bindReviewerParent(activation);
450
- } };
451
- case "collector": return { id: "load-and-install", run: async () => runtime.collector.activate(runtime.context, runtime.event) };
452
- case "doctor": return { id: "load-and-install", run: async () => runtime.doctor.activate() };
453
- case "notary": return {
454
- id: "load-and-install",
455
- run: async () => {
456
- // Envelope owns ticket flag read (ADR 0018); role receives admitted value only.
457
- await runtime.notary.activate(runtime.decodeNotaryAdmitted());
458
- },
459
- };
460
- case "countersign": return { id: "load-and-install", run: async () => runtime.countersign.activate() };
461
- case "gleaner-left": return { id: "load-and-install", run: async () => runtime.gleanerLeft.activate() };
462
- case "inspector": return { id: "load-and-install", run: async () => runtime.inspector.activate() };
463
- case "gatekeeper": return { id: "load-and-install", run: async () => runtime.gatekeeper.activate() };
464
- case "navigator": return { id: "load-and-install", run: async () => runtime.navigator.activate() };
465
- case "auditor": return { id: "load-and-install", run: async () => runtime.auditor.activate() };
466
- case "diarist": return { id: "load-and-install", run: async () => runtime.diarist.activate() };
467
- case "secretariat": return { id: "load-and-install", run: async () => runtime.secretariat.activate() };
468
- case "merger": return { id: "prepare-git-and-install", run: async () => runtime.merger() };
395
+ function activationStage(
396
+ role: PackagedRole,
397
+ activate: Record<PackagedRole, () => Promise<void>>,
398
+ ): { id: string; run(): Promise<void> } {
399
+ const record = packagedRoleMetadata(role);
400
+ if (record === undefined) {
401
+ throw new Error(`Unsupported workflow role: ${String(role)}`);
469
402
  }
403
+ return {
404
+ id: record.activationStage,
405
+ run: () => activate[role](),
406
+ };
470
407
  }
471
408
 
472
409
  function validateActivationTraceRecord(record: unknown): ActivationTraceRecord {
@@ -537,12 +474,14 @@ export function formatNavigatorRoleHelp(role: NavigatorTargetRole): string {
537
474
  `Usage: ak-role ${role}`,
538
475
  ROLE_FLAG.definition.description,
539
476
  ];
540
- if (metadata?.inputFlag !== undefined) {
541
- lines.push(` --${metadata.inputFlag} <value> ${role} input material`);
477
+ const inputFlag = packagedRoleInputFlag(role);
478
+ if (inputFlag !== undefined) {
479
+ lines.push(` --${inputFlag} <value> ${role} input material`);
542
480
  }
543
- if (metadata?.phaseFlag !== undefined) {
481
+ const phaseFlag = packagedRolePhaseFlag(role);
482
+ if (phaseFlag !== undefined && metadata !== undefined) {
544
483
  lines.push(
545
- ` --${metadata.phaseFlag} <value> ${role} phase: ${(metadata.phases.filter((p) => p !== null) as string[]).join(" | ")}`,
484
+ ` --${phaseFlag} <value> ${role} phase: ${(metadata.phases.filter((p) => p !== null) as string[]).join(" | ")}`,
546
485
  );
547
486
  }
548
487
  lines.push(`Public next-command form: ak-role ${role}`);
@@ -564,29 +503,15 @@ export type RoleRuntimeDependencies = {
564
503
  hostAdapters?: readonly import("./public-cli/role-turn-host-resolution.ts").NamedRoleTurnHostAdapter[];
565
504
  /** Non-identity opening materials delivered in the ordinary role brief. */
566
505
  loadRoleReferenceMaterials?(role: PackagedRole): Promise<string>;
567
- loadJudgeSoul(): Promise<string>;
568
- loadFixerSoul?(): Promise<string>;
506
+ /** One soul loader. The role argument selects the registry record. */
507
+ loadRoleSoul(role: PackagedRole): Promise<string>;
569
508
  loadFixPacket?(path: string): Promise<string>;
570
- loadCoderSoul?(): Promise<string>;
571
509
  loadCoderTask?(path: string): Promise<string>;
572
- loadReviewerSoul?(): Promise<string>;
573
- loadCollectorSoul?(): Promise<string>;
574
510
  /** #677: optional packaged seed for first-use general bot handbook. */
575
511
  loadCollectorHandbookSeed?(): Promise<string>;
576
512
  createCollectorTransport?(): CollectorGitHubTransport;
577
- loadDoctorSoul?(): Promise<string>;
578
- loadNotarySoul?(): Promise<string>;
579
513
  loadNotarySourceRun?(path: string): Promise<import("./notary-contracts.ts").NotarySourceRunLocator>;
580
- loadCountersignSoul?(): Promise<string>;
581
- loadGleanerLeftSoul?(): Promise<string>;
582
- loadInspectorSoul?(): Promise<string>;
583
- loadGatekeeperSoul?(): Promise<string>;
584
- loadNavigatorSoul?(): Promise<string>;
585
- loadAuditorSoul?(): Promise<string>;
586
- loadDiaristSoul?(): Promise<string>;
587
- loadSecretariatSoul?(): Promise<string>;
588
514
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
589
- loadMergerSoul?(): Promise<string>;
590
515
  loadMergerInput?(path: string): Promise<unknown>;
591
516
  auditDoctorCompliance?(options: { context: HostContext; signal?: AbortSignal }): Promise<ComplianceDecision>;
592
517
  createCollectorClock?(): CollectorClock;
@@ -643,6 +568,16 @@ function navigatorOutputTool(role: string): string | undefined {
643
568
  return packagedRoleOutputTool(role);
644
569
  }
645
570
 
571
+ /** Status leaves that are not the shared `status` key, in registry order. */
572
+ function receiptStatusFromRegistry(details: Record<string, unknown>): string | undefined {
573
+ for (const entry of PACKAGED_ROLE_REGISTRY) {
574
+ if (!("receiptStatusKey" in entry)) continue;
575
+ const value = details[entry.receiptStatusKey];
576
+ if (typeof value === "string") return value;
577
+ }
578
+ return undefined;
579
+ }
580
+
646
581
  export function publicNavigatorSettlement(role: string, phase: NavigatorPhase, event: { toolName: string; isError?: unknown; details: unknown }): NavigatorSettlement | undefined {
647
582
  // One shared classifier owns terminal discriminant (lifecycle + settlement + extractors).
648
583
  if (event.toolName !== navigatorOutputTool(role)) return undefined;
@@ -662,9 +597,7 @@ export function publicNavigatorSettlement(role: string, phase: NavigatorPhase, e
662
597
  }
663
598
  const status = typeof details.status === "string"
664
599
  ? details.status
665
- : typeof details.judgeStatus === "string" ? details.judgeStatus
666
- : typeof details.countersignStatus === "string" ? details.countersignStatus
667
- : typeof details.secretariatStatus === "string" ? details.secretariatStatus : undefined;
600
+ : receiptStatusFromRegistry(details);
668
601
  if (status !== undefined && status === "escalate") {
669
602
  return { kind: "human_decision", role, phase, status };
670
603
  }
@@ -709,9 +642,8 @@ type FiledOfficerBeforeAccept = (input: {
709
642
  function createFiledOfficerRuntime(
710
643
  roleHost: RoleHost,
711
644
  spec: {
712
- role: string;
645
+ role: PackagedRole;
713
646
  tool: { name: string; label: string; description: string; promptSnippet: string; parameters: unknown };
714
- acceptedText: string;
715
647
  soulTag: string;
716
648
  beforeAccept?: FiledOfficerBeforeAccept;
717
649
  },
@@ -741,7 +673,7 @@ function createFiledOfficerRuntime(
741
673
  // Accept-as-is + terminate only. Shape is not an admission gate
742
674
  // (第 0 条 / ADR 0055); sole-final barrier is ledger-owned (#575).
743
675
  return {
744
- content: [{ type: "text" as const, text: spec.acceptedText }],
676
+ content: [{ type: "text" as const, text: packagedRoleAcceptedText(spec.role) }],
745
677
  details: projected === undefined ? parameters : projected,
746
678
  terminate: true as const,
747
679
  };
@@ -761,6 +693,11 @@ function createFiledOfficerRuntime(
761
693
  };
762
694
  }
763
695
 
696
+ function activationPublishesFlag(role: unknown, flagName: string): boolean {
697
+ if (typeof role !== "string") return false;
698
+ return packagedRoleActivationFlags(role).some((spec) => spec.flag === flagName);
699
+ }
700
+
764
701
  export function createGleanerLeftRoleRuntime(
765
702
  roleHost: RoleHost,
766
703
  dependencies: GleanerLeftRuntimeDependencies,
@@ -770,7 +707,6 @@ export function createGleanerLeftRoleRuntime(
770
707
  {
771
708
  role: "gleaner-left",
772
709
  tool: GLEANER_LEFT_TOOL_SPEC,
773
- acceptedText: GLEANER_LEFT_ACCEPTED_TEXT,
774
710
  soulTag: "gleaner-left",
775
711
  },
776
712
  dependencies,
@@ -794,7 +730,6 @@ export function createInspectorRoleRuntime(
794
730
  {
795
731
  role: "inspector",
796
732
  tool: INSPECTOR_TOOL_SPEC,
797
- acceptedText: INSPECTOR_ACCEPTED_TEXT,
798
733
  soulTag: "inspector",
799
734
  },
800
735
  dependencies,
@@ -811,7 +746,6 @@ export function createGatekeeperRoleRuntime(
811
746
  {
812
747
  role: "gatekeeper",
813
748
  tool: GATEKEEPER_TOOL_SPEC,
814
- acceptedText: GATEKEEPER_ACCEPTED_TEXT,
815
749
  soulTag: "gatekeeper",
816
750
  },
817
751
  dependencies,
@@ -828,7 +762,6 @@ export function createNavigatorRoleRuntime(
828
762
  {
829
763
  role: "navigator",
830
764
  tool: NAVIGATOR_TOOL_SPEC,
831
- acceptedText: NAVIGATOR_ACCEPTED_TEXT,
832
765
  soulTag: "navigator",
833
766
  },
834
767
  dependencies,
@@ -845,7 +778,6 @@ export function createAuditorRoleRuntime(
845
778
  {
846
779
  role: "auditor",
847
780
  tool: AUDITOR_TOOL_SPEC,
848
- acceptedText: AUDITOR_ACCEPTED_TEXT,
849
781
  soulTag: "auditor",
850
782
  },
851
783
  dependencies,
@@ -959,7 +891,6 @@ export function createDiaristRoleRuntime(
959
891
  {
960
892
  role: "diarist",
961
893
  tool: DIARIST_TOOL_SPEC,
962
- acceptedText: DIARIST_ACCEPTED_TEXT,
963
894
  soulTag: "diarist",
964
895
  beforeAccept: async ({ parameters, ctx }) => {
965
896
  const submitted =
@@ -1048,7 +979,7 @@ export function createSecretariatRoleRuntime(
1048
979
  let parentInstruction = "";
1049
980
  // #969: non-pi submission gate only — pi mid-turn summon path stays untouched.
1050
981
  const beforeAccept: FiledOfficerBeforeAccept | undefined =
1051
- hostActions !== undefined && roleHost.requireGatekeeperPass !== undefined
982
+ hostActions !== undefined
1052
983
  ? async ({ toolCallId, parameters, signal, ctx }) => {
1053
984
  const host =
1054
985
  typeof ctx.host === "string" && ctx.host.trim() !== ""
@@ -1057,6 +988,11 @@ export function createSecretariatRoleRuntime(
1057
988
  if (host === undefined || !SECRETARIAT_SUBMISSION_GATE_HOSTS.has(host)) {
1058
989
  return undefined;
1059
990
  }
991
+ if (roleHost.requireGatekeeperPass === undefined) {
992
+ const error = new Error(`host ${host} cannot mount the secretariat submission gate`);
993
+ error.name = "InfrastructureFailure";
994
+ return hostActions.failInfrastructure(error, ctx, toolCallId);
995
+ }
1060
996
  const record =
1061
997
  parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
1062
998
  ? (parameters as Record<string, unknown>)
@@ -1151,7 +1087,6 @@ export function createSecretariatRoleRuntime(
1151
1087
  {
1152
1088
  role: "secretariat",
1153
1089
  tool: SECRETARIAT_OUTPUT_TOOL_SPEC,
1154
- acceptedText: SECRETARIAT_ACCEPTED_TEXT,
1155
1090
  soulTag: "secretariat",
1156
1091
  ...(beforeAccept === undefined ? {} : { beforeAccept }),
1157
1092
  },
@@ -1277,6 +1212,7 @@ export function createSecretariatRoleRuntime(
1277
1212
  }
1278
1213
  }
1279
1214
  },
1215
+ claimsEngineDetour: true,
1280
1216
  };
1281
1217
  }
1282
1218
 
@@ -1290,7 +1226,7 @@ export function createCountersignRoleRuntime(
1290
1226
  // #753 queue: read countersignStatus only — escalate skips gate (thrown to caller);
1291
1227
  // unreadable status returns to countersign; else notary inner gate.
1292
1228
  const beforeAccept: FiledOfficerBeforeAccept | undefined =
1293
- hostActions !== undefined && roleHost.requireGatekeeperPass !== undefined
1229
+ hostActions !== undefined
1294
1230
  ? async ({ toolCallId, parameters, signal, ctx }) => {
1295
1231
  const record =
1296
1232
  parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
@@ -1309,7 +1245,12 @@ export function createCountersignRoleRuntime(
1309
1245
  // Parent escalate → throw to caller as-is; notary does not attend (#753).
1310
1246
  return undefined;
1311
1247
  }
1312
- await roleHost.requireGatekeeperPass!({
1248
+ if (roleHost.requireGatekeeperPass === undefined) {
1249
+ const error = new Error("host cannot mount the countersign gate");
1250
+ error.name = "InfrastructureFailure";
1251
+ return hostActions.failInfrastructure(error, ctx, toolCallId);
1252
+ }
1253
+ await roleHost.requireGatekeeperPass({
1313
1254
  context: ctx,
1314
1255
  subject: { kind: "countersign_verdict" },
1315
1256
  ...(signal === undefined ? {} : { signal }),
@@ -1325,7 +1266,6 @@ export function createCountersignRoleRuntime(
1325
1266
  {
1326
1267
  role: "countersign",
1327
1268
  tool: COUNTERSIGN_TOOL_SPEC,
1328
- acceptedText: COUNTERSIGN_ACCEPTED_TEXT,
1329
1269
  soulTag: "countersign",
1330
1270
  ...(beforeAccept === undefined ? {} : { beforeAccept }),
1331
1271
  },
@@ -1517,9 +1457,9 @@ export function createRoleRuntimeExtension(
1517
1457
  if (role !== undefined && !admitted) return { action: "handled" as const };
1518
1458
  // Reviewer: recover original request; Pi argv may already carry native form.
1519
1459
  if (
1520
- role === "reviewer"
1521
- && admitted
1460
+ admitted
1522
1461
  && activeReviewerParent !== undefined
1462
+ && selectedRole === role
1523
1463
  && reviewerOriginalRequest === undefined
1524
1464
  ) {
1525
1465
  reviewerOriginalRequest =
@@ -1544,7 +1484,7 @@ export function createRoleRuntimeExtension(
1544
1484
  }
1545
1485
  // Notary session bound: envelope-owned lifecycle write (ADR 0018 / #582).
1546
1486
  // Ticket flag register/read + session entry live here; role projects admitted bound only.
1547
- if (role === "notary") {
1487
+ if (activationPublishesFlag(role, NOTARY_SOURCE_RUN_FLAG.name)) {
1548
1488
  const bound = projectNotaryBoundFromFlags((name) => roleHost.getFlag(name));
1549
1489
  if (bound !== undefined) {
1550
1490
  ctx.sessionManager.appendCustomEntry?.(NOTARY_SESSION_BOUND_ENTRY, bound);
@@ -1582,7 +1522,7 @@ export function createRoleRuntimeExtension(
1582
1522
  }
1583
1523
  navigatorAttendance?.prepare();
1584
1524
  // Envelope-owned Reviewer expansion capture + parent prompt assembly (no role-module callback).
1585
- if (role === "reviewer" && activeReviewerParent !== undefined) {
1525
+ if (activeReviewerParent !== undefined && selectedRole === role) {
1586
1526
  if (!reviewerExpansionCaptured) {
1587
1527
  if (reviewerOriginalRequest !== undefined) {
1588
1528
  activeReviewerParent.skillBinding.captureExpansion(
@@ -1600,7 +1540,7 @@ export function createRoleRuntimeExtension(
1600
1540
  };
1601
1541
  }
1602
1542
  // #676 E / J1: collector materials + drift gates share this envelope hook (no parallel register).
1603
- if (role === "collector" && activeCollector !== undefined) {
1543
+ if (activeCollector !== undefined && selectedRole === role) {
1604
1544
  if (!collectorFirstDispatchDone) {
1605
1545
  collectorFirstDispatchDone = true;
1606
1546
  activeCollector.ledger.recordActivation(activeCollector.clock);
@@ -1662,7 +1602,7 @@ export function createRoleRuntimeExtension(
1662
1602
  const role = selectedRole;
1663
1603
  if (role === undefined) return;
1664
1604
  // #676 E / J1: collector operational bookkeeping on the shared tool_result seam.
1665
- if (role === "collector" && activeCollector !== undefined) {
1605
+ if (activeCollector !== undefined && selectedRole === roleHost.getFlag(ROLE_FLAG.name)) {
1666
1606
  collectorBusiness.onToolResult(activeCollector, event);
1667
1607
  }
1668
1608
  const pendingInfra = pendingInfrastructureFailures.get(event.toolCallId);
@@ -1720,7 +1660,11 @@ export function createRoleRuntimeExtension(
1720
1660
  const role = selectedRole ?? roleHost.getFlag(ROLE_FLAG.name);
1721
1661
  // #959: navigator prose exit — final assistant text is the receipt.
1722
1662
  // No typed-tool 催交; no JSON required. Tool path still wins when already accepted.
1723
- if (role === "navigator" && receiptDelivery.nextAction() !== "accepted") {
1663
+ if (
1664
+ typeof role === "string"
1665
+ && packagedRoleOutputTool(role) === NAVIGATOR_TOOL_SPEC.name
1666
+ && receiptDelivery.nextAction() !== "accepted"
1667
+ ) {
1724
1668
  const prose = lastAssistantProse(event.messages);
1725
1669
  if (prose !== undefined && prose.trim() !== "") {
1726
1670
  const accepted = { prose };
@@ -1831,11 +1775,7 @@ export function createRoleRuntimeExtension(
1831
1775
  fetchWrapped = false;
1832
1776
  }
1833
1777
  // #676 J4: collector fatal latch must surface nonzero exit on shutdown (envelope-owned).
1834
- if (
1835
- selectedRole === "collector"
1836
- && activeCollector !== undefined
1837
- && activeCollector.ledger.fatal
1838
- ) {
1778
+ if (activeCollector !== undefined && activeCollector.ledger.fatal) {
1839
1779
  if (process.exitCode === undefined || process.exitCode === 0) {
1840
1780
  process.exitCode = 1;
1841
1781
  }
@@ -1878,22 +1818,18 @@ export function createRoleRuntimeExtension(
1878
1818
  pendingSubmissionNonPassByToolCallId.set(toolCallId, result);
1879
1819
  },
1880
1820
  };
1821
+ const requireRoleSoul = (role: PackagedRole): Promise<string> => dependencies.loadRoleSoul(role);
1881
1822
  const judge = createJudgeRoleRuntime(
1882
1823
  roleHost,
1883
1824
  {
1884
- loadSoul: dependencies.loadJudgeSoul,
1825
+ loadSoul: () => requireRoleSoul("judge"),
1885
1826
  },
1886
1827
  hostActions,
1887
1828
  );
1888
1829
  const fixer = createFixerRoleRuntime(
1889
1830
  roleHost,
1890
1831
  {
1891
- async loadSoul() {
1892
- if (dependencies.loadFixerSoul === undefined) {
1893
- throw new Error("fixer soul loader is not configured");
1894
- }
1895
- return dependencies.loadFixerSoul();
1896
- },
1832
+ loadSoul: () => requireRoleSoul("fixer"),
1897
1833
  async loadPacket(path) {
1898
1834
  if (dependencies.loadFixPacket === undefined) {
1899
1835
  throw new Error("Fixer packet loader is not configured");
@@ -1906,12 +1842,7 @@ export function createRoleRuntimeExtension(
1906
1842
  const coder = createCoderRoleRuntime(
1907
1843
  roleHost,
1908
1844
  {
1909
- async loadSoul() {
1910
- if (dependencies.loadCoderSoul === undefined) {
1911
- throw new Error("coder soul loader is not configured");
1912
- }
1913
- return dependencies.loadCoderSoul();
1914
- },
1845
+ loadSoul: () => requireRoleSoul("coder"),
1915
1846
  async loadTask(path) {
1916
1847
  if (dependencies.loadCoderTask === undefined) {
1917
1848
  throw new Error("Coder task loader is not configured");
@@ -1930,12 +1861,7 @@ export function createRoleRuntimeExtension(
1930
1861
  const reviewer = createReviewerRoleRuntime(
1931
1862
  roleHost,
1932
1863
  {
1933
- async loadSoul() {
1934
- if (dependencies.loadReviewerSoul === undefined) {
1935
- throw new Error("reviewer soul loader is not configured");
1936
- }
1937
- return dependencies.loadReviewerSoul();
1938
- },
1864
+ loadSoul: () => requireRoleSoul("reviewer"),
1939
1865
  async loadCanonicalSkillBinding(name) {
1940
1866
  if (dependencies.loadCanonicalSkillBinding === undefined) {
1941
1867
  throw new Error("Reviewer runtime dependencies are not configured");
@@ -1946,31 +1872,22 @@ export function createRoleRuntimeExtension(
1946
1872
  hostActions,
1947
1873
  );
1948
1874
  const doctor = createDoctorRoleRuntime(roleHost, {
1949
- async loadSoul() { if (!dependencies.loadDoctorSoul) throw new Error("Doctor runtime dependencies are not configured"); return dependencies.loadDoctorSoul(); },
1875
+ loadSoul: () => requireRoleSoul("doctor"),
1950
1876
  async loadCase(path) { if (!dependencies.loadDoctorCase) throw new Error("Doctor runtime dependencies are not configured"); return dependencies.loadDoctorCase(path); },
1951
1877
  async auditCompliance(options) { if (!dependencies.auditDoctorCompliance) throw new Error("Doctor runtime dependencies are not configured"); return dependencies.auditDoctorCompliance(options); },
1952
1878
  }, hostActions);
1953
1879
  const notary = createNotaryRoleRuntime(roleHost, {
1954
- async loadSoul() {
1955
- if (!dependencies.loadNotarySoul) throw new Error("Notary runtime dependencies are not configured");
1956
- return dependencies.loadNotarySoul();
1957
- },
1880
+ loadSoul: () => requireRoleSoul("notary"),
1958
1881
  async loadSourceRunLocator(path) {
1959
1882
  if (!dependencies.loadNotarySourceRun) throw new Error("Notary runtime dependencies are not configured");
1960
1883
  return dependencies.loadNotarySourceRun(path);
1961
1884
  },
1962
1885
  }, hostActions);
1963
1886
  const countersign = createCountersignRoleRuntime(roleHost, {
1964
- async loadSoul() {
1965
- if (!dependencies.loadCountersignSoul) throw new Error("Countersign runtime dependencies are not configured");
1966
- return dependencies.loadCountersignSoul();
1967
- },
1887
+ loadSoul: () => requireRoleSoul("countersign"),
1968
1888
  }, hostActions);
1969
1889
  const gleanerLeftRuntime = createGleanerLeftRoleRuntime(roleHost, {
1970
- async loadSoul() {
1971
- if (!dependencies.loadGleanerLeftSoul) throw new Error("Gleaner-left runtime dependencies are not configured");
1972
- return dependencies.loadGleanerLeftSoul();
1973
- },
1890
+ loadSoul: () => requireRoleSoul("gleaner-left"),
1974
1891
  });
1975
1892
  const gleanerLeft = {
1976
1893
  async activate() {
@@ -1979,43 +1896,25 @@ export function createRoleRuntimeExtension(
1979
1896
  },
1980
1897
  };
1981
1898
  const inspector = createInspectorRoleRuntime(roleHost, {
1982
- async loadSoul() {
1983
- if (!dependencies.loadInspectorSoul) throw new Error("Inspector runtime dependencies are not configured");
1984
- return dependencies.loadInspectorSoul();
1985
- },
1899
+ loadSoul: () => requireRoleSoul("inspector"),
1986
1900
  });
1987
1901
  const gatekeeper = createGatekeeperRoleRuntime(roleHost, {
1988
- async loadSoul() {
1989
- if (!dependencies.loadGatekeeperSoul) throw new Error("Gatekeeper runtime dependencies are not configured");
1990
- return dependencies.loadGatekeeperSoul();
1991
- },
1902
+ loadSoul: () => requireRoleSoul("gatekeeper"),
1992
1903
  });
1993
1904
  const navigator = createNavigatorRoleRuntime(roleHost, {
1994
- async loadSoul() {
1995
- if (!dependencies.loadNavigatorSoul) throw new Error("Navigator runtime dependencies are not configured");
1996
- return dependencies.loadNavigatorSoul();
1997
- },
1905
+ loadSoul: () => requireRoleSoul("navigator"),
1998
1906
  });
1999
1907
  const auditor = createAuditorRoleRuntime(roleHost, {
2000
- async loadSoul() {
2001
- if (!dependencies.loadAuditorSoul) throw new Error("Auditor runtime dependencies are not configured");
2002
- return dependencies.loadAuditorSoul();
2003
- },
1908
+ loadSoul: () => requireRoleSoul("auditor"),
2004
1909
  });
2005
1910
  const diarist = createDiaristRoleRuntime(
2006
1911
  roleHost,
2007
1912
  {
2008
- async loadSoul() {
2009
- if (!dependencies.loadDiaristSoul) throw new Error("Diarist runtime dependencies are not configured");
2010
- return dependencies.loadDiaristSoul();
2011
- },
1913
+ loadSoul: () => requireRoleSoul("diarist"),
2012
1914
  },
2013
1915
  );
2014
1916
  const secretariat = createSecretariatRoleRuntime(roleHost, {
2015
- async loadSoul() {
2016
- if (!dependencies.loadSecretariatSoul) throw new Error("Secretariat runtime dependencies are not configured");
2017
- return dependencies.loadSecretariatSoul();
2018
- },
1917
+ loadSoul: () => requireRoleSoul("secretariat"),
2019
1918
  ...(dependencies.packageRoot === undefined
2020
1919
  ? {}
2021
1920
  : { packageRoot: dependencies.packageRoot }),
@@ -2024,7 +1923,7 @@ export function createRoleRuntimeExtension(
2024
1923
  : { hostAdapters: dependencies.hostAdapters }),
2025
1924
  }, hostActions);
2026
1925
  const merger = createMergerRoleRuntime(roleHost, {
2027
- async loadSoul() { if (!dependencies.loadMergerSoul) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerSoul(); },
1926
+ loadSoul: () => requireRoleSoul("merger"),
2028
1927
  async loadInput(path) { if (!dependencies.loadMergerInput) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerInput(path); },
2029
1928
  });
2030
1929
  // #676 E: shared envelope owns collector lifecycle (mode/fork, tool surface,
@@ -2034,12 +1933,7 @@ export function createRoleRuntimeExtension(
2034
1933
  const collectorBusiness = createCollectorRoleRuntime(
2035
1934
  roleHost,
2036
1935
  {
2037
- async loadSoul() {
2038
- if (dependencies.loadCollectorSoul === undefined) {
2039
- throw new Error("collector soul loader is not configured");
2040
- }
2041
- return dependencies.loadCollectorSoul();
2042
- },
1936
+ loadSoul: () => requireRoleSoul("collector"),
2043
1937
  createTransport() {
2044
1938
  if (dependencies.createCollectorTransport === undefined) {
2045
1939
  throw new Error("Collector GitHub transport is not configured");
@@ -2142,7 +2036,8 @@ export function createRoleRuntimeExtension(
2142
2036
  if (!collectorToolCallRegistered) {
2143
2037
  collectorToolCallRegistered = true;
2144
2038
  roleHost.on("tool_call", (toolEvent) => {
2145
- if (activeCollector === undefined || selectedRole !== "collector") return;
2039
+ const liveRole = roleHost.getFlag(ROLE_FLAG.name);
2040
+ if (activeCollector === undefined || selectedRole === undefined || selectedRole !== liveRole) return;
2146
2041
  if (!(COLLECTOR_REQUIRED_TOOLS as readonly string[]).includes(toolEvent.toolName)) {
2147
2042
  return {
2148
2043
  block: true,
@@ -2264,6 +2159,8 @@ export function createRoleRuntimeExtension(
2264
2159
  selectedRole = undefined;
2265
2160
  roleReferenceMaterials = "";
2266
2161
  activeReviewerParent = undefined;
2162
+ activeCollector = undefined;
2163
+ collectorFirstDispatchDone = false;
2267
2164
  reviewerOriginalRequest = undefined;
2268
2165
  reviewerExpansionCaptured = false;
2269
2166
  receiptDelivery = createReceiptDeliveryPolicy();
@@ -2286,40 +2183,32 @@ export function createRoleRuntimeExtension(
2286
2183
  selectedRole = entry.role;
2287
2184
  await navigatorAttendance?.dispose();
2288
2185
  navigatorAttendance = undefined;
2289
- const runtime: ActivationRuntime = {
2290
- event,
2291
- context: ctx,
2292
- judge,
2293
- fixer,
2294
- coder,
2295
- reviewer,
2296
- decodeReviewerAdmitted() {
2297
- return decodeReviewerAdmittedInputs((name) => roleHost.getFlag(name));
2298
- },
2299
- bindReviewerParent(activation) {
2300
- activeReviewerParent = activation;
2186
+ // One activation call per seat. Stage id still comes from the registry.
2187
+ // Envelope owns Reviewer and Notary flag reads (ADR 0018).
2188
+ const activateByRole = {
2189
+ judge: () => judge.activate(),
2190
+ fixer: () => fixer.activate(),
2191
+ coder: () => coder.activate(ctx),
2192
+ reviewer: async () => {
2193
+ const admitted = decodeReviewerAdmittedInputs((name) => roleHost.getFlag(name));
2194
+ activeReviewerParent = await reviewer.activate(ctx, admitted);
2301
2195
  },
2302
- decodeNotaryAdmitted() {
2303
- const ticketNumber = readNotaryTicketFlag(
2304
- roleHost.getFlag(NOTARY_TICKET_FLAG.name),
2305
- );
2306
- return ticketNumber === undefined ? undefined : { ticketNumber };
2196
+ collector: () => collector.activate(ctx, event),
2197
+ doctor: () => doctor.activate(),
2198
+ notary: () => {
2199
+ const ticketNumber = readNotaryTicketFlag(roleHost.getFlag(NOTARY_TICKET_FLAG.name));
2200
+ return notary.activate(ticketNumber === undefined ? undefined : { ticketNumber });
2307
2201
  },
2308
- collector,
2309
- doctor,
2310
- notary,
2311
- countersign,
2312
- gleanerLeft,
2313
- inspector,
2314
- gatekeeper,
2315
- navigator,
2316
- auditor,
2317
- diarist,
2318
- secretariat,
2319
- merger: async () => {
2320
- await merger.activate();
2321
- },
2322
- };
2202
+ countersign: () => countersign.activate(),
2203
+ "gleaner-left": () => gleanerLeft.activate(),
2204
+ inspector: () => inspector.activate(),
2205
+ gatekeeper: () => gatekeeper.activate(),
2206
+ navigator: () => navigator.activate(),
2207
+ auditor: () => auditor.activate(),
2208
+ diarist: () => diarist.activate(),
2209
+ secretariat: () => secretariat.activate(),
2210
+ merger: () => merger.activate(),
2211
+ } satisfies Record<PackagedRole, () => Promise<void>>;
2323
2212
  try {
2324
2213
  // Production topology only (ADR 0048/0049): no test-only ledger hooks.
2325
2214
  // Admit durable session file first so lifecycle getEntries/appendEntry are truthful:
@@ -2335,7 +2224,7 @@ export function createRoleRuntimeExtension(
2335
2224
  const isStationChild = roleHost.getFlag(STATION_CHILD_FLAG.name) === true;
2336
2225
  if (
2337
2226
  dependencies.createNavigatorAttendance !== undefined
2338
- && entry.role !== "navigator"
2227
+ && entry.outputTool !== NAVIGATOR_TOOL_SPEC.name
2339
2228
  && !isStationChild
2340
2229
  ) {
2341
2230
  navigatorSessionParent = ctx.sessionManager.getSessionFile();
@@ -2420,7 +2309,7 @@ export function createRoleRuntimeExtension(
2420
2309
  }
2421
2310
  }
2422
2311
 
2423
- await executeActivationStage(entry.role, activationStage(entry.role, runtime), { clock, writeTrace });
2312
+ await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { clock, writeTrace });
2424
2313
  roleReferenceMaterials = await dependencies.loadRoleReferenceMaterials?.(entry.role) ?? "";
2425
2314
  // #357 T2 / #378 / #380 / #391 / #818: any role+engine activation registers the package detour tool once.
2426
2315
  // Gate is resolveEngineName (RoleHost flag → env fallback) — no per-engine execute branch; no role-module spawn.
@@ -2430,16 +2319,16 @@ export function createRoleRuntimeExtension(
2430
2319
  // Secretariat owns a declared active surface. The shared engine detour is
2431
2320
  // registered after role activation, so include it here rather than leave
2432
2321
  // a newly registered optional tool unreachable until a later reload.
2433
- if (entry.role === "secretariat" && engineDetourRegistered) {
2322
+ if (secretariat.claimsEngineDetour === true && engineDetourRegistered) {
2434
2323
  roleHost.setActiveTools([
2435
2324
  ...new Set([...roleHost.getActiveTools(), ENGINE_DETOUR_TOOL_NAME]),
2436
2325
  ]);
2437
2326
  }
2438
- // Worker gates ①②: arm records baseline and runs private one-shot hook uninstall (ADR 0070).
2327
+ // Worker gates ①②: registry declares the worker seats (ADR 0066 / 0070).
2439
2328
  // Parent session feeds #216 createRecordSession so baseline/bounce survive resume.
2440
- if (entry.role === "coder" || entry.role === "fixer") {
2441
- if (entry.role === "coder") coder.armSubmissionGate(ctx.cwd, ctx.sessionManager);
2442
- else fixer.armSubmissionGate(ctx.cwd, ctx.sessionManager);
2329
+ if ("worker" in entry && entry.worker === true) {
2330
+ const workerArms = { coder, fixer } as const;
2331
+ workerArms[entry.role].armSubmissionGate(ctx.cwd, ctx.sessionManager);
2443
2332
  }
2444
2333
  admitted = true;
2445
2334
  } catch (error) {