@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.
- package/dist/acp-host/production-host.js +499 -559
- package/dist/analyst-gate-cycles-read.js +11 -1
- package/dist/archivist-record-entry.js +1 -1
- package/dist/auditor-dossier-tool.js +12 -0
- package/dist/dossier-resolution.js +0 -35
- package/dist/gatekeeper-pass-envelope.js +39 -34
- package/dist/gatekeeper-role.js +70 -77
- package/dist/judge-auditor.js +5 -24
- package/dist/judge-role.js +39 -42
- package/dist/notary-contracts.js +2 -17
- package/dist/notary-role.js +3 -6
- package/dist/package-contracts/judge-output.js +0 -2
- package/dist/package-contracts/terminating-tools.js +2 -2
- package/dist/public-cli/inspector-run.js +16 -2
- package/dist/public-cli/instruction-seat-run.js +14 -2
- package/dist/public-cli/main.js +189 -214
- package/dist/public-cli/notary-run.js +23 -20
- package/dist/public-cli/post-admission.js +30 -23
- package/dist/public-cli/run-lifecycle.js +6 -14
- package/dist/public-role-summons.js +38 -11
- package/dist/role-runtime.js +28 -3
- package/dist/submission-correctable-error.js +2 -1
- package/dist/submission-errors.js +26 -11
- package/dist/submission-ledger.js +7 -23
- package/extensions/role-runtime.ts +0 -2
- package/package.json +1 -1
- package/souls/countersign.md +12 -11
- package/souls/notary.md +5 -0
- package/src/acp-host/production-host.ts +1 -4
- package/src/acp-host/role-envelope.ts +1 -1
- package/src/analyst-gate-cycles-read.ts +10 -1
- package/src/archivist-record-entry.ts +7 -3
- package/src/auditor-dossier-tool.ts +21 -0
- package/src/dossier-resolution.ts +0 -39
- package/src/gatekeeper-pass-envelope.ts +59 -43
- package/src/gatekeeper-role.ts +114 -113
- package/src/host-contracts.ts +3 -3
- package/src/judge-auditor.ts +5 -43
- package/src/judge-role.ts +41 -54
- package/src/notary-contracts.ts +8 -27
- package/src/notary-role.ts +2 -7
- package/src/package-contracts/judge-output.ts +0 -1
- package/src/package-contracts/terminating-tools.ts +2 -2
- package/src/public-cli/inspector-run.ts +32 -2
- package/src/public-cli/instruction-seat-run.ts +30 -2
- package/src/public-cli/notary-run.ts +48 -16
- package/src/public-cli/post-admission.ts +31 -37
- package/src/public-cli/run-lifecycle.ts +6 -17
- package/src/public-role-summons.ts +76 -12
- package/src/role-runtime.ts +34 -9
- package/src/submission-correctable-error.ts +2 -0
- package/src/submission-errors.ts +28 -11
- package/src/submission-ledger.ts +8 -32
- 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
|
|
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}
|
|
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,
|
|
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 !== "
|
|
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
|
-
*
|
|
34
|
+
* Review loop has no round cap (#753 no-round-cap).
|
|
35
35
|
*/
|
|
36
36
|
export async function requireGatekeeperPass(options) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/gatekeeper-role.js
CHANGED
|
@@ -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
|
-
|
|
8
|
+
if (stage === "inspector")
|
|
9
|
+
return "察院";
|
|
10
|
+
if (stage === "auditor")
|
|
11
|
+
return "审刑院";
|
|
12
|
+
return "符宝郎";
|
|
11
13
|
}
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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: "
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
package/dist/judge-auditor.js
CHANGED
|
@@ -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
|
|
8
|
-
*
|
|
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
|
|
11
|
-
|
|
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;
|
package/dist/judge-role.js
CHANGED
|
@@ -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 {
|
|
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
|
-
//
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
hostActions
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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) => {
|
package/dist/notary-contracts.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/notary-role.js
CHANGED
|
@@ -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,
|
|
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
|
-
//
|
|
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
|
},
|