@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
package/README.md CHANGED
@@ -53,7 +53,7 @@ Receipts are typed, so callers compose roles without parsing prose; ordering and
53
53
 
54
54
  On completing-side submissions the package may spawn the Menxia province before the run settles: `gatekeeper` reads the subject and dispatches an officer (`inspector` or `notary`); existing auditor hooks stay where already wired. The gate runs inside the submission session; bounce means rewrite-and-resubmit in that same session — not role failure; the final receipt is the post-gate product. `planned` / `refused` / `unfinished` skip the province. Pointers only: [ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md), [ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md). Gate history is projected into the typed TerminalResult: an optional menxia section lists the seats that actually sat, each dispatch round (officer, optional reason verbatim), and each officer report (seat, status, findings). Absent when no gate ran. Do not scrape session prose for gate status — read the typed section.
55
55
 
56
- When a labor-engine detour fails and the seat continues the labor on the main road, the typed receipt may carry a mechanical `engineLaborFallback` field: `{ engine, failure, laborBy: "seat" }`. It appears only after a real detour failure that fell back to seat labor—not on detour success or caller cancel. First failure wins for the activation; model-forged `engineLaborFallback` keys are stripped unless the package latch recorded one. Sole producer: `src/engine-labor-fallback.ts`; decision record: [ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md). This README only projects that contract.
56
+ When a labor-engine detour process fails to spawn, exits nonzero, or produces no usable output, the role run stops through the existing infrastructure-failure path with the original cause visible. The seat does not continue the labor or produce a typed Receipt. Caller cancellation continues to propagate unchanged. See [ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md).
57
57
 
58
58
  ## Call the roles
59
59
 
package/README.zh-CN.md CHANGED
@@ -53,7 +53,7 @@ ak-role config set-auto-resume-limit 3
53
53
 
54
54
  完成侧交卷时,包可能在本局结算前起门下省:`gatekeeper` 读受审物并派官(`inspector` 给事中或 `notary` 符宝郎);既有审刑院挂钩仍在原位。闸在交卷 session 内运行;封驳=当场重写重交,不是角色失败;最终回执即过闸产物。`planned`/`refused`/`unfinished` 不调省。指针:[ADR 0067](docs/adr/0067-menxia-province-founding-jishizhong-fubaolang.md)、[ADR 0072](docs/adr/0072-menxia-pre-pr-submission-hooks.md)。闸史已投影进 typed 回执:可选 menxia 段列出实际在场席位、每轮派官(officer 与逐字 reason)与各官报告(席位/判决/findings);无闸调用时该段缺席。勿刮 session 散文当闸状态——读 typed 段。
55
55
 
56
- 当劳务引擎绕行失败、座席回到主路继续劳务时,typed 回执可带机械字段 `engineLaborFallback`:`{ engine, failure, laborBy: "seat" }`。仅在真实绕行失败并座席顶班后出现——成功绕行或调用方 cancel 不出现。同一次 activation 内先到先得;无包内 latch 时剥离模型伪造的 `engineLaborFallback` 键。唯一构造点:`src/engine-labor-fallback.ts`;决策记录:[ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md)。本文只投影该契约。
56
+ 劳务引擎绕行进程无法启动、非零退出或未产生可用输出时,角色运行沿既有基础设施失败路径立即停止并保留可见真因;座席不继续顶班,也不产出 typed 回执。调用方 cancel 仍原样传播。见 [ADR 0071](docs/adr/0071-engine-detour-failure-seat-fallback-declaration.md)
57
57
 
58
58
  ## 调用百官
59
59
 
@@ -69,14 +69,6 @@ export function projectAuditEscalation(decision, deliveredOutput) {
69
69
  ...(decision.usage === undefined ? {} : { usage: decision.usage }),
70
70
  };
71
71
  }
72
- export function projectAuditIncomplete(decision) {
73
- return {
74
- content: [{ type: "text", text: "Compliance audit incomplete; no role receipt was formed." }],
75
- details: decision,
76
- terminate: true,
77
- ...(decision.usage === undefined ? {} : { usage: decision.usage }),
78
- };
79
- }
80
72
  /**
81
73
  * Discriminator-only recognition (ADR 0040). Shape of conflicts/options/gate
82
74
  * is not a reject gate — element types and cardinality are delivery content.
@@ -106,10 +98,5 @@ export async function disposeComplianceDecision(decision, handlers, deliveredOut
106
98
  return await handlers.revise(decision.violations);
107
99
  case "escalate":
108
100
  return await handlers.escalate(projectAuditEscalation(decision, deliveredOutput));
109
- case "audit-incomplete":
110
- if (handlers.auditIncomplete === undefined) {
111
- throw new Error("Compliance audit-incomplete handler is unavailable");
112
- }
113
- return await handlers.auditIncomplete(projectAuditIncomplete(decision));
114
101
  }
115
102
  }
@@ -1,12 +1,34 @@
1
1
  import { Type } from "typebox";
2
2
  import { executeAuditorChild, } from "./evidence-child-executor.js";
3
3
  import { createAuditorDossierTool } from "./auditor-dossier-tool.js";
4
+ /** Unreadable compliance candidate — infrastructure failure, not a judgment status (#475). */
5
+ export class ComplianceCandidateUnreadableError extends Error {
6
+ observation;
7
+ candidate;
8
+ usage;
9
+ constructor(observation, candidate, usage) {
10
+ const detail = observation.kind === "non-object-arguments"
11
+ ? `${observation.kind}:${observation.type}`
12
+ : observation.kind === "object-status-unreadable"
13
+ ? `${observation.kind}:${observation.status}`
14
+ : observation.kind === "missing-subject"
15
+ ? `${observation.kind}:${observation.subject}`
16
+ : observation.kind;
17
+ super(`Compliance candidate unreadable: ${detail}`);
18
+ this.name = "ComplianceCandidateUnreadableError";
19
+ this.observation = observation;
20
+ this.candidate = candidate;
21
+ if (usage !== undefined)
22
+ this.usage = usage;
23
+ }
24
+ }
4
25
  /** Zero-projection kickoff — soul already carries dossier-fetch duty; no hand-delivered materials. */
5
26
  export const AUDITOR_DOSSIER_PROMPT = "Audit the current run dossier.";
6
27
  const nonblank = Type.String({ minLength: 1, pattern: "\\S" });
7
28
  const decisionGateSchema = Type.Object({ question: nonblank, options: Type.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false });
8
- // Transport must retain malformed candidates so they can settle as typed
9
- // audit-incomplete outcomes; status values are guidance, not a schema gate.
29
+ // Transport retains malformed candidates on ComplianceCandidateUnreadableError so
30
+ // the existing failure channel can publish observation + candidate (#475).
31
+ // Status values are guidance, not a schema gate.
10
32
  export const complianceDecisionSchema = Type.Object({ status: Type.Unknown({ description: "Auditor decision status." }), violations: Type.Array(nonblank, { description: "Observed compliance violations." }), conflicts: Type.Array(nonblank, { description: "Unresolved authority or execution conflicts." }), decisionGate: Type.Union([decisionGateSchema, Type.Null()], { description: "Escalation question and available options." }) }, { additionalProperties: true, required: [] });
11
33
  export function createComplianceDecisionTool(name, description) {
12
34
  return { name, description, parameters: complianceDecisionSchema, async execute(_id, params) { return { content: [{ type: "text", text: "Compliance decision received" }], details: params, terminate: true }; } };
@@ -48,8 +70,9 @@ function retainComplianceResponse(context, response) {
48
70
  }
49
71
  function readListField(value) { return Array.isArray(value) ? value : value === undefined ? [] : [value]; }
50
72
  export function readComplianceCandidate(arguments_, usage) {
51
- if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_))
52
- return { status: "audit-incomplete", observation: { kind: "non-object-arguments", type: arguments_ === null ? "null" : Array.isArray(arguments_) ? "array" : typeof arguments_ }, candidate: arguments_, ...(usage === undefined ? {} : { usage }) };
73
+ if (typeof arguments_ !== "object" || arguments_ === null || Array.isArray(arguments_)) {
74
+ throw new ComplianceCandidateUnreadableError({ kind: "non-object-arguments", type: arguments_ === null ? "null" : Array.isArray(arguments_) ? "array" : typeof arguments_ }, arguments_, usage);
75
+ }
53
76
  const args = arguments_;
54
77
  const status = args.status;
55
78
  if (status === "pass")
@@ -58,7 +81,7 @@ export function readComplianceCandidate(arguments_, usage) {
58
81
  return { status, violations: readListField(args.violations), ...(usage === undefined ? {} : { usage }) };
59
82
  if (status === "escalate")
60
83
  return { status, ...(Object.hasOwn(args, "conflicts") ? { conflicts: args.conflicts } : {}), ...(Object.hasOwn(args, "decisionGate") ? { decisionGate: args.decisionGate } : {}), ...(usage === undefined ? {} : { usage }) };
61
- return { status: "audit-incomplete", observation: { kind: "object-status-unreadable", status: status === undefined ? "missing" : "unknown" }, candidate: arguments_, ...(usage === undefined ? {} : { usage }) };
84
+ throw new ComplianceCandidateUnreadableError({ kind: "object-status-unreadable", status: status === undefined ? "missing" : "unknown" }, arguments_, usage);
62
85
  }
63
86
  export async function runComplianceAudit(options) {
64
87
  const prompt = options.serializedInput ?? AUDITOR_DOSSIER_PROMPT;
@@ -1,6 +1,5 @@
1
1
  import { Type } from "typebox";
2
2
  import { canonicalJson } from "./canonical-json.js";
3
- import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "./engine-labor-fallback.js";
4
3
  import { openToolObjectFromUnion } from "./open-tool-schema.js";
5
4
  export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
6
5
  export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
@@ -67,20 +66,14 @@ catch {
67
66
  } }
68
67
  export function validateDoctorSubmissionShape(value) {
69
68
  const status = read(value, "status");
70
- const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
71
- if (base !== "completed" && base !== "refused")
69
+ if (status !== "completed" && status !== "refused")
72
70
  throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
73
- // ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
74
- if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, value)) {
75
- throw new DoctorSubmissionContractError("Doctor submission has no recognized execution status");
76
- }
77
71
  return value;
78
72
  }
79
73
  export function validateRecordedDoctorOutput(value) {
80
74
  const output = validateDoctorSubmissionShape(value);
81
75
  const status = read(output, "status");
82
- const base = typeof status === "string" ? seatFallbackBaseStatus(status) : status;
83
- if (base === "completed" && read(output, "cost") === undefined)
76
+ if (status === "completed" && read(output, "cost") === undefined)
84
77
  throw new Error("Completed Doctor receipt has no runtime-owned cost testimony");
85
78
  return output;
86
79
  }
@@ -98,6 +98,25 @@ export function readDoctorAuditSubjects(context) {
98
98
  }
99
99
  return { status: "incomplete", observation: { kind: "missing-subject", subject: "candidate-testimony" } };
100
100
  }
101
- export function toAuditIncomplete(observation) {
102
- return { status: "audit-incomplete", observation, candidate: undefined };
101
+ /**
102
+ * Missing dossier/subject is infrastructure failure, not a judgment status (#475).
103
+ * Observation + empty candidate ride the existing failInfrastructure → error artifact path.
104
+ */
105
+ export class AuditMaterialsUnavailableError extends Error {
106
+ observation;
107
+ candidate;
108
+ constructor(observation) {
109
+ const detail = observation.kind === "missing-subject"
110
+ ? `${observation.kind}:${observation.subject}`
111
+ : observation.kind;
112
+ super(`Audit materials unavailable: ${detail}`);
113
+ this.name = "AuditMaterialsUnavailableError";
114
+ this.observation = observation;
115
+ this.candidate = undefined;
116
+ }
117
+ }
118
+ export function requireAuditMaterials(resolution) {
119
+ if (resolution.status === "incomplete") {
120
+ throw new AuditMaterialsUnavailableError(resolution.observation);
121
+ }
103
122
  }
@@ -1,28 +1,12 @@
1
1
  import { Type } from "typebox";
2
2
  import { ENGINE_DETOUR_ALREADY_USED_DIAGNOSTIC, ENGINE_DETOUR_TOOL_NAME, engineDetourFailureDiagnostic, engineNameFromEnv, isEngineDetourFailure, runEngineDetourOnce, } from "./engine-detour.js";
3
- import { activationEngineLaborFallbackLatch, recordEngineLaborFallback, } from "./engine-labor-fallback.js";
4
3
  const engineDetourArgsSchema = Type.Object({
5
4
  argv: Type.Array(Type.String({ minLength: 1 }), {
6
5
  minItems: 1,
7
6
  description: "Executable argv for one engine subprocess. First element is the command (PATH lookup); remaining elements are arguments. Build argv from the host CLI actual interface for the configured engine name; when optional packaged notes are present in the session prompt, follow those bytes. Do not invent package flags.",
8
7
  }),
9
8
  }, { additionalProperties: false });
10
- function seatFallbackToolResult(field, failure) {
11
- return {
12
- content: [
13
- {
14
- type: "text",
15
- text: `Engine detour failed: ${failure}. Perform the labor in this session (seat main road) and submit via the existing typed path. A mechanical fallback declaration will attach to the typed receipt.`,
16
- },
17
- ],
18
- details: {
19
- tool: ENGINE_DETOUR_TOOL_NAME,
20
- detourFailed: true,
21
- ...field,
22
- },
23
- };
24
- }
25
- /** Caller/upper-layer cancel must propagate; process failure is seat-fallback, not cancel. */
9
+ /** Caller/upper-layer cancellation must propagate unchanged. */
26
10
  function isCallerCancellation(error, signal) {
27
11
  if (signal?.aborted === true)
28
12
  return true;
@@ -37,8 +21,8 @@ function isCallerCancellation(error, signal) {
37
21
  * Build one once-latch detour tool definition for a configured engine name.
38
22
  * `latch` is shared so parent registration can reset between activations.
39
23
  * `fail` owns host abort (parent) vs throw (evidence child) for tool misuse only.
40
- * Engine process failure (nonzero/empty/spawn) soft-returns seat fallback (#380).
41
- * Caller AbortSignal cancel propagates without writing fallback.
24
+ * Engine process failures (nonzero/empty/spawn) stop via `fail` with their cause.
25
+ * Caller AbortSignal cancellation propagates unchanged.
42
26
  */
43
27
  export function createEngineDetourToolDefinition(input) {
44
28
  const latch = input.latch ?? { used: false };
@@ -52,7 +36,7 @@ export function createEngineDetourToolDefinition(input) {
52
36
  `Use ${ENGINE_DETOUR_TOOL_NAME} exactly once for the configured engine (${engineName}). Optional packaged notes are guidance when present; a bare engine name alone is also a valid call path.`,
53
37
  "Pass argv for the host CLI of this engine name — first element is the executable name on PATH. Follow optional packaged notes when delivered; otherwise act from the engine name and the host CLI actual interface. Do not invent package flags.",
54
38
  "On success, use the returned stdout as labor content for the existing typed submission / report path.",
55
- "On engine failure the tool returns a soft failure: continue labor in this session and submit via the existing typed path. Do not treat engine failure as a reason to withhold the typed receipt.",
39
+ "An engine process failure stops this activation; do not continue labor in this session.",
56
40
  ],
57
41
  parameters: engineDetourArgsSchema,
58
42
  async execute(toolCallId, params, signal, _onUpdate, ctx) {
@@ -65,15 +49,6 @@ export function createEngineDetourToolDefinition(input) {
65
49
  if (argv.length === 0 || argv.some((part) => typeof part !== "string" || part.length === 0)) {
66
50
  input.fail(new Error("engine detour argv must be a non-empty string array"), toolCallId, ctx);
67
51
  }
68
- const softFail = (failure) => {
69
- // Activation-scoped latch is the sole shared recorder (parent seat + legs).
70
- const fallbackLatch = activationEngineLaborFallbackLatch() ?? { field: undefined };
71
- const field = recordEngineLaborFallback(fallbackLatch, {
72
- engine: engineName,
73
- failure,
74
- });
75
- return seatFallbackToolResult(field, failure);
76
- };
77
52
  let result;
78
53
  try {
79
54
  result = await runEngineDetourOnce({
@@ -83,15 +58,15 @@ export function createEngineDetourToolDefinition(input) {
83
58
  });
84
59
  }
85
60
  catch (error) {
86
- // Caller cancel: propagate. Spawn/engine failure: seat fallback.
87
- if (isCallerCancellation(error, signal)) {
61
+ if (isCallerCancellation(error, signal))
88
62
  throw error;
89
- }
90
- const failure = error instanceof Error ? error.message : String(error);
91
- return softFail(failure.trim() === "" ? "engine detour spawn failed" : failure);
63
+ const cause = error instanceof Error
64
+ ? error
65
+ : new Error(String(error).trim() || "engine detour spawn failed");
66
+ input.fail(cause, toolCallId, ctx);
92
67
  }
93
68
  if (isEngineDetourFailure(result)) {
94
- return softFail(engineDetourFailureDiagnostic(result));
69
+ input.fail(new Error(engineDetourFailureDiagnostic(result)), toolCallId, ctx);
95
70
  }
96
71
  return {
97
72
  content: [{ type: "text", text: result.stdout }],
@@ -475,14 +475,16 @@ export async function executeEvidenceChild(workspace, prompt, context, options =
475
475
  engine: engineName,
476
476
  packageRoot: options.packageRoot,
477
477
  });
478
- // #378/#380: same detour tool as parent seat. Engine process failure soft-returns
479
- // seat main-road fallback (ADR 0069 detour-rejoins-main-road); tool misuse still throws.
478
+ // Evidence legs use the parent detour tool; retain any engine process cause
479
+ // so the enclosing child boundary, rather than a tool-error result, terminates.
480
+ let engineDetourFailure;
480
481
  const engineDetourTool = engineName === undefined
481
482
  ? undefined
482
483
  : createEngineDetourToolDefinition({
483
484
  engineName,
484
485
  fail(error) {
485
- throw error instanceof Error ? error : new Error(String(error));
486
+ engineDetourFailure ??= error instanceof Error ? error : new Error(String(error));
487
+ throw engineDetourFailure;
486
488
  },
487
489
  });
488
490
  // No tools allowlist — Pi defaults + unrestricted evidence surface (ADR 0064).
@@ -521,8 +523,14 @@ export async function executeEvidenceChild(workspace, prompt, context, options =
521
523
  await session.prompt(delivered);
522
524
  }
523
525
  catch (error) {
526
+ if (engineDetourFailure !== undefined) {
527
+ throw classifiedError(engineDetourFailure, "child");
528
+ }
524
529
  throw classifiedError(error, "provider");
525
530
  }
531
+ if (engineDetourFailure !== undefined) {
532
+ throw classifiedError(engineDetourFailure, "child");
533
+ }
526
534
  if (signal?.aborted)
527
535
  throw new Error("Evidence child was cancelled");
528
536
  const lastAssistant = [...session.messages]
@@ -756,7 +764,7 @@ export async function executeAuditorChild(options) {
756
764
  decision = part.arguments;
757
765
  decisionCallId = part.id;
758
766
  // Pi can reject malformed root arguments before invoking execute;
759
- // that remains the existing typed audit-incomplete candidate path.
767
+ // that remains the existing unreadable-candidate failure path.
760
768
  if (part.arguments === undefined)
761
769
  decisionSubmitted = true;
762
770
  }
@@ -1,7 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  import { isFullGitObjectId } from "./git-object-id.js";
3
3
  import { exactUtf8 } from "./exact-utf8.js";
4
- import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "./engine-labor-fallback.js";
5
4
  import { sha256Hex } from "./sha256.js";
6
5
  import { openToolObjectFromUnion } from "./open-tool-schema.js";
7
6
  const oidPattern = "^(?:[0-9a-f]{40}|[0-9a-f]{64})$";
@@ -70,14 +69,9 @@ export function validateMergerOutput(value, expectedAttemptId) {
70
69
  if (!record(value) || (expectedAttemptId !== undefined && value.attemptId !== expectedAttemptId))
71
70
  throw new Error("Merger output attempt mismatch");
72
71
  const status = typeof value.status === "string" ? value.status : undefined;
73
- const statusBase = status !== undefined ? seatFallbackBaseStatus(status) : undefined;
74
- // ADR 0071: tainted status requires latch-shaped engineLaborFallback evidence.
75
- if (status !== undefined && !seatFallbackStatusHasLawfulEvidence(status, value)) {
76
- throw new Error("Merger output has no recognized execution discriminator");
77
- }
78
- if (statusBase === "completed" && isFullGitObjectId(value.mergeCommitId))
72
+ if (status === "completed" && isFullGitObjectId(value.mergeCommitId))
79
73
  return structuredClone(value);
80
- if (statusBase === "escalate")
74
+ if (status === "escalate")
81
75
  return structuredClone(value);
82
76
  throw new Error("Merger output has no recognized execution discriminator");
83
77
  }
@@ -6,7 +6,6 @@ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
6
6
  import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
7
7
  import { Type } from "typebox";
8
8
  import { Value } from "typebox/value";
9
- import { seatFallbackBaseStatus } from "./engine-labor-fallback.js";
10
9
  import {
11
10
  NAVIGATOR_INVOCATION_ENTRY,
12
11
  mintNavigatorInvocationId
@@ -258,13 +257,6 @@ function createNavigatorPrepareTool(onOutput) {
258
257
  }
259
258
  };
260
259
  }
261
- function statusListMatchesSettlement(candidateStatuses, settlementStatus) {
262
- if (candidateStatuses.includes(settlementStatus)) return true;
263
- const settlementBase = seatFallbackBaseStatus(settlementStatus);
264
- return candidateStatuses.some(
265
- (status) => seatFallbackBaseStatus(status) === settlementBase
266
- );
267
- }
268
260
  function selectNavigatorCandidate(candidates, settlement) {
269
261
  if (settlement.kind !== "accepted") return void 0;
270
262
  const usable = candidates.filter((candidate) => candidate.next !== void 0);
@@ -275,7 +267,7 @@ function selectNavigatorCandidate(candidates, settlement) {
275
267
  if (rolePhaseMatched.length > 0) {
276
268
  if (settlement.status !== void 0) {
277
269
  const statusSpecific = rolePhaseMatched.find(
278
- (candidate) => candidate.matches?.statuses !== void 0 && statusListMatchesSettlement(candidate.matches.statuses, settlement.status)
270
+ (candidate) => candidate.matches?.statuses !== void 0 && candidate.matches.statuses.includes(settlement.status)
279
271
  );
280
272
  if (statusSpecific !== void 0) {
281
273
  return { candidate: statusSpecific, matchedToSettlement: true };
@@ -8,6 +8,13 @@ const NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS = [
8
8
  "source",
9
9
  "reasonCode"
10
10
  ];
11
+ const NAVIGATOR_INFRASTRUCTURE_FAILURE_EVIDENCE_KEYS = [
12
+ "observation",
13
+ "candidate",
14
+ "submission",
15
+ "stage",
16
+ "reason"
17
+ ];
11
18
  function buildNavigatorInfrastructureFailureFact() {
12
19
  return {
13
20
  kind: NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND,
@@ -15,16 +22,18 @@ function buildNavigatorInfrastructureFailureFact() {
15
22
  reasonCode: "host_failure"
16
23
  };
17
24
  }
18
- function isNavigatorInfrastructureFailureFact(value) {
25
+ function hasNavigatorInfrastructureFailureBase(value) {
19
26
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
20
27
  const record = value;
21
- const keys = Object.keys(record);
22
- if (keys.length !== NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS.length) return false;
23
28
  for (const key of NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS) {
24
29
  if (!Object.hasOwn(record, key)) return false;
25
30
  }
26
31
  return record.kind === NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND && record.source === "shared-role-lifecycle" && record.reasonCode === "host_failure";
27
32
  }
33
+ function isNavigatorInfrastructureFailureFact(value) {
34
+ if (!hasNavigatorInfrastructureFailureBase(value)) return false;
35
+ return Object.keys(value).length === NAVIGATOR_INFRASTRUCTURE_FAILURE_KEYS.length;
36
+ }
28
37
  const PACKAGED_ROLE_OUTPUT_TOOLS = new Map(
29
38
  PACKAGED_ROLE_REGISTRY.map((entry) => [entry.outputTool, entry.role])
30
39
  );
@@ -68,7 +77,8 @@ function markerMatchesExpectedIdentity(marker, expected) {
68
77
  function classifyPackagedRoleTerminalResult(message) {
69
78
  if (typeof message.toolName !== "string") return { kind: "nonterminal" };
70
79
  if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName)) return { kind: "nonterminal" };
71
- const infraFact = isNavigatorInfrastructureFailureFact(message.details) ? message.details : void 0;
80
+ const hasInfraBase = hasNavigatorInfrastructureFailureBase(message.details);
81
+ const infraFact = hasInfraBase ? buildNavigatorInfrastructureFailureFact() : void 0;
72
82
  if (message.isError === true) {
73
83
  if (infraFact === void 0) return { kind: "nonterminal" };
74
84
  return { kind: "infrastructure", fact: infraFact };
@@ -201,6 +211,7 @@ function currentInvocationMarkerFromSession(entries, beforeIndex = entries.lengt
201
211
  return void 0;
202
212
  }
203
213
  export {
214
+ NAVIGATOR_INFRASTRUCTURE_FAILURE_EVIDENCE_KEYS,
204
215
  NAVIGATOR_INFRASTRUCTURE_FAILURE_KIND,
205
216
  NAVIGATOR_INVOCATION_ENTRY,
206
217
  bindCurrentDurableTerminalToMarker,
@@ -209,6 +220,7 @@ export {
209
220
  currentInvocationMarkerFromSession,
210
221
  currentInvocationPrincipalFromSession,
211
222
  findLatestDurablePackagedRoleTerminal,
223
+ hasNavigatorInfrastructureFailureBase,
212
224
  isAcceptedPackagedRoleTerminalResult,
213
225
  isDurablePackagedRoleTerminalResult,
214
226
  isNavigatorInfrastructureFailureFact,
@@ -1,10 +1,9 @@
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
- import { readActivationEngineLaborFallbackField, seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, withEngineLaborFallbackField, } from "./engine-labor-fallback.js";
8
7
  import { openToolObject } from "./open-tool-schema.js";
9
8
  export const NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
10
9
  export const NOTARY_ACCEPTED_TEXT = "Notary output accepted";
@@ -19,14 +18,11 @@ export const NOTARY_SOURCE_RUN_FLAG = {
19
18
  export const NOTARY_FIXED_KICKOFF = "Notary review. Bound source-run locator is on the session materials; fetch authoritative ticket, git, and dossier evidence yourself; submit one typed decision.";
20
19
  export const notaryOutputSchema = openToolObject(Type.Object({
21
20
  status: Type.Unknown({
22
- description: "pass | bounce | incomplete — guidance, not a schema gate.",
21
+ description: "pass | bounce — guidance, not a schema gate.",
23
22
  }),
24
23
  findings: Type.Unknown({
25
24
  description: "string[] findings retained with pass or bounce.",
26
25
  }),
27
- reason: Type.Unknown({
28
- description: "Why the notary decision is incomplete.",
29
- }),
30
26
  }));
31
27
  function isRecord(value) {
32
28
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -38,24 +34,17 @@ function asStringArray(value) {
38
34
  }
39
35
  /**
40
36
  * Project one explicit Notary release. Throws when there is no lawful explicit
41
- * pass / bounce / incomplete(reason) — callers map that to residual incomplete.
37
+ * pass / bounce — callers map that to the existing non-zero failure channel.
42
38
  */
43
39
  export function validateNotaryOutput(value) {
44
40
  if (!isRecord(value)) {
45
41
  throw new Error("Notary output has no recognized execution discriminator");
46
42
  }
47
43
  const statusRaw = typeof value.status === "string" ? value.status : undefined;
48
- if (statusRaw === undefined || !seatFallbackStatusHasLawfulEvidence(statusRaw, value)) {
44
+ if (statusRaw === undefined) {
49
45
  throw new Error("Notary output has no recognized execution discriminator");
50
46
  }
51
- const status = seatFallbackBaseStatus(statusRaw);
52
- if (status === "incomplete") {
53
- const reason = value.reason;
54
- if (typeof reason !== "string" || reason.trim() === "") {
55
- throw new Error("Notary incomplete requires a non-empty reason");
56
- }
57
- return structuredClone(value);
58
- }
47
+ const status = statusRaw;
59
48
  if (status === "bounce") {
60
49
  const clone = structuredClone(value);
61
50
  if (clone.disposition === undefined)
@@ -72,15 +61,11 @@ export function validateNotaryOutput(value) {
72
61
  }
73
62
  throw new Error("Notary output has no recognized execution discriminator");
74
63
  }
75
- /** Shape check for recorded accepted details (may include engineLaborFallback). */
76
64
  export function validateRecordedNotaryOutput(value) {
77
65
  return validateNotaryOutput(value);
78
66
  }
79
- export function withNotaryAcceptedDetails(output) {
80
- return withEngineLaborFallbackField(output, readActivationEngineLaborFallbackField());
81
- }
82
67
  export function notaryDecisiveFacts(output) {
83
- const status = seatFallbackBaseStatus(String(output.status));
68
+ const status = String(output.status);
84
69
  const facts = { status, officer: "notary" };
85
70
  if (status === "pass" || status === "bounce") {
86
71
  const findings = output.findings;
@@ -89,8 +74,5 @@ export function notaryDecisiveFacts(output) {
89
74
  if (status === "bounce") {
90
75
  facts.disposition = "rewrite";
91
76
  }
92
- if (status === "incomplete") {
93
- facts.reason = output.reason;
94
- }
95
77
  return facts;
96
78
  }
@@ -1,5 +1,4 @@
1
1
  /** Package-owned Judge output leaf — no role registration surface. */
2
- import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "../engine-labor-fallback.js";
3
2
  export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
4
3
  export const JUDGE_ACCEPTED_TEXT = "Judge verdict accepted";
5
4
  export function validateAcceptedJudgeDetails(verdict) {
@@ -15,9 +14,7 @@ export function validateAcceptedJudgeDetails(verdict) {
15
14
  if (typeof judgeStatus !== "string") {
16
15
  throw new Error("Judge verdict has no execution discriminator");
17
16
  }
18
- const base = seatFallbackBaseStatus(judgeStatus);
19
- if (["converged", "continue", "escalate"].includes(base) &&
20
- seatFallbackStatusHasLawfulEvidence(judgeStatus, verdict)) {
17
+ if (["converged", "continue", "escalate"].includes(judgeStatus)) {
21
18
  return verdict;
22
19
  }
23
20
  throw new Error("Judge verdict has no execution discriminator");
@@ -1,5 +1,4 @@
1
1
  /** Package-owned Reviewer intent and runtime-receipt leaves — no role registration surface. */
2
- import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "../engine-labor-fallback.js";
3
2
  export const REVIEWER_OUTPUT_TOOL_NAME = "ak_reviewer_output";
4
3
  export const REVIEWER_ACCEPTED_TEXT = "Reviewer report accepted";
5
4
  function isRecord(value) {
@@ -46,10 +45,6 @@ export function validateReviewerIntent(output) {
46
45
  /** Validate runtime-owned facts at their real identity seams (target pins + plain text). */
47
46
  export function validateRuntimeReviewerReceipt(output) {
48
47
  const status = read(output, "status");
49
- // ADR 0071: tainted top-level status requires latch-shaped engineLaborFallback evidence.
50
- if (typeof status === "string" && !seatFallbackStatusHasLawfulEvidence(status, output)) {
51
- throw new Error("Reviewer receipt has no recognized execution discriminator");
52
- }
53
48
  const acceptedBatch = read(output, "acceptedBatch");
54
49
  const identities = read(output, "identities");
55
50
  const construction = read(identities, "construction");
@@ -118,8 +113,7 @@ export function projectReviewerIntentToReceipt(intentValue, receiptValue) {
118
113
  const intent = validateReviewerIntent(intentValue);
119
114
  const receipt = validateRuntimeReviewerReceipt(receiptValue);
120
115
  const receiptStatus = String(receipt.status);
121
- const receiptBase = seatFallbackBaseStatus(receiptStatus);
122
- if (receiptBase !== intent.status || (intent.status === "completed" ? receipt.diagnostic !== undefined : receipt.diagnostic !== intent.diagnostic)) {
116
+ if (receiptStatus !== intent.status || (intent.status === "completed" ? receipt.diagnostic !== undefined : receipt.diagnostic !== intent.diagnostic)) {
123
117
  throw new Error("Reviewer intent and runtime receipt disagree");
124
118
  }
125
119
  return receipt;
@@ -6,7 +6,6 @@ import { COLLECTOR_ACCEPTED_TEXT, COLLECTOR_OUTPUT_TOOL, validateAcceptedCollect
6
6
  import { JUDGE_ACCEPTED_TEXT, JUDGE_OUTPUT_TOOL_NAME, validateAcceptedJudgeDetails, } from "./judge-output.js";
7
7
  import { REVIEWER_ACCEPTED_TEXT, REVIEWER_OUTPUT_TOOL_NAME, projectReviewerIntentToReceipt, validateReviewerIntent, validateRuntimeReviewerReceipt, } from "./reviewer-output.js";
8
8
  import { isAuditEscalationResult } from "../audit-escalation.js";
9
- import { seatFallbackBaseStatus, seatFallbackStatusHasLawfulEvidence, } from "../engine-labor-fallback.js";
10
9
  import { DOCTOR_OUTPUT_TOOL_NAME, validateDoctorSubmissionShape, validateRecordedDoctorOutput } from "../doctor-contracts.js";
11
10
  import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, validateMergerOutput } from "../merger-contracts.js";
12
11
  import { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, validateRecordedNotaryOutput } from "../notary-contracts.js";
@@ -82,17 +81,13 @@ export function validateAcceptedDetails(toolName, details) {
82
81
  [COLLECTOR_OUTPUT_TOOL]: [],
83
82
  [DOCTOR_OUTPUT_TOOL_NAME]: ["completed", "refused"],
84
83
  [MERGER_OUTPUT_TOOL_NAME]: ["completed", "escalate"],
85
- [NOTARY_OUTPUT_TOOL_NAME]: ["pass", "bounce", "incomplete"],
84
+ [NOTARY_OUTPUT_TOOL_NAME]: ["pass", "bounce"],
86
85
  };
87
86
  const collectorDiscriminator = toolName === COLLECTOR_OUTPUT_TOOL && Array.isArray(candidate?.groups);
88
- const baseDiscriminator = typeof discriminator === "string" ? seatFallbackBaseStatus(discriminator) : discriminator;
89
- // ADR 0071: `-by-fallback` is not independently lawful — require latch-shaped evidence.
90
- const taintedWithoutEvidence = typeof discriminator === "string" &&
91
- !seatFallbackStatusHasLawfulEvidence(discriminator, details);
87
+ const baseDiscriminator = discriminator;
92
88
  const runtimeBindingMissing = (toolName === DOCTOR_OUTPUT_TOOL_NAME && baseDiscriminator === "completed" && !(candidate?.cost !== null && typeof candidate?.cost === "object")) ||
93
89
  (toolName === REVIEWER_OUTPUT_TOOL_NAME && candidate?.version !== 2);
94
- if (taintedWithoutEvidence ||
95
- runtimeBindingMissing ||
90
+ if (runtimeBindingMissing ||
96
91
  (!collectorDiscriminator && (typeof discriminator !== "string" || !lawfulStatuses[toolName].includes(baseDiscriminator)))) {
97
92
  throw new AcceptedDetailsContractError("terminating receipt has no recognized execution discriminator");
98
93
  }
@@ -126,13 +121,13 @@ export function validateAcceptedLifecycle(toolName, argumentsValue, detailsValue
126
121
  const details = validateAcceptedDetails(toolName, detailsValue);
127
122
  if (toolName === DOCTOR_OUTPUT_TOOL_NAME) {
128
123
  const testimony = validateDoctorSubmissionShape(argumentsValue);
129
- if (seatFallbackBaseStatus(String(testimony.status)) === "refused") {
124
+ if ((String(testimony.status)) === "refused") {
130
125
  if (!deepEqual(testimony, details))
131
126
  throw new Error("accepted tool lifecycle details mismatch");
132
127
  return details;
133
128
  }
134
129
  const receipt = details;
135
- if (seatFallbackBaseStatus(String(receipt.status)) !== "completed") {
130
+ if ((String(receipt.status)) !== "completed") {
136
131
  throw new Error("accepted tool lifecycle details mismatch");
137
132
  }
138
133
  const { cost: _runtimeCost, ...projected } = receipt;
@@ -156,7 +151,7 @@ export function acceptedFacts(toolName, details) {
156
151
  case MERGER_OUTPUT_TOOL_NAME: {
157
152
  const output = details;
158
153
  const status = output.status;
159
- return { status, ...(seatFallbackBaseStatus(status) === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {}) };
154
+ return { status, ...(status === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {}) };
160
155
  }
161
156
  case COLLECTOR_OUTPUT_TOOL:
162
157
  return { status: "collected" };