@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.
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/dist/notary-contracts.js +96 -0
- package/dist/package-contracts/terminating-tools.js +10 -2
- package/dist/packaged-role-registry.js +3 -1
- package/dist/public-cli/main.js +3344 -2778
- package/extensions/role-runtime.ts +11 -1
- package/package.json +1 -1
- package/souls/judge-output-guide.md +4 -1
- package/src/notary-contracts.ts +134 -0
- package/src/notary-role.ts +148 -0
- package/src/notary-source-run.ts +168 -0
- package/src/package-contracts/terminating-tools.ts +13 -2
- package/src/packaged-role-registry.ts +2 -0
- package/src/public-cli/cli.ts +51 -0
- package/src/public-cli/doctor-run.ts +18 -250
- package/src/public-cli/invocation.ts +170 -0
- package/src/public-cli/notary-run.ts +117 -0
- package/src/public-cli/one-shot-dispatch.ts +300 -0
- package/src/public-cli/option-definitions.ts +29 -0
- package/src/public-cli/registry.ts +4 -0
- package/src/public-cli/run-lifecycle.ts +5 -2
- package/src/public-cli/settlement.ts +101 -0
- package/src/public-cli/terminal.ts +4 -3
- package/src/role-runtime.ts +16 -0
- package/src/session-opening-materials.ts +1 -0
|
@@ -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 {
|
package/src/role-runtime.ts
CHANGED
|
@@ -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
|
|