@akagilnc/pi-workflow-roles 0.1.4673 → 0.1.4698
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.
- package/dist/acp-host/production-host.js +496 -518
- package/dist/compliance-transport.js +6 -2
- package/dist/gatekeeper-role.js +6 -7
- package/dist/headless-host/production-host.js +492 -514
- package/dist/migrate-book-topology.js +8 -0
- package/dist/public-cli/countersign-run.js +63 -18
- package/dist/public-cli/main.js +488 -513
- package/dist/public-cli/settlement.js +316 -247
- package/dist/public-cli/terminal.js +36 -9
- package/dist/run-terminal-artifacts.js +71 -32
- package/package.json +1 -1
- package/souls/audit-law.md +7 -2
- package/souls/countersign.md +1 -1
- package/souls/gleaner-left.md +1 -1
- package/souls/notary.md +1 -1
- package/souls/quality-law.md +9 -0
- package/souls/reviewer.md +1 -1
- package/src/compliance-transport.ts +8 -2
- package/src/gatekeeper-role.ts +6 -6
- package/src/public-cli/countersign-run.ts +75 -26
- package/src/public-cli/secretariat-run.ts +2 -3
- package/src/public-cli/settlement.ts +380 -355
- package/src/public-cli/terminal.ts +48 -13
- package/src/role-runtime.ts +9 -11
- package/src/run-terminal-artifacts.ts +81 -28
- package/src/secretariat-role.ts +9 -4
|
@@ -17,7 +17,11 @@ export function exitCodeForTerminalOutcome(outcome) {
|
|
|
17
17
|
* auto-resumes occurred during this single LLM call; it is not persisted to
|
|
18
18
|
* run-state.json and does not participate in limit decisions.
|
|
19
19
|
*/
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Current-result payloads on a terminal — accepted/audit role result, or a
|
|
22
|
+
* rare intentional failure face. Run history is TerminalResult.submissions
|
|
23
|
+
* (#836 / #953), not this helper.
|
|
24
|
+
*/
|
|
21
25
|
export function roleResultPayloads(outcome) {
|
|
22
26
|
if (outcome.kind === "accepted" || outcome.kind === "audit_escalation")
|
|
23
27
|
return outcome.payloads ?? [];
|
|
@@ -25,6 +29,18 @@ export function roleResultPayloads(outcome) {
|
|
|
25
29
|
return outcome.payloads ?? [];
|
|
26
30
|
return [];
|
|
27
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Prefer non-empty current-result payloads; an empty array must not shadow
|
|
34
|
+
* top-level submissions (#953 / #836). Callers that need failure history must
|
|
35
|
+
* read TerminalResult.submissions directly — do not coalesce into failure.payloads.
|
|
36
|
+
*/
|
|
37
|
+
export function coalesceSubmissionRows(payloads, submissions) {
|
|
38
|
+
if (payloads !== undefined && payloads.length > 0)
|
|
39
|
+
return payloads;
|
|
40
|
+
if (submissions !== undefined && submissions.length > 0)
|
|
41
|
+
return submissions;
|
|
42
|
+
return payloads ?? submissions ?? [];
|
|
43
|
+
}
|
|
28
44
|
/**
|
|
29
45
|
* Build an advice navigator fact (#959). Prose is presented as submitted;
|
|
30
46
|
* code does not parse route/next or judge usability.
|
|
@@ -109,15 +125,26 @@ export function formatTerminalResult(result) {
|
|
|
109
125
|
}
|
|
110
126
|
// Role-result block: original payloads, newest first for humans (#961).
|
|
111
127
|
// Typed payloads/submissions stay ledger order; only this presentation reverses.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
// #953: failure history is the top-level submissions carrier (#836) — present
|
|
129
|
+
// as recorded-submission, never as this-turn submission/receipt.
|
|
130
|
+
if (result.roleOutcome.kind === "failure") {
|
|
131
|
+
const recorded = result.submissions ?? [];
|
|
132
|
+
for (let i = recorded.length - 1; i >= 0; i -= 1) {
|
|
133
|
+
const payload = recorded[i];
|
|
134
|
+
const rendered = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
135
|
+
lines.push(`recorded-submission\t${encodeTerminalField(rendered)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
const payloads = result.roleOutcome.kind === "accepted" ||
|
|
140
|
+
result.roleOutcome.kind === "audit_escalation"
|
|
141
|
+
? coalesceSubmissionRows(result.roleOutcome.payloads, result.submissions)
|
|
116
142
|
: result.submissions ?? [];
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
143
|
+
for (let i = payloads.length - 1; i >= 0; i -= 1) {
|
|
144
|
+
const payload = payloads[i];
|
|
145
|
+
const rendered = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
146
|
+
lines.push(`submission\t${encodeTerminalField(rendered)}`);
|
|
147
|
+
}
|
|
121
148
|
}
|
|
122
149
|
return `${lines.join("\n")}\n`;
|
|
123
150
|
}
|
|
@@ -137,6 +137,7 @@ export function runIdFromRunDirectory(runDirectory) {
|
|
|
137
137
|
* Shared parent-directory unique fallback may be adopted only when the
|
|
138
138
|
* publisher-owned body.runId equals this run directory's runId. Same-run
|
|
139
139
|
* artifactsDir / runDirectory candidates keep path ownership and skip this.
|
|
140
|
+
* expectedRunId undefined (unparseable run dir) → never bound.
|
|
140
141
|
*/
|
|
141
142
|
function presentUniqueFallbackBoundToRun(body, expectedRunId) {
|
|
142
143
|
if (expectedRunId === undefined)
|
|
@@ -144,49 +145,87 @@ function presentUniqueFallbackBoundToRun(body, expectedRunId) {
|
|
|
144
145
|
return typeof body.runId === "string" && body.runId === expectedRunId;
|
|
145
146
|
}
|
|
146
147
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
148
|
+
* Sole authority for seam-owned unique error.<uuid>.json candidates.
|
|
149
|
+
* Used by both clearOpposite (settlement) and readRunTerminalArtifact — do not
|
|
150
|
+
* re-enumerate the same-run / parent unique set elsewhere.
|
|
151
|
+
* Ownership:
|
|
152
|
+
* - same-run dirs (artifacts/, runDir): path ownership — all unique names
|
|
153
|
+
* - parent runs/: only body.runId-bound faces; unparseable runId or unreadable body → none
|
|
154
|
+
*/
|
|
155
|
+
export async function listSeamOwnedUniqueErrorFacePaths(runDirectory) {
|
|
156
|
+
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
157
|
+
const owned = await listUniqueErrorFallbackPaths([
|
|
158
|
+
artifactsDir,
|
|
159
|
+
runDirectory,
|
|
160
|
+
]);
|
|
161
|
+
const expectedRunId = runIdFromRunDirectory(runDirectory);
|
|
162
|
+
for (const path of await listUniqueErrorFallbackPaths([dirname(runDirectory)])) {
|
|
163
|
+
const read = await readTerminalArtifactAtPath(path, "error.json");
|
|
164
|
+
if (read === undefined || read.status !== "present")
|
|
165
|
+
continue;
|
|
166
|
+
if (!presentUniqueFallbackBoundToRun(read.body, expectedRunId))
|
|
167
|
+
continue;
|
|
168
|
+
owned.push(path);
|
|
169
|
+
}
|
|
170
|
+
return owned;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Publish contract (#953): only failure publish continues after clearOpposite
|
|
174
|
+
* failure, so a multi-class residue (residual report/audit beside a new error
|
|
175
|
+
* face) means the current settlement is failure. Prefer failure-class faces
|
|
176
|
+
* over success/audit — never filesystem mtime (copy/restore/utimes can lie).
|
|
177
|
+
*/
|
|
178
|
+
function failureClassRank(file) {
|
|
179
|
+
return file === "error.json" ? 1 : 0;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Read the current typed terminal artifact for a run directory.
|
|
183
|
+
*
|
|
184
|
+
* Candidate set (publisher-owned faces only):
|
|
149
185
|
* 1) conventional artifacts/{report,error,audit-incomplete}.json
|
|
150
186
|
* 2) publisher fixed failure fallbacks (error.settlement.json faces)
|
|
151
|
-
* 3)
|
|
152
|
-
*
|
|
187
|
+
* 3) seam-owned unique error.<uuid>.json via listSeamOwnedUniqueErrorFacePaths
|
|
188
|
+
* (same-run path ownership + parent body.runId binding — shared with clear)
|
|
153
189
|
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
190
|
+
* Invariant: when more than one present face remains (e.g. clearOpposite failed
|
|
191
|
+
* during failure publish and a fallback was settled beside a residual report),
|
|
192
|
+
* adopt failure-class over success/audit by the publish contract — not mtime.
|
|
193
|
+
* Same-class ties keep candidate enumeration order (conventional before
|
|
194
|
+
* fallbacks before unique).
|
|
195
|
+
*
|
|
196
|
+
* Unreadable faces never outrank a present face. Parent unique unreadable
|
|
197
|
+
* files never enter the shared enumerator (cannot prove run identity).
|
|
198
|
+
* Absence of every known durable face is a valid no-receipt state.
|
|
156
199
|
*/
|
|
157
200
|
export async function readRunTerminalArtifact(runDirectory) {
|
|
158
201
|
const artifactsDir = roleRunArtifactsDirectory(runDirectory);
|
|
202
|
+
const present = [];
|
|
203
|
+
const unreadable = [];
|
|
204
|
+
const consider = (read) => {
|
|
205
|
+
if (read === undefined || read.status === "absent")
|
|
206
|
+
return;
|
|
207
|
+
if (read.status === "present") {
|
|
208
|
+
present.push(read);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
unreadable.push(read);
|
|
212
|
+
};
|
|
159
213
|
for (const file of RUN_TERMINAL_ARTIFACT_FILES) {
|
|
160
|
-
|
|
161
|
-
const read = await readTerminalArtifactAtPath(path, file);
|
|
162
|
-
if (read !== undefined)
|
|
163
|
-
return read;
|
|
214
|
+
consider(await readTerminalArtifactAtPath(join(artifactsDir, file), file));
|
|
164
215
|
}
|
|
165
|
-
// Publisher settled a durable failure outside the conventional error.json name.
|
|
166
216
|
for (const relative of RUN_TERMINAL_ERROR_FALLBACK_RELATIVE_PATHS) {
|
|
167
|
-
|
|
168
|
-
const read = await readTerminalArtifactAtPath(path, "error.json");
|
|
169
|
-
if (read !== undefined)
|
|
170
|
-
return read;
|
|
217
|
+
consider(await readTerminalArtifactAtPath(join(runDirectory, relative), "error.json"));
|
|
171
218
|
}
|
|
172
|
-
//
|
|
173
|
-
for (const path of await
|
|
174
|
-
|
|
175
|
-
if (read !== undefined)
|
|
176
|
-
return read;
|
|
219
|
+
// Unique same-run + parent-bound faces: one enumerator shared with clear.
|
|
220
|
+
for (const path of await listSeamOwnedUniqueErrorFacePaths(runDirectory)) {
|
|
221
|
+
consider(await readTerminalArtifactAtPath(path, "error.json"));
|
|
177
222
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (read.status === "present") {
|
|
185
|
-
if (!presentUniqueFallbackBoundToRun(read.body, expectedRunId))
|
|
186
|
-
continue;
|
|
187
|
-
return read;
|
|
188
|
-
}
|
|
189
|
-
// Unreadable parent unique file cannot prove run identity — do not adopt.
|
|
223
|
+
if (present.length > 0) {
|
|
224
|
+
present.sort((a, b) => failureClassRank(b.file) - failureClassRank(a.file));
|
|
225
|
+
return present[0];
|
|
226
|
+
}
|
|
227
|
+
if (unreadable.length > 0) {
|
|
228
|
+
return unreadable[0];
|
|
190
229
|
}
|
|
191
230
|
return { status: "absent" };
|
|
192
231
|
}
|
package/package.json
CHANGED
package/souls/audit-law.md
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
## 取证授权
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
> owner 2026-09-20 逐字:「说了无数次不准限制审核衙门的工具和方式。只有事后清理干净就行」
|
|
10
|
+
> 指针:源卷 `~/.claude/projects/-Users-akagilnc-WorkSpace-ak-pi-workflow-roles/e4b50c8e-15c7-4c92-9263-959efadb24eb.jsonl`,owner uuid `54e54b19-27d8-48f9-a334-fc1409b66558`。
|
|
11
|
+
|
|
12
|
+
一切取证席位的工具与方式不受限:读、写、装、跑、下探针皆可。唯一义务是**用毕清理干净并如实记载**,包括清理本次取证在 worktree 与临时目录之外创建的任何东西。
|
|
10
13
|
|
|
11
14
|
## 审官纪律
|
|
12
15
|
|
|
@@ -18,4 +21,6 @@ finding 提**类别**;实例只作证据。一次找齐全,不逐轮抽样
|
|
|
18
21
|
|
|
19
22
|
## 取证边界(owner 2026-09-05)
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
> 逐字:「角色的 bash 不该有权删 worktree 和 tmpdir 之外的东西……加上这个限制」
|
|
25
|
+
|
|
26
|
+
本条只禁销毁**既存之物**:本次工作区(worktree)与系统临时目录之外的任何既存路径,不得删除、覆写或移动。本次取证自己在那之外创建的东西不在此列——按《取证授权》用毕清理干净。这两处之内不设限制。
|
package/souls/countersign.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
**五、符合陛下意图** 先根据起居录对话,判断陛下意图。再证实票面是否是陛下想要的,对于修复票,判断 根因是什么、证据在哪 。对于新功能设计票,判断设计是不是合理,是不是陛下想要的方向。以及 因果链本身站不站得住
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
各步取证按《审法·取证授权》:工具与方式不受限,读码、开卷、传召、下探针皆可;不得删除、覆写或移动 worktree 与临时目录之外的既存路径(《审法·取证边界》)。交付时候清理回原样
|
|
27
27
|
|
|
28
28
|
## 审读五问
|
|
29
29
|
|
package/souls/gleaner-left.md
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
- 无锚定:不接收票面、判词与庭审史。案件=合并候选的全幅 diff 与仓库现状,看其本身对不对劲,此外不接收方向性指令。
|
|
7
7
|
- 拾遗之责:bug 与边界、安全、命名与一致性、文档漂移、复杂度增长、测试质量、跨片接缝等。
|
|
8
8
|
- 量尺:宿主宪法、仓级 ADR 与《复杂度与测试质量法》。
|
|
9
|
-
-
|
|
9
|
+
- 取证工具与方式不受限,一切自取,按《审法·取证授权》:用毕清理干净并如实记载。不得删除、覆写或移动 worktree 与临时目录之外的既存路径(《审法·取证边界》)。
|
|
10
10
|
- 只上弹章,不修复、不裁决。
|
package/souls/notary.md
CHANGED
package/souls/quality-law.md
CHANGED
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
取舍位次:过测试 → 显意图 → 无重复 → 最少元素。
|
|
10
10
|
|
|
11
|
+
## 先搜,再想简单的(owner 2026-09-20)
|
|
12
|
+
|
|
13
|
+
> 逐字:「请你们思考实现方式的时候。多搜搜互联网,多想简单的方式,不是在那加加加,加一堆烂机制最终还要烧我的token来删!」
|
|
14
|
+
> 指针:源卷 `~/.claude/projects/-Users-akagilnc-WorkSpace-ak-pi-workflow-roles/e4b50c8e-15c7-4c92-9263-959efadb24eb.jsonl`,owner uuid `04255552-6b2a-45ee-9e9e-59a068b960cc`。
|
|
15
|
+
|
|
16
|
+
动手写机制之前:先搜互联网,确认这是不是已知问题、生态里有没有现成解法(官方文档优先)。再问最简单的做法是什么——包括「换一个前置选择」而不是「给现有选择打补丁」。
|
|
17
|
+
|
|
18
|
+
自撰机制是最后手段。同一处只增不减时,回查根因,不得靠再补一层收场。
|
|
19
|
+
|
|
11
20
|
## 测试对行为负责
|
|
12
21
|
|
|
13
22
|
测试只保**调用输入正确**的最短确定性案:从真实入口进入,在外部可见的结构化结果上断言,秒级完成。涉真实宿主、真实 LLM、真实并发、真实网络的行为,不写测试;以真跑为证。
|
package/souls/reviewer.md
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
- 风闻奏事,言不为狱:弹章(finding)不是权威、不负坐实义务。弹章须指明所劾之处(文件/条文指针),说错无罪。
|
|
6
6
|
- 独立机关,直属陛下:不接收输入 prompt。案件=固定的被评审目标,目标或事实前提无法成立时如实拒绝;方法=canonical Skill;其余一切自取。
|
|
7
|
-
-
|
|
7
|
+
- 取证工具与方式不受限,一切自取,按《审法·取证授权》:读、写、装、跑、下探针皆可,用毕清理干净并如实记载。不得删除、覆写或移动 worktree 与临时目录之外的既存路径(《审法·取证边界》)。有权抽查≠有责核实。
|
|
8
8
|
- 劾前逐项过改动清单。固定目标内每项改动都要标明由票面哪条驱动:标不出来的即为夹带(默认值、护栏、平行机制、仅测试用的生产钩子同论);票面有要求而清单中没有的即为缺口。二者同为 finding。
|
|
9
9
|
- 重点劾三事:违宪(与既裁法源 ADR、铁律相抵)、测试必要性(最小必要成本法)、复杂度(可删不删、同形状第二份)。劾的范围=眼前一切:票面、实现、测试、输入 prompt 与给你的派单命令本身。唯一例外是陛下亲笔——援引此例外必须引陛下原话为证。
|
|
@@ -5,6 +5,7 @@ import type { HostContext } from "./host-contracts.ts";
|
|
|
5
5
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
6
6
|
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
7
7
|
import { OFFICER_CONCLUSION_REASK } from "./gatekeeper-role.ts";
|
|
8
|
+
import { coalesceSubmissionRows } from "./public-cli/terminal.ts";
|
|
8
9
|
|
|
9
10
|
export type ComplianceNoReceipt = NoReceiptLifecycleFacts & { status: "no-receipt"; usage?: Usage };
|
|
10
11
|
/**
|
|
@@ -149,7 +150,9 @@ async function projectAuditorTerminal(summoned: PublicSummonResult): Promise<Com
|
|
|
149
150
|
};
|
|
150
151
|
}
|
|
151
152
|
if (outcome.kind === "failure") {
|
|
152
|
-
|
|
153
|
+
// #953: history stays on the historical carrier; do not read failure.payloads
|
|
154
|
+
// as if they were the current failure result (#836 A.3 beside-channel).
|
|
155
|
+
const rows = summoned.terminal?.submissions ?? [];
|
|
153
156
|
return {
|
|
154
157
|
status: "transport_failure",
|
|
155
158
|
diagnostic: outcome.diagnostic,
|
|
@@ -159,7 +162,10 @@ async function projectAuditorTerminal(summoned: PublicSummonResult): Promise<Com
|
|
|
159
162
|
};
|
|
160
163
|
}
|
|
161
164
|
if (outcome.kind === "accepted") {
|
|
162
|
-
const rows =
|
|
165
|
+
const rows = coalesceSubmissionRows(
|
|
166
|
+
outcome.payloads,
|
|
167
|
+
summoned.terminal?.submissions,
|
|
168
|
+
);
|
|
163
169
|
if (rows.length === 0) {
|
|
164
170
|
return readComplianceCandidate({}, usage);
|
|
165
171
|
}
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -260,20 +260,20 @@ function projectOfficerDecision(
|
|
|
260
260
|
*/
|
|
261
261
|
function thisCourtOfficerPayloads(terminal: TerminalResult | undefined): readonly unknown[] {
|
|
262
262
|
const outcome = terminal?.roleOutcome;
|
|
263
|
+
// This-court identity only from accepted/audit_escalation settlement payloads
|
|
264
|
+
// (#879). Failure history is not this-court (#953) — it rides submissions.
|
|
263
265
|
if (outcome !== undefined && (outcome.kind === "accepted" || outcome.kind === "audit_escalation")) {
|
|
264
266
|
if (outcome.payloads !== undefined && outcome.payloads.length > 0) return outcome.payloads;
|
|
265
267
|
}
|
|
266
|
-
if (outcome?.kind === "failure" && outcome.payloads !== undefined && outcome.payloads.length > 0) {
|
|
267
|
-
return outcome.payloads;
|
|
268
|
-
}
|
|
269
268
|
// No sole-row identity guess: undivided submissions are not this-court (#879).
|
|
270
269
|
return [];
|
|
271
270
|
}
|
|
272
271
|
|
|
273
|
-
/**
|
|
272
|
+
/**
|
|
273
|
+
* Failure/transport channel surfaces recorded history beside the failure
|
|
274
|
+
* (#836 A.3) via the historical carrier only (#953 — not failure.payloads).
|
|
275
|
+
*/
|
|
274
276
|
function officerFailurePayloads(terminal: TerminalResult | undefined): readonly unknown[] {
|
|
275
|
-
const outcome = terminal?.roleOutcome;
|
|
276
|
-
if (outcome?.kind === "failure") return outcome.payloads ?? terminal?.submissions ?? [];
|
|
277
277
|
return terminal?.submissions ?? [];
|
|
278
278
|
}
|
|
279
279
|
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../package-resources/engine-material.ts";
|
|
32
32
|
import { CliUsageError } from "./cli-errors.ts";
|
|
33
33
|
import { projectCourtTicketNumbers } from "../diarist-contracts.ts";
|
|
34
|
+
import { readableGateItem } from "../readable-gate-item.ts";
|
|
34
35
|
import { isSafePositiveTicketNumber } from "../run-ticket-number.ts";
|
|
35
36
|
import {
|
|
36
37
|
admitCountersignInvocation,
|
|
@@ -139,13 +140,74 @@ export type CourtDiaristIdentity =
|
|
|
139
140
|
readonly courtTicketNumbers?: readonly number[];
|
|
140
141
|
}
|
|
141
142
|
| { readonly kind: "unbound" }
|
|
142
|
-
| {
|
|
143
|
+
| {
|
|
144
|
+
readonly kind: "escalate";
|
|
145
|
+
/** Honest diagnostic from diarist escalate payload (#953) — never invent 认不出. */
|
|
146
|
+
readonly diagnostic: string;
|
|
147
|
+
};
|
|
143
148
|
|
|
144
149
|
export type CourtDiaristInvocationResult = {
|
|
145
150
|
readonly identity: CourtDiaristIdentity;
|
|
146
151
|
readonly failedWithoutEscalate?: { readonly diagnostic: string };
|
|
147
152
|
};
|
|
148
153
|
|
|
154
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
155
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isEscalatePayload(payload: unknown): boolean {
|
|
159
|
+
if (!isRecord(payload)) return false;
|
|
160
|
+
return (
|
|
161
|
+
payload.status === "escalate" || payload.countersignStatus === "escalate"
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Rows a parent may read from a nested diarist terminal (#836 / #953).
|
|
167
|
+
* Accepted/audit use role result payloads; failure history is submissions only.
|
|
168
|
+
*/
|
|
169
|
+
function courtDiaristPayloadRows(
|
|
170
|
+
roleOutcome: TerminalRoleOutcome | undefined,
|
|
171
|
+
submissions: readonly unknown[] | undefined,
|
|
172
|
+
): readonly unknown[] {
|
|
173
|
+
if (roleOutcome === undefined) return submissions ?? [];
|
|
174
|
+
if (roleOutcome.kind === "accepted" || roleOutcome.kind === "audit_escalation") {
|
|
175
|
+
return roleOutcome.payloads ?? [];
|
|
176
|
+
}
|
|
177
|
+
if (roleOutcome.kind === "failure") return submissions ?? [];
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Parent diagnostic for court-diarist escalate (#953 / 失败诚实 / 传话).
|
|
183
|
+
* Relays diarist escalate payload(s) via readableGateItem — never invents
|
|
184
|
+
* "cannot identify court target" when a payload already carries other facts
|
|
185
|
+
* (e.g. ticketNumber present with a different reason).
|
|
186
|
+
*
|
|
187
|
+
* Multiple escalate payloads remain reachable after the run (ADR 0003/0041;
|
|
188
|
+
* no courtAttempt sole-filter on diarist). Same law as secretariat parent face:
|
|
189
|
+
* every escalate receipt as-is, last = currentConclusion.
|
|
190
|
+
*/
|
|
191
|
+
function courtDiaristEscalateDiagnostic(
|
|
192
|
+
roleOutcome: TerminalRoleOutcome | undefined,
|
|
193
|
+
submissions?: readonly unknown[],
|
|
194
|
+
): string {
|
|
195
|
+
const payloads = courtDiaristPayloadRows(roleOutcome, submissions);
|
|
196
|
+
const escalatePayloads: unknown[] = [];
|
|
197
|
+
for (const payload of payloads) {
|
|
198
|
+
if (isEscalatePayload(payload)) escalatePayloads.push(payload);
|
|
199
|
+
}
|
|
200
|
+
if (escalatePayloads.length === 0) return "court diarist station escalated";
|
|
201
|
+
// Single escalate: prior carrier (payload body only) — no shape churn.
|
|
202
|
+
if (escalatePayloads.length === 1) {
|
|
203
|
+
return `court diarist station escalated: ${readableGateItem(escalatePayloads[0])}`;
|
|
204
|
+
}
|
|
205
|
+
return `court diarist station escalated: ${readableGateItem({
|
|
206
|
+
receipts: escalatePayloads,
|
|
207
|
+
currentConclusion: escalatePayloads[escalatePayloads.length - 1],
|
|
208
|
+
})}`;
|
|
209
|
+
}
|
|
210
|
+
|
|
149
211
|
/** Env slice shared by court diarist identity summons (countersign / secretariat). */
|
|
150
212
|
export type CourtDiaristSummonEnv = Pick<
|
|
151
213
|
CountersignRunEnv,
|
|
@@ -171,14 +233,10 @@ export type CourtDiaristSummonEnv = Pick<
|
|
|
171
233
|
function courtTicketNumbersFromOutcome(
|
|
172
234
|
roleOutcome: TerminalRoleOutcome | undefined,
|
|
173
235
|
principalTicket: number,
|
|
236
|
+
submissions?: readonly unknown[],
|
|
174
237
|
): readonly number[] | undefined {
|
|
175
238
|
if (roleOutcome === undefined) return undefined;
|
|
176
|
-
const payloads =
|
|
177
|
-
roleOutcome.kind === "accepted" ||
|
|
178
|
-
roleOutcome.kind === "audit_escalation" ||
|
|
179
|
-
roleOutcome.kind === "failure"
|
|
180
|
-
? (roleOutcome.payloads ?? [])
|
|
181
|
-
: [];
|
|
239
|
+
const payloads = courtDiaristPayloadRows(roleOutcome, submissions);
|
|
182
240
|
let latest: readonly number[] | undefined;
|
|
183
241
|
for (const payload of payloads) {
|
|
184
242
|
if (
|
|
@@ -216,18 +274,7 @@ function courtDiaristEscalated(
|
|
|
216
274
|
if (roleOutcome === undefined) return false;
|
|
217
275
|
if (roleOutcome.kind === "audit_escalation") return true;
|
|
218
276
|
if (roleOutcome.kind !== "accepted") return false;
|
|
219
|
-
return (roleOutcome.payloads ?? []).some(
|
|
220
|
-
if (
|
|
221
|
-
typeof payload !== "object" ||
|
|
222
|
-
payload === null ||
|
|
223
|
-
Array.isArray(payload)
|
|
224
|
-
)
|
|
225
|
-
return false;
|
|
226
|
-
const record = payload as Record<string, unknown>;
|
|
227
|
-
return (
|
|
228
|
-
record.status === "escalate" || record.countersignStatus === "escalate"
|
|
229
|
-
);
|
|
230
|
-
});
|
|
277
|
+
return (roleOutcome.payloads ?? []).some(isEscalatePayload);
|
|
231
278
|
}
|
|
232
279
|
|
|
233
280
|
/**
|
|
@@ -292,11 +339,15 @@ export async function invokeCourtDiarist(
|
|
|
292
339
|
});
|
|
293
340
|
|
|
294
341
|
const roleOutcome = result.terminal?.roleOutcome;
|
|
342
|
+
const submissions = result.terminal?.submissions;
|
|
295
343
|
// Escalate routing is a boolean over the preserved sequence (#881). Reasons and
|
|
296
344
|
// payload bodies stay on roleOutcome — never rewritten into a sole identity reason.
|
|
297
345
|
if (courtDiaristEscalated(roleOutcome)) {
|
|
298
346
|
return {
|
|
299
|
-
identity: {
|
|
347
|
+
identity: {
|
|
348
|
+
kind: "escalate",
|
|
349
|
+
diagnostic: courtDiaristEscalateDiagnostic(roleOutcome, submissions),
|
|
350
|
+
},
|
|
300
351
|
};
|
|
301
352
|
}
|
|
302
353
|
|
|
@@ -319,6 +370,7 @@ export async function invokeCourtDiarist(
|
|
|
319
370
|
const courtTicketNumbers = courtTicketNumbersFromOutcome(
|
|
320
371
|
roleOutcome,
|
|
321
372
|
asserted,
|
|
373
|
+
submissions,
|
|
322
374
|
);
|
|
323
375
|
return {
|
|
324
376
|
identity: {
|
|
@@ -390,9 +442,7 @@ export async function runCountersignCourtDiaristStation(
|
|
|
390
442
|
);
|
|
391
443
|
|
|
392
444
|
if (outcome.identity.kind === "escalate") {
|
|
393
|
-
throw new StationChildExhaustedError(
|
|
394
|
-
"court diarist station escalated (cannot identify court target)",
|
|
395
|
-
);
|
|
445
|
+
throw new StationChildExhaustedError(outcome.identity.diagnostic);
|
|
396
446
|
}
|
|
397
447
|
if (outcome.failedWithoutEscalate !== undefined) {
|
|
398
448
|
throw new StationChildExhaustedError(
|
|
@@ -509,6 +559,7 @@ export async function runPublicCountersign(
|
|
|
509
559
|
|
|
510
560
|
if (outcome.identity.kind === "escalate") {
|
|
511
561
|
// 御批: 识别不了就上抛 — materialize and settle this countersign run.
|
|
562
|
+
// Diagnostic relays diarist escalate payload facts (#953).
|
|
512
563
|
await materializeAdmission();
|
|
513
564
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
514
565
|
return await presentControlledFailure(
|
|
@@ -517,9 +568,7 @@ export async function runPublicCountersign(
|
|
|
517
568
|
timedOut: false,
|
|
518
569
|
code: null,
|
|
519
570
|
stderr: "",
|
|
520
|
-
thrown: new Error(
|
|
521
|
-
"court diarist station escalated (cannot identify court target)",
|
|
522
|
-
),
|
|
571
|
+
thrown: new Error(outcome.identity.diagnostic),
|
|
523
572
|
},
|
|
524
573
|
countersignAdapters(),
|
|
525
574
|
env.principalAuthority,
|
|
@@ -139,15 +139,14 @@ export async function runPublicSecretariat(
|
|
|
139
139
|
);
|
|
140
140
|
|
|
141
141
|
if (outcome.identity.kind === "escalate") {
|
|
142
|
+
// Diagnostic relays diarist escalate payload facts (#953) — never invent 认不出.
|
|
142
143
|
return await presentControlledFailure(
|
|
143
144
|
admitted,
|
|
144
145
|
{
|
|
145
146
|
timedOut: false,
|
|
146
147
|
code: null,
|
|
147
148
|
stderr: "",
|
|
148
|
-
thrown: new Error(
|
|
149
|
-
"court diarist station escalated (cannot identify court target)",
|
|
150
|
-
),
|
|
149
|
+
thrown: new Error(outcome.identity.diagnostic),
|
|
151
150
|
},
|
|
152
151
|
secretariatAdapters(),
|
|
153
152
|
env.principalAuthority,
|