@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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/audit-escalation.js +0 -13
  4. package/dist/compliance-transport.js +28 -5
  5. package/dist/doctor-contracts.js +2 -9
  6. package/dist/dossier-resolution.js +21 -2
  7. package/dist/engine-detour-tool.js +10 -35
  8. package/dist/evidence-child-executor.js +12 -4
  9. package/dist/merger-contracts.js +2 -8
  10. package/dist/navigator-attendance.js +1 -9
  11. package/dist/navigator-invocation-identity.js +16 -4
  12. package/dist/notary-contracts.js +7 -25
  13. package/dist/package-contracts/judge-output.js +1 -4
  14. package/dist/package-contracts/reviewer-output.js +1 -7
  15. package/dist/package-contracts/terminating-tools.js +6 -11
  16. package/dist/public-cli/main.js +125 -475
  17. package/package.json +1 -1
  18. package/scripts/build-package.mjs +1 -2
  19. package/src/analyst-gate-cycles-read.ts +5 -8
  20. package/src/analyst-metric-families/acceptance-success-rework.ts +3 -6
  21. package/src/audit-escalation.ts +0 -25
  22. package/src/collector-role.ts +1 -8
  23. package/src/compliance-transport.ts +38 -6
  24. package/src/doctor-auditor.ts +3 -3
  25. package/src/doctor-contracts.ts +3 -18
  26. package/src/doctor-role.ts +1 -5
  27. package/src/dossier-resolution.ts +25 -4
  28. package/src/engine-detour-tool.ts +16 -51
  29. package/src/evidence-child-executor.ts +12 -4
  30. package/src/factory-board.ts +2 -4
  31. package/src/gatekeeper-role.ts +33 -43
  32. package/src/judge-auditor.ts +3 -3
  33. package/src/judge-role.ts +1 -8
  34. package/src/merger-contracts.ts +3 -17
  35. package/src/merger-role.ts +1 -8
  36. package/src/navigator-attendance.ts +1 -14
  37. package/src/navigator-invocation-identity.ts +34 -13
  38. package/src/notary-contracts.ts +9 -43
  39. package/src/notary-role.ts +3 -5
  40. package/src/package-contracts/judge-output.ts +2 -17
  41. package/src/package-contracts/reviewer-output.ts +2 -21
  42. package/src/package-contracts/terminating-tools.ts +5 -14
  43. package/src/package-contracts/worker-output.ts +2 -10
  44. package/src/public-cli/doctor-run.ts +0 -2
  45. package/src/public-cli/fixer-run.ts +0 -15
  46. package/src/public-cli/judge-run.ts +0 -16
  47. package/src/public-cli/notary-run.ts +1 -1
  48. package/src/public-cli/one-shot-dispatch.ts +1 -1
  49. package/src/public-cli/reviewer-run.ts +0 -15
  50. package/src/public-cli/settlement.ts +110 -368
  51. package/src/public-cli/terminal.ts +3 -71
  52. package/src/reviewer-auditor.ts +3 -3
  53. package/src/reviewer-role.ts +2 -11
  54. package/src/role-runtime.ts +43 -37
  55. package/src/worker-role.ts +2 -14
  56. package/dist/engine-labor-fallback.js +0 -136
  57. package/src/engine-labor-fallback.ts +0 -270
@@ -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: "incomplete";
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" | "incomplete" | "no_receipt" }
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 | incomplete — guidance, not a schema gate." }),
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 | incomplete — guidance, not a schema gate." }),
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, bounce, or incomplete decision.",
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, or report incomplete.",
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
- /** Neutral bookkeeping when no explicit release path is present — no format judgment. */
147
- function noExplicitReleaseIncomplete(
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: "incomplete" }> {
151
+ ): Extract<GatekeeperResult, { status: "transport_failure" }> {
151
152
  return {
152
- status: "incomplete",
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 noExplicitReleaseIncomplete("gatekeeper", decision);
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 noExplicitReleaseIncomplete("gatekeeper", decision);
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 noExplicitReleaseIncomplete(officer, decision);
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 noExplicitReleaseIncomplete(officer, decision);
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 or submit typed incomplete.",
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/incomplete/no_receipt→typed throw; pass silent. */
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
- options.hostActions.failInfrastructure(
278
- new Error(`Gatekeeper transport failure at ${gatekeeper.stage}: ${gatekeeper.reason}`),
279
- options.context,
280
- options.toolCallId,
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);
@@ -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
- if (dossier.status === "incomplete") return toAuditIncomplete(dossier.observation);
40
+ requireAuditMaterials(dossier);
41
41
  const subjects = readJudgeAuditSubjects(options.context);
42
- if (subjects.status === "incomplete") return toAuditIncomplete(subjects.observation);
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
- // #380: attach sole-built engineLaborFallback when detour fell back to seat labor.
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,
@@ -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 MergerOutputClean =
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
- const statusBase = status !== undefined ? seatFallbackBaseStatus(status) : undefined;
79
- // ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
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
  }
@@ -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 = withEngineLaborFallbackField(
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
- statusListMatchesSettlement(candidate.matches.statuses, settlement.status!),
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 exact closed infrastructure fact
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
- /** Closed fact keys — extras/missing/wrong keys fail closed. */
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
- * Exact closed infrastructure-failure fact.
59
- * Rejects extras, missing keys, wrong values/types, and non-objects.
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 isNavigatorInfrastructureFailureFact(
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
- const infraFact = isNavigatorInfrastructureFailureFact(message.details)
195
- ? message.details
196
- : undefined;
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 + exact closed infra fact.
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 };
@@ -1,17 +1,10 @@
1
1
  /**
2
2
  * Public Notary (符宝郎) terminating receipt contracts.
3
- * Lawful explicit releases: pass | bounce | incomplete(with non-empty reason).
4
- * Residual incomplete (no explicit release) is projected by public settlement, not here.
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 | incomplete — guidance, not a schema gate.",
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 NotaryOutputClean =
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 / incomplete(reason) — callers map that to residual incomplete.
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 || !seatFallbackStatusHasLawfulEvidence(statusRaw, value)) {
67
+ if (statusRaw === undefined) {
84
68
  throw new Error("Notary output has no recognized execution discriminator");
85
69
  }
86
- const status = seatFallbackBaseStatus(statusRaw);
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 = seatFallbackBaseStatus(String(output.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
  }
@@ -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, bounce, or incomplete decision on quote fidelity and ticket alignment.",
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 project residual incomplete (layer ②).
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: acceptedDetails,
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 JudgeVerdictClean =
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(base) &&
57
- seatFallbackStatusHasLawfulEvidence(judgeStatus, verdict)
42
+ ["converged", "continue", "escalate"].includes(judgeStatus)
58
43
  ) {
59
44
  return verdict as JudgeVerdict;
60
45
  }