@akagilnc/pi-workflow-roles 0.1.2383 → 0.1.2402
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/audit-escalation.js +0 -13
- package/dist/compliance-transport.js +28 -5
- package/dist/doctor-contracts.js +2 -9
- package/dist/dossier-resolution.js +21 -2
- package/dist/engine-detour-tool.js +10 -35
- package/dist/evidence-child-executor.js +12 -4
- package/dist/merger-contracts.js +2 -8
- package/dist/navigator-attendance.js +1 -9
- package/dist/navigator-invocation-identity.js +16 -4
- package/dist/notary-contracts.js +7 -25
- package/dist/package-contracts/judge-output.js +1 -4
- package/dist/package-contracts/reviewer-output.js +1 -7
- package/dist/package-contracts/terminating-tools.js +6 -11
- package/dist/public-cli/main.js +125 -475
- package/package.json +1 -1
- package/scripts/build-package.mjs +1 -2
- package/src/analyst-gate-cycles-read.ts +5 -8
- package/src/analyst-metric-families/acceptance-success-rework.ts +3 -6
- package/src/audit-escalation.ts +0 -25
- package/src/collector-role.ts +1 -8
- package/src/compliance-transport.ts +38 -6
- package/src/doctor-auditor.ts +3 -3
- package/src/doctor-contracts.ts +3 -18
- package/src/doctor-role.ts +1 -5
- package/src/dossier-resolution.ts +25 -4
- package/src/engine-detour-tool.ts +16 -51
- package/src/evidence-child-executor.ts +12 -4
- package/src/factory-board.ts +2 -4
- package/src/gatekeeper-role.ts +33 -43
- package/src/judge-auditor.ts +3 -3
- package/src/judge-role.ts +1 -8
- package/src/merger-contracts.ts +3 -17
- package/src/merger-role.ts +1 -8
- package/src/navigator-attendance.ts +1 -14
- package/src/navigator-invocation-identity.ts +34 -13
- package/src/notary-contracts.ts +9 -43
- package/src/notary-role.ts +3 -5
- package/src/package-contracts/judge-output.ts +2 -17
- package/src/package-contracts/reviewer-output.ts +2 -21
- package/src/package-contracts/terminating-tools.ts +5 -14
- package/src/package-contracts/worker-output.ts +2 -10
- package/src/public-cli/doctor-run.ts +0 -2
- package/src/public-cli/fixer-run.ts +0 -15
- package/src/public-cli/judge-run.ts +0 -16
- package/src/public-cli/notary-run.ts +1 -1
- package/src/public-cli/one-shot-dispatch.ts +1 -1
- package/src/public-cli/reviewer-run.ts +0 -15
- package/src/public-cli/settlement.ts +110 -368
- package/src/public-cli/terminal.ts +3 -71
- package/src/reviewer-auditor.ts +3 -3
- package/src/reviewer-role.ts +2 -11
- package/src/role-runtime.ts +43 -37
- package/src/worker-role.ts +2 -14
- package/dist/engine-labor-fallback.js +0 -136
- package/src/engine-labor-fallback.ts +0 -270
package/src/gatekeeper-role.ts
CHANGED
|
@@ -24,18 +24,18 @@ export type GatekeeperResult =
|
|
|
24
24
|
readonly findings: readonly string[];
|
|
25
25
|
readonly submission: unknown;
|
|
26
26
|
}
|
|
27
|
+
| { readonly status: "no_receipt"; readonly stage: "gatekeeper" | "inspector" | "notary"; readonly reason: string; readonly facts: NoReceiptLifecycleFacts }
|
|
27
28
|
| {
|
|
28
|
-
readonly status: "
|
|
29
|
+
readonly status: "transport_failure";
|
|
29
30
|
readonly stage: "gatekeeper" | "inspector" | "notary";
|
|
30
31
|
readonly reason: string;
|
|
32
|
+
/** Original unusable submission retained for the failure channel. */
|
|
31
33
|
readonly submission?: unknown;
|
|
32
|
-
}
|
|
33
|
-
| { readonly status: "no_receipt"; readonly stage: "gatekeeper" | "inspector" | "notary"; readonly reason: string; readonly facts: NoReceiptLifecycleFacts }
|
|
34
|
-
| { readonly status: "transport_failure"; readonly stage: "gatekeeper" | "inspector" | "notary"; readonly reason: string };
|
|
34
|
+
};
|
|
35
35
|
|
|
36
36
|
export type GatekeeperNonPassResult = Extract<
|
|
37
37
|
GatekeeperResult,
|
|
38
|
-
{ status: "bounce" | "
|
|
38
|
+
{ status: "bounce" | "no_receipt" }
|
|
39
39
|
>;
|
|
40
40
|
|
|
41
41
|
function nonPassMessage(result: GatekeeperNonPassResult): string {
|
|
@@ -74,15 +74,13 @@ export type GatekeeperPassHostActions = {
|
|
|
74
74
|
// Unknown fields so wrong types/spellings still reach projection (ADR 0055/0057; 仓第 0 条).
|
|
75
75
|
// Opening goes through the sole openToolObject owner — no parallel transport helper.
|
|
76
76
|
const officerDecisionSchema = openToolObject(Type.Object({
|
|
77
|
-
status: Type.Unknown({ description: "pass | bounce
|
|
77
|
+
status: Type.Unknown({ description: "pass | bounce — guidance, not a schema gate." }),
|
|
78
78
|
findings: Type.Unknown({ description: "string[] findings retained with pass or bounce." }),
|
|
79
|
-
reason: Type.Unknown({ description: "Why the officer decision is incomplete." }),
|
|
80
79
|
}));
|
|
81
80
|
|
|
82
81
|
const gatekeeperDecisionSchema = openToolObject(Type.Object({
|
|
83
|
-
status: Type.Unknown({ description: "dispatch
|
|
82
|
+
status: Type.Unknown({ description: "dispatch — guidance, not a schema gate." }),
|
|
84
83
|
officer: Type.Unknown({ description: "inspector | notary when status is dispatch." }),
|
|
85
|
-
reason: Type.Unknown({ description: "Why Gatekeeper dispatch is incomplete." }),
|
|
86
84
|
}));
|
|
87
85
|
|
|
88
86
|
const INVOCATION_OVERLAY = "取证工具不受白名单限制;若取证产生临时副作用,取证结束后须自行恢复。";
|
|
@@ -104,7 +102,7 @@ function subjectTool(subject: GatekeeperSubject): AuditorDecisionTool {
|
|
|
104
102
|
export function createOfficerDecisionTool(name: string): AuditorDecisionTool {
|
|
105
103
|
return {
|
|
106
104
|
name,
|
|
107
|
-
description: "Submit one typed pass
|
|
105
|
+
description: "Submit one typed pass or bounce decision.",
|
|
108
106
|
parameters: officerDecisionSchema,
|
|
109
107
|
async execute(_id, args) { return result(`accepted ${String((args as { status?: unknown })?.status)}`, args); },
|
|
110
108
|
};
|
|
@@ -114,7 +112,7 @@ export function createOfficerDecisionTool(name: string): AuditorDecisionTool {
|
|
|
114
112
|
export function createGatekeeperOutputTool(): AuditorDecisionTool {
|
|
115
113
|
return {
|
|
116
114
|
name: GATEKEEPER_OUTPUT_TOOL,
|
|
117
|
-
description: "Dispatch the admitted subject to one officer
|
|
115
|
+
description: "Dispatch the admitted subject to one officer.",
|
|
118
116
|
parameters: gatekeeperDecisionSchema,
|
|
119
117
|
async execute(_id, args) { return result(`accepted ${String((args as { status?: unknown })?.status)}`, args); },
|
|
120
118
|
};
|
|
@@ -143,15 +141,18 @@ function retainedSubmission(decision: unknown): unknown {
|
|
|
143
141
|
return decision === undefined ? MISSING_ARGUMENTS_SUBMISSION : decision;
|
|
144
142
|
}
|
|
145
143
|
|
|
146
|
-
/**
|
|
147
|
-
|
|
144
|
+
/**
|
|
145
|
+
* No usable explicit release is infrastructure failure, not a judgment status.
|
|
146
|
+
* Original submission is retained for the failure channel (#475).
|
|
147
|
+
*/
|
|
148
|
+
function noUsableReleaseFailure(
|
|
148
149
|
stage: "gatekeeper" | "inspector" | "notary",
|
|
149
150
|
decision: unknown,
|
|
150
|
-
): Extract<GatekeeperResult, { status: "
|
|
151
|
+
): Extract<GatekeeperResult, { status: "transport_failure" }> {
|
|
151
152
|
return {
|
|
152
|
-
status: "
|
|
153
|
+
status: "transport_failure",
|
|
153
154
|
stage,
|
|
154
|
-
reason: stage === "gatekeeper" ? "decision 无显式 dispatch" : "decision 无显式 pass",
|
|
155
|
+
reason: stage === "gatekeeper" ? "decision 无显式 dispatch" : "decision 无显式 pass/bounce",
|
|
155
156
|
submission: retainedSubmission(decision),
|
|
156
157
|
};
|
|
157
158
|
}
|
|
@@ -163,19 +164,11 @@ function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
163
164
|
|
|
164
165
|
function projectProvinceDecision(decision: unknown): GatekeeperResult | { status: "dispatch"; officer: "inspector" | "notary" } {
|
|
165
166
|
const record = readRecord(decision);
|
|
166
|
-
if (record === undefined) return
|
|
167
|
-
if (record.status === "incomplete") {
|
|
168
|
-
const reason = record.reason;
|
|
169
|
-
if (typeof reason === "string" && reason.trim() !== "") {
|
|
170
|
-
// Role's own incomplete reason is kept as-is; machine does not rewrite it.
|
|
171
|
-
return { status: "incomplete", stage: "gatekeeper", reason, submission: retainedSubmission(decision) };
|
|
172
|
-
}
|
|
173
|
-
return noExplicitReleaseIncomplete("gatekeeper", decision);
|
|
174
|
-
}
|
|
167
|
+
if (record === undefined) return noUsableReleaseFailure("gatekeeper", decision);
|
|
175
168
|
if (record.status === "dispatch" && (record.officer === "inspector" || record.officer === "notary")) {
|
|
176
169
|
return { status: "dispatch", officer: record.officer };
|
|
177
170
|
}
|
|
178
|
-
return
|
|
171
|
+
return noUsableReleaseFailure("gatekeeper", decision);
|
|
179
172
|
}
|
|
180
173
|
|
|
181
174
|
function projectOfficerDecision(
|
|
@@ -183,15 +176,7 @@ function projectOfficerDecision(
|
|
|
183
176
|
decision: unknown,
|
|
184
177
|
): GatekeeperResult {
|
|
185
178
|
const record = readRecord(decision);
|
|
186
|
-
if (record === undefined) return
|
|
187
|
-
if (record.status === "incomplete") {
|
|
188
|
-
const reason = record.reason;
|
|
189
|
-
if (typeof reason === "string" && reason.trim() !== "") {
|
|
190
|
-
// Role's own incomplete reason is kept as-is; machine does not rewrite it.
|
|
191
|
-
return { status: "incomplete", stage: officer, reason, submission: retainedSubmission(decision) };
|
|
192
|
-
}
|
|
193
|
-
return noExplicitReleaseIncomplete(officer, decision);
|
|
194
|
-
}
|
|
179
|
+
if (record === undefined) return noUsableReleaseFailure(officer, decision);
|
|
195
180
|
if (record.status === "bounce") {
|
|
196
181
|
return {
|
|
197
182
|
status: "bounce",
|
|
@@ -208,7 +193,7 @@ function projectOfficerDecision(
|
|
|
208
193
|
findings: asStringArray(record.findings),
|
|
209
194
|
};
|
|
210
195
|
}
|
|
211
|
-
return
|
|
196
|
+
return noUsableReleaseFailure(officer, decision);
|
|
212
197
|
}
|
|
213
198
|
|
|
214
199
|
export async function runGatekeeper(options: RunGatekeeperOptions): Promise<GatekeeperResult> {
|
|
@@ -221,7 +206,7 @@ export async function runGatekeeper(options: RunGatekeeperOptions): Promise<Gate
|
|
|
221
206
|
roleLabel: "Gatekeeper",
|
|
222
207
|
menxiaSeat: "gatekeeper",
|
|
223
208
|
systemPrompt: `${await loadSoul("gatekeeper")}\n\n${INVOCATION_OVERLAY}`,
|
|
224
|
-
prompt: "Read the admitted subject with ak_gatekeeper_subject, then dispatch it
|
|
209
|
+
prompt: "Read the admitted subject with ak_gatekeeper_subject, then dispatch it to one officer.",
|
|
225
210
|
tool: createGatekeeperOutputTool(),
|
|
226
211
|
dossierTool: subjectTool(options.subject),
|
|
227
212
|
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
@@ -259,7 +244,7 @@ export async function runGatekeeper(options: RunGatekeeperOptions): Promise<Gate
|
|
|
259
244
|
}
|
|
260
245
|
}
|
|
261
246
|
|
|
262
|
-
/** Project GatekeeperResult onto a submit path: transport→failInfrastructure; bounce/
|
|
247
|
+
/** Project GatekeeperResult onto a submit path: transport→failInfrastructure; bounce/no_receipt→typed throw; pass silent. */
|
|
263
248
|
export async function requireGatekeeperPass(options: {
|
|
264
249
|
readonly context: ExtensionContext;
|
|
265
250
|
readonly subject: GatekeeperSubject;
|
|
@@ -274,11 +259,16 @@ export async function requireGatekeeperPass(options: {
|
|
|
274
259
|
});
|
|
275
260
|
if (gatekeeper.status === "pass") return;
|
|
276
261
|
if (gatekeeper.status === "transport_failure") {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
262
|
+
// Typed stage/reason/submission ride failInfrastructure → durable tool_result (#475).
|
|
263
|
+
const error = new Error(`Gatekeeper transport failure at ${gatekeeper.stage}: ${gatekeeper.reason}`) as Error & {
|
|
264
|
+
stage: typeof gatekeeper.stage;
|
|
265
|
+
reason: string;
|
|
266
|
+
submission?: unknown;
|
|
267
|
+
};
|
|
268
|
+
error.stage = gatekeeper.stage;
|
|
269
|
+
error.reason = gatekeeper.reason;
|
|
270
|
+
if (gatekeeper.submission !== undefined) error.submission = gatekeeper.submission;
|
|
271
|
+
options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
|
|
282
272
|
}
|
|
283
273
|
// Envelope owns the execute→tool_result bridge; this module only projects + throws.
|
|
284
274
|
options.hostActions.bindGatekeeperNonPass(options.toolCallId, gatekeeper);
|
package/src/judge-auditor.ts
CHANGED
|
@@ -10,8 +10,8 @@ import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
|
10
10
|
import { loadAuditorSoul } from "./auditor-soul.ts";
|
|
11
11
|
import {
|
|
12
12
|
readJudgeAuditSubjects,
|
|
13
|
+
requireAuditMaterials,
|
|
13
14
|
resolveAuditDossier,
|
|
14
|
-
toAuditIncomplete,
|
|
15
15
|
} from "./dossier-resolution.ts";
|
|
16
16
|
|
|
17
17
|
export const JUDGE_AUDIT_TOOL_NAME = "ak_soul_audit_decision";
|
|
@@ -37,9 +37,9 @@ export function createPiJudgeAuditor(
|
|
|
37
37
|
): (options: JudgeAuditOptions) => Promise<ComplianceDecision> {
|
|
38
38
|
return async (options) => {
|
|
39
39
|
const dossier = resolveAuditDossier();
|
|
40
|
-
|
|
40
|
+
requireAuditMaterials(dossier);
|
|
41
41
|
const subjects = readJudgeAuditSubjects(options.context);
|
|
42
|
-
|
|
42
|
+
requireAuditMaterials(subjects);
|
|
43
43
|
|
|
44
44
|
return runComplianceAudit({
|
|
45
45
|
tool: auditDecisionTool,
|
package/src/judge-role.ts
CHANGED
|
@@ -8,10 +8,6 @@ import { Type, type Static } from "typebox";
|
|
|
8
8
|
|
|
9
9
|
import { disposeComplianceDecision } from "./audit-escalation.ts";
|
|
10
10
|
import type { ComplianceDecision } from "./compliance-transport.ts";
|
|
11
|
-
import {
|
|
12
|
-
readActivationEngineLaborFallbackField,
|
|
13
|
-
withEngineLaborFallbackField,
|
|
14
|
-
} from "./engine-labor-fallback.ts";
|
|
15
11
|
import { requireGatekeeperPass, type GatekeeperPassHostActions } from "./gatekeeper-role.ts";
|
|
16
12
|
|
|
17
13
|
import {
|
|
@@ -137,9 +133,7 @@ export function createJudgeRoleRuntime(
|
|
|
137
133
|
} catch (error) {
|
|
138
134
|
hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
139
135
|
}
|
|
140
|
-
|
|
141
|
-
const laborFallback = readActivationEngineLaborFallbackField();
|
|
142
|
-
const acceptedDetails = withEngineLaborFallbackField(verdict, laborFallback);
|
|
136
|
+
const acceptedDetails = verdict;
|
|
143
137
|
return disposeComplianceDecision<AgentToolResult<unknown>>(
|
|
144
138
|
audit,
|
|
145
139
|
{
|
|
@@ -161,7 +155,6 @@ export function createJudgeRoleRuntime(
|
|
|
161
155
|
);
|
|
162
156
|
},
|
|
163
157
|
escalate: (result) => result,
|
|
164
|
-
auditIncomplete: (result) => result,
|
|
165
158
|
},
|
|
166
159
|
// #380: escalate deliveredOutput must carry the same mechanical projection.
|
|
167
160
|
acceptedDetails,
|
package/src/merger-contracts.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
2
|
import { isFullGitObjectId } from "./git-object-id.ts";
|
|
3
3
|
import { exactUtf8 } from "./exact-utf8.ts";
|
|
4
|
-
import {
|
|
5
|
-
seatFallbackBaseStatus,
|
|
6
|
-
seatFallbackStatusHasLawfulEvidence,
|
|
7
|
-
type WithEngineLaborFallback,
|
|
8
|
-
} from "./engine-labor-fallback.ts";
|
|
9
4
|
import { sha256Hex } from "./sha256.ts";
|
|
10
5
|
import { openToolObjectFromUnion } from "./open-tool-schema.ts";
|
|
11
6
|
|
|
@@ -29,13 +24,9 @@ export const mergerOutputSchema = openToolObjectFromUnion(mergerOutputVariants);
|
|
|
29
24
|
export type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends readonly (infer U)[] ? readonly DeepReadonly<U>[] : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
|
|
30
25
|
export type MergerMaterial = DeepReadonly<Static<typeof materialSchema>>;
|
|
31
26
|
export type MergerInput = DeepReadonly<Static<typeof mergerInputSchema>>;
|
|
32
|
-
type
|
|
27
|
+
export type MergerOutput =
|
|
33
28
|
| { status: "completed"; attemptId: string; report: string; mergeCommitId: string }
|
|
34
29
|
| { status: "escalate"; attemptId: string; diagnosis: string; report: string };
|
|
35
|
-
/** Clean submission shape or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
36
|
-
export type MergerOutput =
|
|
37
|
-
| MergerOutputClean
|
|
38
|
-
| WithEngineLaborFallback<MergerOutputClean>;
|
|
39
30
|
export const MERGER_OUTPUT_TOOL_NAME = "ak_merger_output";
|
|
40
31
|
export const MERGER_ACCEPTED_TEXT = "Merger output accepted";
|
|
41
32
|
|
|
@@ -75,12 +66,7 @@ export function validateMergerInput(value: unknown): MergerInput {
|
|
|
75
66
|
export function validateMergerOutput(value: unknown, expectedAttemptId?: string): MergerOutput {
|
|
76
67
|
if (!record(value) || (expectedAttemptId !== undefined && value.attemptId !== expectedAttemptId)) throw new Error("Merger output attempt mismatch");
|
|
77
68
|
const status = typeof value.status === "string" ? value.status : undefined;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (status !== undefined && !seatFallbackStatusHasLawfulEvidence(status, value)) {
|
|
81
|
-
throw new Error("Merger output has no recognized execution discriminator");
|
|
82
|
-
}
|
|
83
|
-
if (statusBase === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value) as MergerOutput;
|
|
84
|
-
if (statusBase === "escalate") return structuredClone(value) as MergerOutput;
|
|
69
|
+
if (status === "completed" && isFullGitObjectId(value.mergeCommitId)) return structuredClone(value) as MergerOutput;
|
|
70
|
+
if (status === "escalate") return structuredClone(value) as MergerOutput;
|
|
85
71
|
throw new Error("Merger output has no recognized execution discriminator");
|
|
86
72
|
}
|
package/src/merger-role.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
readActivationEngineLaborFallbackField,
|
|
4
|
-
withEngineLaborFallbackField,
|
|
5
|
-
} from "./engine-labor-fallback.ts";
|
|
6
2
|
import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, mergerOutputSchema, validateMergerInput, validateMergerOutput, type MergerInput } from "./merger-contracts.ts";
|
|
7
3
|
import type { MergerGitState } from "./merger-git-state.ts";
|
|
8
4
|
import { exactUtf8 } from "./exact-utf8.ts";
|
|
@@ -51,10 +47,7 @@ export function createMergerRoleRuntime(pi: ExtensionAPI, dependencies: MergerRo
|
|
|
51
47
|
if (state.mergeCommitId !== output.mergeCommitId || !same(state.parentObjectIds, [activation.input.targetObjectId, activation.input.sourceObjectId]) || state.unmergedPaths.length !== 0 || !state.worktreeClean || state.resolutionChangedPaths.some(path => !scope.has(path))) host.failInfrastructure(new Error("Merger completed-state verification failed"), ctx, id);
|
|
52
48
|
}
|
|
53
49
|
accepted = true;
|
|
54
|
-
const acceptedDetails =
|
|
55
|
-
output,
|
|
56
|
-
readActivationEngineLaborFallbackField(),
|
|
57
|
-
);
|
|
50
|
+
const acceptedDetails = output;
|
|
58
51
|
return { content: [{ type: "text" as const, text: MERGER_ACCEPTED_TEXT }], details: acceptedDetails, terminate: true as const };
|
|
59
52
|
} });
|
|
60
53
|
pi.on("before_agent_start", event => { if (!activation) throw new Error("Merger is not activated"); const admitted = { attemptId: activation.input.attemptId, targetObjectId: activation.input.targetObjectId, sourceObjectId: activation.input.sourceObjectId, task: materialText(activation.input, "task"), authority: materialText(activation.input, "authority"), targetIntent: materialText(activation.input, "targetIntent"), sourceIntent: materialText(activation.input, "sourceIntent"), expectedConflictPaths: activation.input.expectedConflictPaths, resolutionScope: activation.input.resolutionScope, authorizedChecks: activation.input.authorizedChecks }; return { systemPrompt: `${event.systemPrompt}\n\n<merger_soul>\n${activation.soul}\n</merger_soul>\n\n<merger_assignment>\n${JSON.stringify(admitted)}\n</merger_assignment>` }; });
|
|
@@ -8,7 +8,6 @@ import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
|
|
|
8
8
|
import { Type, type Static } from "typebox";
|
|
9
9
|
import { Value } from "typebox/value";
|
|
10
10
|
|
|
11
|
-
import { seatFallbackBaseStatus } from "./engine-labor-fallback.ts";
|
|
12
11
|
import {
|
|
13
12
|
NAVIGATOR_INVOCATION_ENTRY,
|
|
14
13
|
mintNavigatorInvocationId,
|
|
@@ -462,18 +461,6 @@ export type NavigatorCandidateSelection = {
|
|
|
462
461
|
readonly matchedToSettlement: boolean;
|
|
463
462
|
};
|
|
464
463
|
|
|
465
|
-
/** Match route-playbook statuses against settlement, treating seat-fallback taint as base-equivalent. */
|
|
466
|
-
function statusListMatchesSettlement(
|
|
467
|
-
candidateStatuses: readonly string[],
|
|
468
|
-
settlementStatus: string,
|
|
469
|
-
): boolean {
|
|
470
|
-
if (candidateStatuses.includes(settlementStatus)) return true;
|
|
471
|
-
const settlementBase = seatFallbackBaseStatus(settlementStatus);
|
|
472
|
-
return candidateStatuses.some(
|
|
473
|
-
(status) => seatFallbackBaseStatus(status) === settlementBase,
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
464
|
export function selectNavigatorCandidate(
|
|
478
465
|
candidates: readonly NavigatorCandidate[],
|
|
479
466
|
settlement: NavigatorSettlement,
|
|
@@ -492,7 +479,7 @@ export function selectNavigatorCandidate(
|
|
|
492
479
|
const statusSpecific = rolePhaseMatched.find(
|
|
493
480
|
(candidate) =>
|
|
494
481
|
candidate.matches?.statuses !== undefined &&
|
|
495
|
-
|
|
482
|
+
candidate.matches.statuses.includes(settlement.status!),
|
|
496
483
|
);
|
|
497
484
|
if (statusSpecific !== undefined) {
|
|
498
485
|
return { candidate: statusSpecific, matchedToSettlement: true };
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* One shared typed terminal classifier owns durable completion for lifecycle,
|
|
16
16
|
* publicNavigatorSettlement, and every public CLI Receipt extractor:
|
|
17
17
|
* - accepted/human: isError exactly false and no infrastructure-failure fact
|
|
18
|
-
* - infrastructure: isError exactly true plus
|
|
18
|
+
* - infrastructure: isError exactly true plus base kind/source/reasonCode identity
|
|
19
19
|
* - retryable/missing/nonboolean/contradictory/malformed: nonterminal
|
|
20
20
|
*
|
|
21
21
|
* One truth table also owns marker↔terminal cardinality:
|
|
@@ -33,13 +33,25 @@ export const NAVIGATOR_INVOCATION_ENTRY = "ak-navigator-invocation" as const;
|
|
|
33
33
|
/** Typed durable infrastructure-failure fact on a packaged role output toolResult. */
|
|
34
34
|
export const NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND = "role_infrastructure_failure" as const;
|
|
35
35
|
|
|
36
|
-
/**
|
|
36
|
+
/** Required base identity keys for infrastructure-failure recognition. */
|
|
37
37
|
const NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS = [
|
|
38
38
|
"kind",
|
|
39
39
|
"source",
|
|
40
40
|
"reasonCode",
|
|
41
41
|
] as const;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Known typed failure evidence keys projected onto durable infrastructure details (#475).
|
|
45
|
+
* Extraction whitelist only — classification does not reject unknown extras (ADR 0040).
|
|
46
|
+
*/
|
|
47
|
+
export const NAVIGATOR_INFRASTRUCTURE_FAILURE_EVIDENCE_KEYS = [
|
|
48
|
+
"observation",
|
|
49
|
+
"candidate",
|
|
50
|
+
"submission",
|
|
51
|
+
"stage",
|
|
52
|
+
"reason",
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
43
55
|
export type NavigatorInfrastructureFailureFact = {
|
|
44
56
|
kind: typeof NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND;
|
|
45
57
|
source: "shared-role-lifecycle";
|
|
@@ -55,16 +67,13 @@ export function buildNavigatorInfrastructureFailureFact(): NavigatorInfrastructu
|
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
70
|
+
* Base infrastructure-failure identity on durable details.
|
|
71
|
+
* Only kind/source/reasonCode discriminate; extra fields are allowed and retained
|
|
72
|
+
* (ADR 0040 — discriminators select the branch, they do not ban extras).
|
|
60
73
|
*/
|
|
61
|
-
export function
|
|
62
|
-
value: unknown,
|
|
63
|
-
): value is NavigatorInfrastructureFailureFact {
|
|
74
|
+
export function hasNavigatorInfrastructureFailureBase(value: unknown): boolean {
|
|
64
75
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
65
76
|
const record = value as Record<string, unknown>;
|
|
66
|
-
const keys = Object.keys(record);
|
|
67
|
-
if (keys.length !== NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS.length) return false;
|
|
68
77
|
for (const key of NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS) {
|
|
69
78
|
if (!Object.hasOwn(record, key)) return false;
|
|
70
79
|
}
|
|
@@ -75,6 +84,18 @@ export function isNavigatorInfrastructureFailureFact(
|
|
|
75
84
|
);
|
|
76
85
|
}
|
|
77
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Exact closed infrastructure-failure fact (no evidence extensions).
|
|
89
|
+
* Classifier uses {@link hasNavigatorInfrastructureFailureBase} so enriched
|
|
90
|
+
* durable details still complete as infrastructure (#475).
|
|
91
|
+
*/
|
|
92
|
+
export function isNavigatorInfrastructureFailureFact(
|
|
93
|
+
value: unknown,
|
|
94
|
+
): value is NavigatorInfrastructureFailureFact {
|
|
95
|
+
if (!hasNavigatorInfrastructureFailureBase(value)) return false;
|
|
96
|
+
return Object.keys(value as object).length === NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS.length;
|
|
97
|
+
}
|
|
98
|
+
|
|
78
99
|
const PACKAGED_ROLE_OUTPUT_TOOLS: ReadonlyMap<string, string> = new Map(
|
|
79
100
|
PACKAGED_ROLE_REGISTRY.map((entry) => [entry.outputTool, entry.role]),
|
|
80
101
|
);
|
|
@@ -191,11 +212,11 @@ export function classifyPackagedRoleTerminalResult(
|
|
|
191
212
|
if (typeof message.toolName !== "string") return { kind: "nonterminal" };
|
|
192
213
|
if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName)) return { kind: "nonterminal" };
|
|
193
214
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
// Base identity is enough; durable details may carry typed failure evidence (#475).
|
|
216
|
+
const hasInfraBase = hasNavigatorInfrastructureFailureBase(message.details);
|
|
217
|
+
const infraFact = hasInfraBase ? buildNavigatorInfrastructureFailureFact() : undefined;
|
|
197
218
|
|
|
198
|
-
// Infrastructure completion: exact isError === true +
|
|
219
|
+
// Infrastructure completion: exact isError === true + infra base identity.
|
|
199
220
|
if (message.isError === true) {
|
|
200
221
|
if (infraFact === undefined) return { kind: "nonterminal" };
|
|
201
222
|
return { kind: "infrastructure", fact: infraFact };
|
package/src/notary-contracts.ts
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public Notary (符宝郎) terminating receipt contracts.
|
|
3
|
-
* Lawful explicit releases: pass | bounce
|
|
4
|
-
*
|
|
3
|
+
* Lawful explicit releases: pass | bounce.
|
|
4
|
+
* No usable result is infrastructure failure via public settlement, not a judgment status (#475).
|
|
5
5
|
*/
|
|
6
6
|
import { Type } from "typebox";
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
readActivationEngineLaborFallbackField,
|
|
10
|
-
seatFallbackBaseStatus,
|
|
11
|
-
seatFallbackStatusHasLawfulEvidence,
|
|
12
|
-
withEngineLaborFallbackField,
|
|
13
|
-
type WithEngineLaborFallback,
|
|
14
|
-
} from "./engine-labor-fallback.ts";
|
|
15
8
|
import { openToolObject } from "./open-tool-schema.ts";
|
|
16
9
|
|
|
17
10
|
export const NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
|
|
@@ -31,14 +24,11 @@ export const NOTARY_FIXED_KICKOFF =
|
|
|
31
24
|
export const notaryOutputSchema = openToolObject(
|
|
32
25
|
Type.Object({
|
|
33
26
|
status: Type.Unknown({
|
|
34
|
-
description: "pass | bounce
|
|
27
|
+
description: "pass | bounce — guidance, not a schema gate.",
|
|
35
28
|
}),
|
|
36
29
|
findings: Type.Unknown({
|
|
37
30
|
description: "string[] findings retained with pass or bounce.",
|
|
38
31
|
}),
|
|
39
|
-
reason: Type.Unknown({
|
|
40
|
-
description: "Why the notary decision is incomplete.",
|
|
41
|
-
}),
|
|
42
32
|
}),
|
|
43
33
|
);
|
|
44
34
|
|
|
@@ -48,19 +38,13 @@ export type NotarySourceRunLocator = {
|
|
|
48
38
|
readonly role: string;
|
|
49
39
|
};
|
|
50
40
|
|
|
51
|
-
type
|
|
41
|
+
export type NotaryOutput =
|
|
52
42
|
| { readonly status: "pass"; readonly findings: readonly string[] }
|
|
53
43
|
| {
|
|
54
44
|
readonly status: "bounce";
|
|
55
45
|
readonly disposition: "rewrite";
|
|
56
46
|
readonly findings: readonly string[];
|
|
57
|
-
}
|
|
58
|
-
| { readonly status: "incomplete"; readonly reason: string };
|
|
59
|
-
|
|
60
|
-
/** Clean submission or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
61
|
-
export type NotaryOutput =
|
|
62
|
-
| NotaryOutputClean
|
|
63
|
-
| WithEngineLaborFallback<NotaryOutputClean>;
|
|
47
|
+
};
|
|
64
48
|
|
|
65
49
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
66
50
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -73,24 +57,17 @@ function asStringArray(value: unknown): readonly string[] {
|
|
|
73
57
|
|
|
74
58
|
/**
|
|
75
59
|
* Project one explicit Notary release. Throws when there is no lawful explicit
|
|
76
|
-
* pass / bounce
|
|
60
|
+
* pass / bounce — callers map that to the existing non-zero failure channel.
|
|
77
61
|
*/
|
|
78
62
|
export function validateNotaryOutput(value: unknown): NotaryOutput {
|
|
79
63
|
if (!isRecord(value)) {
|
|
80
64
|
throw new Error("Notary output has no recognized execution discriminator");
|
|
81
65
|
}
|
|
82
66
|
const statusRaw = typeof value.status === "string" ? value.status : undefined;
|
|
83
|
-
if (statusRaw === undefined
|
|
67
|
+
if (statusRaw === undefined) {
|
|
84
68
|
throw new Error("Notary output has no recognized execution discriminator");
|
|
85
69
|
}
|
|
86
|
-
const status =
|
|
87
|
-
if (status === "incomplete") {
|
|
88
|
-
const reason = value.reason;
|
|
89
|
-
if (typeof reason !== "string" || reason.trim() === "") {
|
|
90
|
-
throw new Error("Notary incomplete requires a non-empty reason");
|
|
91
|
-
}
|
|
92
|
-
return structuredClone(value) as NotaryOutput;
|
|
93
|
-
}
|
|
70
|
+
const status = statusRaw;
|
|
94
71
|
if (status === "bounce") {
|
|
95
72
|
const clone = structuredClone(value) as Record<string, unknown>;
|
|
96
73
|
if (clone.disposition === undefined) clone.disposition = "rewrite";
|
|
@@ -105,20 +82,12 @@ export function validateNotaryOutput(value: unknown): NotaryOutput {
|
|
|
105
82
|
throw new Error("Notary output has no recognized execution discriminator");
|
|
106
83
|
}
|
|
107
84
|
|
|
108
|
-
/** Shape check for recorded accepted details (may include engineLaborFallback). */
|
|
109
85
|
export function validateRecordedNotaryOutput(value: unknown): NotaryOutput {
|
|
110
86
|
return validateNotaryOutput(value);
|
|
111
87
|
}
|
|
112
88
|
|
|
113
|
-
export function withNotaryAcceptedDetails(output: NotaryOutput): NotaryOutput {
|
|
114
|
-
return withEngineLaborFallbackField(
|
|
115
|
-
output,
|
|
116
|
-
readActivationEngineLaborFallbackField(),
|
|
117
|
-
) as NotaryOutput;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
89
|
export function notaryDecisiveFacts(output: NotaryOutput): Record<string, unknown> {
|
|
121
|
-
const status =
|
|
90
|
+
const status = String(output.status);
|
|
122
91
|
const facts: Record<string, unknown> = { status, officer: "notary" };
|
|
123
92
|
if (status === "pass" || status === "bounce") {
|
|
124
93
|
const findings = (output as { findings?: unknown }).findings;
|
|
@@ -127,8 +96,5 @@ export function notaryDecisiveFacts(output: NotaryOutput): Record<string, unknow
|
|
|
127
96
|
if (status === "bounce") {
|
|
128
97
|
facts.disposition = "rewrite";
|
|
129
98
|
}
|
|
130
|
-
if (status === "incomplete") {
|
|
131
|
-
facts.reason = (output as { reason: string }).reason;
|
|
132
|
-
}
|
|
133
99
|
return facts;
|
|
134
100
|
}
|
package/src/notary-role.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
NOTARY_SOURCE_RUN_FLAG,
|
|
15
15
|
notaryOutputSchema,
|
|
16
16
|
validateNotaryOutput,
|
|
17
|
-
withNotaryAcceptedDetails,
|
|
18
17
|
type NotarySourceRunLocator,
|
|
19
18
|
} from "./notary-contracts.ts";
|
|
20
19
|
|
|
@@ -86,7 +85,7 @@ export function createNotaryRoleRuntime(
|
|
|
86
85
|
name: NOTARY_OUTPUT_TOOL_NAME,
|
|
87
86
|
label: "Notary Output",
|
|
88
87
|
description:
|
|
89
|
-
"Submit one typed pass
|
|
88
|
+
"Submit one typed pass or bounce decision on quote fidelity and ticket alignment.",
|
|
90
89
|
promptSnippet: "Submit the Notary decision",
|
|
91
90
|
promptGuidelines: [
|
|
92
91
|
`Use ${NOTARY_OUTPUT_TOOL_NAME} as the sole final action.`,
|
|
@@ -109,15 +108,14 @@ export function createNotaryRoleRuntime(
|
|
|
109
108
|
output = validateNotaryOutput(parameters);
|
|
110
109
|
} catch (error) {
|
|
111
110
|
// Non-explicit release stays a rejected terminating call so public
|
|
112
|
-
// settlement can
|
|
111
|
+
// settlement can map it to the existing non-zero failure channel (#475).
|
|
113
112
|
throw error instanceof Error
|
|
114
113
|
? error
|
|
115
114
|
: new Error(String(error));
|
|
116
115
|
}
|
|
117
|
-
const acceptedDetails = withNotaryAcceptedDetails(output);
|
|
118
116
|
return {
|
|
119
117
|
content: [{ type: "text" as const, text: NOTARY_ACCEPTED_TEXT }],
|
|
120
|
-
details:
|
|
118
|
+
details: output,
|
|
121
119
|
terminate: true as const,
|
|
122
120
|
};
|
|
123
121
|
},
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
/** Package-owned Judge output leaf — no role registration surface. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
seatFallbackBaseStatus,
|
|
5
|
-
seatFallbackStatusHasLawfulEvidence,
|
|
6
|
-
type SeatFallbackTaintedStatus,
|
|
7
|
-
type WithEngineLaborFallback,
|
|
8
|
-
} from "../engine-labor-fallback.ts";
|
|
9
3
|
|
|
10
4
|
export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
|
|
11
5
|
export const JUDGE_ACCEPTED_TEXT = "Judge verdict accepted";
|
|
@@ -17,7 +11,7 @@ export type JudgeClass = {
|
|
|
17
11
|
disposition: string;
|
|
18
12
|
};
|
|
19
13
|
|
|
20
|
-
type
|
|
14
|
+
export type JudgeVerdict =
|
|
21
15
|
| { judgeStatus: "converged"; note?: string; evidence?: unknown }
|
|
22
16
|
| {
|
|
23
17
|
judgeStatus: "continue";
|
|
@@ -33,11 +27,6 @@ type JudgeVerdictClean =
|
|
|
33
27
|
evidence?: unknown;
|
|
34
28
|
};
|
|
35
29
|
|
|
36
|
-
/** Clean submission shape or seat-fallback tainted accepted receipt (ADR 0071). */
|
|
37
|
-
export type JudgeVerdict =
|
|
38
|
-
| JudgeVerdictClean
|
|
39
|
-
| WithEngineLaborFallback<JudgeVerdictClean>;
|
|
40
|
-
|
|
41
30
|
export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
|
|
42
31
|
if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) throw new Error("Judge verdict has no execution discriminator");
|
|
43
32
|
let judgeStatus: unknown;
|
|
@@ -49,12 +38,8 @@ export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
|
|
|
49
38
|
if (typeof judgeStatus !== "string") {
|
|
50
39
|
throw new Error("Judge verdict has no execution discriminator");
|
|
51
40
|
}
|
|
52
|
-
const base = seatFallbackBaseStatus(judgeStatus) as SeatFallbackTaintedStatus<
|
|
53
|
-
"converged" | "continue" | "escalate"
|
|
54
|
-
>;
|
|
55
41
|
if (
|
|
56
|
-
["converged", "continue", "escalate"].includes(
|
|
57
|
-
seatFallbackStatusHasLawfulEvidence(judgeStatus, verdict)
|
|
42
|
+
["converged", "continue", "escalate"].includes(judgeStatus)
|
|
58
43
|
) {
|
|
59
44
|
return verdict as JudgeVerdict;
|
|
60
45
|
}
|