@akagilnc/pi-workflow-roles 0.1.4980 → 0.1.4989

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/acp-host/production-host.js +438 -456
  2. package/dist/analyst-gate-cycles-read.js +16 -11
  3. package/dist/analyst-metric-families/gate-cycles.js +2 -2
  4. package/dist/audit-escalation.js +4 -4
  5. package/dist/compliance-transport.js +2 -2
  6. package/dist/countersign-contracts.js +6 -5
  7. package/dist/doctor-evidence.js +2 -1
  8. package/dist/gatekeeper-role.js +20 -41
  9. package/dist/headless-host/production-host.js +438 -456
  10. package/dist/inspector-contracts.js +3 -2
  11. package/dist/migrate-book-topology.js +59 -54
  12. package/dist/navigator-invocation-identity.js +6 -4
  13. package/dist/notary-contracts.js +3 -19
  14. package/dist/package-contracts/auditor-output.js +4 -19
  15. package/dist/package-contracts/judge-output.js +6 -5
  16. package/dist/package-contracts/terminating-tools.js +9 -3
  17. package/dist/packaged-role-registry.js +10 -12
  18. package/dist/public-cli/countersign-run.js +1 -1
  19. package/dist/public-cli/main.js +90 -86
  20. package/dist/public-cli/option-definitions.js +2 -2
  21. package/dist/public-cli/settlement.js +11 -11
  22. package/dist/review-submission.js +20 -0
  23. package/dist/submission-errors.js +2 -2
  24. package/dist/submission-ledger.js +14 -2
  25. package/package.json +1 -1
  26. package/souls/doctor-auditor.md +1 -1
  27. package/souls/fixer-auditor.md +4 -4
  28. package/souls/gate-output-guide.md +2 -2
  29. package/souls/inspector.md +1 -1
  30. package/souls/judge-auditor.md +2 -2
  31. package/souls/notary.md +1 -1
  32. package/souls/quality-law.md +1 -1
  33. package/src/analyst-gate-cycles-read.ts +17 -9
  34. package/src/analyst-metric-families/gate-cycles.ts +2 -2
  35. package/src/audit-escalation.ts +8 -8
  36. package/src/auditor-role.ts +1 -1
  37. package/src/compliance-transport.ts +7 -7
  38. package/src/countersign-contracts.ts +10 -8
  39. package/src/countersign-role.ts +6 -35
  40. package/src/doctor-evidence.ts +2 -1
  41. package/src/doctor-role.ts +1 -2
  42. package/src/gatekeeper-role.ts +30 -55
  43. package/src/host-contracts.ts +4 -3
  44. package/src/inspector-contracts.ts +7 -6
  45. package/src/inspector-role.ts +3 -20
  46. package/src/judge-role.ts +29 -49
  47. package/src/navigator-invocation-identity.ts +16 -4
  48. package/src/notary-contracts.ts +8 -26
  49. package/src/notary-role.ts +1 -1
  50. package/src/package-contracts/auditor-output.ts +6 -27
  51. package/src/package-contracts/judge-output.ts +10 -8
  52. package/src/package-contracts/terminating-tools.ts +9 -3
  53. package/src/packaged-role-registry.ts +12 -13
  54. package/src/pi/adapter.ts +6 -5
  55. package/src/public-cli/countersign-run.ts +1 -1
  56. package/src/public-cli/option-definitions.ts +2 -2
  57. package/src/public-cli/settlement.ts +13 -14
  58. package/src/review-submission.ts +29 -0
  59. package/src/role-envelope.ts +17 -9
  60. package/src/role-runtime.ts +32 -15
  61. package/src/submission-errors.ts +6 -6
  62. package/src/{gatekeeper-pass-envelope.ts → submission-gate.ts} +29 -16
  63. package/src/submission-ledger.ts +13 -3
  64. package/src/ticket-trajectory.ts +9 -4
  65. package/src/worker-role.ts +16 -2
@@ -1,8 +1,5 @@
1
1
  import type { Static } from "typebox";
2
- import { Type } from "typebox";
3
-
4
- import { openToolObject } from "./open-tool-schema.ts";
5
- import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
2
+ import { reviewSubmissionSchema } from "./review-submission.ts";
6
3
  import {
7
4
  INSPECTOR_OUTPUT_TOOL_NAME,
8
5
  validateRecordedInspectorOutput,
@@ -17,21 +14,7 @@ export type { InspectorOutput };
17
14
  export { validateRecordedInspectorOutput };
18
15
 
19
16
  /** 台院事后察举交卷形状;形状指引,非 schema 闸。 */
20
- export const inspectorOutputSchema = withTerminatingOutputDeclarations(
21
- openToolObject(
22
- Type.Object({
23
- status: Type.Unknown({
24
- description: "pass | bounce | escalate — 形状指引,非 schema 闸",
25
- }),
26
- findings: Type.Unknown({
27
- description: "随交卷留存的问题记录",
28
- }),
29
- reason: Type.Optional(Type.Unknown({
30
- description: "status 为 escalate 时的上呈理由",
31
- })),
32
- }),
33
- ),
34
- );
17
+ export const inspectorOutputSchema = reviewSubmissionSchema;
35
18
 
36
19
  export type InspectorOutputParameters = Static<typeof inspectorOutputSchema>;
37
20
 
@@ -45,7 +28,7 @@ export type InspectorRuntimeDependencies = {
45
28
  export const INSPECTOR_TOOL_SPEC = {
46
29
  name: INSPECTOR_OUTPUT_TOOL_NAME,
47
30
  label: "台院输出",
48
- description: "台院终局回执,状态为 pass、bounce 或 escalate。",
31
+ description: "台院终局回执,状态为 converged、continue 或 escalate。",
49
32
  promptSnippet: "台院终局回执",
50
33
  parameters: inspectorOutputSchema,
51
34
  } as const;
package/src/judge-role.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
2
- import { Type, type Static } from "typebox";
3
-
4
- import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
2
+ import type { Static } from "typebox";
3
+ import { reviewSubmissionSchema } from "./review-submission.ts";
5
4
  import { GatekeeperDecisionError, ParentQueueReaskError } from "./submission-errors.ts";
6
5
  import { projectGatekeeperEscalation } from "./audit-escalation.ts";
7
6
 
@@ -14,50 +13,17 @@ import {
14
13
 
15
14
  const JUDGE_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
16
15
  const JUDGE_STATUS_REASK =
17
- "judgeStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
16
+ "status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
18
17
 
19
18
  export { JUDGE_OUTPUT_TOOL_NAME };
20
19
  export type { JudgeVerdict };
21
20
 
22
21
  // #836 r16 class 1: fix/classes/note/decisionGate are LLM/human-read narrative
23
22
  // content — 符宝郎/审刑院 read the raw receipt, no code branches on their length
24
- // or nested presence. `judgeStatus` alone is the machine discriminator the
25
- // queue reads to pick pass/bounce/escalate (src/judge-role.ts:90-125).
26
- // #836 (ADR 0003 Amendment): judgeStatus kept open like countersignStatus
27
- // (src/countersign-role.ts) — a closed domain here would reject an unknown
28
- // value before the rawStatus/ParentQueueReaskError check below ever runs.
29
- export const judgeVerdictSchema = withTerminatingOutputDeclarations(
30
- Type.Object(
31
- {
32
- judgeStatus: Type.Unknown({ description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
33
- fix: Type.Optional(
34
- Type.Object(
35
- { summary: Type.Optional(Type.String({ description: "continue 时的补救摘要" })) },
36
- { additionalProperties: true, description: "continue 时的补救说明" },
37
- ),
38
- ),
39
- classes: Type.Optional(Type.Array(Type.Object({
40
- name: Type.Optional(Type.String()),
41
- owner: Type.Optional(Type.String()),
42
- boundary: Type.Optional(Type.String()),
43
- disposition: Type.Optional(Type.String()),
44
- }, { additionalProperties: true }), { description: "已裁决 finding 类及其 owner 与修理边界" })),
45
- note: Type.Optional(Type.String({ description: "可选裁决附注" })),
46
- evidence: Type.Optional(Type.Unknown({ description: "留存的裁决证据" })),
47
- decisionGate: Type.Optional(
48
- Type.Object(
49
- {
50
- question: Type.Optional(Type.String()),
51
- options: Type.Optional(Type.Array(Type.String())),
52
- },
53
- { additionalProperties: true, description: "需人权威处置的问题与选项" },
54
- ),
55
- ),
56
- },
57
- { additionalProperties: true },
58
- ),
59
- );
60
- (judgeVerdictSchema as unknown as { required: string[] }).required = [];
23
+ // or nested presence. `status` alone is the machine discriminator the
24
+ // queue reads to pick converged/continue/escalate (below).
25
+ // The shared open schema preserves role-specific prose without adding another contract.
26
+ export const judgeVerdictSchema = reviewSubmissionSchema;
61
27
 
62
28
  type JudgeVerdictParameters = Static<typeof judgeVerdictSchema>;
63
29
 
@@ -97,12 +63,12 @@ export function createJudgeRoleRuntime(
97
63
  parameters: judgeVerdictSchema,
98
64
  async execute(toolCallId: string, parameters: Static<typeof judgeVerdictSchema>, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext): Promise<HostToolResult<unknown>> {
99
65
  if (soul === undefined) throw new Error("大理寺职分未装载");
100
- // #756 queue: read judgeStatus only — escalate skips gates (thrown to caller);
66
+ // #756 queue: read shared status only — escalate skips gates;
101
67
  // unreadable status returns to judge; else 符宝郎内闸 then 审刑院合规.
102
68
  const rawStatus =
103
69
  parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
104
- && typeof (parameters as Record<string, unknown>).judgeStatus === "string"
105
- ? (parameters as Record<string, unknown>).judgeStatus as string
70
+ && typeof (parameters as Record<string, unknown>).status === "string"
71
+ ? (parameters as Record<string, unknown>).status as string
106
72
  : undefined;
107
73
  if (rawStatus === undefined || !JUDGE_QUEUE_STATUSES.has(rawStatus)) {
108
74
  throw new ParentQueueReaskError(JUDGE_STATUS_REASK);
@@ -119,10 +85,10 @@ export function createJudgeRoleRuntime(
119
85
  const verdict = validateVerdict(parameters);
120
86
  // Candidate verdict is already on the parent session books as this
121
87
  // tool-call leaf (first-record-then-audit; run 019fea05 L61/L62).
122
- // #753: 符宝郎内闸 — bounce returns raw receipt; escalate pauses.
123
- let draftPass: { receipt: unknown } | undefined;
88
+ // #753: 符宝郎内闸 — continue returns the raw receipt as a nonterminal tool result.
89
+ let draftPass: { status: "converged" | "continue"; receipt: unknown } | undefined;
124
90
  try {
125
- const obtainedDraftPass = await pi.requireGatekeeperPass!({
91
+ const obtainedDraftPass = await pi.requireSubmissionGate!({
126
92
  context: ctx,
127
93
  subject: { kind: "judge_draft" },
128
94
  ...(signal === undefined ? {} : { signal }),
@@ -132,11 +98,18 @@ export function createJudgeRoleRuntime(
132
98
  submission: parameters,
133
99
  });
134
100
  draftPass = obtainedDraftPass || undefined;
101
+ if (draftPass?.status === "continue") {
102
+ return {
103
+ content: [{ type: "text" as const, text: readableGateItem(draftPass.receipt) }],
104
+ details: verdict,
105
+ terminate: false,
106
+ };
107
+ }
135
108
  if (draftPass !== undefined) hostActions.bindPriorGatePass(toolCallId, draftPass.receipt);
136
109
  // #756: 审刑院合规路径 — same review-queue law as 符宝郎/台院.
137
- // pass → accept; bounce → raw auditor receipt; escalate → pause;
110
+ // converged → accept; continue → raw auditor receipt; escalate → pause;
138
111
  // not three-state → resume auditor; no round cap; no disposeCompliance mapping.
139
- const compliancePass = await pi.requireGatekeeperPass!({
112
+ const compliancePass = await pi.requireSubmissionGate!({
140
113
  context: ctx,
141
114
  subject: { kind: "judge_compliance" },
142
115
  ...(signal === undefined ? {} : { signal }),
@@ -145,6 +118,13 @@ export function createJudgeRoleRuntime(
145
118
  // #879: same parent payload for 审刑院; not recovered from session latest.
146
119
  submission: parameters,
147
120
  });
121
+ if (compliancePass?.status === "continue") {
122
+ return {
123
+ content: [draftPass, compliancePass].filter((pass) => pass !== undefined).map((pass) => ({ type: "text" as const, text: readableGateItem(pass.receipt) })),
124
+ details: verdict,
125
+ terminate: false,
126
+ };
127
+ }
148
128
  return {
149
129
  content: [draftPass, compliancePass].filter((pass) => pass !== undefined).map((pass) => ({ type: "text" as const, text: readableGateItem(pass.receipt) })),
150
130
  details: verdict,
@@ -24,7 +24,7 @@
24
24
  * - marker role/phase/subjectKey must match the terminal + independent expected
25
25
  */
26
26
 
27
- import { PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
27
+ import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "./packaged-role-registry.ts";
28
28
  import { isUuidV7, uuidv7 } from "./uuidv7.ts";
29
29
  import { workSubjectKeysEqual } from "./work-subject-identity.ts";
30
30
 
@@ -113,7 +113,8 @@ export function isNavigatorInfrastructureFailureFact(
113
113
  }
114
114
 
115
115
  const PACKAGED_ROLE_OUTPUT_TOOLS: ReadonlyMap<string, string> = new Map(
116
- PACKAGED_ROLE_REGISTRY.map((entry) => [entry.outputTool, entry.role]),
116
+ [...PACKAGED_ROLE_REGISTRY.filter((entry) => entry.outputTool !== "ak_submission_output")
117
+ .map((entry) => [entry.outputTool, entry.role] as const), ...LEGACY_REVIEW_OUTPUT_ROLES],
117
118
  );
118
119
 
119
120
  export type NavigatorInvocationEntryLike = {
@@ -226,7 +227,7 @@ export function classifyPackagedRoleTerminalResult(
226
227
  message: PackagedRoleTerminalMessage,
227
228
  ): PackagedRoleTerminalClassification {
228
229
  if (typeof message.toolName !== "string") return { kind: "nonterminal" };
229
- if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName)) return { kind: "nonterminal" };
230
+ if (!PACKAGED_ROLE_OUTPUT_TOOLS.has(message.toolName) && message.toolName !== "ak_submission_output") return { kind: "nonterminal" };
230
231
 
231
232
  // Base identity is enough; durable details may carry typed failure evidence (#475).
232
233
  const hasInfraBase = hasNavigatorInfrastructureFailureBase(message.details);
@@ -285,12 +286,23 @@ function durableTerminalAt(
285
286
  ? entry.message
286
287
  : undefined;
287
288
  if (typeof message?.toolName !== "string") return undefined;
288
- const role = PACKAGED_ROLE_OUTPUT_TOOLS.get(message.toolName);
289
+ const marker = message.toolName === "ak_submission_output"
290
+ ? entries.slice(0, index).reverse().find(isInvocationMarkerEntry)
291
+ : undefined;
292
+ const role = message.toolName === "ak_submission_output"
293
+ ? parseInvocationMarkerIdentity(marker?.data)?.role ?? "unknown"
294
+ : PACKAGED_ROLE_OUTPUT_TOOLS.get(message.toolName);
289
295
  if (role === undefined) return undefined;
290
296
  const classification = classifyPackagedRoleTerminalResult(message);
291
297
  if (classification.kind !== "accepted" && classification.kind !== "infrastructure") {
292
298
  return undefined;
293
299
  }
300
+ // A non-error toolResult is only a submitted candidate: the gate may still
301
+ // return continue even when the parent's own status was converged. Current
302
+ // invocations seal via the ledger-owned closure entry. Historical named
303
+ // review tools predate that entry and remain readable as legacy terminals.
304
+ if (entry?.type === "message" && classification.kind === "accepted"
305
+ && !LEGACY_REVIEW_OUTPUT_ROLES.has(message.toolName)) return undefined;
294
306
  return {
295
307
  index,
296
308
  role,
@@ -1,14 +1,11 @@
1
1
  /**
2
2
  * Public Notary (符宝郎) terminating receipt contracts.
3
- * Lawful explicit releases: pass | bounce | escalate.
3
+ * Lawful explicit releases: converged | continue | escalate.
4
4
  * No usable result is infrastructure failure via public settlement, not a judgment status (#475).
5
5
  */
6
- import { Type } from "typebox";
6
+ import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME, reviewSubmissionSchema } from "./review-submission.ts";
7
7
 
8
- import { openToolObject } from "./open-tool-schema.ts";
9
- import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
10
-
11
- export const NOTARY_OUTPUT_TOOL_NAME = "ak_notary_output";
8
+ export const NOTARY_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
12
9
  export const NOTARY_SOURCE_RUN_FLAG = {
13
10
  name: "ak-notary-source-run",
14
11
  definition: {
@@ -30,21 +27,7 @@ export const NOTARY_TICKET_FLAG = {
30
27
  export const NOTARY_FIXED_KICKOFF =
31
28
  "符宝郎案卷已受理;来源 run 定位见会话材料。";
32
29
 
33
- export const notaryOutputSchema = withTerminatingOutputDeclarations(
34
- openToolObject(
35
- Type.Object({
36
- status: Type.Unknown({
37
- description: "pass | bounce | escalate — 形状指引,非 schema 闸",
38
- }),
39
- findings: Type.Unknown({
40
- description: "string[] findings,随 pass、bounce 或 escalate 留存",
41
- }),
42
- reason: Type.Optional(Type.Unknown({
43
- description: "status 为 escalate 时的上呈理由",
44
- })),
45
- }),
46
- ),
47
- );
30
+ export const notaryOutputSchema = reviewSubmissionSchema;
48
31
 
49
32
  export type NotarySourceRunLocator = {
50
33
  readonly runDirectory: string;
@@ -54,8 +37,8 @@ export type NotarySourceRunLocator = {
54
37
 
55
38
  /** Recognition face only — fields retained as submitted (#753, no disposition/findings forge). */
56
39
  export type NotaryOutput =
57
- | { readonly status: "pass"; readonly findings?: unknown }
58
- | { readonly status: "bounce"; readonly findings?: unknown }
40
+ | { readonly status: "converged"; readonly findings?: unknown }
41
+ | { readonly status: "continue"; readonly findings?: unknown }
59
42
  | {
60
43
  readonly status: "escalate";
61
44
  readonly reason?: unknown;
@@ -67,7 +50,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
67
50
  }
68
51
 
69
52
  /**
70
- * Recognize one lawful explicit Notary release (pass | bounce | escalate).
53
+ * Recognize one lawful explicit Notary release (converged | continue | escalate).
71
54
  * #753: no field rewrite — submitted params retained as-is (no disposition forge,
72
55
  * no findings array rewrite). Recognition only for recording/settlement callers.
73
56
  */
@@ -89,7 +72,7 @@ export function retainNotarySubmission(value: unknown): unknown {
89
72
  }
90
73
 
91
74
  /**
92
- * Settlement/recording path: only lawful recorded pass/bounce/escalate.
75
+ * Settlement/recording path: only lawful recorded converged/continue/escalate.
93
76
  * Does not gate role admission — callers must not use this to reject a submission.
94
77
  */
95
78
  export function validateRecordedNotaryOutput(value: unknown): NotaryOutput {
@@ -98,4 +81,3 @@ export function validateRecordedNotaryOutput(value: unknown): NotaryOutput {
98
81
  }
99
82
  return value as NotaryOutput;
100
83
  }
101
-
@@ -159,7 +159,7 @@ export function createNotaryRoleRuntime(
159
159
  pi.registerTool({
160
160
  name: NOTARY_OUTPUT_TOOL_NAME,
161
161
  label: "符宝郎输出",
162
- description: "提交引文保真与票面对齐的 typed pass/bounce/escalate 决议。",
162
+ description: "提交引文保真与票面对齐的 converged/continue/escalate 决议。",
163
163
  promptSnippet: "提交符宝郎决议",
164
164
  parameters: notaryOutputSchema,
165
165
  async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext, ): Promise<HostToolResult<unknown>> {
@@ -1,36 +1,16 @@
1
1
  /**
2
2
  * Public Auditor (审刑院) terminating receipt contracts (#675 / #754).
3
- * Lawful explicit releases: pass | bounce | escalate.
3
+ * Lawful explicit releases: converged | continue | escalate.
4
4
  */
5
- import { Type } from "typebox";
5
+ import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME, reviewSubmissionSchema } from "../review-submission.ts";
6
6
 
7
- import { openToolObject } from "../open-tool-schema.ts";
8
- import { withTerminatingOutputDeclarations } from "./terminating-infrastructure.ts";
7
+ export const AUDITOR_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
9
8
 
10
- export const AUDITOR_OUTPUT_TOOL_NAME = "ak_auditor_output" as const;
11
-
12
- export const auditorOutputSchema = withTerminatingOutputDeclarations(
13
- openToolObject(
14
- Type.Object({
15
- status: Type.Unknown({
16
- description: "pass | bounce | escalate — 形状指引,非 schema 闸",
17
- }),
18
- violations: Type.Optional(Type.Unknown({
19
- description: "status 为 bounce 时的违规条目",
20
- })),
21
- conflicts: Type.Optional(Type.Unknown({
22
- description: "status 为 escalate 时的冲突",
23
- })),
24
- decisionGate: Type.Optional(Type.Unknown({
25
- description: "status 为 escalate 时的决策闸",
26
- })),
27
- }),
28
- ),
29
- );
9
+ export const auditorOutputSchema = reviewSubmissionSchema;
30
10
 
31
11
  export type AuditorOutput =
32
- | { readonly status: "pass" }
33
- | { readonly status: "bounce"; readonly violations?: unknown }
12
+ | { readonly status: "converged" }
13
+ | { readonly status: "continue"; readonly violations?: unknown }
34
14
  | {
35
15
  readonly status: "escalate";
36
16
  readonly conflicts?: unknown;
@@ -50,4 +30,3 @@ export function validateRecordedAuditorOutput(value: unknown): AuditorOutput {
50
30
  if (!isRecord(value)) throw new Error("Auditor output is not an object");
51
31
  return value as AuditorOutput;
52
32
  }
53
-
@@ -1,7 +1,9 @@
1
1
  /** Package-owned Judge output leaf — no role registration surface. */
2
2
 
3
3
 
4
- export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
4
+ import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "../review-submission.ts";
5
+
6
+ export const JUDGE_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
5
7
 
6
8
  export type JudgeClass = {
7
9
  name: string;
@@ -11,16 +13,16 @@ export type JudgeClass = {
11
13
  };
12
14
 
13
15
  export type JudgeVerdict =
14
- | { judgeStatus: "converged"; note?: string; evidence?: unknown }
16
+ | { status: "converged"; note?: string; evidence?: unknown }
15
17
  | {
16
- judgeStatus: "continue";
18
+ status: "continue";
17
19
  fix: { summary: string };
18
20
  classes: JudgeClass[];
19
21
  note?: string;
20
22
  evidence?: unknown;
21
23
  }
22
24
  | {
23
- judgeStatus: "escalate";
25
+ status: "escalate";
24
26
  decisionGate: { question: string; options: string[] };
25
27
  note?: string;
26
28
  evidence?: unknown;
@@ -28,17 +30,17 @@ export type JudgeVerdict =
28
30
 
29
31
  export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
30
32
  if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) throw new Error("Judge verdict has no execution discriminator");
31
- let judgeStatus: unknown;
33
+ let status: unknown;
32
34
  try {
33
- judgeStatus = (verdict as Record<string, unknown>).judgeStatus;
35
+ status = (verdict as Record<string, unknown>).status;
34
36
  } catch {
35
37
  throw new Error("Judge verdict has no execution discriminator");
36
38
  }
37
- if (typeof judgeStatus !== "string") {
39
+ if (typeof status !== "string") {
38
40
  throw new Error("Judge verdict has no execution discriminator");
39
41
  }
40
42
  if (
41
- ["converged", "continue", "escalate"].includes(judgeStatus)
43
+ ["converged", "continue", "escalate"].includes(status)
42
44
  ) {
43
45
  return verdict as JudgeVerdict;
44
46
  }
@@ -18,7 +18,7 @@ import {
18
18
  validateReviewerIntent,
19
19
  type ReviewerIntent,
20
20
  } from "./reviewer-output.ts";
21
- import { PACKAGED_ROLE_REGISTRY } from "../packaged-role-registry.ts";
21
+ import { LEGACY_REVIEW_OUTPUT_ROLES, PACKAGED_ROLE_REGISTRY } from "../packaged-role-registry.ts";
22
22
  import { CorrectableSubmissionError } from "../submission-correctable-error.ts";
23
23
  import { DOCTOR_OUTPUT_TOOL_NAME, validateDoctorSubmissionShape, validateRecordedDoctorOutput, type DoctorOutput, type DoctorSubmission } from "../doctor-contracts.ts";
24
24
  import { GATEKEEPER_OUTPUT_TOOL_NAME, validateRecordedGatekeeperOutput, type GatekeeperDirectOutput } from "./gatekeeper-output.ts";
@@ -80,7 +80,10 @@ export type {
80
80
  SecretariatVerdict,
81
81
  };
82
82
 
83
- export const TERMINATING_TOOL_NAMES = PACKAGED_ROLE_REGISTRY.map((entry) => entry.outputTool);
83
+ export const TERMINATING_TOOL_NAMES = [...new Set([
84
+ ...PACKAGED_ROLE_REGISTRY.map((entry) => entry.outputTool),
85
+ ...LEGACY_REVIEW_OUTPUT_ROLES.keys(),
86
+ ])];
84
87
 
85
88
  export type TerminatingToolName = (typeof TERMINATING_TOOL_NAMES)[number];
86
89
 
@@ -152,7 +155,10 @@ export type AcceptedFacts = {
152
155
  export function acceptedFacts(toolName: TerminatingToolName, details: AcceptedDetails): AcceptedFacts {
153
156
  const record = details as Record<string, unknown>;
154
157
  const entry = PACKAGED_ROLE_REGISTRY.find((candidate) => candidate.outputTool === toolName);
155
- const statusKey = entry !== undefined && "receiptStatusKey" in entry ? entry.receiptStatusKey : "status";
158
+ const legacyRole = LEGACY_REVIEW_OUTPUT_ROLES.get(toolName);
159
+ const statusKey = legacyRole === "judge" || legacyRole === "countersign"
160
+ ? `${legacyRole}Status`
161
+ : entry !== undefined && "receiptStatusKey" in entry ? entry.receiptStatusKey : "status";
156
162
  const status = typeof record[statusKey] === "string" ? record[statusKey] : undefined;
157
163
  const commitKey = entry !== undefined && "receiptCommitKey" in entry ? entry.receiptCommitKey : undefined;
158
164
  const commitWhen = entry !== undefined && "receiptCommitWhen" in entry ? entry.receiptCommitWhen : undefined;
@@ -17,6 +17,15 @@ import { DIARIST_OUTPUT_TOOL_NAME } from "./diarist-contracts.ts";
17
17
  import { SECRETARIAT_OUTPUT_TOOL_NAME } from "./secretariat-contracts.ts";
18
18
  import { JUDGE_AUDIT_TOOL_NAME } from "./judge-auditor.ts";
19
19
 
20
+ /** Historical durable session names; these are read aliases, never registered tools. */
21
+ export const LEGACY_REVIEW_OUTPUT_ROLES = new Map<string, string>([
22
+ ["ak_judge_output", "judge"],
23
+ ["ak_notary_output", "notary"],
24
+ ["ak_countersign_output", "countersign"],
25
+ ["ak_inspector_output", "inspector"],
26
+ ["ak_auditor_output", "auditor"],
27
+ ]);
28
+
20
29
  /**
21
30
  * Success-face fields that still differ by seat. Presence and omission match
22
31
  * the former per-seat publishers. Key order is not part of the contract.
@@ -92,7 +101,7 @@ export const PUBLIC_ROLE_RECORDS = [
92
101
  /** Navigator subject is the public admitted instruction when the run is bound. */
93
102
  navigatorSubject: "public-instruction",
94
103
  activationStage: "load-and-install",
95
- receiptStatusKey: "judgeStatus",
104
+ receiptStatusKey: "status",
96
105
  sessionMaterials: [
97
106
  "CLAUDE.md",
98
107
  "souls/judge.md",
@@ -374,15 +383,13 @@ export const PUBLIC_ROLE_RECORDS = [
374
383
  outputTool: COUNTERSIGN_OUTPUT_TOOL_NAME,
375
384
  settlement: "accepted",
376
385
  gateStageLabel: "给事中",
377
- /** Gate queue reads countersignStatus, not the shared three-state status. */
378
- gateDecision: "countersign-status",
379
386
  /** Gate summon carries the parent payload as the instruction and a parent run id. */
380
387
  gateSummon: "parent-instruction",
381
388
  activationFlags: [
382
389
  { field: "ticketNumber" },
383
390
  ],
384
391
  activationStage: "load-and-install",
385
- receiptStatusKey: "countersignStatus",
392
+ receiptStatusKey: "status",
386
393
  // #924: 公用《票面法》三席同装
387
394
  sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md"],
388
395
  },
@@ -804,7 +811,7 @@ export function packagedGateStageLabel(role: string): string | undefined {
804
811
  return record.gateStageLabel;
805
812
  }
806
813
 
807
- /** Countersign gate queue reads countersignStatus. Every other officer uses the shared three-state. */
814
+ /** How the gate caller builds one officer summons. Pointer is the inspector face. */
808
815
  export type PackagedGateSummon = "source-run" | "subject-source" | "parent-instruction" | "pointer";
809
816
 
810
817
  /** How the gate caller builds one officer summons. Pointer is the inspector face. */
@@ -813,11 +820,3 @@ export function packagedGateSummon(role: string): PackagedGateSummon {
813
820
  if (record !== undefined && "gateSummon" in record) return record.gateSummon;
814
821
  return "pointer";
815
822
  }
816
-
817
- export function packagedGateDecision(role: string): "countersign-status" | "three-state" {
818
- const record = packagedRoleMetadata(role);
819
- if (record !== undefined && "gateDecision" in record && record.gateDecision === "countersign-status") {
820
- return "countersign-status";
821
- }
822
- return "three-state";
823
- }
package/src/pi/adapter.ts CHANGED
@@ -7,8 +7,8 @@ import {
7
7
  type ToolDefinition,
8
8
  } from "@earendil-works/pi-coding-agent";
9
9
  import type { Static, TSchema } from "typebox";
10
- import type { GatekeeperNonPassResult, GatekeeperSubject } from "../gatekeeper-role.ts";
11
- import { requireGatekeeperPass } from "../gatekeeper-pass-envelope.ts";
10
+ import type { SubmissionGateNonPassResult, GatekeeperSubject } from "../gatekeeper-role.ts";
11
+ import { requireSubmissionGate } from "../submission-gate.ts";
12
12
  import type {
13
13
  HostContext,
14
14
  HostEventRegistration,
@@ -85,7 +85,7 @@ export function toPiContext(context: HostContext): ExtensionContext {
85
85
  }
86
86
 
87
87
  /** Gatekeeper receives HostContext directly (#518 §1③) without recovering raw ExtensionContext via WeakMap. */
88
- function requirePiGatekeeperPass(options: {
88
+ function requirePiSubmissionGate(options: {
89
89
  context: HostContext;
90
90
  subject: GatekeeperSubject;
91
91
  signal?: AbortSignal;
@@ -93,11 +93,12 @@ function requirePiGatekeeperPass(options: {
93
93
  toolCallId: string;
94
94
  submission?: unknown;
95
95
  }): Promise<void | {
96
+ readonly status: "converged" | "continue";
96
97
  readonly officer: string;
97
98
  readonly receipt: unknown;
98
99
  readonly runId?: string;
99
100
  }> {
100
- return requireGatekeeperPass({
101
+ return requireSubmissionGate({
101
102
  context: options.context,
102
103
  subject: options.subject,
103
104
  ...(options.signal === undefined ? {} : { signal: options.signal }),
@@ -203,7 +204,7 @@ export function createPiRoleHostAdapter(
203
204
  })),
204
205
  setActiveTools: (names) => pi.setActiveTools(names),
205
206
  getActiveTools: () => pi.getActiveTools(),
206
- requireGatekeeperPass: requirePiGatekeeperPass,
207
+ requireSubmissionGate: requirePiSubmissionGate,
207
208
  on(...registration: HostEventRegistration) {
208
209
  const context = (value: ExtensionContext) => projectPiContext(value, options.transcriptFromContext);
209
210
  if (registration[0] === "before_agent_start") {
@@ -89,7 +89,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
89
89
  function isEscalatePayload(payload: unknown): boolean {
90
90
  if (!isRecord(payload)) return false;
91
91
  return (
92
- payload.status === "escalate" || payload.countersignStatus === "escalate"
92
+ payload.status === "escalate"
93
93
  );
94
94
  }
95
95
 
@@ -1199,7 +1199,7 @@ const ROLE_COMMAND_HELP = {
1199
1199
  },
1200
1200
  inspector: {
1201
1201
  command: "inspector",
1202
- summary: "Direct Inspector (台院) complexity and test-quality check; pass or bounce.",
1202
+ summary: "Direct Inspector (台院) complexity and test-quality check; converged, continue, or escalate.",
1203
1203
  usage: ["ak-role inspector [options] [instruction]"],
1204
1204
  examples: [
1205
1205
  'ak-role inspector --attach ./change.patch "Review this material."',
@@ -1223,7 +1223,7 @@ const ROLE_COMMAND_HELP = {
1223
1223
  },
1224
1224
  auditor: {
1225
1225
  command: "auditor",
1226
- summary: "Direct Auditor (审刑院) compliance audit: pass, bounce, or escalate.",
1226
+ summary: "Direct Auditor (审刑院) compliance audit: converged, continue, or escalate.",
1227
1227
  usage: ["ak-role auditor --subject <judge|doctor> --source-run <runId@role|path> [options] [instruction]"],
1228
1228
  examples: [
1229
1229
  'ak-role auditor --subject judge --source-run 01abc…@judge --attach ./dossier "审:本 run 是否合规。"',
@@ -2935,10 +2935,9 @@ async function settleLawfulSeatAcceptedTerminalResult(
2935
2935
  const entries = await readLawfulSettlementEntries(sessionFile) ?? [];
2936
2936
  const submissions = await recordedSubmissionPayloads(admitted, scope);
2937
2937
  // #843: collector shape (ledger closed first) plus current user-turn freshness.
2938
- // A lawful bound non-error seat toolResult (isError === false + one-to-one call
2939
- // bind, same grade as residual) means this turn sealed — a prior bounce residual
2940
- // in the same turn must not outrank it. Missing isError, unbound, or mis-bound
2941
- // results do not establish success and do not reject/abort. A current-attempt
2938
+ // Only the ledger-owned closure establishes this turn's success. A non-error
2939
+ // toolResult may be a candidate whose nested gate continued the conversation.
2940
+ // A current-attempt
2942
2941
  // residual without that success is this turn's own failure and must not be
2943
2942
  // masked by run-scoped stale acceptance (bare resume without courtAttemptId).
2944
2943
  // Same-turn accept-then-bounce keeps the success marker: terminal stays
@@ -2947,18 +2946,16 @@ async function settleLawfulSeatAcceptedTerminalResult(
2947
2946
  let thisAttemptHasSeatSuccess = false;
2948
2947
  let residual: BoundErroredToolCandidate | undefined;
2949
2948
  for (let index = entries.length - 1; index >= scanStart; index -= 1) {
2949
+ const entry = entries[index];
2950
+ if (entry?.type === "custom" && entry.customType === "ak-role-submission-closure"
2951
+ && isRecord(entry.data) && entry.data.toolName === spec.toolName
2952
+ && classifyPackagedRoleTerminalResult(entry.data).kind === "accepted") {
2953
+ thisAttemptHasSeatSuccess = true;
2954
+ }
2950
2955
  const message = entries[index]?.message;
2951
2956
  if (message?.role !== "toolResult") continue;
2952
2957
  if (message.toolName !== spec.toolName) continue;
2953
- if (message.isError === false) {
2954
- if (
2955
- boundRoleToolCallForResult(entries, index, message, spec.toolName) !==
2956
- undefined
2957
- ) {
2958
- thisAttemptHasSeatSuccess = true;
2959
- }
2960
- continue;
2961
- }
2958
+ if (message.isError === false) continue;
2962
2959
  if (residual === undefined) {
2963
2960
  residual = boundErroredToolCandidate(
2964
2961
  entries,
@@ -2969,7 +2966,9 @@ async function settleLawfulSeatAcceptedTerminalResult(
2969
2966
  }
2970
2967
  }
2971
2968
  const roleOutcome = await sealedLedgerOutcome(admitted, spec.role as TerminalRoleName, scope);
2972
- if (roleOutcome !== undefined && (thisAttemptHasSeatSuccess || residual === undefined)) {
2969
+ if (roleOutcome !== undefined && (
2970
+ thisAttemptHasSeatSuccess || residual === undefined || (scope?.courtAttemptId !== undefined && scope.courtAttemptId.length > 0)
2971
+ )) {
2973
2972
  const navigator = extractNavigatorFact(entries);
2974
2973
  const artifacts = await publishDeclaredSeatArtifacts(
2975
2974
  admitted,