@akagilnc/pi-workflow-roles 0.1.3685 → 0.1.3702

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 (54) hide show
  1. package/dist/acp-host/production-host.js +499 -559
  2. package/dist/analyst-gate-cycles-read.js +11 -1
  3. package/dist/archivist-record-entry.js +1 -1
  4. package/dist/auditor-dossier-tool.js +12 -0
  5. package/dist/dossier-resolution.js +0 -35
  6. package/dist/gatekeeper-pass-envelope.js +39 -34
  7. package/dist/gatekeeper-role.js +70 -77
  8. package/dist/judge-auditor.js +5 -24
  9. package/dist/judge-role.js +39 -42
  10. package/dist/notary-contracts.js +2 -17
  11. package/dist/notary-role.js +3 -6
  12. package/dist/package-contracts/judge-output.js +0 -2
  13. package/dist/package-contracts/terminating-tools.js +2 -2
  14. package/dist/public-cli/inspector-run.js +16 -2
  15. package/dist/public-cli/instruction-seat-run.js +14 -2
  16. package/dist/public-cli/main.js +189 -214
  17. package/dist/public-cli/notary-run.js +23 -20
  18. package/dist/public-cli/post-admission.js +30 -23
  19. package/dist/public-cli/run-lifecycle.js +6 -14
  20. package/dist/public-role-summons.js +38 -11
  21. package/dist/role-runtime.js +28 -3
  22. package/dist/submission-correctable-error.js +2 -1
  23. package/dist/submission-errors.js +26 -11
  24. package/dist/submission-ledger.js +7 -23
  25. package/extensions/role-runtime.ts +0 -2
  26. package/package.json +1 -1
  27. package/souls/countersign.md +12 -11
  28. package/souls/notary.md +5 -0
  29. package/src/acp-host/production-host.ts +1 -4
  30. package/src/acp-host/role-envelope.ts +1 -1
  31. package/src/analyst-gate-cycles-read.ts +10 -1
  32. package/src/archivist-record-entry.ts +7 -3
  33. package/src/auditor-dossier-tool.ts +21 -0
  34. package/src/dossier-resolution.ts +0 -39
  35. package/src/gatekeeper-pass-envelope.ts +59 -43
  36. package/src/gatekeeper-role.ts +114 -113
  37. package/src/host-contracts.ts +3 -3
  38. package/src/judge-auditor.ts +5 -43
  39. package/src/judge-role.ts +41 -54
  40. package/src/notary-contracts.ts +8 -27
  41. package/src/notary-role.ts +2 -7
  42. package/src/package-contracts/judge-output.ts +0 -1
  43. package/src/package-contracts/terminating-tools.ts +2 -2
  44. package/src/public-cli/inspector-run.ts +32 -2
  45. package/src/public-cli/instruction-seat-run.ts +30 -2
  46. package/src/public-cli/notary-run.ts +48 -16
  47. package/src/public-cli/post-admission.ts +31 -37
  48. package/src/public-cli/run-lifecycle.ts +6 -17
  49. package/src/public-role-summons.ts +76 -12
  50. package/src/role-runtime.ts +34 -9
  51. package/src/submission-correctable-error.ts +2 -0
  52. package/src/submission-errors.ts +28 -11
  53. package/src/submission-ledger.ts +8 -32
  54. package/dist/shape-unreadable-failure.js +0 -34
@@ -350,6 +350,10 @@ export async function readAnalystGateCyclesFromAuditorRoles(auditorRolesDirector
350
350
  ? [auditorRolesDirectory]
351
351
  : auditorRolesDirectory;
352
352
  const volumes = [];
353
+ // Same officer session pointed at N times (historical multi-mint pointers, or
354
+ // one parent booking many leaves) must classify once — else each accepted
355
+ // seal is counted ×N in terminal gate-round (#753 acceptance A).
356
+ const classifiedOfficerSessions = new Set();
353
357
  for (const directory of directories) {
354
358
  let names;
355
359
  try {
@@ -367,11 +371,17 @@ export async function readAnalystGateCyclesFromAuditorRoles(auditorRolesDirector
367
371
  }
368
372
  for (const name of names) {
369
373
  const path = join(directory, name);
370
- const sessionPath = name.endsWith(".pointer.json")
374
+ const fromPointer = name.endsWith(".pointer.json");
375
+ const sessionPath = fromPointer
371
376
  ? await resolveOfficerSessionFromPointerFile(path)
372
377
  : path;
373
378
  if (sessionPath === undefined)
374
379
  continue;
380
+ if (fromPointer) {
381
+ if (classifiedOfficerSessions.has(sessionPath))
382
+ continue;
383
+ classifiedOfficerSessions.add(sessionPath);
384
+ }
375
385
  const classified = await classifyAuditorVolume(sessionPath);
376
386
  for (const volume of classified) {
377
387
  if (options.parentSessionFile !== undefined &&
@@ -142,7 +142,7 @@ function bookDirectOfficerRunPointer(options) {
142
142
  ...options.runDirectory !== void 0 && options.runDirectory.trim() !== "" ? { runDirectory: options.runDirectory } : {}
143
143
  };
144
144
  writeFileSync(
145
- join(nest, `${options.officer}-${Date.now().toString(36)}.pointer.json`),
145
+ join(nest, `${options.officer}.pointer.json`),
146
146
  `${JSON.stringify(pointer)}
147
147
  `,
148
148
  "utf8"
@@ -29,6 +29,17 @@ function readLatestToolCallLeaf(context) {
29
29
  function gateSubmissionCandidatePath(runDirectory) {
30
30
  return join(runDirectory, "artifacts", GATE_SUBMISSION_CANDIDATE_FILE);
31
31
  }
32
+ function buildGateOfficerReviewInstruction(input) {
33
+ const lines = [
34
+ "\u672C\u8F6E\u7236\u5E2D\u4EA4\u5377\u5F85\u5BA1\u3002",
35
+ `\u6765\u6E90 run\uFF1A${input.sourceRunDirectory}`
36
+ ];
37
+ const candidate = input.submissionCandidatePath?.trim();
38
+ if (candidate !== void 0 && candidate.length > 0) {
39
+ lines.push(`\u4EA4\u5377\u5019\u9009\uFF08\u51BB\u7ED3\u5FEB\u7167\uFF09\uFF1A${candidate}`);
40
+ }
41
+ return lines.join("\n");
42
+ }
32
43
  function persistGateSubmissionCandidate(runDirectory, context) {
33
44
  const leaf = readLatestToolCallLeaf(context);
34
45
  if (leaf === void 0) return void 0;
@@ -71,6 +82,7 @@ export {
71
82
  AUDITOR_DOSSIER_TOOL_NAME,
72
83
  GATE_SUBMISSION_CANDIDATE_FILE,
73
84
  auditorRunDirectory,
85
+ buildGateOfficerReviewInstruction,
74
86
  createAuditorDossierTool,
75
87
  gateSubmissionCandidatePath,
76
88
  persistGateSubmissionCandidate,
@@ -4,8 +4,6 @@
4
4
  */
5
5
  import { existsSync, statSync } from "node:fs";
6
6
  import { resolve } from "node:path";
7
- import { JUDGE_OUTPUT_TOOL_NAME as JUDGE_OUTPUT_TOOL } from "./package-contracts/judge-output.js";
8
- export const JUDGE_OUTPUT_TOOL_NAME = JUDGE_OUTPUT_TOOL;
9
7
  export const AUDIT_RUN_DIR_ENV = "AK_ROLE_RUN_DIR";
10
8
  export const DOCTOR_CANDIDATE_ENTRY_TYPE = "ak_doctor_audit_candidate";
11
9
  /**
@@ -36,39 +34,6 @@ export function resolveAuditDossier(env = process.env) {
36
34
  function isRecord(value) {
37
35
  return typeof value === "object" && value !== null && !Array.isArray(value);
38
36
  }
39
- export function readJudgeAuditSubjects(context) {
40
- const entries = context.sessionManager.getEntries?.() ?? [];
41
- let hasAssignment = false;
42
- let hasCandidate = false;
43
- for (const entry of entries) {
44
- if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message))
45
- continue;
46
- const message = entry.message;
47
- if (message.role === "user") {
48
- const text = typeof message.content === "string"
49
- ? message.content
50
- : Array.isArray(message.content)
51
- ? message.content.map((part) => (isRecord(part) && part.type === "text" && typeof part.text === "string" ? part.text : "")).join("")
52
- : "";
53
- if (text.trim().length > 0)
54
- hasAssignment = true;
55
- }
56
- if (message.role === "assistant" && Array.isArray(message.content)) {
57
- for (const part of message.content) {
58
- if (isRecord(part) && part.type === "toolCall" && part.name === JUDGE_OUTPUT_TOOL_NAME && isRecord(part.arguments)) {
59
- hasCandidate = true;
60
- }
61
- }
62
- }
63
- }
64
- if (!hasAssignment) {
65
- return { status: "incomplete", observation: { kind: "missing-subject", subject: "assignment" } };
66
- }
67
- if (!hasCandidate) {
68
- return { status: "incomplete", observation: { kind: "missing-subject", subject: "candidate-verdict" } };
69
- }
70
- return { status: "ok" };
71
- }
72
37
  /**
73
38
  * Doctor candidate testimony must be recorded before audit.
74
39
  */
@@ -1,5 +1,5 @@
1
1
  import { bookDirectOfficerRunPointer } from "./archivist-record-entry.js";
2
- import { GatekeeperDecisionError, GatekeeperEscalationError, projectGatekeeperRun, } from "./gatekeeper-role.js";
2
+ import { GatekeeperDecisionError, OFFICER_CONCLUSION_REASK, projectGatekeeperRun, } from "./gatekeeper-role.js";
3
3
  import { sessionFileFromPublicSummon } from "./session-assistant-usage.js";
4
4
  /**
5
5
  * Book a typed pointer under parent session/auditor-roles (archivist-owned write).
@@ -9,7 +9,7 @@ function bookDirectOfficerPointer(context, officer, result, summoned) {
9
9
  if (result.status !== "pass"
10
10
  && result.status !== "bounce"
11
11
  && result.status !== "escalate"
12
- && result.status !== "unreadable") {
12
+ && result.status !== "needs_reask") {
13
13
  return;
14
14
  }
15
15
  const parentFile = context.sessionManager?.getSessionFile?.();
@@ -31,42 +31,47 @@ function bookDirectOfficerPointer(context, officer, result, summoned) {
31
31
  }
32
32
  /**
33
33
  * Shared envelope: project gate, book officer pointer, map onto host actions.
34
- * unreadable = parent stands (ADR 0055); never mechanical NonPass reject.
34
+ * Review loop has no round cap (#753 no-round-cap).
35
35
  */
36
36
  export async function requireGatekeeperPass(options) {
37
- const projected = await projectGatekeeperRun({
38
- context: options.context,
39
- subject: options.subject,
40
- ...(options.signal === undefined ? {} : { signal: options.signal }),
41
- ...(options.summonOfficer === undefined ? {} : { summonOfficer: options.summonOfficer }),
42
- });
43
- const gatekeeper = projected.result;
44
- // Envelope-owned pointer book. Failure is host infrastructure — single face.
45
- if (projected.summoned !== undefined) {
46
- try {
47
- bookDirectOfficerPointer(options.context, projected.officer, gatekeeper, projected.summoned);
37
+ let reask;
38
+ // No round cap — end only on pass, bounce/escalate-to-parent, or real failure.
39
+ for (;;) {
40
+ const projected = await projectGatekeeperRun({
41
+ context: options.context,
42
+ subject: options.subject,
43
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
44
+ ...(options.summonOfficer === undefined ? {} : { summonOfficer: options.summonOfficer }),
45
+ ...(reask === undefined ? {} : { reask }),
46
+ });
47
+ const gatekeeper = projected.result;
48
+ // Envelope-owned pointer book. Failure is host infrastructure — single face.
49
+ if (projected.summoned !== undefined) {
50
+ try {
51
+ bookDirectOfficerPointer(options.context, projected.officer, gatekeeper, projected.summoned);
52
+ }
53
+ catch (error) {
54
+ options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
55
+ }
56
+ }
57
+ if (gatekeeper.status === "pass")
58
+ return;
59
+ if (gatekeeper.status === "needs_reask") {
60
+ // Resume the speaker with plain-language re-ask — never bounce the parent (#753).
61
+ reask = OFFICER_CONCLUSION_REASK;
62
+ continue;
48
63
  }
49
- catch (error) {
64
+ if (gatekeeper.status === "transport_failure") {
65
+ const error = new Error(`交卷闸 transport_failure(${gatekeeper.stage}):${gatekeeper.reason}`);
66
+ error.stage = gatekeeper.stage;
67
+ error.reason = gatekeeper.reason;
68
+ if (gatekeeper.submission !== undefined)
69
+ error.submission = gatekeeper.submission;
50
70
  options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
51
71
  }
72
+ // bounce | escalate | no_receipt: raw receipt (or lifecycle fact) back to parent.
73
+ // escalate is NOT thrown as parent next-step — parent reads the officer words (#753).
74
+ options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
75
+ throw new GatekeeperDecisionError(gatekeeper);
52
76
  }
53
- if (gatekeeper.status === "pass")
54
- return;
55
- // ADR 0055 / #675: shape-unreadable officer output must not mechanically reject parent.
56
- if (gatekeeper.status === "unreadable")
57
- return;
58
- if (gatekeeper.status === "transport_failure") {
59
- const error = new Error(`交卷闸 transport_failure(${gatekeeper.stage}):${gatekeeper.reason}`);
60
- error.stage = gatekeeper.stage;
61
- error.reason = gatekeeper.reason;
62
- if (gatekeeper.submission !== undefined)
63
- error.submission = gatekeeper.submission;
64
- options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
65
- }
66
- if (gatekeeper.status === "escalate") {
67
- throw new GatekeeperEscalationError(gatekeeper);
68
- }
69
- // bounce / no_receipt: typed non-pass — envelope owns execute→tool_result bridge.
70
- options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
71
- throw new GatekeeperDecisionError(gatekeeper);
72
77
  }
@@ -1,23 +1,25 @@
1
1
  import { auditorRunDirectory, persistGateSubmissionCandidate, } from "./auditor-dossier-tool.js";
2
2
  import { GatekeeperDecisionError } from "./submission-errors.js";
3
- import { readableGateItem } from "./readable-gate-item.js";
4
3
  import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
5
4
  import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
6
- import { retainedShapeUnreadable } from "./shape-unreadable-failure.js";
7
5
  export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
8
6
  export const NOTARY_OUTPUT_TOOL = "ak_notary_output";
9
7
  function gateSeatLabel(stage) {
10
- return stage === "inspector" ? "察院" : "符宝郎";
8
+ if (stage === "inspector")
9
+ return "察院";
10
+ if (stage === "auditor")
11
+ return "审刑院";
12
+ return "符宝郎";
11
13
  }
12
- export { GatekeeperDecisionError } from "./submission-errors.js";
13
- export class GatekeeperEscalationError extends Error {
14
- gatekeeper;
15
- constructor(gatekeeper) {
16
- super(`门下省${gateSeatLabel(gatekeeper.officer)}上呈`);
17
- this.name = "GatekeeperEscalationError";
18
- this.gatekeeper = gatekeeper;
19
- }
14
+ /** Subject kind → review officer (#753 countersign/notary, #756 judge/auditor + worker/inspector). */
15
+ export function gateOfficerForSubject(subject) {
16
+ if (subject.kind === "worker_completion")
17
+ return "inspector";
18
+ if (subject.kind === "judge_compliance")
19
+ return "auditor";
20
+ return "notary";
20
21
  }
22
+ export { GatekeeperDecisionError } from "./submission-errors.js";
21
23
  /**
22
24
  * Direct-seat decision tool spec (#639). Lifecycle assembly stays on the
23
25
  * registration envelope — src/role-runtime.ts (ADR 0018). Schema authority is
@@ -49,20 +51,13 @@ function failureReason(error) {
49
51
  return error.errors.map(failureReason).join("; ");
50
52
  return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
51
53
  }
52
- function asStringArray(value) {
53
- if (!Array.isArray(value))
54
- return [];
55
- // Officer findings may be structured objects (category/law/evidence); the
56
- // parent role must see them verbatim, not an empty list (#750 / #775).
57
- return value.map(readableGateItem);
58
- }
59
54
  /** Serializable stand-in when the child tool call had no arguments object. */
60
55
  export const MISSING_ARGUMENTS_SUBMISSION = Object.freeze({ missing: "arguments" });
61
56
  function isRecord(value) {
62
57
  return typeof value === "object" && value !== null && !Array.isArray(value);
63
58
  }
64
59
  /** Keep original decision bytes for the next reader; undefined becomes a serializable missing-args fact. */
65
- function retainedSubmission(decision) {
60
+ function retainedReceipt(decision) {
66
61
  // undefined must not be stored: JSON drops it and the missing-args fact vanishes.
67
62
  // Through the real provider adapter an undefined root argument arrives as an
68
63
  // empty object after serialization; that must also project a missing-args fact.
@@ -70,56 +65,36 @@ function retainedSubmission(decision) {
70
65
  ? MISSING_ARGUMENTS_SUBMISSION
71
66
  : decision;
72
67
  }
73
- /**
74
- * Shape-unreadable officer decision — retain original candidate + typed reason.
75
- * Not a forged bounce, not transport abort (CLAUDE.md §0 / ADR 0055).
76
- * Real provider/engine/disk failures stay transport_failure elsewhere.
77
- */
78
- function shapeUnreadable(officer, decision, reason = "decision 无显式 pass/bounce/escalate") {
79
- return {
80
- status: "unreadable",
81
- officer,
82
- reason,
83
- submission: retainedSubmission(decision),
84
- };
85
- }
86
68
  function readRecord(value) {
87
69
  if (typeof value !== "object" || value === null || Array.isArray(value))
88
70
  return undefined;
89
71
  return value;
90
72
  }
91
- function projectOfficerDecision(officer, decision) {
73
+ /**
74
+ * Read only the conclusion field for queueing (#753).
75
+ * pass | bounce | escalate → queue signal + raw receipt.
76
+ * Anything else accepted → needs_reask (resume speaker), never unreadable/parent-stand.
77
+ * `fallbackStatus` is the terminal outcome.status when the receipt body has no status key
78
+ * (keeps missing-args sentinel intact as the receipt).
79
+ */
80
+ function projectOfficerDecision(officer, decision, fallbackStatus) {
81
+ const receipt = retainedReceipt(decision);
92
82
  const record = readRecord(decision);
93
- if (record === undefined)
94
- return shapeUnreadable(officer, decision);
95
- if (record.status === "bounce") {
96
- return {
97
- status: "bounce",
98
- officer,
99
- disposition: "rewrite",
100
- findings: asStringArray(record.findings),
101
- submission: retainedSubmission(decision),
102
- };
83
+ const status = (record !== undefined && typeof record.status === "string" ? record.status : undefined)
84
+ ?? fallbackStatus;
85
+ if (status === "pass") {
86
+ return { status: "pass", officer, receipt };
103
87
  }
104
- if (record.status === "pass") {
105
- return {
106
- status: "pass",
107
- officer,
108
- findings: asStringArray(record.findings),
109
- };
88
+ if (status === "bounce" || status === "escalate") {
89
+ return { status, officer, receipt };
110
90
  }
111
- if (record.status === "escalate") {
112
- return {
113
- status: "escalate",
114
- officer,
115
- ...(Object.hasOwn(record, "reason") ? { reason: record.reason } : {}),
116
- findings: record.findings,
117
- submission: retainedSubmission(decision),
118
- };
119
- }
120
- return shapeUnreadable(officer, decision);
91
+ return { status: "needs_reask", officer, receipt };
121
92
  }
122
- /** Map a public-role terminal onto the gate officer result surface. */
93
+ /**
94
+ * Project a public-role terminal onto the gate queue surface.
95
+ * Lifecycle facts (no_receipt / transport) stay loud; conclusion reads only
96
+ * the status field. No unusable/unreadable judgment (#753).
97
+ */
123
98
  function projectOfficerTerminal(officer, summoned) {
124
99
  const terminal = summoned.terminal;
125
100
  const outcome = terminal?.roleOutcome;
@@ -143,11 +118,7 @@ function projectOfficerTerminal(officer, summoned) {
143
118
  };
144
119
  }
145
120
  if (outcome.kind === "failure") {
146
- // Single settlement marker only (ADR 0055 / #675) — no cause=output re-derivation.
147
- const shape = retainedShapeUnreadable(outcome.decisiveFacts);
148
- if (shape !== undefined) {
149
- return shapeUnreadable(officer, shape.candidate, outcome.diagnostic);
150
- }
121
+ // Real provider/engine/disk failure — keep loud. Not a shape judgment.
151
122
  return {
152
123
  status: "transport_failure",
153
124
  stage: officer,
@@ -155,25 +126,42 @@ function projectOfficerTerminal(officer, summoned) {
155
126
  submission: outcome.decisiveFacts,
156
127
  };
157
128
  }
129
+ if (outcome.kind === "audit_escalation") {
130
+ // Residual/compliance escalate face: queue as escalate, receipt as written.
131
+ // Nested officer escalate itself seals accepted+status escalate (no rewrite).
132
+ return {
133
+ status: "escalate",
134
+ officer,
135
+ receipt: retainedReceipt(outcome.decisiveFacts),
136
+ };
137
+ }
158
138
  if (outcome.kind === "accepted") {
159
- return projectOfficerDecision(officer, {
160
- status: outcome.status,
161
- ...outcome.decisiveFacts,
162
- });
139
+ // Prefer the officer's own decisiveFacts as the receipt body. outcome.status is
140
+ // only a fallback when facts have no status key (missing-args sentinel stays intact).
141
+ const facts = outcome.decisiveFacts;
142
+ if (isRecord(facts) && Object.keys(facts).length > 0) {
143
+ return projectOfficerDecision(officer, facts, outcome.status);
144
+ }
145
+ return projectOfficerDecision(officer, { status: outcome.status });
163
146
  }
147
+ // Unknown terminal kind: still not a shape judgment — ask the speaker again.
164
148
  return {
165
- status: "transport_failure",
166
- stage: officer,
167
- reason: `${gateSeatLabel(officer)} public summon returned unusable terminal kind`,
168
- submission: outcome,
149
+ status: "needs_reask",
150
+ officer,
151
+ receipt: retainedReceipt(outcome),
169
152
  };
170
153
  }
154
+ /**
155
+ * Plain-language re-ask when the officer conclusion is not pass|bounce|escalate.
156
+ * Not a packaged engine handbook line (#755 exception for 读不出三态).
157
+ */
158
+ export const OFFICER_CONCLUSION_REASK = "上次交卷的结论不是 pass、bounce、escalate 三态之一。请重新输出,结论字段写明其一;打回或上呈的话就是给对方看的原文。";
171
159
  /**
172
160
  * Summon + project only. Lifecycle book and host abort face live on the shared
173
161
  * submit envelope (`gatekeeper-pass-envelope.ts`, ADR 0018 / #675).
174
162
  */
175
163
  export async function projectGatekeeperRun(options) {
176
- const officer = options.subject.kind === "worker_completion" ? "inspector" : "notary";
164
+ const officer = gateOfficerForSubject(options.subject);
177
165
  const runDirectory = options.runDirectory ?? auditorRunDirectory(options.context);
178
166
  if (runDirectory === undefined) {
179
167
  return {
@@ -187,20 +175,25 @@ export async function projectGatekeeperRun(options) {
187
175
  }
188
176
  // Pointer-only summons need a resolvable leaf: Grok session.jsonl is header-only
189
177
  // (#617 DK-4); write the in-memory tool-call candidate as a run artifact first (#632).
190
- persistGateSubmissionCandidate(runDirectory, options.context);
178
+ // Candidate path also rides same-parent officer resume as 人读材料 (#753 / #750).
179
+ const submissionCandidatePath = persistGateSubmissionCandidate(runDirectory, options.context);
191
180
  let summoned;
192
181
  try {
193
182
  const summon = options.summonOfficer
194
- ?? (async (nextOfficer, sourceRunDirectory, officerSignal) => {
183
+ ?? (async (nextOfficer, sourceRunDirectory, officerSignal, reask) => {
195
184
  const { summonGateOfficer } = await import("./public-role-summons.js");
196
185
  return summonGateOfficer({
197
186
  officer: nextOfficer,
198
187
  sourceRunDirectory,
199
188
  cwd: options.context.cwd ?? process.cwd(),
200
189
  ...(officerSignal === undefined ? {} : { signal: officerSignal }),
190
+ ...(reask === undefined ? {} : { reask }),
191
+ ...(submissionCandidatePath === undefined
192
+ ? {}
193
+ : { submissionCandidatePath }),
201
194
  });
202
195
  });
203
- summoned = await summon(officer, runDirectory, options.signal);
196
+ summoned = await summon(officer, runDirectory, options.signal, options.reask);
204
197
  }
205
198
  catch (error) {
206
199
  return {
@@ -1,26 +1,7 @@
1
- import { runComplianceAudit, } from "./compliance-transport.js";
2
- import { auditorRunDirectory } from "./auditor-dossier-tool.js";
3
- import { readJudgeAuditSubjects, requireAuditMaterials, resolveAuditDossier, } from "./dossier-resolution.js";
4
- export const JUDGE_AUDIT_TOOL_NAME = "ak_soul_audit_decision";
5
- export const SOUL_AUDIT_TOOL_NAME = JUDGE_AUDIT_TOOL_NAME;
6
1
  /**
7
- * Judge compliance via public auditor activation (#675 / ADR 0062 / owner r11).
8
- * 审刑院 is an independent role; subject=judge selects souls/judge-auditor.md.
2
+ * Judge nested-audit tool name retained for settlement / historical session faces.
3
+ * Live judge↔auditor review queue is the shared gate envelope (#756) — no
4
+ * createPiJudgeAuditor compliance wrapper remains.
9
5
  */
10
- export function createPiJudgeAuditor() {
11
- return async (options) => {
12
- const dossier = resolveAuditDossier();
13
- requireAuditMaterials(dossier);
14
- const subjects = readJudgeAuditSubjects(options.context);
15
- requireAuditMaterials(subjects);
16
- return runComplianceAudit({
17
- subject: "judge",
18
- context: options.context,
19
- ...(auditorRunDirectory(options.context) === undefined
20
- ? {}
21
- : { runDirectory: auditorRunDirectory(options.context) }),
22
- ...(options.signal === undefined ? {} : { signal: options.signal }),
23
- ...(options.summonAuditor === undefined ? {} : { summonAuditor: options.summonAuditor }),
24
- });
25
- };
26
- }
6
+ export const JUDGE_AUDIT_TOOL_NAME = "ak_soul_audit_decision";
7
+ export const SOUL_AUDIT_TOOL_NAME = JUDGE_AUDIT_TOOL_NAME;
@@ -1,9 +1,10 @@
1
1
  import { stringEnum } from "./host-contracts.js";
2
2
  import { Type } from "typebox";
3
- import { disposeComplianceDecision } from "./audit-escalation.js";
4
- import { joinReadableGateItems } from "./readable-gate-item.js";
5
3
  import { withInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
6
- import { JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT, JUDGE_ACCEPTED_TEXT, JUDGE_OUTPUT_TOOL_NAME, validateAcceptedJudgeDetails, } from "./package-contracts/judge-output.js";
4
+ import { ParentQueueReaskError } from "./submission-errors.js";
5
+ import { JUDGE_ACCEPTED_TEXT, JUDGE_OUTPUT_TOOL_NAME, validateAcceptedJudgeDetails, } from "./package-contracts/judge-output.js";
6
+ const JUDGE_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
7
+ const JUDGE_STATUS_REASK = "judgeStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。";
7
8
  export { JUDGE_OUTPUT_TOOL_NAME };
8
9
  export const judgeVerdictSchema = withInfrastructureFailureDeclaration(Type.Object({
9
10
  judgeStatus: stringEnum(["converged", "continue", "escalate"], { description: "converged | continue | escalate — 形状指引,非 schema 闸" }),
@@ -38,16 +39,34 @@ export function createJudgeRoleRuntime(pi, dependencies, hostActions) {
38
39
  pi.registerTool({
39
40
  name: JUDGE_OUTPUT_TOOL_NAME,
40
41
  label: "大理寺输出",
41
- description: "提交大理寺终局判词;受理前经审刑院审计。",
42
+ description: "提交大理寺终局判词;受理前经符宝郎内闸与审刑院合规审核。",
42
43
  promptSnippet: "提交大理寺终局判词",
43
44
  parameters: judgeVerdictSchema,
44
45
  async execute(toolCallId, parameters, signal, _onUpdate, ctx) {
45
46
  if (soul === undefined)
46
47
  throw new Error("大理寺职分未装载");
48
+ // #756 queue: read judgeStatus only — escalate skips gates (thrown to caller);
49
+ // unreadable status returns to judge; else 符宝郎内闸 then 审刑院合规.
50
+ const rawStatus = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
51
+ && typeof parameters.judgeStatus === "string"
52
+ ? parameters.judgeStatus
53
+ : undefined;
54
+ if (rawStatus === undefined || !JUDGE_QUEUE_STATUSES.has(rawStatus)) {
55
+ throw new ParentQueueReaskError(JUDGE_STATUS_REASK);
56
+ }
57
+ if (rawStatus === "escalate") {
58
+ // Parent escalate → throw to caller as-is; officers do not attend (#753 / #756).
59
+ const verdict = validateVerdict(parameters);
60
+ return {
61
+ content: [{ type: "text", text: JUDGE_ACCEPTED_TEXT }],
62
+ details: verdict,
63
+ terminate: true,
64
+ };
65
+ }
47
66
  const verdict = validateVerdict(parameters);
48
67
  // Candidate verdict is already on the parent session books as this
49
68
  // tool-call leaf (first-record-then-audit; run 019fea05 L61/L62).
50
- // Gatekeeper runs after the draft is booked and before existing auditor.
69
+ // #753: 符宝郎内闸 — queue only, raw receipt on bounce/escalate.
51
70
  await pi.requireGatekeeperPass({
52
71
  context: ctx,
53
72
  subject: { kind: "judge_draft" },
@@ -55,43 +74,21 @@ export function createJudgeRoleRuntime(pi, dependencies, hostActions) {
55
74
  hostActions,
56
75
  toolCallId,
57
76
  });
58
- let audit;
59
- try {
60
- audit = await dependencies.auditSoulCompliance(signal === undefined
61
- ? { context: ctx }
62
- : { context: ctx, signal });
63
- }
64
- catch (error) {
65
- hostActions.failInfrastructure(error, ctx, toolCallId);
66
- }
67
- const acceptedDetails = verdict;
68
- return disposeComplianceDecision(audit, {
69
- pass: (usage) => ({
70
- content: [{ type: "text", text: JUDGE_ACCEPTED_TEXT }],
71
- details: acceptedDetails,
72
- terminate: true,
73
- ...(usage === undefined ? {} : { usage }),
74
- }),
75
- noReceipt: (auditNoReceipt, usageProjection) => ({
76
- content: [{ type: "text", text: JUDGE_ACCEPTED_AUDIT_NO_RECEIPT_TEXT }],
77
- details: { ...acceptedDetails, auditNoReceipt },
78
- terminate: true,
79
- ...usageProjection,
80
- }),
81
- // #757: parent stands with auditor raw reply — no unreadable judgment.
82
- received: (auditReceived, usageProjection) => ({
83
- content: [{ type: "text", text: JUDGE_ACCEPTED_TEXT }],
84
- details: { ...acceptedDetails, audit: auditReceived.reply },
85
- terminate: true,
86
- ...usageProjection,
87
- }),
88
- bounce: (violations) => {
89
- throw new Error(`大理寺回执违 soul:${joinReadableGateItems(violations)}`);
90
- },
91
- escalate: (result) => result,
92
- },
93
- // #380: escalate deliveredOutput must carry the same mechanical projection.
94
- acceptedDetails);
77
+ // #756: 审刑院合规路径 — same review-queue law as 符宝郎/察院.
78
+ // pass → accept; bounce|escalate → raw auditor receipt back to judge;
79
+ // not three-state → resume auditor; no round cap; no disposeCompliance mapping.
80
+ await pi.requireGatekeeperPass({
81
+ context: ctx,
82
+ subject: { kind: "judge_compliance" },
83
+ ...(signal === undefined ? {} : { signal }),
84
+ hostActions,
85
+ toolCallId,
86
+ });
87
+ return {
88
+ content: [{ type: "text", text: JUDGE_ACCEPTED_TEXT }],
89
+ details: verdict,
90
+ terminate: true,
91
+ };
95
92
  },
96
93
  });
97
94
  pi.on("before_agent_start", (event) => {
@@ -36,26 +36,11 @@ const notaryOutputSchema = withInfrastructureFailureDeclaration(
36
36
  function isRecord(value) {
37
37
  return typeof value === "object" && value !== null && !Array.isArray(value);
38
38
  }
39
- function asStringArray(value) {
40
- if (!Array.isArray(value)) return [];
41
- return value.filter((item) => typeof item === "string");
42
- }
43
39
  function projectLawfulNotaryOutput(value) {
44
40
  if (!isRecord(value)) return void 0;
45
41
  const status = typeof value.status === "string" ? value.status : void 0;
46
- if (status === "bounce") {
47
- const clone = structuredClone(value);
48
- if (clone.disposition === void 0) clone.disposition = "rewrite";
49
- if (!Array.isArray(clone.findings)) clone.findings = asStringArray(clone.findings);
50
- return clone;
51
- }
52
- if (status === "pass") {
53
- const clone = structuredClone(value);
54
- if (!Array.isArray(clone.findings)) clone.findings = asStringArray(clone.findings);
55
- return clone;
56
- }
57
- if (status === "escalate") {
58
- return structuredClone(value);
42
+ if (status === "bounce" || status === "pass" || status === "escalate") {
43
+ return value;
59
44
  }
60
45
  return void 0;
61
46
  }
@@ -4,7 +4,7 @@
4
4
  * Ticket flag register/read/session bind is envelope-owned (ADR 0018 / #582).
5
5
  * This module keeps evidence assembly + projection only.
6
6
  */
7
- import { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG, notaryOutputSchema, projectLawfulNotaryOutput, retainNotarySubmission, } from "./notary-contracts.js";
7
+ import { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG, notaryOutputSchema, } from "./notary-contracts.js";
8
8
  export { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG, };
9
9
  /** Optional ticket flag: absent/blank = unbound; non-empty invalid = honest fail. */
10
10
  export function readNotaryTicketFlag(flag) {
@@ -95,14 +95,11 @@ export function createNotaryRoleRuntime(pi, dependencies, host) {
95
95
  if (activation === undefined) {
96
96
  throw new Error("符宝郎未激活");
97
97
  }
98
- // Unique submission + terminate only. Shape is not an admission gate
99
- // (第 0 条 / ADR 0055): lawful pass/bounce/escalate projected; else params as-is.
98
+ // #753: handler only records — params as submitted, no findings/disposition rewrite.
100
99
  // #541 infra declaration + sole-final barrier are ledger-owned (#575).
101
- const lawful = projectLawfulNotaryOutput(parameters);
102
- const details = lawful ?? retainNotarySubmission(parameters);
103
100
  return {
104
101
  content: [{ type: "text", text: NOTARY_ACCEPTED_TEXT }],
105
- details,
102
+ details: parameters,
106
103
  terminate: true,
107
104
  };
108
105
  },