@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
@@ -1,18 +1,10 @@
1
- /**
2
- * Public Notary Role run: admit source-run locator → shared post-admission coordinator
3
- * → settle Terminal result (#448 / #517). Zero caller prompt/attachment. Lifecycle is
4
- * the shared post-admission seam; this module keeps only Notary adapters.
5
- * #637 / #747: same-parent (--source-run) re-summons resume the seat's previous run.
6
- */
7
- import { existsSync } from "node:fs";
8
- import { gateSubmissionCandidatePath } from "../auditor-dossier-tool.js";
9
1
  import { NotarySourceRunError, resolveNotarySourceRunLocator, } from "../notary-source-run.js";
10
2
  import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
11
3
  import { CliUsageError } from "./cli-errors.js";
12
4
  import { admitNotaryInvocation, buildNotaryTransportPrompt, } from "./invocation.js";
13
5
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
14
- import { runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
15
- import { appendResumeMaterialReread, loadResumableNotaryRun, markRunAdmitted, } from "./run-lifecycle.js";
6
+ import { prepareSummonsResumeMaterials, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
7
+ import { loadResumableNotaryRun, markRunAdmitted, } from "./run-lifecycle.js";
16
8
  import { presentStructuralRejection, trySettleNotaryTerminalResult, } from "./settlement.js";
17
9
  import { projectRoleTurnRequest, } from "./turn-request.js";
18
10
  /** Project admitted invocation onto the host-neutral turn request. */
@@ -60,10 +52,16 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
60
52
  throw error;
61
53
  }
62
54
  // #747: officer resume key is this parent source-run path (not ticket number).
55
+ // #753: gate re-ask and new-submission pointers share summons.instruction
56
+ // (reask wins when both present; no parallel stack).
63
57
  {
58
+ const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
64
59
  const summons = {
65
60
  sourceRunPath: source.runDirectory,
66
61
  sourceRun: source,
62
+ ...(resumeInstruction === undefined
63
+ ? {}
64
+ : { instruction: resumeInstruction, instructionEmpty: false }),
67
65
  };
68
66
  const resumed = await tryResumeSameTicketSeatRun({
69
67
  home: env.home,
@@ -76,6 +74,13 @@ export async function runPublicNotary(argv, env, io, parseNotaryArgv) {
76
74
  });
77
75
  if (resumed !== undefined)
78
76
  return resumed;
77
+ // Reask without a prior same-parent run cannot deliver the plain-language ask
78
+ // on a fresh mint without inventing a second prompt path — fail loud (#753).
79
+ // gateReviewInstruction alone is resume-only materials; fresh mint ignores it.
80
+ if (env.reviewReask !== undefined) {
81
+ presentStructuralRejection(new CliUsageError("notary review reask requires a prior same-parent run to resume"), io);
82
+ return { exitCode: 2 };
83
+ }
79
84
  }
80
85
  let admitted;
81
86
  try {
@@ -161,16 +166,14 @@ export async function runPublicNotaryResume(request, env, io) {
161
166
  }
162
167
  return loaded;
163
168
  },
164
- buildTurnRequest: (admitted, effective) => {
165
- const projection = resumeTurnRequestProjectionOptions(admitted, effective, env);
166
- const candidatePath = gateSubmissionCandidatePath(admitted.sourceRunPath);
167
- const prompt = existsSync(candidatePath)
168
- ? appendResumeMaterialReread(projection.continuation.prompt, candidatePath)
169
- : projection.continuation.prompt;
170
- return buildNotaryTurnRequest(admitted, {
171
- ...projection,
172
- continuation: { kind: "resume", prompt },
173
- });
169
+ buildTurnRequest: async (admitted, effective) => {
170
+ // #755: review continuation is plain dialogue / resume envelope — no
171
+ //「重新读」candidate line, no engine handbook packaging.
172
+ // #753: non-three-state re-ask rides as summons.instruction (人话重问).
173
+ const summonsPrepared = effective.summons === undefined
174
+ ? undefined
175
+ : await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
176
+ return buildNotaryTurnRequest(admitted, resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared));
174
177
  },
175
178
  adapters: notaryAdapters(),
176
179
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
@@ -8,11 +8,10 @@
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { readFile, writeFile } from "node:fs/promises";
10
10
  import { isAbsolute, join, resolve } from "node:path";
11
- import { buildResumeContinuationPrompt, instructResumeHandbookRead, } from "./run-lifecycle.js";
11
+ import { buildResumeContinuationPrompt, RESUME_TRANSPORT_ENVELOPE, } from "./run-lifecycle.js";
12
12
  import { CliUsageError } from "./cli-errors.js";
13
13
  import { buildInstructionTransportPrompt, freezeAttachmentsIntoRun, } from "./invocation.js";
14
14
  import { pathContainedIn } from "../activation-ledger-topology.js";
15
- import { engineSessionMaterialFromOptions } from "../package-resources/engine-material.js";
16
15
  import { projectCaseDossierPointerSection } from "./case-dossier-delivery.js";
17
16
  /** Append one system section to a continuation prompt, keeping its kind. */
18
17
  function appendContinuationSection(continuation, section) {
@@ -259,36 +258,37 @@ export async function dispatchPostAdmissionTurn(input) {
259
258
  }
260
259
  }
261
260
  /**
262
- * Shared resume continuation projection (#471 / #600 / #633 / #637): seat-table
263
- * model/engine/timeout axes, restored correlation, and either
264
- * - manual resume: package envelope / optional caller message (unchanged), or
265
- * - same-ticket summons: this turn's instruction + frozen attachment paths.
266
- * Caller message and summons materials each keep their place: a resume message
267
- * keeps manual-resume prompt semantics while open-court frozen attachments still
268
- * ride; summons instruction is only the prompt base when no caller message.
261
+ * Shared resume continuation projection (#471 / #600 / #633 / #637 / #755):
262
+ * seat-table model/engine/timeout axes, restored correlation, and either
263
+ * - manual resume (no same-ticket summons): package envelope / optional caller
264
+ * message, with engine-axis handbook via buildResumeContinuationPrompt, or
265
+ * - same-ticket summons (审核循环续话): caller/peer words + optional frozen
266
+ * attachment paths only — no「重新读」、no engine handbook packaging
267
+ * (#750/#755), whether or not attachments are present.
268
+ * Caller message wins as prompt base when supplied (bytes unchanged, including
269
+ * blank/whitespace); else summons instruction. Attachment projection must not
270
+ * re-interpret the caller message as instructionEmpty.
269
271
  * Seats add only their activation projection. Call prepareSummonsResumeMaterials
270
- * first when request.summons carries attachment paths.
272
+ * first when request.summons carries instruction or attachment paths.
271
273
  */
272
274
  export function resumeTurnRequestProjectionOptions(admitted, request, env, summonsPrepared) {
273
- const engineMaterial = engineSessionMaterialFromOptions({
274
- ...(env.engine === undefined ? {} : { engine: env.engine }),
275
- packageRoot: env.packageRoot,
276
- });
277
275
  let prompt;
278
276
  if (request.message !== undefined) {
279
- // Manual resume caller-message semantics stay authoritative for the prompt:
280
- // message present → base bytes unchanged (including blank/whitespace).
281
- // Open-court frozen attachments still continue when present; attachment
282
- // projection must not re-interpret the caller message as instructionEmpty.
283
- if (summonsPrepared !== undefined &&
284
- summonsPrepared.attachments.length > 0) {
285
- prompt = instructResumeHandbookRead(buildInstructionTransportPrompt({
277
+ if (summonsPrepared !== undefined) {
278
+ // #755: same-ticket review / open-court — caller words + optional paths.
279
+ // Attachments are not a gate: message-only summons must stay plain too.
280
+ prompt = buildInstructionTransportPrompt({
286
281
  instruction: request.message,
287
282
  instructionEmpty: false,
288
283
  attachments: summonsPrepared.attachments,
289
- }, engineMaterial), engineMaterial);
284
+ });
285
+ }
286
+ else if (request.summons !== undefined) {
287
+ // #755: same-ticket summons without prepared materials — caller words only.
288
+ prompt = request.message;
290
289
  }
291
290
  else {
291
+ // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
292
292
  prompt = buildResumeContinuationPrompt({
293
293
  packageRoot: env.packageRoot,
294
294
  ...(env.engine === undefined ? {} : { engine: env.engine }),
@@ -297,9 +297,16 @@ export function resumeTurnRequestProjectionOptions(admitted, request, env, summo
297
297
  }
298
298
  }
299
299
  else if (summonsPrepared !== undefined) {
300
- prompt = instructResumeHandbookRead(buildInstructionTransportPrompt(summonsPrepared, engineMaterial), engineMaterial);
300
+ // #755: same-ticket review summons — instruction/attachments only.
301
+ prompt = buildInstructionTransportPrompt(summonsPrepared);
302
+ }
303
+ else if (request.summons !== undefined) {
304
+ // #755: same-ticket summons with no instruction/attachments (e.g. notary
305
+ // source-run pointer) — plain resume envelope, no handbook / 重新读.
306
+ prompt = RESUME_TRANSPORT_ENVELOPE;
301
307
  }
302
308
  else {
309
+ // Bare manual resume — outsourcing engine axis keeps handbook (#600/#736).
303
310
  prompt = buildResumeContinuationPrompt({
304
311
  packageRoot: env.packageRoot,
305
312
  ...(env.engine === undefined ? {} : { engine: env.engine }),
@@ -32,11 +32,10 @@ function resumeRereadInstruction(materialPath, handbook) {
32
32
  : `重新读 ${materialPath}`;
33
33
  }
34
34
  /**
35
- * Resume-only material lines (#736 ticket exemption to ADR 0073 §3).
36
- * Neutral `- <absolute path>` pointers already on the continuation
37
- * (handbook from appendEngineSessionMaterial; frozen attachments from
38
- * instruction-seat transport) become reread instructions.
39
- * Handbook keeps the argv suffix; other materials are path-only.
35
+ * Engine-axis resume-only material lines (#736 / ADR 0069·0071).
36
+ * #755 withdrew this rewrite from 审核循环续话; review same-ticket resume stays
37
+ * plain dialogue via resumeTurnRequestProjectionOptions. Outsourcing resume
38
+ * still rewrites neutral handbook/path pointers into 重新读 instructions.
40
39
  * First-round delivery is unchanged; never pastes material body.
41
40
  */
42
41
  export function instructResumeHandbookRead(prompt, engineMaterial) {
@@ -56,16 +55,9 @@ export function instructResumeHandbookRead(prompt, engineMaterial) {
56
55
  })
57
56
  .join("\n");
58
57
  }
59
- /** Append a live-path reread instruction when the pointer is not already on the continuation. */
60
- export function appendResumeMaterialReread(prompt, materialPath) {
61
- const instruction = resumeRereadInstruction(materialPath, false);
62
- const lines = prompt.split("\n");
63
- if (lines.includes(instruction))
64
- return prompt;
65
- return prompt.length === 0 ? instruction : `${prompt}\n${instruction}`;
66
- }
67
58
  /**
68
- * Unique continuation-prompt selector for manual/auto resume (#471 / #600 / #736).
59
+ * Unique continuation-prompt selector for manual/auto engine-axis resume
60
+ * (#471 / #600 / #736). Not used for 审核循环 same-ticket summons (#755).
69
61
  * Message present → base bytes unchanged; absent → package transport envelope.
70
62
  * When engine material is present, append structured engine coordinates then
71
63
  * rewrite absolute material pointer lines into 重新读 instructions. Zero parse,
@@ -162,7 +162,11 @@ async function summonPublicRole(options) {
162
162
  // Host config passthrough only — same face as public CLI (#422 / #675).
163
163
  ...config.autoResumeLimit === void 0 ? {} : { autoResumeLimit: config.autoResumeLimit },
164
164
  // Parent cancellation reaches the nested activation's own turn dispatch.
165
- ...options.signal === void 0 ? {} : { signal: options.signal }
165
+ ...options.signal === void 0 ? {} : { signal: options.signal },
166
+ // #753: reask rides the existing notary same-ticket resume summons.instruction.
167
+ ...options.reviewReask === void 0 ? {} : { reviewReask: options.reviewReask },
168
+ // #753/#750: new-submission pointers on same-parent resume (not conclusion re-ask).
169
+ ...options.gateReviewInstruction === void 0 ? {} : { gateReviewInstruction: options.gateReviewInstruction }
166
170
  };
167
171
  const captured = options.io === void 0 ? createCapturingIo() : void 0;
168
172
  const io = options.io ?? captured.io;
@@ -258,25 +262,48 @@ async function summonGateOfficer(options) {
258
262
  const { homeFromRunDirectory } = await import("./activation-ledger-topology.js");
259
263
  home = homeFromRunDirectory(options.sourceRunDirectory);
260
264
  }
265
+ let gateReviewInstruction;
266
+ if (options.reask === void 0) {
267
+ const { buildGateOfficerReviewInstruction } = await import("./auditor-dossier-tool.js");
268
+ gateReviewInstruction = buildGateOfficerReviewInstruction({
269
+ sourceRunDirectory: options.sourceRunDirectory,
270
+ ...options.submissionCandidatePath === void 0 ? {} : { submissionCandidatePath: options.submissionCandidatePath }
271
+ });
272
+ }
273
+ const common = {
274
+ cwd: options.cwd,
275
+ ...home === void 0 ? {} : { home },
276
+ ...options.packageRoot === void 0 ? {} : { packageRoot: options.packageRoot },
277
+ ...options.io === void 0 ? {} : { io: options.io },
278
+ ...options.signal === void 0 ? {} : { signal: options.signal },
279
+ ...options.reask === void 0 ? {} : { reviewReask: options.reask },
280
+ ...gateReviewInstruction === void 0 ? {} : { gateReviewInstruction }
281
+ };
261
282
  if (options.officer === "notary") {
262
283
  return summonPublicRole({
263
284
  role: "notary",
264
285
  argv: ["--source-run", options.sourceRunDirectory, "--project", options.cwd],
265
- cwd: options.cwd,
266
- ...home === void 0 ? {} : { home },
267
- ...options.packageRoot === void 0 ? {} : { packageRoot: options.packageRoot },
268
- ...options.io === void 0 ? {} : { io: options.io },
269
- ...options.signal === void 0 ? {} : { signal: options.signal }
286
+ ...common
287
+ });
288
+ }
289
+ if (options.officer === "auditor") {
290
+ const { AUDITOR_DOSSIER_PROMPT } = await import("./compliance-transport.js");
291
+ return summonPublicRole({
292
+ role: "auditor",
293
+ argv: [
294
+ "--subject",
295
+ "judge",
296
+ "--source-run",
297
+ options.sourceRunDirectory,
298
+ AUDITOR_DOSSIER_PROMPT
299
+ ],
300
+ ...common
270
301
  });
271
302
  }
272
303
  return summonPublicRole({
273
304
  role: "inspector",
274
305
  argv: [`\u5377\u5B97\u6307\u9488\uFF1A${options.sourceRunDirectory}`],
275
- cwd: options.cwd,
276
- ...home === void 0 ? {} : { home },
277
- ...options.packageRoot === void 0 ? {} : { packageRoot: options.packageRoot },
278
- ...options.io === void 0 ? {} : { io: options.io },
279
- ...options.signal === void 0 ? {} : { signal: options.signal }
306
+ ...common
280
307
  });
281
308
  }
282
309
  export {
@@ -183,6 +183,8 @@ export { ACCEPTED_ACTIVATION_EVENT, ACCEPTED_ACTIVATION_FACT_KEYS, ActivationGit
183
183
  export { DISPATCH_STUB_EVENT, buildDispatchStubFact, reconcileInvocation, } from "./activation-reconciliation.js";
184
184
  export { TOOL_EXECUTION_UPDATE_HEARTBEAT, TOOL_EXECUTION_UPDATE_THROTTLE_MS, createToolExecutionObservationFace, isProducingToolUpdate, systemToolExecutionObservationMonoNow, toolExecutionObservationRecordSchema, validateToolExecutionObservationRecord, writeToolExecutionObservationRecord, } from "./tool-execution-observation.js";
185
185
  export { NOTARY_OUTPUT_TOOL, INSPECTOR_OUTPUT_TOOL, GatekeeperDecisionError, createGatekeeperOutputTool, runGatekeeper, } from "./gatekeeper-role.js";
186
+ export { gateOfficerForSubject } from "./gatekeeper-role.js";
187
+ import { ParentQueueReaskError } from "./submission-errors.js";
186
188
  export { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME, } from "./doctor-role.js";
187
189
  export { validateDoctorSubmissionShape, validateDoctorOutput, DoctorEvidenceStore } from "./doctor-contracts.js";
188
190
  export { loadDoctorCase } from "./doctor-evidence.js";
@@ -194,7 +196,7 @@ export { fixerOutputSchema, validateFixerOutput, validateFixerOutputForPacket }
194
196
  export { fixerPrerequisiteSchema, fixerPrerequisitesSchema, parseFixerPrerequisites, validateFixerPrerequisites } from "./package-contracts/fixer-packet.js";
195
197
  export { AUDIT_ESCALATION_KIND, buildAuditEscalationResult, disposeComplianceDecision, isAuditEscalationResult, projectAuditEscalation } from "./audit-escalation.js";
196
198
  export { AUDITOR_SOUL_ROLES, AK_ROLE_AUDITOR_SUBJECT_ENV, loadAuditorSoul, loadAuditorSoulFromSubjectInput, resolveAuditorSubject, } from "./auditor-soul.js";
197
- export { JUDGE_AUDIT_TOOL_NAME, SOUL_AUDIT_TOOL_NAME, createPiJudgeAuditor } from "./judge-auditor.js";
199
+ export { JUDGE_AUDIT_TOOL_NAME, SOUL_AUDIT_TOOL_NAME } from "./judge-auditor.js";
198
200
  export { DOCTOR_AUDIT_TOOL_NAME, createPiDoctorAuditor } from "./doctor-auditor.js";
199
201
  export { COLLECTOR_OBSERVE_TOOL, COLLECTOR_OUTPUT_TOOL, COLLECTOR_READ_TOOL, COLLECTOR_REQUEST_TOOL, COLLECTOR_WAIT_TOOL, } from "./collector-role.js";
200
202
  export * from "./navigator-attendance.js";
@@ -630,11 +632,35 @@ function loadFrozenDiaristCatalog(getFlag) {
630
632
  }
631
633
  return loadDiaristSourceCatalog(raw);
632
634
  }
635
+ /** Countersign status words the queue reads (#753). */
636
+ const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
637
+ /**
638
+ * Plain-language re-ask when countersignStatus is not a known queue word.
639
+ * Back to countersign itself — notary is not summoned (#753).
640
+ */
641
+ const COUNTERSIGN_STATUS_REASK = "countersignStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。";
633
642
  export function createCountersignRoleRuntime(roleHost, dependencies, hostActions) {
634
643
  // Notary inner gate difference only — lifecycle stays on the shared envelope.
635
644
  // Pointer-only summons: officer self-fetches from run dossier (#632 / ADR 0079).
645
+ // #753 queue: read countersignStatus only — escalate skips gate (thrown to caller);
646
+ // unreadable status returns to countersign; else notary inner gate.
636
647
  const beforeAccept = hostActions !== undefined && roleHost.requireGatekeeperPass !== undefined
637
- ? async ({ toolCallId, signal, ctx }) => {
648
+ ? async ({ toolCallId, parameters, signal, ctx }) => {
649
+ const record = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
650
+ ? parameters
651
+ : undefined;
652
+ const status = record !== undefined && typeof record.countersignStatus === "string"
653
+ ? record.countersignStatus
654
+ : undefined;
655
+ if (status === undefined || !COUNTERSIGN_QUEUE_STATUSES.has(status)) {
656
+ // Parent status unreadable → back to countersign itself; do not summon notary,
657
+ // do not forge an officer bounce face (#753).
658
+ throw new ParentQueueReaskError(COUNTERSIGN_STATUS_REASK);
659
+ }
660
+ if (status === "escalate") {
661
+ // Parent escalate → throw to caller as-is; notary does not attend (#753).
662
+ return undefined;
663
+ }
638
664
  await roleHost.requireGatekeeperPass({
639
665
  context: ctx,
640
666
  subject: { kind: "countersign_verdict" },
@@ -1000,7 +1026,6 @@ export function createRoleRuntimeExtension(dependencies) {
1000
1026
  };
1001
1027
  const judge = createJudgeRoleRuntime(roleHost, {
1002
1028
  loadSoul: dependencies.loadJudgeSoul,
1003
- auditSoulCompliance: dependencies.auditSoulCompliance,
1004
1029
  }, hostActions);
1005
1030
  const fixer = createFixerRoleRuntime(roleHost, {
1006
1031
  async loadSoul() {
@@ -1,4 +1,4 @@
1
- import { GatekeeperDecisionError, WorkerCommitReminderError, WorkerPrefixReminderError, WorkerUnfinishedReasonReminderError, } from "./submission-errors.js";
1
+ import { GatekeeperDecisionError, ParentQueueReaskError, WorkerCommitReminderError, WorkerPrefixReminderError, WorkerUnfinishedReasonReminderError, } from "./submission-errors.js";
2
2
  /** Shared, unforgeable identity for submission errors that the same session may correct. */
3
3
  const correctableSubmissionErrorBrand = Symbol("ak-roles.correctable-submission-error");
4
4
  export class CorrectableSubmissionError extends Error {
@@ -15,6 +15,7 @@ export function isCorrectableSubmissionError(error) {
15
15
  export function isCorrectableExecuteError(error) {
16
16
  return (isCorrectableSubmissionError(error)
17
17
  || error instanceof GatekeeperDecisionError
18
+ || error instanceof ParentQueueReaskError
18
19
  || error instanceof WorkerCommitReminderError
19
20
  || error instanceof WorkerPrefixReminderError
20
21
  || error instanceof WorkerUnfinishedReasonReminderError);
@@ -1,21 +1,25 @@
1
- import { joinReadableGateItems } from "./readable-gate-item.js";
1
+ import { readableGateItem } from "./readable-gate-item.js";
2
+ /**
3
+ * Tool-result text the parent model sees (#753 / #750 evidence).
4
+ * bounce | escalate → officer receipt verbatim (JSON when structured).
5
+ * no_receipt → honest lifecycle fact. No findings rewrite, no「(无 findings)」.
6
+ * #775: structured field content via readableGateItem (DRY with other gate seams).
7
+ */
8
+ function serializeReceipt(receipt) {
9
+ return readableGateItem(receipt);
10
+ }
2
11
  function gatekeeperNonPassMessage(result) {
3
- if (result.status === "bounce") {
4
- const findings = result.findings.length === 0
5
- ? "(无 findings)"
6
- : joinReadableGateItems(result.findings);
7
- return `门下省打回重写,findings:${findings}`;
8
- }
9
- if (result.status === "unreadable") {
10
- return `门下省官回执形状不可读(${result.officer}):${result.reason}`;
12
+ if (result.status === "bounce" || result.status === "escalate") {
13
+ return serializeReceipt(result.receipt);
11
14
  }
15
+ // no_receipt
12
16
  return `门下省 ${result.status}(${result.stage}):${result.reason}`;
13
17
  }
14
18
  /** Structured non-pass; `.result` is session-projected via tool_result, message feeds the model. */
15
19
  export class GatekeeperDecisionError extends Error {
16
20
  result;
17
- constructor(result) {
18
- super(gatekeeperNonPassMessage(result));
21
+ constructor(result, message) {
22
+ super(message ?? gatekeeperNonPassMessage(result));
19
23
  this.name = "GatekeeperDecisionError";
20
24
  this.result = result;
21
25
  }
@@ -41,3 +45,14 @@ export class WorkerUnfinishedReasonReminderError extends Error {
41
45
  this.name = "WorkerUnfinishedReasonReminderError";
42
46
  }
43
47
  }
48
+ /**
49
+ * Parent seat status unreadable for queueing (#753).
50
+ * Correctable back to the parent itself — not a gate officer bounce, no officer field.
51
+ */
52
+ export class ParentQueueReaskError extends Error {
53
+ code = "parent_queue_reask";
54
+ constructor(message) {
55
+ super(message);
56
+ this.name = "ParentQueueReaskError";
57
+ }
58
+ }
@@ -1,6 +1,5 @@
1
1
  import { resolveActivationLedgerHome, tryHomeFromAkRolesPath, } from "./activation-ledger-topology.js";
2
- import { isAuditEscalationProjection, projectAuditEscalation } from "./audit-escalation.js";
3
- import { GatekeeperEscalationError } from "./gatekeeper-role.js";
2
+ import { isAuditEscalationProjection } from "./audit-escalation.js";
4
3
  import { acceptedFacts, isTerminatingToolName, } from "./package-contracts/terminating-tools.js";
5
4
  import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
6
5
  import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport } from "./sitian-facade.js";
@@ -293,14 +292,6 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
293
292
  // correctable-rejection path as execute-thrown rejections below. The
294
293
  // candidate is booked first so the bounced attempt stays observable.
295
294
  append({ type: "candidate", attemptId, toolCallId, toolName: tool.name, sequence: ++state.sequence });
296
- const projectOfficerEscalation = (officer, original, deliveredOutput = original) => projectAuditEscalation({
297
- ...original,
298
- status: "escalate",
299
- officer,
300
- reason: original.reason === undefined
301
- ? `门下省${officer}上呈(原卷未附 reason)`
302
- : original.reason,
303
- }, deliveredOutput);
304
295
  let result;
305
296
  try {
306
297
  failOnInfrastructureFailureDeclaration(params, {
@@ -311,11 +302,9 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
311
302
  result = await tool.execute(toolCallId, params, signal, update, context);
312
303
  }
313
304
  catch (error) {
314
- if (error instanceof GatekeeperEscalationError) {
315
- const decision = error.gatekeeper;
316
- result = projectOfficerEscalation(error.gatekeeper.officer, decision, params);
317
- }
318
- else if (isCorrectableExecuteError(error)) {
305
+ // #753: gate no longer throws GatekeeperEscalationError to select parent
306
+ // next-step. Officer bounce|escalate returns as correctable with raw receipt.
307
+ if (isCorrectableExecuteError(error)) {
319
308
  append({
320
309
  type: "outcome",
321
310
  attemptId,
@@ -337,14 +326,9 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
337
326
  throw error;
338
327
  }
339
328
  }
340
- if ((role === "inspector" || role === "notary") &&
341
- typeof result.details === "object" &&
342
- result.details !== null &&
343
- !Array.isArray(result.details) &&
344
- result.details.status === "escalate") {
345
- const details = result.details;
346
- result = projectOfficerEscalation(role, details);
347
- }
329
+ // #753 escalate-thrown-verbatim: officer escalate seals as accepted with the
330
+ // raw receipt — no projectOfficerEscalation rewrite, no fabricated reason.
331
+ // Compliance seats still return live audit_escalation projections below.
348
332
  if (isAuditEscalationProjection(result.details)) {
349
333
  const candidates = rounds.get(attemptId) ?? [];
350
334
  candidates.push({
@@ -35,7 +35,6 @@ import {
35
35
  createProductionMergerGitState,
36
36
  formatNavigatorRoleHelp,
37
37
  } from "../src/role-runtime.ts";
38
- import { createPiJudgeAuditor } from "../src/judge-auditor.ts";
39
38
  import { loadAuditorSoulFromSubjectInput } from "../src/auditor-soul.ts";
40
39
  import { loadGatekeeperSessionMaterials, loadMainRoleSessionMaterials } from "../src/session-opening-materials.ts";
41
40
  const extensionPath = fileURLToPath(import.meta.url);
@@ -175,7 +174,6 @@ export default function roleRuntime(pi: ExtensionAPI): void {
175
174
  },
176
175
  runReviewerDispatch: (dispatch, options) => reviewerAgent.run(dispatch, options),
177
176
  shutdownReviewerAgent: () => reviewerAgent.shutdown(),
178
- auditSoulCompliance: (options) => createPiJudgeAuditor()(options),
179
177
  }, {
180
178
  transcriptFromContext,
181
179
  oauthKeepalive: { providers: oauthKeepaliveProviders },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3685",
3
+ "version": "0.1.3702",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -7,26 +7,26 @@
7
7
 
8
8
  **一、立法。** 先为本票立「适用法」,写进判词;后面每一步只对着它问,未立法不得往下走。适用法三部分:
9
9
  1 票面本文,含置顶冻结原话;起居录里决策过程
10
- 2 第一法源立法(最高法):该票起居录与陛下原话;
10
+ 2 第一法源立法(最高法):该票起居录与陛下原话,全局宪法;
11
11
  3 第二发源立法:
12
- 适用 ADR——由你自行检索,按票面触及的席位、接缝、术语在 docs/adr 与 CONTEXT.md 查全,票面引没引都算——并逐键列出其决策键;全局宪法、仓级 CLAUDE.md、质量法、审刑院法典;
12
+ 适用 ADR——由你自行检索,按票面触及的席位、接缝、术语在 docs/adr 与 CONTEXT.md 查全,票面引没引都算——并逐键列出其决策键;仓级 CLAUDE.md、质量法、审刑院法典;
13
13
  既有契约:CONTEXT.md 词表、被改动接缝的现行行为、相关票与在飞分支。
14
14
 
15
- 4 第二法源合法性检查:根据起居录对话记录,判断第二法源 以及 输入的 prombt 是否有超出或不足,曲解陛下原话的。如有,直接封驳要求改正或陛下批准。
15
+ 4 第二法源合法性检查:第一法源,判断第二法源 以及 输入的 prombt 是否有超出或不足,曲解。如有,除非有陛下原话批准,否则直接封驳要求改正或陛下批准。
16
16
 
17
- **二、相抵。** 票面与适用法有无抵触:违宪、违 ADR 决策键、违既有契约、与在飞票冲突。
17
+ **二、相抵。** 票面与法源有无抵触,有无与在飞票冲突。
18
18
 
19
- **三、缺失。** 适用法要求的,票面有没有漏:适用 ADR 未被引用即缺失,引用须写 `docs/adr/...md` 路径(御史台 Spec 轴只按路径取 ADR 字节);ADR 每个决策键须有落点或明写「由 #x 承接」;验收面须覆盖票面每条主张;前置产物、决定、答复须在;承重主张的源头不存在、不支持、已过时,同为缺失。
19
+ **三、缺失。** 法源要求的,票面有没有漏:适用 ADR 未被引用即缺失,引用须写 `docs/adr/...md` 路径;ADR 每个决策键须有落点或明写「由 #x 承接」;验收面须覆盖票面每条主张;前置产物、决定、答复须在;承重主张的源头不存在、不支持、已过时,同为缺失。
20
20
 
21
- **四、擅加。** 票面有没有超出法源的机制、约束、验收、术语:每条承重主张追到陛下原话为止;ADR 决策键只是索引,键的表述超出同行原话即加戏,追不到原话即加戏。
21
+ **四、擅加。** 票面有没有超出法源的机制、约束、验收、术语。
22
22
 
23
- 各步取证按《审法·取证边界》:读码、开卷、传召皆可;只读,探针只落 worktree 与临时目录,不得删改其外任何路径。
23
+ 各步取证按《审法·取证边界》:读码、开卷、传召皆可;探针只落 worktree 与临时目录,不得删改其外任何路径。交付时候清理回原样
24
24
 
25
25
  ## 审读五问
26
26
 
27
27
  四步是审读,五问是裁决口径,对应关系固定,不重做:
28
28
  1. 票面是否符合既定制度——由「相抵」答;
29
- 2. 授权是否真实——由「立法②」答:以该票起居录为据(起居郎掌修;无录或关键条不在录中,记明后依证据裁决,不替起居郎补录),陛下原话与 decision key 可溯,不认转述与口头;
29
+ 2. 授权是否真实——由「立法②」答:不认转述与口头;
30
30
  3. 文书是否与原意一致——由「缺失」「擅加」答:无过度解释、无夹带、无缺口;
31
31
  4. 是否存在必须退回重议的问题;
32
32
  5. 是否已具备正式发布与执行资格。
@@ -35,12 +35,13 @@
35
35
 
36
36
  ## 判词
37
37
 
38
- 事实与处方分开对宪:裁决落法度与事实,不落施工设计与实现细节过早堆上票面即为失职。
38
+ 事实与处方分开对宪:裁决落法度与事实;施工设计与实现细节过早堆上票面即为失职。
39
39
 
40
40
  - 署(converged)——四步无碍、五问皆有据可答,具备开工资格;
41
41
  - 封驳(continue)——存在必须退回重议的问题;写明退给谁、缺什么、属哪一步;
42
- - 上呈(escalate)——授权存疑、法源冲突或须陛下取舍。
42
+ - 上呈(escalate)——授权存疑、法源冲突或须陛下取舍。**上呈原样抛给调用者。**
43
+
43
44
 
44
45
  主张必须附证据或法的具体条目。finding 提类别,同类一次说全(审刑院法典·审官纪律)。
45
46
 
46
- 无法解决的上抛陛下。
47
+ 交卷后由符宝郎内闸核旨。无法解决的上抛陛下。
package/souls/notary.md CHANGED
@@ -7,3 +7,8 @@
7
7
  **二、核旨**:以所读起居录为据,核受审之物——**票面与判词**——有没有加戏:每一条**重大决策/设计/改变实际行为的**机制/约束条款逐条追到陛下原话为止——ADR 决策键只是索引,键的表述超出同行原话即加戏(门槛见审刑院法典;2026-09-06 owner:#636/#637「每次独立 run」判例);票面本身是不是陛下要的同样逐条追,**不因票面已签押、判词已引用而免核**。引 ADR 之名而无 key 绑定、引票面而票面无原话,同为加戏。核键的做法:把键值与所绑原话并排逐字比——原话没说的动作(删、改、加机制、定顺序)键值不得含;同题在起居录里有更早原话的,一并对撞,后话只覆盖它明说的部分,没说的部分仍以前话为准。键值已写进 ADR、票面已过庭、判词已引用,都不算原话,不豁免。判词与票面对不对得上不归你。任一病成立,点名该条驳回;拿不准陛下本意的,上呈。
8
8
 
9
9
  方法:按条封驳。取证按《审法·取证边界》:只读;探针只落 worktree 与临时目录,不得删改其外任何路径。无法解决的上抛陛下。
10
+
11
+ ## 结论
12
+
13
+ 结论以三态明示(不规定格式):`pass`(过)/`bounce`(打回)/`escalate`(上呈)。
14
+ **打回或上呈的话就是给对方看的原文**——给事中收到的就是你写的回执,不是代码改写的摘要。
@@ -14,7 +14,6 @@ import { createGhCollectorGitHubTransport, createGhIssueSoftFetcher } from "../c
14
14
  import { createPiDoctorAuditor } from "../doctor-auditor.ts";
15
15
  import { loadDoctorCase } from "../doctor-evidence.ts";
16
16
  import type { DurablePrincipalAuthority, RoleTurnHost } from "../host-contracts.ts";
17
- import { createPiJudgeAuditor } from "../judge-auditor.ts";
18
17
  import { createProductionMergerGitState } from "../merger-git-state.ts";
19
18
  import { createNativeNavigatorSessionFactory, createNavigatorAttendance } from "../navigator-attendance.ts";
20
19
  import { loadNavigatorWorkContext } from "../navigator-work-context.ts";
@@ -42,7 +41,6 @@ const navigatorRoutePlaybookPath = fileURLToPath(
42
41
 
43
42
  /** Host-neutral packaged role runtime deps for the ACP parent-process envelope. */
44
43
  export function createAcpRoleRuntimeDependencies(packageRoot: string): RoleRuntimeDependencies {
45
- const judgeAuditor = createPiJudgeAuditor();
46
44
  const doctorAuditor = createPiDoctorAuditor();
47
45
  const reviewerAgent = createPerDispatchReviewerAgent({ packageRoot });
48
46
  const navigatorSessionFactory = createNativeNavigatorSessionFactory();
@@ -80,8 +78,7 @@ export function createAcpRoleRuntimeDependencies(packageRoot: string): RoleRunti
80
78
  }
81
79
  return loadHomeCanonicalSkillBinding(name);
82
80
  },
83
- // #590: four sub-legs on the shared institutional child seam (host-neutral).
84
- auditSoulCompliance: (options) => judgeAuditor(options),
81
+ // #590: doctor compliance still on disposeCompliance path; judge→auditor is gate queue (#756).
85
82
  auditDoctorCompliance: (options) => doctorAuditor(options),
86
83
  runReviewerDispatch: (dispatch, options) => reviewerAgent.run(dispatch, options),
87
84
  shutdownReviewerAgent: () => reviewerAgent.shutdown(),
@@ -298,7 +298,7 @@ export async function prepareAcpRoleEnvelope(options: {
298
298
  if (record.kind === "role_infrastructure_failure") return;
299
299
  const code = typeof record.code === "string" && record.code.length > 0
300
300
  ? record.code
301
- : record.status === "bounce" || record.status === "no_receipt" || record.status === "unreadable"
301
+ : record.status === "bounce" || record.status === "escalate" || record.status === "no_receipt"
302
302
  ? record.status
303
303
  : undefined;
304
304
  if (code === undefined) return;
@@ -477,6 +477,10 @@ export async function readAnalystGateCyclesFromAuditorRoles(
477
477
  ? [auditorRolesDirectory]
478
478
  : auditorRolesDirectory;
479
479
  const volumes: ClassifiedVolume[] = [];
480
+ // Same officer session pointed at N times (historical multi-mint pointers, or
481
+ // one parent booking many leaves) must classify once — else each accepted
482
+ // seal is counted ×N in terminal gate-round (#753 acceptance A).
483
+ const classifiedOfficerSessions = new Set<string>();
480
484
  for (const directory of directories) {
481
485
  let names: string[];
482
486
  try {
@@ -495,10 +499,15 @@ export async function readAnalystGateCyclesFromAuditorRoles(
495
499
  }
496
500
  for (const name of names) {
497
501
  const path = join(directory, name);
498
- const sessionPath = name.endsWith(".pointer.json")
502
+ const fromPointer = name.endsWith(".pointer.json");
503
+ const sessionPath = fromPointer
499
504
  ? await resolveOfficerSessionFromPointerFile(path)
500
505
  : path;
501
506
  if (sessionPath === undefined) continue;
507
+ if (fromPointer) {
508
+ if (classifiedOfficerSessions.has(sessionPath)) continue;
509
+ classifiedOfficerSessions.add(sessionPath);
510
+ }
502
511
  const classified = await classifyAuditorVolume(sessionPath);
503
512
  for (const volume of classified) {
504
513
  if (