@akagilnc/pi-workflow-roles 0.1.4631 → 0.1.4641

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.
@@ -2,6 +2,7 @@ import { auditorRunDirectory } from "./auditor-dossier-tool.js";
2
2
  import { GatekeeperDecisionError } from "./submission-errors.js";
3
3
  import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.js";
4
4
  import { GATEKEEPER_OUTPUT_TOOL_NAME, gatekeeperDecisionSchema, gatekeeperOutputSchema, } from "./package-contracts/gatekeeper-output.js";
5
+ import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
5
6
  export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
6
7
  export const NOTARY_OUTPUT_TOOL = "ak_notary_output";
7
8
  function gateSeatLabel(stage) {
@@ -9,14 +10,18 @@ function gateSeatLabel(stage) {
9
10
  return "台院";
10
11
  if (stage === "auditor")
11
12
  return "审刑院";
13
+ if (stage === "countersign")
14
+ return "给事中";
12
15
  return "符宝郎";
13
16
  }
14
- /** Subject kind → review officer (#753 countersign/notary, #756 judge/auditor + worker/inspector). */
17
+ /** Subject kind → review officer (#753 countersign/notary, #756 judge/auditor + worker/inspector, #969 secretariat/countersign). */
15
18
  export function gateOfficerForSubject(subject) {
16
19
  if (subject.kind === "worker_completion")
17
20
  return "inspector";
18
21
  if (subject.kind === "judge_compliance")
19
22
  return "auditor";
23
+ if (subject.kind === "secretariat_verdict")
24
+ return "countersign";
20
25
  return "notary";
21
26
  }
22
27
  export { GatekeeperDecisionError } from "./submission-errors.js";
@@ -70,9 +75,37 @@ function readRecord(value) {
70
75
  * `fallbackStatus` is the terminal outcome.status when the receipt body has no status key
71
76
  * (keeps missing-args sentinel intact as the receipt).
72
77
  */
78
+ /** Map 给事中 countersignStatus onto the shared gate queue words (#969). */
79
+ function gateStatusFromCountersign(status) {
80
+ if (status === "converged")
81
+ return "pass";
82
+ if (status === "continue")
83
+ return "bounce";
84
+ if (status === "escalate")
85
+ return "escalate";
86
+ return undefined;
87
+ }
73
88
  function projectOfficerDecision(officer, decision, fallbackStatus) {
74
89
  const receipt = retainedReceipt(decision);
75
90
  const record = readRecord(decision);
91
+ // 给事中: only countersignStatus, strict three-word map; never generic status,
92
+ // never unmapped raw word, never fallbackStatus (#969 / ADR 0040/0055).
93
+ if (officer === "countersign") {
94
+ const countersignStatus = record !== undefined && typeof record.countersignStatus === "string"
95
+ ? record.countersignStatus
96
+ : undefined;
97
+ const status = typeof countersignStatus === "string"
98
+ ? gateStatusFromCountersign(countersignStatus)
99
+ : undefined;
100
+ if (status === "pass") {
101
+ return { status: "pass", officer, receipt };
102
+ }
103
+ if (status === "bounce" || status === "escalate") {
104
+ return { status, officer, receipt };
105
+ }
106
+ return { status: "needs_reask", officer, receipt };
107
+ }
108
+ // inspector / notary / auditor: shared gate words on generic status.
76
109
  const status = (record !== undefined && typeof record.status === "string" ? record.status : undefined)
77
110
  ?? fallbackStatus;
78
111
  if (status === "pass") {
@@ -116,6 +149,35 @@ function projectOfficerPayloads(officer, payloads, fallbackStatus) {
116
149
  // Single this-court seal is the common path. Never fold history into an array receipt.
117
150
  return projectOfficerDecision(officer, payloads[payloads.length - 1], fallbackStatus);
118
151
  }
152
+ /** Nested officer runId from summoned.runDirectory, else terminal.runId (#969). */
153
+ export function officerRunIdFromSummoned(summoned) {
154
+ if (typeof summoned.runDirectory === "string" && summoned.runDirectory.trim() !== "") {
155
+ const fromDir = runIdFromRunDirectory(summoned.runDirectory);
156
+ if (fromDir !== undefined)
157
+ return fromDir;
158
+ }
159
+ const terminal = summoned.terminal;
160
+ if (terminal !== undefined
161
+ && typeof terminal.runId === "string"
162
+ && terminal.runId.trim() !== "") {
163
+ return terminal.runId;
164
+ }
165
+ return undefined;
166
+ }
167
+ function withOfficerRunId(result, summoned) {
168
+ if (result.status !== "pass"
169
+ && result.status !== "bounce"
170
+ && result.status !== "escalate"
171
+ && result.status !== "needs_reask") {
172
+ return result;
173
+ }
174
+ if (typeof result.runId === "string" && result.runId.trim() !== "")
175
+ return result;
176
+ const runId = officerRunIdFromSummoned(summoned);
177
+ if (runId === undefined)
178
+ return result;
179
+ return { ...result, runId };
180
+ }
119
181
  function projectOfficerTerminal(officer, summoned) {
120
182
  const terminal = summoned.terminal;
121
183
  const outcome = terminal?.roleOutcome;
@@ -152,31 +214,39 @@ function projectOfficerTerminal(officer, summoned) {
152
214
  };
153
215
  }
154
216
  if (outcome.kind === "audit_escalation") {
155
- return {
217
+ return withOfficerRunId({
156
218
  status: "escalate",
157
219
  officer,
158
220
  // This-court receipt only (#879) — historical rows remain on terminal.submissions.
159
221
  receipt: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
160
- };
222
+ }, summoned);
161
223
  }
162
224
  if (outcome.kind === "accepted") {
163
225
  // outcome.status is the fixture/compat leaf: production settlement leaves
164
226
  // it undefined once payloads are recorded, so this only matters when a
165
227
  // caller still supplies status without any recorded payload (#836 hang).
166
- return projectOfficerPayloads(officer, thisCourt, outcome.status);
228
+ return withOfficerRunId(projectOfficerPayloads(officer, thisCourt, outcome.status), summoned);
167
229
  }
168
- return {
230
+ return withOfficerRunId({
169
231
  status: "needs_reask",
170
232
  officer,
171
233
  // This-court receipt only (#879).
172
234
  receipt: thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome),
173
- };
235
+ }, summoned);
174
236
  }
175
237
  /**
176
238
  * Plain-language re-ask when the officer conclusion is not pass|bounce|escalate.
177
239
  * Not a packaged engine handbook line (#755 exception for 读不出三态).
240
+ * inspector / notary / auditor keep shared gate words; 给事中 uses own three-state
241
+ * field and words so reask does not invite non-contract values (#969 / ADR 0055).
178
242
  */
179
243
  export const OFFICER_CONCLUSION_REASK = "上次交卷的结论不是 pass、bounce、escalate 三态之一。请重新输出,结论字段写明其一;打回或上呈的话就是给对方看的原文。";
244
+ /** 给事中 re-ask: countersignStatus converged|continue|escalate only (#969). */
245
+ export const COUNTERSIGN_CONCLUSION_REASK = "上次交卷的 countersignStatus 不是 converged、continue、escalate 三态之一。请重新输出,countersignStatus 写明其一;封驳或上呈的话就是给对方看的原文。";
246
+ /** Pick the re-ask line for the officer under review. */
247
+ export function officerConclusionReask(officer) {
248
+ return officer === "countersign" ? COUNTERSIGN_CONCLUSION_REASK : OFFICER_CONCLUSION_REASK;
249
+ }
180
250
  /**
181
251
  * Summon (via injected seam) + project. Default summonGateOfficer drive lives
182
252
  * on the shared envelope (`gatekeeper-pass-envelope.ts`, ADR 0018 / #675) —