@akagilnc/pi-workflow-roles 0.1.2296 → 0.1.2306

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.
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Shared one-shot public Role dispatch seam (Doctor-isomorphic path).
3
+ * Post-admission lifecycle — mark admitted → writer lease → running → spawn →
4
+ * settle/fail → terminal → release — lives here once. Role runners supply only
5
+ * activation args and settlement adapters (ADR 0018 / #448).
6
+ */
7
+ import { writeFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+
10
+ import { applyEngineChildEnv } from "../engine-detour.ts";
11
+ import {
12
+ runExplicitInternalActivation,
13
+ type ExplicitInternalKnownFailure,
14
+ type ExplicitInternalPiRunner,
15
+ type ExplicitInternalPiResult,
16
+ } from "./explicit-internal.ts";
17
+ import type { CredentialProviders, SeatModelConfig } from "./config.ts";
18
+ import {
19
+ missingCredentialPreDispatchFailure,
20
+ postRunMissingCredentialFailure,
21
+ } from "./public-run-credentials.ts";
22
+ import {
23
+ acquireRunWriterLease,
24
+ clearTypedProviderHttpObservation,
25
+ markRunAdmitted,
26
+ markRunRunning,
27
+ markRunTerminal,
28
+ RunWriterLeaseHeldError,
29
+ type RunWriterLease,
30
+ } from "./run-lifecycle.ts";
31
+ import {
32
+ classifyPostAdmissionFailure,
33
+ exitCodeForTerminalOutcome,
34
+ formatTerminalResult,
35
+ inspectJudgeSession,
36
+ presentFailureTerminal,
37
+ presentStructuralRejection,
38
+ resolveAuditedRunnerKnownFailure,
39
+ explicitInternalKnownFailureClassificationInput,
40
+ settleFailureTerminalResult,
41
+ } from "./settlement.ts";
42
+ import type { CliIo } from "./cli-io.ts";
43
+ import type { AdmittedRoleInvocation } from "./invocation.ts";
44
+ import type { TerminalResult } from "./terminal.ts";
45
+
46
+ export type OneShotRunEnv = {
47
+ home: string;
48
+ agentDir: string;
49
+ packageRoot: string;
50
+ cwd: string;
51
+ correlationId?: string;
52
+ piRunner?: ExplicitInternalPiRunner;
53
+ model?: SeatModelConfig;
54
+ engine?: string;
55
+ credentials?: CredentialProviders;
56
+ timeoutMs?: number;
57
+ };
58
+
59
+ /**
60
+ * Role-specific settlement hooks. Lifecycle coordination stays in this module.
61
+ * - Doctor: present only lawful typed outcomes; optional compliance-audit secondary.
62
+ * - Notary: present any settled terminal (accepted + residual incomplete).
63
+ */
64
+ export type OneShotSettlementAdapters<A extends AdmittedRoleInvocation> = {
65
+ trySettle: (admitted: A) => Promise<TerminalResult | undefined>;
66
+ /** Default true. Doctor sets false-filter via predicate. */
67
+ shouldPresentSettled?: (terminal: TerminalResult) => boolean;
68
+ trySettleSecondary?: (admitted: A) => Promise<TerminalResult | undefined>;
69
+ };
70
+
71
+ async function presentControlledFailure<A extends AdmittedRoleInvocation>(
72
+ admitted: A,
73
+ failureInput: {
74
+ timedOut: boolean;
75
+ code: number | null;
76
+ stderr: string;
77
+ thrown?: unknown;
78
+ knownFailure?: ExplicitInternalKnownFailure;
79
+ },
80
+ io: CliIo,
81
+ ): Promise<{
82
+ exitCode: number;
83
+ admitted: A;
84
+ terminal: TerminalResult;
85
+ }> {
86
+ const hasThrown = Object.hasOwn(failureInput, "thrown");
87
+ const session =
88
+ !hasThrown &&
89
+ !failureInput.timedOut &&
90
+ failureInput.knownFailure === undefined
91
+ ? await inspectJudgeSession(admitted.sessionFile)
92
+ : undefined;
93
+ const failure = classifyPostAdmissionFailure({
94
+ timedOut: failureInput.timedOut,
95
+ code: failureInput.code,
96
+ stderr: failureInput.stderr,
97
+ ...(hasThrown ? { thrown: failureInput.thrown } : {}),
98
+ ...explicitInternalKnownFailureClassificationInput(failureInput.knownFailure),
99
+ ...(session === undefined ? {} : { session }),
100
+ });
101
+
102
+ // One-shot roles do not support resume — always terminal.
103
+ await markRunTerminal(admitted.runDirectory).catch(() => undefined);
104
+
105
+ const terminal = await settleFailureTerminalResult(admitted, failure);
106
+ presentFailureTerminal(terminal, io);
107
+ return {
108
+ exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
109
+ admitted,
110
+ terminal,
111
+ };
112
+ }
113
+
114
+ function presentSecondaryTerminal(terminal: TerminalResult, io: CliIo): void {
115
+ if (terminal.roleOutcome.kind === "failure") {
116
+ presentFailureTerminal(terminal, io);
117
+ } else {
118
+ io.stdout(formatTerminalResult(terminal));
119
+ }
120
+ }
121
+
122
+ async function dispatchAdmittedOneShotRole<A extends AdmittedRoleInvocation>(input: {
123
+ admitted: A;
124
+ env: OneShotRunEnv;
125
+ io: CliIo;
126
+ extraArgs: string[];
127
+ lease: RunWriterLease;
128
+ effectiveEngine?: string;
129
+ adapters: OneShotSettlementAdapters<A>;
130
+ }): Promise<{
131
+ exitCode: number;
132
+ admitted: A;
133
+ terminal?: TerminalResult;
134
+ }> {
135
+ const { admitted, env, io, extraArgs, lease, effectiveEngine, adapters } = input;
136
+ const shouldPresent =
137
+ adapters.shouldPresentSettled ?? ((_terminal: TerminalResult) => true);
138
+ try {
139
+ const missingCredential = missingCredentialPreDispatchFailure(
140
+ env.model,
141
+ env.credentials,
142
+ );
143
+ if (missingCredential !== undefined) {
144
+ return await presentControlledFailure(admitted, missingCredential, io);
145
+ }
146
+ await markRunRunning(admitted.runDirectory, env.model, effectiveEngine);
147
+ await clearTypedProviderHttpObservation(admitted.runDirectory);
148
+
149
+ const childEnv: NodeJS.ProcessEnv = {
150
+ ...process.env,
151
+ HOME: env.home,
152
+ PI_CODING_AGENT_DIR: env.agentDir,
153
+ AK_ROLE_RUN_DIR: admitted.runDirectory,
154
+ };
155
+ applyEngineChildEnv(childEnv, env.engine);
156
+ if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
157
+ childEnv.AK_CORRELATION_ID = env.correlationId;
158
+ }
159
+
160
+ let result: ExplicitInternalPiResult;
161
+ try {
162
+ result = await runExplicitInternalActivation({
163
+ packageRoot: env.packageRoot,
164
+ extraArgs,
165
+ cwd: admitted.projectRoot,
166
+ home: env.home,
167
+ agentDir: env.agentDir,
168
+ env: childEnv,
169
+ timeoutMs: env.timeoutMs,
170
+ ...(env.piRunner === undefined ? {} : { runner: env.piRunner }),
171
+ });
172
+ } catch (error) {
173
+ return await presentControlledFailure(
174
+ admitted,
175
+ {
176
+ timedOut: false,
177
+ code: null,
178
+ stderr: "",
179
+ thrown: error,
180
+ },
181
+ io,
182
+ );
183
+ }
184
+
185
+ try {
186
+ await writeFile(
187
+ join(admitted.runDirectory, "stderr.log"),
188
+ result.stderr,
189
+ "utf8",
190
+ );
191
+ } catch {
192
+ // continue to lawful / controlled-failure settlement
193
+ }
194
+
195
+ let settled: TerminalResult | undefined;
196
+ try {
197
+ settled = await adapters.trySettle(admitted);
198
+ } catch (error) {
199
+ return await presentControlledFailure(
200
+ admitted,
201
+ {
202
+ timedOut: false,
203
+ code: result.code,
204
+ stderr: result.stderr,
205
+ thrown: error,
206
+ },
207
+ io,
208
+ );
209
+ }
210
+ if (settled !== undefined && shouldPresent(settled)) {
211
+ await markRunTerminal(admitted.runDirectory).catch(() => undefined);
212
+ io.stdout(formatTerminalResult(settled));
213
+ return {
214
+ exitCode: exitCodeForTerminalOutcome(settled.roleOutcome),
215
+ admitted,
216
+ terminal: settled,
217
+ };
218
+ }
219
+
220
+ if (adapters.trySettleSecondary !== undefined) {
221
+ const secondary = await adapters.trySettleSecondary(admitted);
222
+ if (secondary !== undefined) {
223
+ await markRunTerminal(admitted.runDirectory).catch(() => undefined);
224
+ presentSecondaryTerminal(secondary, io);
225
+ return {
226
+ exitCode: exitCodeForTerminalOutcome(secondary.roleOutcome),
227
+ admitted,
228
+ terminal: secondary,
229
+ };
230
+ }
231
+ }
232
+
233
+ const credentialFailure = postRunMissingCredentialFailure(
234
+ result,
235
+ env.model,
236
+ env.credentials,
237
+ );
238
+ const knownFailure = await resolveAuditedRunnerKnownFailure({
239
+ runner: result.knownFailure,
240
+ sessionFile: admitted.sessionFile,
241
+ credential: credentialFailure,
242
+ runDirectory: admitted.runDirectory,
243
+ });
244
+ return await presentControlledFailure(
245
+ admitted,
246
+ {
247
+ timedOut: result.timedOut,
248
+ code: result.code,
249
+ stderr: result.stderr,
250
+ ...(knownFailure === undefined ? {} : { knownFailure }),
251
+ },
252
+ io,
253
+ );
254
+ } finally {
255
+ await lease.release();
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Shared post-admission one-shot path: durable admitted mark, writer lease,
261
+ * then Doctor-isomorphic dispatch. Role runners call this after role-specific admit.
262
+ */
263
+ export async function runAdmittedOneShotRole<A extends AdmittedRoleInvocation>(input: {
264
+ admitted: A;
265
+ env: OneShotRunEnv;
266
+ io: CliIo;
267
+ extraArgs: string[];
268
+ adapters: OneShotSettlementAdapters<A>;
269
+ effectiveEngine?: string;
270
+ }): Promise<{
271
+ exitCode: number;
272
+ admitted: A;
273
+ terminal?: TerminalResult;
274
+ }> {
275
+ const { admitted, env, io, extraArgs, adapters, effectiveEngine } = input;
276
+ await markRunAdmitted(admitted);
277
+
278
+ let lease: RunWriterLease;
279
+ try {
280
+ lease = await acquireRunWriterLease(admitted.runDirectory, (diagnostic) =>
281
+ io.stderr(diagnostic),
282
+ );
283
+ } catch (error) {
284
+ if (error instanceof RunWriterLeaseHeldError) {
285
+ presentStructuralRejection(error, io);
286
+ return { exitCode: 2, admitted };
287
+ }
288
+ throw error;
289
+ }
290
+
291
+ return await dispatchAdmittedOneShotRole({
292
+ admitted,
293
+ env,
294
+ io,
295
+ extraArgs,
296
+ lease,
297
+ adapters,
298
+ ...(effectiveEngine === undefined ? {} : { effectiveEngine }),
299
+ });
300
+ }
@@ -28,6 +28,7 @@ export type OptionOwner =
28
28
  | "collector"
29
29
  | "doctor"
30
30
  | "merger"
31
+ | "notary"
31
32
  | "analyst";
32
33
 
33
34
  /**
@@ -537,6 +538,24 @@ const DOCTOR_OPTIONS = [
537
538
  },
538
539
  ] as const satisfies readonly PublicOptionDefinition[];
539
540
 
541
+ const NOTARY_OPTIONS = [
542
+ bindOwner("notary", SHARED_PROJECT_SEMANTICS),
543
+ {
544
+ id: "source-run",
545
+ owner: "notary",
546
+ canonical: "--source-run",
547
+ aliases: [],
548
+ valueMetavar: "runId@role|path",
549
+ required: true,
550
+ repeatable: false,
551
+ form: "option",
552
+ description: {
553
+ en: "Required source run locator (runId@role under the book home, or path to that run directory). Zero prompt/attachment projection.",
554
+ zh: "必填源 run 定位符(簿内 runId@role,或该 run 目录路径)。零 prompt/附件投影。",
555
+ },
556
+ },
557
+ ] as const satisfies readonly PublicOptionDefinition[];
558
+
540
559
  const MERGER_OPTIONS = [
541
560
  // Merger project face differs: requires an in-progress ordinary merge root.
542
561
  {
@@ -701,6 +720,7 @@ export const PUBLIC_OPTION_TABLE = {
701
720
  collector: COLLECTOR_OPTIONS,
702
721
  doctor: DOCTOR_OPTIONS,
703
722
  merger: MERGER_OPTIONS,
723
+ notary: NOTARY_OPTIONS,
704
724
  analyst: ANALYST_OPTIONS,
705
725
  } as const satisfies Record<OptionOwner, readonly PublicOptionDefinition[]>;
706
726
 
@@ -715,6 +735,7 @@ export const PUBLIC_ROLE_OPTION_OWNERS = [
715
735
  "collector",
716
736
  "doctor",
717
737
  "merger",
738
+ "notary",
718
739
  "analyst",
719
740
  ] as const satisfies readonly PublicRoleOptionOwner[];
720
741
 
@@ -1068,6 +1089,14 @@ const ROLE_COMMAND_HELP = {
1068
1089
  'ak-role merger --project /path/to/worktree "Reconcile the active merge."',
1069
1090
  ],
1070
1091
  },
1092
+ notary: {
1093
+ command: "notary",
1094
+ summary: "Direct Notary document check (quote fidelity + ticket alignment); zero prompt/attachment.",
1095
+ usage: ["ak-role notary --source-run <runId@role|path> [options]"],
1096
+ examples: [
1097
+ "ak-role notary --source-run 01a034f1-75bf-71a6-bcf5-d1299145b1a5@judge",
1098
+ ],
1099
+ },
1071
1100
  analyst: {
1072
1101
  command: "analyst",
1073
1102
  summary: "Deterministic analysis seat (issue / sweep / cohort).",
@@ -86,6 +86,10 @@ const STARTUP_CANDIDATES: Record<PublicConfigurableSeat, readonly ModelRef[]> =
86
86
  { provider: "openai-codex", model: "gpt-5.6-luna", thinking: "high" },
87
87
  { provider: "xai", model: "grok-4.5", thinking: "high" },
88
88
  ],
89
+ notary: [
90
+ { provider: "openai-codex", model: "gpt-5.6-luna", thinking: "high" },
91
+ { provider: "xai", model: "grok-4.5", thinking: "high" },
92
+ ],
89
93
  navigator: [
90
94
  { provider: "openai-codex", model: "gpt-5.6-luna", thinking: "medium" },
91
95
  { provider: "xai", model: "grok-4.5", thinking: "high" },
@@ -67,7 +67,8 @@ export type RoleRunRecord = {
67
67
  | "collector"
68
68
  | "doctor"
69
69
  | "reviewer"
70
- | "merger";
70
+ | "merger"
71
+ | "notary";
71
72
  readonly state: RoleRunState;
72
73
  readonly bookKey: string;
73
74
  readonly projectRoot: string;
@@ -161,7 +162,8 @@ export async function readRoleRunState(
161
162
  record.role !== "collector" &&
162
163
  record.role !== "doctor" &&
163
164
  record.role !== "reviewer" &&
164
- record.role !== "merger"
165
+ record.role !== "merger" &&
166
+ record.role !== "notary"
165
167
  ) {
166
168
  return undefined;
167
169
  }
@@ -995,6 +997,7 @@ export async function peekRoleRunRole(
995
997
  | "doctor"
996
998
  | "reviewer"
997
999
  | "merger"
1000
+ | "notary"
998
1001
  | undefined
999
1002
  > {
1000
1003
  const runDirectory = await findRunDirectoryById(home, runId);
@@ -74,6 +74,12 @@ import {
74
74
  validateMergerOutput,
75
75
  type MergerOutput,
76
76
  } from "../merger-contracts.ts";
77
+ import {
78
+ NOTARY_OUTPUT_TOOL_NAME,
79
+ notaryDecisiveFacts,
80
+ validateRecordedNotaryOutput,
81
+ type NotaryOutput,
82
+ } from "../notary-contracts.ts";
77
83
  import {
78
84
  observePackagedMethodSkillInvocation,
79
85
  type ObservedPackagedMethodSkillInvocation,
@@ -98,6 +104,7 @@ import {
98
104
  type AdmittedFixerInvocation,
99
105
  type AdmittedJudgeInvocation,
100
106
  type AdmittedMergerInvocation,
107
+ type AdmittedNotaryInvocation,
101
108
  type AdmittedReviewerInvocation,
102
109
  type AdmittedRoleInvocation,
103
110
  } from "./invocation.ts";
@@ -3206,6 +3213,100 @@ export async function trySettleDoctorTerminalResult(
3206
3213
  return settleLawfulDoctorTerminalResult(admitted);
3207
3214
  }
3208
3215
 
3216
+ /** Lawful Notary accepted outcome (pass/bounce/incomplete-with-reason). */
3217
+ export type LawfulNotaryRoleOutcome = {
3218
+ kind: "accepted";
3219
+ role: "notary";
3220
+ status: string;
3221
+ decisiveFacts: Readonly<Record<string, unknown>>;
3222
+ };
3223
+
3224
+ export function extractNotaryRoleOutcome(
3225
+ entries: readonly SessionEntry[],
3226
+ ): { outcome: LawfulNotaryRoleOutcome; output: NotaryOutput } | undefined {
3227
+ if (!isReceiptSettlementBindingClear(entries)) return undefined;
3228
+ for (let i = entries.length - 1; i >= 0; i -= 1) {
3229
+ const entry = entries[i];
3230
+ if (entry?.type !== "message") continue;
3231
+ const message = entry.message;
3232
+ if (message?.role !== "toolResult") continue;
3233
+ if (message.toolName !== NOTARY_OUTPUT_TOOL_NAME) continue;
3234
+ if (!isAcceptedPackagedRoleTerminalResult(message)) continue;
3235
+ try {
3236
+ const output = validateRecordedNotaryOutput(message.details);
3237
+ const outcome: LawfulNotaryRoleOutcome = {
3238
+ kind: "accepted",
3239
+ role: "notary",
3240
+ status: String(output.status),
3241
+ decisiveFacts: notaryDecisiveFacts(output),
3242
+ };
3243
+ return { output, outcome };
3244
+ } catch {
3245
+ continue;
3246
+ }
3247
+ }
3248
+ return undefined;
3249
+ }
3250
+
3251
+ async function settleLawfulNotaryTerminalResult(
3252
+ admitted: AdmittedNotaryInvocation,
3253
+ ): Promise<TerminalResult | undefined> {
3254
+ const entries = await readLawfulSettlementEntries(admitted);
3255
+ if (entries === undefined) return undefined;
3256
+ const extracted = extractNotaryRoleOutcome(entries);
3257
+ if (extracted === undefined) {
3258
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
3259
+ const message = entries[index]?.message;
3260
+ if (message?.role !== "toolResult") continue;
3261
+ const residual = boundErroredToolCandidate(
3262
+ entries,
3263
+ index,
3264
+ message,
3265
+ NOTARY_OUTPUT_TOOL_NAME,
3266
+ );
3267
+ if (residual === undefined) continue;
3268
+ return {
3269
+ roleOutcome: buildResidualIncompleteTerminalOutcome({
3270
+ role: "notary",
3271
+ candidate: residual.candidate,
3272
+ diagnostic: residual.diagnostic,
3273
+ }),
3274
+ navigator: extractNavigatorFact(entries),
3275
+ artifacts: [],
3276
+ runId: admitted.runId,
3277
+ };
3278
+ }
3279
+ return undefined;
3280
+ }
3281
+ const navigator = extractNavigatorFact(entries);
3282
+ return {
3283
+ roleOutcome: extracted.outcome,
3284
+ navigator,
3285
+ artifacts: [],
3286
+ runId: admitted.runId,
3287
+ };
3288
+ }
3289
+
3290
+ /** Settle a lawful Notary Terminal from the admitted session. */
3291
+ export async function settleNotaryTerminalResult(
3292
+ admitted: AdmittedNotaryInvocation,
3293
+ ): Promise<TerminalResult> {
3294
+ const settled = await settleLawfulNotaryTerminalResult(admitted);
3295
+ if (settled === undefined) {
3296
+ throw new Error(
3297
+ "Notary Role run completed without a lawful typed terminal result",
3298
+ );
3299
+ }
3300
+ return settled;
3301
+ }
3302
+
3303
+ /** Try to settle a lawful Notary Terminal; undefined only for genuine absence. */
3304
+ export async function trySettleNotaryTerminalResult(
3305
+ admitted: AdmittedNotaryInvocation,
3306
+ ): Promise<TerminalResult | undefined> {
3307
+ return settleLawfulNotaryTerminalResult(admitted);
3308
+ }
3309
+
3209
3310
  /** Try to settle a lawful Coder Terminal; undefined only for genuine absence. */
3210
3311
  export async function trySettleCoderTerminalResult(
3211
3312
  admitted: AdmittedCoderInvocation,
@@ -38,11 +38,12 @@ export type TerminalRoleName =
38
38
  | "collector"
39
39
  | "doctor"
40
40
  | "reviewer"
41
- | "merger";
41
+ | "merger"
42
+ | "notary";
42
43
 
43
44
  export type ResidualIncompleteTerminalOutcome = {
44
45
  kind: "incomplete";
45
- role: "merger" | "collector";
46
+ role: "merger" | "collector" | "notary";
46
47
  status: "incomplete";
47
48
  decision: "no-usable-result";
48
49
  candidate: unknown;
@@ -130,7 +131,7 @@ export function exitCodeForTerminalOutcome(
130
131
  }
131
132
 
132
133
  export function buildResidualIncompleteTerminalOutcome(input: {
133
- role: "merger" | "collector";
134
+ role: "merger" | "collector" | "notary";
134
135
  candidate: unknown;
135
136
  diagnostic: string;
136
137
  }): ResidualIncompleteTerminalOutcome {
@@ -45,6 +45,7 @@ import {
45
45
  } from "./collector-role.ts";
46
46
  import type { ComplianceDecision } from "./compliance-transport.ts";
47
47
  import { createDoctorRoleRuntime } from "./doctor-role.ts";
48
+ import { createNotaryRoleRuntime } from "./notary-role.ts";
48
49
  import { decorateSettlementWithNavigation, formatNavigatorReport, NAVIGATOR_EVENT_TYPE, navigatorSubjectKey, navigatorUnavailableError, subjectPath, type NavigatorAttendance, type NavigatorEvent, type NavigatorPhase, type NavigatorReport, type NavigatorSettlement, type NavigatorSubjectProvenance, type NavigatorWorkContext } from "./navigator-attendance.ts";
49
50
  import {
50
51
  buildNavigatorInfrastructureFailureFact,
@@ -377,6 +378,7 @@ type ActivationRuntime = {
377
378
  bindReviewerParent(activation: ReviewerActivation): void;
378
379
  collector: { activate(context: ExtensionContext, event: { reason: string }): Promise<void> };
379
380
  doctor: { activate(): Promise<void> };
381
+ notary: { activate(): Promise<void> };
380
382
  merger(): Promise<void>;
381
383
  };
382
384
 
@@ -413,6 +415,7 @@ function activationStage(role: PackagedRole, runtime: ActivationRuntime): { id:
413
415
  } };
414
416
  case "collector": return { id: "load-and-install", run: async () => runtime.collector.activate(runtime.context, runtime.event) };
415
417
  case "doctor": return { id: "load-and-install", run: async () => runtime.doctor.activate() };
418
+ case "notary": return { id: "load-and-install", run: async () => runtime.notary.activate() };
416
419
  case "merger": return { id: "prepare-git-and-install", run: async () => runtime.merger() };
417
420
  }
418
421
  }
@@ -494,6 +497,8 @@ export type RoleRuntimeDependencies = {
494
497
  loadCollectorSoul?(): Promise<string>;
495
498
  createCollectorTransport?(): CollectorGitHubTransport;
496
499
  loadDoctorSoul?(): Promise<string>;
500
+ loadNotarySoul?(): Promise<string>;
501
+ loadNotarySourceRun?(path: string): Promise<import("./notary-contracts.ts").NotarySourceRunLocator>;
497
502
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
498
503
  loadMergerSoul?(): Promise<string>;
499
504
  loadMergerInput?(path: string): Promise<unknown>;
@@ -982,6 +987,16 @@ export function createRoleRuntimeExtension(
982
987
  async loadCase(path) { if (!dependencies.loadDoctorCase) throw new Error("Doctor runtime dependencies are not configured"); return dependencies.loadDoctorCase(path); },
983
988
  async auditCompliance(options) { if (!dependencies.auditDoctorCompliance) throw new Error("Doctor runtime dependencies are not configured"); return dependencies.auditDoctorCompliance(options); },
984
989
  }, hostActions);
990
+ const notary = createNotaryRoleRuntime(pi, {
991
+ async loadSoul() {
992
+ if (!dependencies.loadNotarySoul) throw new Error("Notary runtime dependencies are not configured");
993
+ return dependencies.loadNotarySoul();
994
+ },
995
+ async loadSourceRunLocator(path) {
996
+ if (!dependencies.loadNotarySourceRun) throw new Error("Notary runtime dependencies are not configured");
997
+ return dependencies.loadNotarySourceRun(path);
998
+ },
999
+ }, hostActions);
985
1000
  let sessionMergerGitState = dependencies.mergerGitState;
986
1001
  const merger = createMergerRoleRuntime(pi, {
987
1002
  async loadSoul() { if (!dependencies.loadMergerSoul) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerSoul(); },
@@ -1133,6 +1148,7 @@ export function createRoleRuntimeExtension(
1133
1148
  },
1134
1149
  collector,
1135
1150
  doctor,
1151
+ notary,
1136
1152
  merger: async () => {
1137
1153
  if (dependencies.mergerGitState === undefined) {
1138
1154
  sessionMergerGitState = dependencies.createMergerGitState?.(ctx.cwd);
@@ -46,6 +46,7 @@ export const MAIN_ROLE_SESSION_MATERIALS = {
46
46
  collector: ["CLAUDE.md", "souls/collector.md"],
47
47
  doctor: ["CLAUDE.md", "souls/doctor.md"],
48
48
  merger: ["CLAUDE.md", "souls/merger.md"],
49
+ notary: ["CLAUDE.md", "souls/notary.md"],
49
50
  navigator: ["CLAUDE.md", "souls/navigator.md"],
50
51
  } as const;
51
52