@akagilnc/pi-workflow-roles 0.1.4980 → 0.1.4989
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/dist/acp-host/production-host.js +438 -456
- package/dist/analyst-gate-cycles-read.js +16 -11
- package/dist/analyst-metric-families/gate-cycles.js +2 -2
- package/dist/audit-escalation.js +4 -4
- package/dist/compliance-transport.js +2 -2
- package/dist/countersign-contracts.js +6 -5
- package/dist/doctor-evidence.js +2 -1
- package/dist/gatekeeper-role.js +20 -41
- package/dist/headless-host/production-host.js +438 -456
- package/dist/inspector-contracts.js +3 -2
- package/dist/migrate-book-topology.js +59 -54
- package/dist/navigator-invocation-identity.js +6 -4
- package/dist/notary-contracts.js +3 -19
- package/dist/package-contracts/auditor-output.js +4 -19
- package/dist/package-contracts/judge-output.js +6 -5
- package/dist/package-contracts/terminating-tools.js +9 -3
- package/dist/packaged-role-registry.js +10 -12
- package/dist/public-cli/countersign-run.js +1 -1
- package/dist/public-cli/main.js +90 -86
- package/dist/public-cli/option-definitions.js +2 -2
- package/dist/public-cli/settlement.js +11 -11
- package/dist/review-submission.js +20 -0
- package/dist/submission-errors.js +2 -2
- package/dist/submission-ledger.js +14 -2
- package/package.json +1 -1
- package/souls/doctor-auditor.md +1 -1
- package/souls/fixer-auditor.md +4 -4
- package/souls/gate-output-guide.md +2 -2
- package/souls/inspector.md +1 -1
- package/souls/judge-auditor.md +2 -2
- package/souls/notary.md +1 -1
- package/souls/quality-law.md +1 -1
- package/src/analyst-gate-cycles-read.ts +17 -9
- package/src/analyst-metric-families/gate-cycles.ts +2 -2
- package/src/audit-escalation.ts +8 -8
- package/src/auditor-role.ts +1 -1
- package/src/compliance-transport.ts +7 -7
- package/src/countersign-contracts.ts +10 -8
- package/src/countersign-role.ts +6 -35
- package/src/doctor-evidence.ts +2 -1
- package/src/doctor-role.ts +1 -2
- package/src/gatekeeper-role.ts +30 -55
- package/src/host-contracts.ts +4 -3
- package/src/inspector-contracts.ts +7 -6
- package/src/inspector-role.ts +3 -20
- package/src/judge-role.ts +29 -49
- package/src/navigator-invocation-identity.ts +16 -4
- package/src/notary-contracts.ts +8 -26
- package/src/notary-role.ts +1 -1
- package/src/package-contracts/auditor-output.ts +6 -27
- package/src/package-contracts/judge-output.ts +10 -8
- package/src/package-contracts/terminating-tools.ts +9 -3
- package/src/packaged-role-registry.ts +12 -13
- package/src/pi/adapter.ts +6 -5
- package/src/public-cli/countersign-run.ts +1 -1
- package/src/public-cli/option-definitions.ts +2 -2
- package/src/public-cli/settlement.ts +13 -14
- package/src/review-submission.ts +29 -0
- package/src/role-envelope.ts +17 -9
- package/src/role-runtime.ts +32 -15
- package/src/submission-errors.ts +6 -6
- package/src/{gatekeeper-pass-envelope.ts → submission-gate.ts} +29 -16
- package/src/submission-ledger.ts +13 -3
- package/src/ticket-trajectory.ts +9 -4
- package/src/worker-role.ts +16 -2
package/souls/quality-law.md
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
unfinished 只认两因:**缺前置**——处置当下仍缺少已声明的前置产物、决定或答复,且该缺失使剩余派工当前无法合法执行;**违宪约束**——继续施工将违反 authority 或既裁法源。两因之外的一切理由(执行窗口、时间、工作量、难度等),不得作为 unfinished 成立依据,审官一律封驳。
|
|
49
49
|
|
|
50
|
-
据实以两因交卷不构成违规,但不自动
|
|
50
|
+
据实以两因交卷不构成违规,但不自动 converged;所称阻断须可由卷宗独立核对,声明与引用本身不能证明缺失或因果。
|
|
51
51
|
|
|
52
52
|
## 预警参考线
|
|
53
53
|
|
|
@@ -140,7 +140,7 @@ type GateToolCall = {
|
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
function isGateTerminatingToolName(toolName: string): boolean {
|
|
143
|
-
return DISPATCH_TOOLS.has(toolName) || OFFICER_TOOL_TO_FACE[toolName] !== undefined;
|
|
143
|
+
return toolName === "ak_submission_output" || DISPATCH_TOOLS.has(toolName) || OFFICER_TOOL_TO_FACE[toolName] !== undefined;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
@@ -278,6 +278,7 @@ function projectAcceptedGateCall(
|
|
|
278
278
|
readonly attemptEntryId?: string;
|
|
279
279
|
readonly parentSessionFile?: string;
|
|
280
280
|
},
|
|
281
|
+
pointerOfficer?: "inspector" | "notary",
|
|
281
282
|
): ClassifiedVolume | undefined {
|
|
282
283
|
// Continuous memory volumes carry many summons; span only this binding's interval.
|
|
283
284
|
const span = requireAcceptedGateSpan(intervalRowsForGateCall(rows, call.rowIndex), filePath);
|
|
@@ -312,7 +313,9 @@ function projectAcceptedGateCall(
|
|
|
312
313
|
};
|
|
313
314
|
}
|
|
314
315
|
|
|
315
|
-
const officer =
|
|
316
|
+
const officer = call.toolName === "ak_submission_output"
|
|
317
|
+
? pointerOfficer
|
|
318
|
+
: OFFICER_TOOL_TO_FACE[call.toolName];
|
|
316
319
|
if (officer === undefined) {
|
|
317
320
|
// isGateTerminatingToolName already screened; keep loud if tables drift.
|
|
318
321
|
throw new Error(
|
|
@@ -334,6 +337,7 @@ function projectAcceptedGateCall(
|
|
|
334
337
|
|
|
335
338
|
async function classifyAuditorVolume(
|
|
336
339
|
filePath: string,
|
|
340
|
+
pointerOfficer?: "inspector" | "notary",
|
|
337
341
|
): Promise<readonly ClassifiedVolume[]> {
|
|
338
342
|
// Canonical JSONL errors propagate — failure honesty (never wash to fewer rounds).
|
|
339
343
|
const rows = await readLedgerSessionJsonl(filePath);
|
|
@@ -342,7 +346,9 @@ async function classifyAuditorVolume(
|
|
|
342
346
|
if (accepted.length === 0) return [];
|
|
343
347
|
const volumes: ClassifiedVolume[] = [];
|
|
344
348
|
for (const call of accepted) {
|
|
345
|
-
const projected =
|
|
349
|
+
const projected = call.toolName === "ak_submission_output" && pointerOfficer === undefined
|
|
350
|
+
? undefined // Shared tool alone carries no officer identity; a typed pointer does.
|
|
351
|
+
: projectAcceptedGateCall(filePath, rows, call, pointerOfficer);
|
|
346
352
|
if (projected !== undefined) volumes.push(projected);
|
|
347
353
|
}
|
|
348
354
|
return volumes;
|
|
@@ -425,7 +431,7 @@ function pairGateRounds(
|
|
|
425
431
|
/** Resolve a direct-officer-run-pointer file to the officer session 正本 path. */
|
|
426
432
|
async function resolveOfficerSessionFromPointerFile(
|
|
427
433
|
pointerPath: string,
|
|
428
|
-
): Promise<string |
|
|
434
|
+
): Promise<{ sessionFile: string; officer?: "inspector" | "notary" }> {
|
|
429
435
|
const { readFile } = await import("node:fs/promises");
|
|
430
436
|
let raw: unknown;
|
|
431
437
|
try {
|
|
@@ -443,7 +449,9 @@ async function resolveOfficerSessionFromPointerFile(
|
|
|
443
449
|
if (typeof sessionFile !== "string" || sessionFile.trim() === "") {
|
|
444
450
|
throw new Error(`direct officer run pointer missing sessionFile in ${pointerPath}`);
|
|
445
451
|
}
|
|
446
|
-
|
|
452
|
+
const officer = raw.officer === "inspector" || raw.officer === "notary"
|
|
453
|
+
? raw.officer : undefined;
|
|
454
|
+
return { sessionFile, ...(officer === undefined ? {} : { officer }) };
|
|
447
455
|
}
|
|
448
456
|
|
|
449
457
|
/**
|
|
@@ -488,15 +496,15 @@ export async function readAnalystGateCyclesFromAuditorRoles(
|
|
|
488
496
|
for (const name of names) {
|
|
489
497
|
const path = join(directory, name);
|
|
490
498
|
const fromPointer = name.endsWith(".pointer.json");
|
|
491
|
-
const
|
|
499
|
+
const pointer = fromPointer
|
|
492
500
|
? await resolveOfficerSessionFromPointerFile(path)
|
|
493
|
-
:
|
|
494
|
-
|
|
501
|
+
: undefined;
|
|
502
|
+
const sessionPath = pointer?.sessionFile ?? path;
|
|
495
503
|
if (fromPointer) {
|
|
496
504
|
if (classifiedOfficerSessions.has(sessionPath)) continue;
|
|
497
505
|
classifiedOfficerSessions.add(sessionPath);
|
|
498
506
|
}
|
|
499
|
-
const classified = await classifyAuditorVolume(sessionPath);
|
|
507
|
+
const classified = await classifyAuditorVolume(sessionPath, pointer?.officer);
|
|
500
508
|
for (const volume of classified) {
|
|
501
509
|
if (
|
|
502
510
|
options.parentSessionFile !== undefined &&
|
|
@@ -93,8 +93,8 @@ function emptyAccum(): OfficerAccum {
|
|
|
93
93
|
function absorbRound(accum: OfficerAccum, round: AnalystGateCyclesRoundRow): void {
|
|
94
94
|
accum.rounds += 1;
|
|
95
95
|
accum.wallSum += round.officerWallMs;
|
|
96
|
-
if (round.status === "bounce") accum.bounceCount += 1;
|
|
97
|
-
if (round.status === "pass") accum.passCount += 1;
|
|
96
|
+
if (round.status === "bounce" || round.status === "continue") accum.bounceCount += 1;
|
|
97
|
+
if (round.status === "pass" || round.status === "converged") accum.passCount += 1;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function finishOfficer(
|
package/src/audit-escalation.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
ComplianceDecision,
|
|
5
5
|
} from "./compliance-transport.ts";
|
|
6
6
|
import { GatekeeperDecisionError } from "./submission-errors.ts";
|
|
7
|
-
import type {
|
|
7
|
+
import type { SubmissionGateNonPassResult } from "./gatekeeper-role.ts";
|
|
8
8
|
|
|
9
9
|
export const AUDIT_ESCALATION_KIND = "audit_escalation" as const;
|
|
10
10
|
|
|
@@ -116,7 +116,7 @@ export function projectAuditEscalation(
|
|
|
116
116
|
|
|
117
117
|
/** Preserve the escalating officer's own gate alongside the raw receipt. */
|
|
118
118
|
export function projectGatekeeperEscalation(
|
|
119
|
-
result: Extract<
|
|
119
|
+
result: Extract<SubmissionGateNonPassResult, { status: "escalate" }>,
|
|
120
120
|
deliveredOutput?: unknown,
|
|
121
121
|
): AuditEscalationToolResult {
|
|
122
122
|
const receipt = result.receipt;
|
|
@@ -147,7 +147,7 @@ export function isAuditEscalationResult(
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
export type ComplianceDecisionHandlers<T> = {
|
|
150
|
-
|
|
150
|
+
converged: (usage: Usage | undefined) => T | PromiseLike<T>;
|
|
151
151
|
/** Project the accepted parent candidate beside the typed audit-leg facts. */
|
|
152
152
|
noReceipt?: (
|
|
153
153
|
facts: Extract<ComplianceDecision, { status: "no-receipt" }>,
|
|
@@ -161,7 +161,7 @@ export type ComplianceDecisionHandlers<T> = {
|
|
|
161
161
|
facts: Extract<ComplianceDecision, { status: "received" }>,
|
|
162
162
|
usageProjection: { usage?: Usage },
|
|
163
163
|
) => T | PromiseLike<T>;
|
|
164
|
-
|
|
164
|
+
continue: (violations: readonly unknown[]) => T | PromiseLike<T>;
|
|
165
165
|
escalate: (result: AuditEscalationToolResult) => T | PromiseLike<T>;
|
|
166
166
|
/** Host failure beside any recorded auditor payloads — parent is not accepted. */
|
|
167
167
|
transportFailure?: (
|
|
@@ -179,8 +179,8 @@ export async function disposeComplianceDecision<T>(
|
|
|
179
179
|
deliveredOutput?: unknown,
|
|
180
180
|
): Promise<Awaited<T>> {
|
|
181
181
|
switch (decision.status) {
|
|
182
|
-
case "
|
|
183
|
-
return await handlers.
|
|
182
|
+
case "converged":
|
|
183
|
+
return await handlers.converged(decision.usage);
|
|
184
184
|
case "no-receipt":
|
|
185
185
|
// The parent candidate remains accepted, but its parallel audit leg is a
|
|
186
186
|
// public typed fact and must not be collapsed into an ordinary pass.
|
|
@@ -200,8 +200,8 @@ export async function disposeComplianceDecision<T>(
|
|
|
200
200
|
decision,
|
|
201
201
|
decision.usage === undefined ? {} : { usage: decision.usage },
|
|
202
202
|
);
|
|
203
|
-
case "
|
|
204
|
-
return await handlers.
|
|
203
|
+
case "continue":
|
|
204
|
+
return await handlers.continue(decision.violations);
|
|
205
205
|
case "escalate":
|
|
206
206
|
return await handlers.escalate(
|
|
207
207
|
projectAuditEscalation(decision, deliveredOutput),
|
package/src/auditor-role.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type AuditorRuntimeDependencies = {
|
|
|
15
15
|
export const AUDITOR_TOOL_SPEC = {
|
|
16
16
|
name: AUDITOR_OUTPUT_TOOL_NAME,
|
|
17
17
|
label: "审刑院输出",
|
|
18
|
-
description: "
|
|
18
|
+
description: "审刑院终局回执,status 为 converged、continue 或 escalate。",
|
|
19
19
|
promptSnippet: "审刑院终局回执",
|
|
20
20
|
parameters: auditorOutputSchema,
|
|
21
21
|
} as const;
|
|
@@ -11,7 +11,7 @@ import { readableGateItem } from "./readable-gate-item.ts";
|
|
|
11
11
|
export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt"; usage?: Usage };
|
|
12
12
|
/**
|
|
13
13
|
* #757 / #750: no unreadable/unusable judgment on auditor replies.
|
|
14
|
-
* Known three-state (
|
|
14
|
+
* Known three-state (converged/continue/escalate) is read for queueing only.
|
|
15
15
|
* Unknown accepted shape rides as `received` with the raw reply — parent stands,
|
|
16
16
|
* no forged pass, no shape-death label. Resume-speaker for three pairs is #753/#756.
|
|
17
17
|
*/
|
|
@@ -29,8 +29,8 @@ export type ComplianceTransportFailure = {
|
|
|
29
29
|
readonly usage?: Usage;
|
|
30
30
|
};
|
|
31
31
|
export type ComplianceDecision =
|
|
32
|
-
| { status: "
|
|
33
|
-
| { status: "
|
|
32
|
+
| { status: "converged"; receipt?: unknown; usage?: Usage }
|
|
33
|
+
| { status: "continue"; violations: readonly unknown[]; receipt?: unknown; usage?: Usage }
|
|
34
34
|
| { status: "escalate"; conflicts?: unknown; decisionGate?: unknown; receipt?: unknown; usage?: Usage }
|
|
35
35
|
| ComplianceNoReceipt
|
|
36
36
|
| ComplianceReceived
|
|
@@ -60,15 +60,15 @@ export type AuditorParentAttemptBinding = {
|
|
|
60
60
|
|
|
61
61
|
function readListField(value: unknown): readonly unknown[] { return Array.isArray(value) ? value : value === undefined ? [] : [value]; }
|
|
62
62
|
|
|
63
|
-
/** Try to project a known three-state compliance decision; undefined when not
|
|
63
|
+
/** Try to project a known three-state compliance decision; undefined when not converged/continue/escalate. */
|
|
64
64
|
export function tryReadComplianceCandidate(arguments_: unknown, usage?: Usage): ComplianceDecision | undefined {
|
|
65
65
|
if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_)) {
|
|
66
66
|
return undefined;
|
|
67
67
|
}
|
|
68
68
|
const args = arguments_ as Record<string, unknown>;
|
|
69
69
|
const status = args.status;
|
|
70
|
-
if (status === "
|
|
71
|
-
if (status === "
|
|
70
|
+
if (status === "converged") return { status, receipt: arguments_, ...(usage === undefined ? {} : { usage }) };
|
|
71
|
+
if (status === "continue") return { status, violations: readListField(args.violations), receipt: arguments_, ...(usage === undefined ? {} : { usage }) };
|
|
72
72
|
if (status === "escalate") {
|
|
73
73
|
return {
|
|
74
74
|
status,
|
|
@@ -129,7 +129,7 @@ async function usageFromSummonedSession(summoned: PublicSummonResult): Promise<U
|
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
131
|
* Project a public auditor terminal onto the parent compliance decision.
|
|
132
|
-
* Known
|
|
132
|
+
* Known converged/continue/escalate/no-receipt flow through for queueing.
|
|
133
133
|
* Accepted-but-not-three-state → `received` with raw reply (#757) — no unreadable label,
|
|
134
134
|
* never forged pass, never parent abort. Real provider/engine/disk failures stay loud.
|
|
135
135
|
* Accepted audits always carry real session usage when present (#675 metering).
|
|
@@ -5,18 +5,20 @@
|
|
|
5
5
|
* is defaulted, rewritten, or dropped (ADR 0055).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
9
|
+
|
|
10
|
+
export const COUNTERSIGN_OUTPUT_TOOL_NAME = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
|
|
9
11
|
|
|
10
12
|
export type CountersignVerdict =
|
|
11
|
-
| {
|
|
13
|
+
| { status: "converged"; note?: string; evidence?: unknown }
|
|
12
14
|
| {
|
|
13
|
-
|
|
15
|
+
status: "continue";
|
|
14
16
|
fix?: { summary: string };
|
|
15
17
|
note?: string;
|
|
16
18
|
evidence?: unknown;
|
|
17
19
|
}
|
|
18
20
|
| {
|
|
19
|
-
|
|
21
|
+
status: "escalate";
|
|
20
22
|
decisionGate?: { question: string; options: string[] };
|
|
21
23
|
note?: string;
|
|
22
24
|
evidence?: unknown;
|
|
@@ -26,16 +28,16 @@ export function validateRecordedCountersignOutput(verdict: unknown): Countersign
|
|
|
26
28
|
if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) {
|
|
27
29
|
throw new Error("Countersign verdict has no execution discriminator");
|
|
28
30
|
}
|
|
29
|
-
let
|
|
31
|
+
let status: unknown;
|
|
30
32
|
try {
|
|
31
|
-
|
|
33
|
+
status = (verdict as Record<string, unknown>).status;
|
|
32
34
|
} catch {
|
|
33
35
|
throw new Error("Countersign verdict has no execution discriminator");
|
|
34
36
|
}
|
|
35
|
-
if (typeof
|
|
37
|
+
if (typeof status !== "string") {
|
|
36
38
|
throw new Error("Countersign verdict has no execution discriminator");
|
|
37
39
|
}
|
|
38
|
-
if (["converged", "continue", "escalate"].includes(
|
|
40
|
+
if (["converged", "continue", "escalate"].includes(status)) {
|
|
39
41
|
return verdict as CountersignVerdict;
|
|
40
42
|
}
|
|
41
43
|
throw new Error("Countersign verdict has no execution discriminator");
|
package/src/countersign-role.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Type } from "typebox";
|
|
3
|
-
|
|
4
|
-
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
1
|
+
import { reviewSubmissionSchema } from "./review-submission.ts";
|
|
5
2
|
import {
|
|
6
3
|
COUNTERSIGN_OUTPUT_TOOL_NAME,
|
|
7
4
|
validateRecordedCountersignOutput,
|
|
@@ -12,44 +9,18 @@ export { COUNTERSIGN_OUTPUT_TOOL_NAME } from "./countersign-contracts.ts";
|
|
|
12
9
|
export type { CountersignVerdict };
|
|
13
10
|
|
|
14
11
|
// #836 r16 class 1: fix/note/decisionGate are LLM/human-read narrative content —
|
|
15
|
-
// no code branches on their length or nested presence. `
|
|
16
|
-
//
|
|
12
|
+
// no code branches on their length or nested presence. `status` alone is the
|
|
13
|
+
// machine discriminator the queue reads.
|
|
17
14
|
// #836 (2026-09-11 御批 / ADR 0003 Amendment): registered-to-provider closed
|
|
18
|
-
// value domains reject an unknown
|
|
15
|
+
// value domains reject an unknown status before the submission
|
|
19
16
|
// ledger ever records it, defeating 读不出三态→resume 说话者本人. Kept open
|
|
20
17
|
// (Type.Unknown) like every other gate-queue status field in this package
|
|
21
18
|
// (notary/auditor/gatekeeper/navigator); src/role-runtime.ts:997-1025 still
|
|
22
19
|
// classifies the recorded value and reasks the countersign itself when it
|
|
23
20
|
// isn't one of the three states — code, not the transport, does that work.
|
|
24
21
|
/** 给事中票庭审读五问的交卷形状(ADR 0074);形状指引,非 schema 闸。 */
|
|
25
|
-
export const countersignVerdictSchema =
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
countersignStatus: Type.Unknown({ description: "converged | continue | escalate。非三态时请重读后重交,勿改标。" }),
|
|
29
|
-
fix: Type.Optional(
|
|
30
|
-
Type.Object(
|
|
31
|
-
{ summary: Type.Optional(Type.String({ description: "退回摘要" })) },
|
|
32
|
-
{ additionalProperties: true, description: "退回说明" },
|
|
33
|
-
),
|
|
34
|
-
),
|
|
35
|
-
note: Type.Optional(Type.String({ description: "附注" })),
|
|
36
|
-
evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
|
|
37
|
-
decisionGate: Type.Optional(
|
|
38
|
-
Type.Object(
|
|
39
|
-
{
|
|
40
|
-
question: Type.Optional(Type.String()),
|
|
41
|
-
options: Type.Optional(Type.Array(Type.String())),
|
|
42
|
-
},
|
|
43
|
-
{ additionalProperties: true, description: "需陛下处置的问题与选项" },
|
|
44
|
-
),
|
|
45
|
-
),
|
|
46
|
-
},
|
|
47
|
-
{ additionalProperties: true },
|
|
48
|
-
),
|
|
49
|
-
);
|
|
50
|
-
(countersignVerdictSchema as unknown as { required: string[] }).required = [];
|
|
51
|
-
|
|
52
|
-
export type CountersignVerdictParameters = Static<typeof countersignVerdictSchema>;
|
|
22
|
+
export const countersignVerdictSchema = reviewSubmissionSchema;
|
|
23
|
+
export type CountersignVerdictParameters = import("./review-submission.ts").ReviewSubmission;
|
|
53
24
|
|
|
54
25
|
export type CountersignRuntimeDependencies = {
|
|
55
26
|
loadSoul(): Promise<string>;
|
package/src/doctor-evidence.ts
CHANGED
|
@@ -20,10 +20,11 @@ function deriveSession(content: string, id: string): SessionDerivation {
|
|
|
20
20
|
let accepted: Record<string, unknown> | undefined, observedCommit: string | undefined, turns = 0, calls = 0, tokens = 0;
|
|
21
21
|
const statuses: DoctorCaseCost["statuses"] = [], commits: DoctorCaseCost["commits"] = [];
|
|
22
22
|
for (const row of rows) {
|
|
23
|
+
if (row.type === "custom" && row.customType === "ak-role-submission-closure" && record(row.data) && row.data.isError === false && typeof row.data.toolName === "string" && isTerminatingToolName(row.data.toolName)) accepted = row;
|
|
23
24
|
const message = record(row.message) ? row.message : undefined;
|
|
24
25
|
if (message?.role === "assistant") { for (const part of Array.isArray(message.content) ? message.content : []) if (record(part) && part.type === "toolCall") calls++; if (typeof message.responseId === "string") { turns++; const usage = record(message.usage) ? message.usage : undefined; if (usage && typeof usage.output === "number") tokens += usage.output; } }
|
|
25
26
|
if (message?.role === "toolResult" && message.isError !== true && typeof message.toolName === "string" && isTerminatingToolName(message.toolName) && record(message.details)) { let details; try { details = validateAcceptedDetails(message.toolName, message.details); } catch (error) { // Contract: README.md#Doctor — non-accepted terminating receipts are expected-negative evidence and are skipped; all other validation failures propagate with their cause.
|
|
26
|
-
if (error instanceof AcceptedDetailsContractError) continue; throw error; }
|
|
27
|
+
if (error instanceof AcceptedDetailsContractError) continue; throw error; } const facts = acceptedFacts(message.toolName, details); if (facts.commit && facts.commit !== observedCommit) { commits.push({ source: id, commit: facts.commit }); observedCommit = facts.commit; } /* #836 B10.3: do not wipe earlier statuses or invent a code status sentence */ if (facts.status !== undefined) { statuses.push({ source: id, status: facts.status }); } }
|
|
27
28
|
}
|
|
28
29
|
const acceptedAt = accepted && timestamp(accepted); const final = acceptedAt ? accepted! : rows.at(-1); const endedAt = final && timestamp(final); const wall = startedAt && endedAt ? Date.parse(endedAt) - Date.parse(startedAt) : undefined;
|
|
29
30
|
if (wall !== undefined && wall < 0) degradationReasons.push(`non-monotonic session timestamps in ${id}`);
|
package/src/doctor-role.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { RoleHost, HostContext, HostToolResult } from "./host-contracts.ts";
|
|
2
2
|
import { disposeComplianceDecision } from "./audit-escalation.ts";
|
|
3
3
|
import { ComplianceResponseRetentionError, type ComplianceDecision } from "./compliance-transport.ts";
|
|
4
|
-
import { GatekeeperDecisionError } from "./submission-errors.ts";
|
|
5
4
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
|
|
6
5
|
import { readableGateItem } from "./readable-gate-item.ts";
|
|
7
6
|
import { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_DESCRIPTION, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, doctorSubmissionSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
|
|
@@ -37,7 +36,7 @@ export function createDoctorRoleRuntime(pi: RoleHost, dependencies: DoctorRoleDe
|
|
|
37
36
|
let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
|
|
38
37
|
return { async activate() { const path = pi.getFlag(DOCTOR_CASE_FLAG.name); if (typeof path !== "string" || !path.trim()) throw new Error("Doctor requires --ak-doctor-case"); const soul = (await dependencies.loadSoul()).trim(); if (!soul) throw new Error("Doctor soul is empty"); const patient = await dependencies.loadCase(path); activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
|
|
39
38
|
if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "太医署证据", description: "分页读取留存的 Pi session 字节。", parameters: doctorEvidenceReadSchema, async execute(_id: string, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("太医署未激活"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
|
|
40
|
-
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id: string, params: unknown, signal: AbortSignal | undefined, _update: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); const active = activation; const testimony = validateDoctorOutput(params, active.patient, active.store); try { appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx, submission: testimony } : { context: ctx, signal, submission: testimony }); } catch (error) { host.failInfrastructure(error, ctx, id); } const acceptedDetails = testimony; return disposeComplianceDecision<HostToolResult<unknown>>(audit, {
|
|
39
|
+
pi.registerTool({ name: DOCTOR_OUTPUT_TOOL_NAME, label: "太医署输出", description: DOCTOR_OUTPUT_TOOL_DESCRIPTION, parameters: doctorSubmissionSchema, async execute(id: string, params: unknown, signal: AbortSignal | undefined, _update: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> { if (!activation) throw new Error("太医署未激活"); const active = activation; const testimony = validateDoctorOutput(params, active.patient, active.store); try { appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity }); } catch (error) { host.failInfrastructure(error, ctx, id); } let audit: ComplianceDecision; try { audit = await dependencies.auditCompliance(signal === undefined ? { context: ctx, submission: testimony } : { context: ctx, signal, submission: testimony }); } catch (error) { host.failInfrastructure(error, ctx, id); } const acceptedDetails = testimony; return disposeComplianceDecision<HostToolResult<unknown>>(audit, { converged: (usage) => ({ content: audit.status === "converged" && audit.receipt !== undefined ? [{ type: "text" as const, text: readableGateItem(audit.receipt) }] : [], details: acceptedDetails, terminate: true as const, ...(usage === undefined ? {} : { usage }) }), noReceipt: (auditNoReceipt, usageProjection) => { try { appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, auditNoReceipt, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity }); } catch (error) { host.failInfrastructure(error, ctx, id); } return { content: [], details: acceptedDetails, terminate: true as const, ...usageProjection }; }, received: () => { throw new Error("审刑院非三态应交 runComplianceAudit 重问说话者"); }, continue: (violations) => ({ content: [{ type: "text" as const, text: readableGateItem(audit.status === "continue" ? audit.receipt ?? violations : violations) }], details: acceptedDetails, terminate: false as const }), escalate: (result) => result, transportFailure: (facts) => { host.failInfrastructure(Object.assign(new Error(facts.diagnostic), { name: "ComplianceTransportFailure", ...(facts.submissions === undefined && facts.terminal === undefined ? {} : { submission: facts.submissions ?? facts.terminal }) }), ctx, id); } }, acceptedDetails); } });
|
|
41
40
|
pi.on("before_agent_start", (event) => { if (!activation) throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; }); }
|
|
42
41
|
const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME]; const names = pi.getAllTools().map((tool) => tool.name); for (const name of required) if (names.filter((item) => item === name).length !== 1) throw new Error(`Doctor required tool collision or missing: ${name}`); pi.setActiveTools(required); const active = pi.getActiveTools?.() ?? required; if (active.length !== 2 || !required.every((name) => active.includes(name))) throw new Error("Doctor active tool narrowing failed"); } };
|
|
43
42
|
}
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -2,10 +2,11 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { HostContext } from "./host-contracts.ts";
|
|
3
3
|
|
|
4
4
|
import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { packagedGateStageLabel } from "./packaged-role-registry.ts";
|
|
6
6
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
7
7
|
import { GatekeeperDecisionError } from "./submission-errors.ts";
|
|
8
8
|
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
|
|
9
|
+
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
9
10
|
import {
|
|
10
11
|
GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
11
12
|
gatekeeperDecisionSchema,
|
|
@@ -15,7 +16,7 @@ import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
|
15
16
|
import type { TerminalResult } from "./public-cli/terminal.ts";
|
|
16
17
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
|
|
17
18
|
export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
|
|
18
|
-
export const NOTARY_OUTPUT_TOOL =
|
|
19
|
+
export const NOTARY_OUTPUT_TOOL = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
|
|
19
20
|
|
|
20
21
|
/** Gate review officers — 台院 / 符宝郎 / 审刑院 / 给事中 (#753 / #756 / #969). */
|
|
21
22
|
export type GateOfficer = "inspector" | "notary" | "auditor" | "countersign";
|
|
@@ -38,14 +39,14 @@ export type GatekeeperSubject =
|
|
|
38
39
|
*/
|
|
39
40
|
export type GatekeeperResult =
|
|
40
41
|
| {
|
|
41
|
-
readonly status: "
|
|
42
|
+
readonly status: "converged";
|
|
42
43
|
readonly officer: GateOfficer;
|
|
43
44
|
readonly receipt: unknown;
|
|
44
45
|
readonly runId?: string;
|
|
45
46
|
}
|
|
46
|
-
/**
|
|
47
|
+
/** continue | escalate: both return the officer receipt to the parent (#753 / #756). */
|
|
47
48
|
| {
|
|
48
|
-
readonly status: "
|
|
49
|
+
readonly status: "continue";
|
|
49
50
|
readonly officer: GateOfficer;
|
|
50
51
|
readonly receipt: unknown;
|
|
51
52
|
readonly runId?: string;
|
|
@@ -58,9 +59,9 @@ export type GatekeeperResult =
|
|
|
58
59
|
}
|
|
59
60
|
| {
|
|
60
61
|
/**
|
|
61
|
-
* Accepted reply whose conclusion is not
|
|
62
|
+
* Accepted reply whose conclusion is not converged|continue|escalate.
|
|
62
63
|
* Envelope resumes the officer with plain-language re-ask — never parent-stands,
|
|
63
|
-
* never forges
|
|
64
|
+
* never forges continue (#753 / unreadable-conclusion-resume-speaker).
|
|
64
65
|
*/
|
|
65
66
|
readonly status: "needs_reask";
|
|
66
67
|
readonly officer: GateOfficer;
|
|
@@ -77,9 +78,9 @@ export type GatekeeperResult =
|
|
|
77
78
|
};
|
|
78
79
|
|
|
79
80
|
/** Non-pass faces returned to the parent session (correctable; #836 never kill leg). */
|
|
80
|
-
export type
|
|
81
|
+
export type SubmissionGateNonPassResult = Extract<
|
|
81
82
|
GatekeeperResult,
|
|
82
|
-
{ status: "
|
|
83
|
+
{ status: "continue" | "escalate" | "no_receipt" | "transport_failure" }
|
|
83
84
|
>;
|
|
84
85
|
|
|
85
86
|
function gateSeatLabel(stage: GateOfficer): string {
|
|
@@ -138,16 +139,16 @@ export type RunGatekeeperOptions = {
|
|
|
138
139
|
readonly submission?: unknown;
|
|
139
140
|
/**
|
|
140
141
|
* Officer summon seam. Production default lives on the shared envelope
|
|
141
|
-
* (`createDefaultGateOfficerSummon` /
|
|
142
|
+
* (`createDefaultGateOfficerSummon` / requireSubmissionGate — ADR 0018).
|
|
142
143
|
* Role module only projects; callers must supply the summon.
|
|
143
144
|
*/
|
|
144
145
|
readonly summonOfficer: GateOfficerSummon;
|
|
145
146
|
};
|
|
146
147
|
|
|
147
|
-
export type
|
|
148
|
+
export type SubmissionGateHostActions = {
|
|
148
149
|
failInfrastructure(error: unknown, ctx: ExtensionContext | HostContext, toolCallId?: string): never;
|
|
149
150
|
/** Envelope-owned execute→tool_result bridge (role-runtime); role module only throws typed error. */
|
|
150
|
-
bindSubmissionNonPass(toolCallId: string, result:
|
|
151
|
+
bindSubmissionNonPass(toolCallId: string, result: SubmissionGateNonPassResult): void;
|
|
151
152
|
};
|
|
152
153
|
|
|
153
154
|
/**
|
|
@@ -204,15 +205,15 @@ function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
204
205
|
|
|
205
206
|
/**
|
|
206
207
|
* Read only the conclusion field for queueing (#753).
|
|
207
|
-
*
|
|
208
|
+
* converged | continue | escalate → queue signal + raw receipt.
|
|
208
209
|
* Anything else accepted → needs_reask (resume speaker), never unreadable/parent-stand.
|
|
209
210
|
* `fallbackStatus` is the terminal outcome.status when the receipt body has no status key
|
|
210
211
|
* (keeps missing-args sentinel intact as the receipt).
|
|
211
212
|
*/
|
|
212
|
-
/** Map
|
|
213
|
-
function
|
|
214
|
-
if (status === "converged") return "
|
|
215
|
-
if (status === "continue") return "
|
|
213
|
+
/** Map the shared review verdict onto the gate queue words (#1028). */
|
|
214
|
+
function reviewQueueStatus(status: string): string | undefined {
|
|
215
|
+
if (status === "converged") return "converged";
|
|
216
|
+
if (status === "continue") return "continue";
|
|
216
217
|
if (status === "escalate") return "escalate";
|
|
217
218
|
return undefined;
|
|
218
219
|
}
|
|
@@ -224,34 +225,15 @@ function projectOfficerDecision(
|
|
|
224
225
|
): GatekeeperResult {
|
|
225
226
|
const receipt = retainedReceipt(decision);
|
|
226
227
|
const record = readRecord(decision);
|
|
227
|
-
// 给事中: only countersignStatus, strict three-word map; never generic status,
|
|
228
|
-
// never unmapped raw word, never fallbackStatus (#969 / ADR 0040/0055).
|
|
229
|
-
if (packagedGateDecision(officer) === "countersign-status") {
|
|
230
|
-
const countersignStatus =
|
|
231
|
-
record !== undefined && typeof record.countersignStatus === "string"
|
|
232
|
-
? record.countersignStatus
|
|
233
|
-
: undefined;
|
|
234
|
-
const status =
|
|
235
|
-
typeof countersignStatus === "string"
|
|
236
|
-
? gateStatusFromCountersign(countersignStatus)
|
|
237
|
-
: undefined;
|
|
238
|
-
if (status === "pass") {
|
|
239
|
-
return { status: "pass", officer, receipt };
|
|
240
|
-
}
|
|
241
|
-
if (status === "bounce" || status === "escalate") {
|
|
242
|
-
return { status, officer, receipt };
|
|
243
|
-
}
|
|
244
|
-
return { status: "needs_reask", officer, receipt };
|
|
245
|
-
}
|
|
246
|
-
// inspector / notary / auditor: shared gate words on generic status.
|
|
247
228
|
const status =
|
|
248
229
|
(record !== undefined && typeof record.status === "string" ? record.status : undefined)
|
|
249
230
|
?? fallbackStatus;
|
|
250
|
-
|
|
251
|
-
|
|
231
|
+
const queueStatus = typeof status === "string" ? reviewQueueStatus(status) : undefined;
|
|
232
|
+
if (queueStatus === "converged") {
|
|
233
|
+
return { status: "converged", officer, receipt };
|
|
252
234
|
}
|
|
253
|
-
if (
|
|
254
|
-
return { status, officer, receipt };
|
|
235
|
+
if (queueStatus === "continue" || queueStatus === "escalate") {
|
|
236
|
+
return { status: queueStatus, officer, receipt };
|
|
255
237
|
}
|
|
256
238
|
return { status: "needs_reask", officer, receipt };
|
|
257
239
|
}
|
|
@@ -316,8 +298,8 @@ function withOfficerRunId(
|
|
|
316
298
|
summoned: PublicSummonResult,
|
|
317
299
|
): GatekeeperResult {
|
|
318
300
|
if (
|
|
319
|
-
result.status !== "
|
|
320
|
-
&& result.status !== "
|
|
301
|
+
result.status !== "converged"
|
|
302
|
+
&& result.status !== "continue"
|
|
321
303
|
&& result.status !== "escalate"
|
|
322
304
|
&& result.status !== "needs_reask"
|
|
323
305
|
) {
|
|
@@ -409,28 +391,21 @@ export type GatekeeperProjection = {
|
|
|
409
391
|
};
|
|
410
392
|
|
|
411
393
|
/**
|
|
412
|
-
* Plain-language re-ask when the officer conclusion is not
|
|
394
|
+
* Plain-language re-ask when the officer conclusion is not converged|continue|escalate.
|
|
413
395
|
* Not a packaged engine handbook line (#755 exception for 读不出三态).
|
|
414
|
-
*
|
|
415
|
-
* field and words so reask does not invite non-contract values (#969 / ADR 0055).
|
|
396
|
+
* Every review officer uses the same `status` field and three queue words (#1028).
|
|
416
397
|
*/
|
|
417
398
|
export const OFFICER_CONCLUSION_REASK =
|
|
418
|
-
"
|
|
419
|
-
|
|
420
|
-
/** 给事中 re-ask: countersignStatus converged|continue|escalate only (#969). */
|
|
421
|
-
export const COUNTERSIGN_CONCLUSION_REASK =
|
|
422
|
-
"上次交卷的 countersignStatus 不是 converged、continue、escalate 三态之一。请重新输出,countersignStatus 写明其一;封驳或上呈的话就是给对方看的原文。" as const;
|
|
399
|
+
"上次交卷的 status 不是 converged、continue、escalate 三态之一。请重新输出,status 写明其一;continue 或 escalate 的话就是给对方看的原文。" as const;
|
|
423
400
|
|
|
424
401
|
/** Pick the re-ask line for the officer under review. */
|
|
425
402
|
export function officerConclusionReask(officer: GateOfficer): string {
|
|
426
|
-
return
|
|
427
|
-
? COUNTERSIGN_CONCLUSION_REASK
|
|
428
|
-
: OFFICER_CONCLUSION_REASK;
|
|
403
|
+
return OFFICER_CONCLUSION_REASK;
|
|
429
404
|
}
|
|
430
405
|
|
|
431
406
|
/**
|
|
432
407
|
* Summon (via injected seam) + project. Default summonGateOfficer drive lives
|
|
433
|
-
* on the shared envelope (`
|
|
408
|
+
* on the shared envelope (`submission-gate.ts`, ADR 0018 / #675) —
|
|
434
409
|
* role module keeps projection only.
|
|
435
410
|
*/
|
|
436
411
|
export async function projectGatekeeperRun(
|
package/src/host-contracts.ts
CHANGED
|
@@ -349,7 +349,7 @@ type HostGatekeeperSubject = {
|
|
|
349
349
|
| "secretariat_verdict";
|
|
350
350
|
};
|
|
351
351
|
/** Gatekeeper non-pass faces returned to parent (#836 includes transport_failure; never kill leg). */
|
|
352
|
-
type HostGatekeeperNonPass = { readonly status: "
|
|
352
|
+
type HostGatekeeperNonPass = { readonly status: "continue" | "escalate" | "no_receipt" | "transport_failure" } & Record<string, unknown>;
|
|
353
353
|
export type HostSubmissionNonPass =
|
|
354
354
|
| HostGatekeeperNonPass
|
|
355
355
|
| { readonly code: "coder_skill_expansion_evidence_missing" };
|
|
@@ -400,10 +400,10 @@ export interface RoleHost {
|
|
|
400
400
|
setActiveTools(names: string[]): void;
|
|
401
401
|
getActiveTools(): string[];
|
|
402
402
|
/**
|
|
403
|
-
* Shared gate
|
|
403
|
+
* Shared submission gate. On converged/continue returns officer snapshot (receipt + nested
|
|
404
404
|
* runId) for seat public-terminal projection (#969); callers may ignore it.
|
|
405
405
|
*/
|
|
406
|
-
|
|
406
|
+
requireSubmissionGate?(options: {
|
|
407
407
|
context: HostContext;
|
|
408
408
|
subject: HostGatekeeperSubject;
|
|
409
409
|
signal?: AbortSignal;
|
|
@@ -411,6 +411,7 @@ export interface RoleHost {
|
|
|
411
411
|
toolCallId: string;
|
|
412
412
|
submission?: unknown;
|
|
413
413
|
}): Promise<void | {
|
|
414
|
+
readonly status: "converged" | "continue";
|
|
414
415
|
readonly officer: string;
|
|
415
416
|
readonly receipt: unknown;
|
|
416
417
|
readonly runId?: string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public Inspector (台院) terminating receipt contracts.
|
|
3
|
-
* Lawful explicit releases:
|
|
3
|
+
* Lawful explicit releases: converged | continue | escalate.
|
|
4
4
|
* Dual path: gate-dispatched and independently callable (#568 / ADR 0074).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
8
|
+
|
|
9
|
+
export const INSPECTOR_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
|
|
8
10
|
export const INSPECTOR_SOURCE_RUN_FLAG = {
|
|
9
11
|
name: "ak-inspector-source-run",
|
|
10
12
|
definition: {
|
|
@@ -14,8 +16,8 @@ export const INSPECTOR_SOURCE_RUN_FLAG = {
|
|
|
14
16
|
} as const;
|
|
15
17
|
|
|
16
18
|
export type InspectorOutput =
|
|
17
|
-
| { readonly status: "
|
|
18
|
-
| { readonly status: "
|
|
19
|
+
| { readonly status: "converged"; readonly findings?: unknown }
|
|
20
|
+
| { readonly status: "continue"; readonly findings?: unknown }
|
|
19
21
|
| { readonly status: "escalate"; readonly reason?: unknown; readonly findings?: unknown };
|
|
20
22
|
|
|
21
23
|
export function validateRecordedInspectorOutput(value: unknown): InspectorOutput {
|
|
@@ -28,9 +30,8 @@ export function validateRecordedInspectorOutput(value: unknown): InspectorOutput
|
|
|
28
30
|
} catch {
|
|
29
31
|
throw new Error("Inspector output has no execution discriminator");
|
|
30
32
|
}
|
|
31
|
-
if (status === "
|
|
33
|
+
if (status === "converged" || status === "continue" || status === "escalate") {
|
|
32
34
|
return value as InspectorOutput;
|
|
33
35
|
}
|
|
34
36
|
throw new Error("Inspector output has no execution discriminator");
|
|
35
37
|
}
|
|
36
|
-
|