@akagilnc/pi-workflow-roles 0.1.3280 → 0.1.3353
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/README.md +5 -5
- package/README.zh-CN.md +5 -5
- package/dist/audit-escalation.js +31 -4
- package/dist/gatekeeper-role.js +25 -4
- package/dist/grok/production-host.js +1167 -862
- package/dist/inspector-contracts.js +5 -2
- package/dist/notary-contracts.js +16 -5
- package/dist/packaged-role-registry.js +0 -7
- package/dist/public-cli/main.js +867 -757
- package/package.json +1 -1
- package/src/audit-escalation.ts +52 -8
- package/src/collector-ledger.ts +309 -74
- package/src/collector-receipt.ts +0 -1
- package/src/collector-role.ts +21 -14
- package/src/diarist-mechanical.ts +16 -5
- package/src/gatekeeper-role.ts +33 -4
- package/src/grok/role-envelope.ts +68 -52
- package/src/inspector-contracts.ts +6 -3
- package/src/inspector-role.ts +5 -2
- package/src/notary-contracts.ts +20 -5
- package/src/notary-role.ts +2 -2
- package/src/packaged-role-registry.ts +0 -12
- package/src/public-cli/cli.ts +50 -125
- package/src/public-cli/coder-run.ts +5 -19
- package/src/public-cli/collector-run.ts +65 -21
- package/src/public-cli/countersign-run.ts +14 -40
- package/src/public-cli/doctor-run.ts +50 -6
- package/src/public-cli/fixer-run.ts +5 -19
- package/src/public-cli/gleaner-left-run.ts +14 -40
- package/src/public-cli/inspector-run.ts +51 -7
- package/src/public-cli/instruction-seat-run.ts +27 -0
- package/src/public-cli/judge-run.ts +11 -36
- package/src/public-cli/merger-run.ts +5 -19
- package/src/public-cli/notary-run.ts +54 -5
- package/src/public-cli/option-definitions.ts +3 -3
- package/src/public-cli/post-admission.ts +76 -0
- package/src/public-cli/reviewer-run.ts +5 -19
- package/src/public-cli/run-lifecycle.ts +342 -107
- package/src/public-cli/settlement.ts +134 -71
- package/src/role-runtime.ts +18 -1
- package/src/submission-ledger.ts +55 -5
package/src/collector-role.ts
CHANGED
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
COLLECTOR_READ_TOOL,
|
|
22
22
|
COLLECTOR_REQUEST_TOOL,
|
|
23
23
|
COLLECTOR_WAIT_TOOL,
|
|
24
|
-
createCollectorLedger,
|
|
25
24
|
projectEvidenceEntryView,
|
|
25
|
+
type CollectorConfigState,
|
|
26
26
|
type CollectorLedger,
|
|
27
27
|
} from "./collector-ledger.ts";
|
|
28
28
|
import {
|
|
@@ -88,6 +88,7 @@ export type CollectorRoleDependencies = {
|
|
|
88
88
|
loadSoul(): Promise<string>;
|
|
89
89
|
createTransport(): CollectorGitHubTransport;
|
|
90
90
|
createClock?(): CollectorClock;
|
|
91
|
+
createLedger(config: CollectorConfigState, clock: CollectorClock, ctx: HostContext): CollectorLedger;
|
|
91
92
|
packageExtensionPath?: string;
|
|
92
93
|
};
|
|
93
94
|
|
|
@@ -249,15 +250,16 @@ export function createCollectorRoleRuntime(
|
|
|
249
250
|
reason: `通进司禁用工具 ${event.toolName}`,
|
|
250
251
|
};
|
|
251
252
|
}
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
if (event.toolName === COLLECTOR_OUTPUT_TOOL) {
|
|
254
|
+
activation.ledger.beginOperational(COLLECTOR_OUTPUT_TOOL, event.toolCallId);
|
|
255
|
+
}
|
|
254
256
|
if (
|
|
255
|
-
activation.ledger.
|
|
257
|
+
activation.ledger.outputCandidate &&
|
|
256
258
|
event.toolName !== COLLECTOR_OUTPUT_TOOL
|
|
257
259
|
) {
|
|
258
260
|
return {
|
|
259
261
|
block: true,
|
|
260
|
-
reason: "
|
|
262
|
+
reason: "通进司已产出输出候选,本局不再受理操作",
|
|
261
263
|
};
|
|
262
264
|
}
|
|
263
265
|
return undefined;
|
|
@@ -270,7 +272,7 @@ export function createCollectorRoleRuntime(
|
|
|
270
272
|
|
|
271
273
|
pi.on("session_shutdown", () => {
|
|
272
274
|
if (activation === undefined) return;
|
|
273
|
-
if (
|
|
275
|
+
if (activation.ledger.fatal) {
|
|
274
276
|
if (process.exitCode === undefined || process.exitCode === 0) {
|
|
275
277
|
process.exitCode = 1;
|
|
276
278
|
}
|
|
@@ -452,8 +454,7 @@ export function createCollectorRoleRuntime(
|
|
|
452
454
|
params,
|
|
453
455
|
activation.clock,
|
|
454
456
|
);
|
|
455
|
-
activation.ledger.
|
|
456
|
-
activation.ledger.completeOperational(toolCallId);
|
|
457
|
+
activation.ledger.recordOutputCandidate();
|
|
457
458
|
const acceptedDetails = receipt;
|
|
458
459
|
return {
|
|
459
460
|
content: [{
|
|
@@ -472,6 +473,8 @@ export function createCollectorRoleRuntime(
|
|
|
472
473
|
hostActions.failInfrastructure(error, ctx, toolCallId);
|
|
473
474
|
}
|
|
474
475
|
throw error;
|
|
476
|
+
} finally {
|
|
477
|
+
activation.ledger.completeOperational(toolCallId);
|
|
475
478
|
}
|
|
476
479
|
},
|
|
477
480
|
});
|
|
@@ -488,7 +491,6 @@ export function createCollectorRoleRuntime(
|
|
|
488
491
|
);
|
|
489
492
|
}
|
|
490
493
|
if (
|
|
491
|
-
event.reason === "resume" ||
|
|
492
494
|
event.reason === "fork" ||
|
|
493
495
|
event.reason === "reload"
|
|
494
496
|
) {
|
|
@@ -581,11 +583,16 @@ export function createCollectorRoleRuntime(
|
|
|
581
583
|
|
|
582
584
|
const clock = dependencies.createClock?.() ?? createSystemCollectorClock();
|
|
583
585
|
const transport = dependencies.createTransport();
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
prNumber,
|
|
587
|
-
|
|
588
|
-
|
|
586
|
+
|
|
587
|
+
const ledger = dependencies.createLedger(
|
|
588
|
+
{ repository, prNumber, manifest },
|
|
589
|
+
clock,
|
|
590
|
+
ctx,
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
if (ledger.activationRecorded) {
|
|
594
|
+
firstDispatchDone = true;
|
|
595
|
+
}
|
|
589
596
|
|
|
590
597
|
activation = {
|
|
591
598
|
soul,
|
|
@@ -223,7 +223,7 @@ function isNotificationRow(type: string, text: string): boolean {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
|
-
* Enumerate cc session user/assistant turns under Claude projects roots.
|
|
226
|
+
* Enumerate cc session user/assistant turns and human queued-command attachments under Claude projects roots.
|
|
227
227
|
* cc-sessions-first source family (ADR 0075).
|
|
228
228
|
* Directory/file/non-empty JSONL line failures throw DiaristSourceReadError
|
|
229
229
|
* (失败诚实:不得把未读懂洗成没有来源块).
|
|
@@ -301,11 +301,22 @@ export function readCcSessionBlocks(
|
|
|
301
301
|
{ cause: error },
|
|
302
302
|
);
|
|
303
303
|
}
|
|
304
|
-
// Unknown row shape (not user/assistant) is ignored — not a read failure.
|
|
304
|
+
// Unknown row shape (not user/assistant or a human queued-command attachment) is ignored — not a read failure.
|
|
305
305
|
if (!isRecord(parsed)) continue;
|
|
306
306
|
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
const queuedCommand =
|
|
308
|
+
type === "attachment" &&
|
|
309
|
+
isRecord(parsed.attachment) &&
|
|
310
|
+
parsed.attachment.type === "queued_command" &&
|
|
311
|
+
isRecord(parsed.attachment.origin) &&
|
|
312
|
+
parsed.attachment.origin.kind === "human" &&
|
|
313
|
+
typeof parsed.attachment.prompt === "string"
|
|
314
|
+
? parsed.attachment.prompt
|
|
315
|
+
: undefined;
|
|
316
|
+
if (type !== "user" && type !== "assistant" && queuedCommand === undefined) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const transcript = queuedCommand ?? messageText(parsed.message ?? parsed);
|
|
309
320
|
if (transcript.trim() === "") continue;
|
|
310
321
|
const entryId =
|
|
311
322
|
typeof parsed.uuid === "string"
|
|
@@ -323,7 +334,7 @@ export function readCcSessionBlocks(
|
|
|
323
334
|
sourceRef: { sessionFile, entryId },
|
|
324
335
|
transcript,
|
|
325
336
|
timestamp,
|
|
326
|
-
isUserTurn: type === "user",
|
|
337
|
+
isUserTurn: type === "user" || queuedCommand !== undefined,
|
|
327
338
|
isNotification: notification,
|
|
328
339
|
});
|
|
329
340
|
}
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -36,6 +36,13 @@ export type GatekeeperResult =
|
|
|
36
36
|
readonly findings: readonly string[];
|
|
37
37
|
readonly submission: unknown;
|
|
38
38
|
}
|
|
39
|
+
| {
|
|
40
|
+
readonly status: "escalate";
|
|
41
|
+
readonly officer: "inspector" | "notary";
|
|
42
|
+
readonly reason?: unknown;
|
|
43
|
+
readonly findings: unknown;
|
|
44
|
+
readonly submission: unknown;
|
|
45
|
+
}
|
|
39
46
|
| { readonly status: "no_receipt"; readonly stage: "gatekeeper" | "inspector" | "notary"; readonly reason: string; readonly facts: NoReceiptLifecycleFacts }
|
|
40
47
|
| {
|
|
41
48
|
readonly status: "transport_failure";
|
|
@@ -63,6 +70,15 @@ function gateSeatLabel(stage: "gatekeeper" | "inspector" | "notary"): string {
|
|
|
63
70
|
|
|
64
71
|
export { GatekeeperDecisionError } from "./submission-errors.ts";
|
|
65
72
|
|
|
73
|
+
export class GatekeeperEscalationError extends Error {
|
|
74
|
+
readonly gatekeeper: Extract<GatekeeperResult, { status: "escalate" }>;
|
|
75
|
+
constructor(gatekeeper: Extract<GatekeeperResult, { status: "escalate" }>) {
|
|
76
|
+
super(`门下省${gateSeatLabel(gatekeeper.officer)}上呈`);
|
|
77
|
+
this.name = "GatekeeperEscalationError";
|
|
78
|
+
this.gatekeeper = gatekeeper;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
66
82
|
export type RunGatekeeperOptions = {
|
|
67
83
|
readonly context: ExtensionContext | HostContext;
|
|
68
84
|
readonly subject: GatekeeperSubject;
|
|
@@ -82,8 +98,9 @@ export type GatekeeperPassHostActions = {
|
|
|
82
98
|
// Unknown fields so wrong types/spellings still reach projection (ADR 0055/0057; 仓第 0 条).
|
|
83
99
|
// Opening goes through the sole openToolObject owner — no parallel transport helper.
|
|
84
100
|
const officerDecisionSchema = openToolObject(Type.Object({
|
|
85
|
-
status: Type.Unknown({ description: "pass | bounce — 形状指引,非 schema 闸" }),
|
|
86
|
-
|
|
101
|
+
status: Type.Unknown({ description: "pass | bounce | escalate — 形状指引,非 schema 闸" }),
|
|
102
|
+
reason: Type.Optional(Type.Unknown({ description: "status 为 escalate 时的理由" })),
|
|
103
|
+
findings: Type.Unknown({ description: "string[] findings,随 pass、bounce 或 escalate 留存" }),
|
|
87
104
|
}));
|
|
88
105
|
|
|
89
106
|
|
|
@@ -121,7 +138,7 @@ function subjectTool(subject: GatekeeperSubject): AuditorDecisionTool {
|
|
|
121
138
|
export function createOfficerDecisionTool(name: string): AuditorDecisionTool {
|
|
122
139
|
return {
|
|
123
140
|
name,
|
|
124
|
-
description: "提交一份 typed pass/bounce 决议。",
|
|
141
|
+
description: "提交一份 typed pass/bounce/escalate 决议。",
|
|
125
142
|
parameters: officerDecisionSchema,
|
|
126
143
|
async execute(_id, args) { return result(`已收 ${String((args as { status?: unknown })?.status)}`, args); },
|
|
127
144
|
};
|
|
@@ -179,7 +196,7 @@ function noUsableReleaseFailure(
|
|
|
179
196
|
return {
|
|
180
197
|
status: "transport_failure",
|
|
181
198
|
stage,
|
|
182
|
-
reason: stage === "gatekeeper" ? "decision 无显式 dispatch" : "decision 无显式 pass/bounce",
|
|
199
|
+
reason: stage === "gatekeeper" ? "decision 无显式 dispatch" : "decision 无显式 pass/bounce/escalate",
|
|
183
200
|
submission: retainedSubmission(decision),
|
|
184
201
|
};
|
|
185
202
|
}
|
|
@@ -224,6 +241,15 @@ function projectOfficerDecision(
|
|
|
224
241
|
findings: asStringArray(record.findings),
|
|
225
242
|
};
|
|
226
243
|
}
|
|
244
|
+
if (record.status === "escalate") {
|
|
245
|
+
return {
|
|
246
|
+
status: "escalate",
|
|
247
|
+
officer,
|
|
248
|
+
...(Object.hasOwn(record, "reason") ? { reason: record.reason } : {}),
|
|
249
|
+
findings: record.findings,
|
|
250
|
+
submission: retainedSubmission(decision),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
227
253
|
return noUsableReleaseFailure(officer, decision);
|
|
228
254
|
}
|
|
229
255
|
|
|
@@ -301,6 +327,9 @@ export async function requireGatekeeperPass(options: {
|
|
|
301
327
|
if (gatekeeper.submission !== undefined) error.submission = gatekeeper.submission;
|
|
302
328
|
options.hostActions.failInfrastructure(error, options.context, options.toolCallId);
|
|
303
329
|
}
|
|
330
|
+
if (gatekeeper.status === "escalate") {
|
|
331
|
+
throw new GatekeeperEscalationError(gatekeeper);
|
|
332
|
+
}
|
|
304
333
|
// Envelope owns the execute→tool_result bridge; this module only projects + throws.
|
|
305
334
|
options.hostActions.bindSubmissionNonPass(options.toolCallId, gatekeeper);
|
|
306
335
|
throw new GatekeeperDecisionError(gatekeeper);
|
|
@@ -623,59 +623,75 @@ export async function prepareGrokRoleEnvelope(options: {
|
|
|
623
623
|
|
|
624
624
|
// Shared envelope activation. systemPrompt must be ready before session/new
|
|
625
625
|
// (ACP delivers it there), so activation runs during prepare.
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
626
|
+
try {
|
|
627
|
+
await emit("session_start", { reason: request.continuation.kind });
|
|
628
|
+
const inputResults = await emit("input", { text: request.continuation.prompt, source: "interactive" });
|
|
629
|
+
let prompt = request.continuation.prompt;
|
|
630
|
+
for (const value of inputResults) {
|
|
631
|
+
if (typeof value !== "object" || value === null) continue;
|
|
632
|
+
const record = value as Record<string, unknown>;
|
|
633
|
+
if (record.action === "transform" && typeof record.text === "string") prompt = record.text;
|
|
634
|
+
}
|
|
635
|
+
// Book the user assignment so judge audit subjects recover it from parent books.
|
|
636
|
+
if (typeof prompt === "string" && prompt.trim() !== "") {
|
|
637
|
+
sessionEntries.push({
|
|
638
|
+
type: "message",
|
|
639
|
+
message: { role: "user", content: prompt },
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
const basePrompt = await loadMainRoleSessionMaterials(request.activation.role);
|
|
643
|
+
const methodPrompt = (await Promise.all(request.methods.map(({ path }) => readFile(path, "utf8")))).join("\n\n");
|
|
644
|
+
const promptResults = await emit("before_agent_start", {
|
|
645
|
+
prompt,
|
|
646
|
+
systemPrompt: [basePrompt, methodPrompt].filter(Boolean).join("\n\n"),
|
|
647
|
+
systemPromptOptions: {},
|
|
639
648
|
});
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
// Folded into the provider-visible systemPrompt by the adapter at the send boundary.
|
|
653
|
-
const readingMaterials: unknown[] = [];
|
|
654
|
-
for (const value of promptResults) {
|
|
655
|
-
if (typeof value !== "object" || value === null) continue;
|
|
656
|
-
if (!("readingMaterial" in value)) continue;
|
|
657
|
-
const material = (value as { readingMaterial?: unknown }).readingMaterial;
|
|
658
|
-
if (material !== undefined) readingMaterials.push(material);
|
|
659
|
-
}
|
|
649
|
+
const systemPromptBody = [...promptResults].reverse().find((value): value is { systemPrompt: string } =>
|
|
650
|
+
typeof value === "object" && value !== null && "systemPrompt" in value && typeof value.systemPrompt === "string")?.systemPrompt
|
|
651
|
+
?? [basePrompt, methodPrompt].filter(Boolean).join("\n\n");
|
|
652
|
+
// Typed reading materials from agent-start handlers (machine face; independent of prompt bytes).
|
|
653
|
+
// Folded into the provider-visible systemPrompt by the adapter at the send boundary.
|
|
654
|
+
const readingMaterials: unknown[] = [];
|
|
655
|
+
for (const value of promptResults) {
|
|
656
|
+
if (typeof value !== "object" || value === null) continue;
|
|
657
|
+
if (!("readingMaterial" in value)) continue;
|
|
658
|
+
const material = (value as { readingMaterial?: unknown }).readingMaterial;
|
|
659
|
+
if (material !== undefined) readingMaterials.push(material);
|
|
660
|
+
}
|
|
660
661
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
662
|
+
priorAkRoleRunDir = process.env.AK_ROLE_RUN_DIR;
|
|
663
|
+
process.env.AK_ROLE_RUN_DIR = request.runDirectory;
|
|
664
|
+
runDirInjected = true;
|
|
664
665
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
666
|
+
return {
|
|
667
|
+
mcpServers: [{
|
|
668
|
+
name: `ak-${request.activation.role}`,
|
|
669
|
+
command: process.execPath,
|
|
670
|
+
args: [relay],
|
|
671
|
+
env: [
|
|
672
|
+
{ name: "AK_GROK_MCP_SOCKET", value: options.socketPath },
|
|
673
|
+
{ name: "AK_GROK_MCP_TOKEN", value: token },
|
|
674
|
+
],
|
|
675
|
+
}],
|
|
676
|
+
systemPrompt: { body: systemPromptBody, materials: readingMaterials },
|
|
677
|
+
prompt,
|
|
678
|
+
abortSignal: hostAbort.signal,
|
|
679
|
+
closeRound,
|
|
680
|
+
dispose,
|
|
681
|
+
};
|
|
682
|
+
} catch (error) {
|
|
683
|
+
// listen already succeeded; dispose is not yet caller-owned. Release the
|
|
684
|
+
// unix listener before surfacing the activation/prepare failure. Keep both
|
|
685
|
+
// causes when dispose also fails (failure-honesty; do not erase cleanup).
|
|
686
|
+
try {
|
|
687
|
+
await dispose();
|
|
688
|
+
} catch (cleanupFailure) {
|
|
689
|
+
throw new AggregateError(
|
|
690
|
+
[error, cleanupFailure],
|
|
691
|
+
"prepareGrokRoleEnvelope activation failed and its dispose cleanup also failed",
|
|
692
|
+
{ cause: error },
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
throw error;
|
|
696
|
+
}
|
|
681
697
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public Inspector (察院) terminating receipt contracts.
|
|
3
|
-
* Lawful explicit releases: pass | bounce.
|
|
3
|
+
* Lawful explicit releases: pass | bounce | escalate.
|
|
4
4
|
* Dual path: gate-dispatched and independently callable (#568 / ADR 0074).
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -9,7 +9,8 @@ export const INSPECTOR_ACCEPTED_TEXT = "察院回执已接受";
|
|
|
9
9
|
|
|
10
10
|
export type InspectorOutput =
|
|
11
11
|
| { readonly status: "pass"; readonly findings?: unknown }
|
|
12
|
-
| { readonly status: "bounce"; readonly findings?: unknown }
|
|
12
|
+
| { readonly status: "bounce"; readonly findings?: unknown }
|
|
13
|
+
| { readonly status: "escalate"; readonly reason?: unknown; readonly findings?: unknown };
|
|
13
14
|
|
|
14
15
|
export function validateRecordedInspectorOutput(value: unknown): InspectorOutput {
|
|
15
16
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -21,7 +22,7 @@ export function validateRecordedInspectorOutput(value: unknown): InspectorOutput
|
|
|
21
22
|
} catch {
|
|
22
23
|
throw new Error("Inspector output has no execution discriminator");
|
|
23
24
|
}
|
|
24
|
-
if (status === "pass" || status === "bounce") {
|
|
25
|
+
if (status === "pass" || status === "bounce" || status === "escalate") {
|
|
25
26
|
return value as InspectorOutput;
|
|
26
27
|
}
|
|
27
28
|
throw new Error("Inspector output has no execution discriminator");
|
|
@@ -32,5 +33,7 @@ export function inspectorDecisiveFacts(output: InspectorOutput): Record<string,
|
|
|
32
33
|
const facts: Record<string, unknown> = { status: output.status };
|
|
33
34
|
const findings = (output as { findings?: unknown }).findings;
|
|
34
35
|
if (findings !== undefined) facts.findings = findings;
|
|
36
|
+
const reason = (output as { reason?: unknown }).reason;
|
|
37
|
+
if (reason !== undefined) facts.reason = reason;
|
|
35
38
|
return facts;
|
|
36
39
|
}
|
package/src/inspector-role.ts
CHANGED
|
@@ -23,11 +23,14 @@ export const inspectorOutputSchema = withInfrastructureFailureDeclaration(
|
|
|
23
23
|
openToolObject(
|
|
24
24
|
Type.Object({
|
|
25
25
|
status: Type.Unknown({
|
|
26
|
-
description: "pass | bounce — 形状指引,非 schema 闸",
|
|
26
|
+
description: "pass | bounce | escalate — 形状指引,非 schema 闸",
|
|
27
27
|
}),
|
|
28
28
|
findings: Type.Unknown({
|
|
29
29
|
description: "随交卷留存的问题记录",
|
|
30
30
|
}),
|
|
31
|
+
reason: Type.Optional(Type.Unknown({
|
|
32
|
+
description: "status 为 escalate 时的上呈理由",
|
|
33
|
+
})),
|
|
31
34
|
}),
|
|
32
35
|
),
|
|
33
36
|
);
|
|
@@ -44,7 +47,7 @@ export type InspectorRuntimeDependencies = {
|
|
|
44
47
|
export const INSPECTOR_TOOL_SPEC = {
|
|
45
48
|
name: INSPECTOR_OUTPUT_TOOL_NAME,
|
|
46
49
|
label: "察院输出",
|
|
47
|
-
description: "察院终局回执,状态为 pass 或
|
|
50
|
+
description: "察院终局回执,状态为 pass、bounce 或 escalate。",
|
|
48
51
|
promptSnippet: "察院终局回执",
|
|
49
52
|
parameters: inspectorOutputSchema,
|
|
50
53
|
} as const;
|
package/src/notary-contracts.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public Notary (符宝郎) terminating receipt contracts.
|
|
3
|
-
* Lawful explicit releases: pass | bounce.
|
|
3
|
+
* Lawful explicit releases: pass | bounce | escalate.
|
|
4
4
|
* No usable result is infrastructure failure via public settlement, not a judgment status (#475).
|
|
5
5
|
*/
|
|
6
6
|
import { Type } from "typebox";
|
|
@@ -35,11 +35,14 @@ export const notaryOutputSchema = withInfrastructureFailureDeclaration(
|
|
|
35
35
|
openToolObject(
|
|
36
36
|
Type.Object({
|
|
37
37
|
status: Type.Unknown({
|
|
38
|
-
description: "pass | bounce — 形状指引,非 schema 闸",
|
|
38
|
+
description: "pass | bounce | escalate — 形状指引,非 schema 闸",
|
|
39
39
|
}),
|
|
40
40
|
findings: Type.Unknown({
|
|
41
|
-
description: "string[] findings,随 pass 或
|
|
41
|
+
description: "string[] findings,随 pass、bounce 或 escalate 留存",
|
|
42
42
|
}),
|
|
43
|
+
reason: Type.Optional(Type.Unknown({
|
|
44
|
+
description: "status 为 escalate 时的上呈理由",
|
|
45
|
+
})),
|
|
43
46
|
}),
|
|
44
47
|
),
|
|
45
48
|
);
|
|
@@ -56,6 +59,11 @@ export type NotaryOutput =
|
|
|
56
59
|
readonly status: "bounce";
|
|
57
60
|
readonly disposition: "rewrite";
|
|
58
61
|
readonly findings: readonly string[];
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
readonly status: "escalate";
|
|
65
|
+
readonly reason?: unknown;
|
|
66
|
+
readonly findings?: unknown;
|
|
59
67
|
};
|
|
60
68
|
|
|
61
69
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -68,7 +76,7 @@ function asStringArray(value: unknown): readonly string[] {
|
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
/**
|
|
71
|
-
* Project one lawful explicit Notary release (pass | bounce).
|
|
79
|
+
* Project one lawful explicit Notary release (pass | bounce | escalate).
|
|
72
80
|
* No throw on shape — ADR 0055 / 第 0 条: already-submitted params are retained as-is;
|
|
73
81
|
* public-terminal projects non-usable releases via typed failure cause.
|
|
74
82
|
*/
|
|
@@ -86,6 +94,9 @@ export function projectLawfulNotaryOutput(value: unknown): NotaryOutput | undefi
|
|
|
86
94
|
if (!Array.isArray(clone.findings)) clone.findings = asStringArray(clone.findings);
|
|
87
95
|
return clone as NotaryOutput;
|
|
88
96
|
}
|
|
97
|
+
if (status === "escalate") {
|
|
98
|
+
return structuredClone(value) as NotaryOutput;
|
|
99
|
+
}
|
|
89
100
|
return undefined;
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -100,7 +111,7 @@ export function retainNotarySubmission(value: unknown): unknown {
|
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
/**
|
|
103
|
-
* Settlement/recording path: only lawful recorded pass/bounce.
|
|
114
|
+
* Settlement/recording path: only lawful recorded pass/bounce/escalate.
|
|
104
115
|
* Does not gate role admission — callers must not use this to reject a submission.
|
|
105
116
|
*/
|
|
106
117
|
export function validateRecordedNotaryOutput(value: unknown): NotaryOutput {
|
|
@@ -121,5 +132,9 @@ export function notaryDecisiveFacts(output: NotaryOutput): Record<string, unknow
|
|
|
121
132
|
if (status === "bounce") {
|
|
122
133
|
facts.disposition = "rewrite";
|
|
123
134
|
}
|
|
135
|
+
if (status === "escalate") {
|
|
136
|
+
const reason = (output as { reason?: unknown }).reason;
|
|
137
|
+
if (reason !== undefined) facts.reason = reason;
|
|
138
|
+
}
|
|
124
139
|
return facts;
|
|
125
140
|
}
|
package/src/notary-role.ts
CHANGED
|
@@ -163,7 +163,7 @@ export function createNotaryRoleRuntime(
|
|
|
163
163
|
pi.registerTool({
|
|
164
164
|
name: NOTARY_OUTPUT_TOOL_NAME,
|
|
165
165
|
label: "符宝郎输出",
|
|
166
|
-
description: "提交引文保真与票面对齐的 typed pass/bounce 决议。",
|
|
166
|
+
description: "提交引文保真与票面对齐的 typed pass/bounce/escalate 决议。",
|
|
167
167
|
promptSnippet: "提交符宝郎决议",
|
|
168
168
|
parameters: notaryOutputSchema,
|
|
169
169
|
async execute(toolCallId: string, parameters: unknown, _signal: AbortSignal | undefined, _onUpdate: unknown, ctx: HostContext, ): Promise<HostToolResult<unknown>> {
|
|
@@ -171,7 +171,7 @@ export function createNotaryRoleRuntime(
|
|
|
171
171
|
throw new Error("符宝郎未激活");
|
|
172
172
|
}
|
|
173
173
|
// Unique submission + terminate only. Shape is not an admission gate
|
|
174
|
-
// (第 0 条 / ADR 0055): lawful pass/bounce projected; else params as-is.
|
|
174
|
+
// (第 0 条 / ADR 0055): lawful pass/bounce/escalate projected; else params as-is.
|
|
175
175
|
// #541 infra declaration + sole-final barrier are ledger-owned (#575).
|
|
176
176
|
const lawful = projectLawfulNotaryOutput(parameters);
|
|
177
177
|
const details = lawful ?? retainNotarySubmission(parameters);
|
|
@@ -190,18 +190,6 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
190
190
|
export type PublicRoleRecord = (typeof PUBLIC_ROLE_RECORDS)[number];
|
|
191
191
|
export type PackagedRole = PublicRoleRecord["role"];
|
|
192
192
|
|
|
193
|
-
/**
|
|
194
|
-
* Seats that refuse resume. Collector/doctor/notary remain pending independent
|
|
195
|
-
* authority review; countersign/gleaner-left revoked by #599 (DK-1/2/3).
|
|
196
|
-
* Inspector is one-shot on the filed-officer envelope (#568 / ADR 0074).
|
|
197
|
-
*/
|
|
198
|
-
export const ONE_SHOT_ROLES: readonly PackagedRole[] = [
|
|
199
|
-
"collector",
|
|
200
|
-
"doctor",
|
|
201
|
-
"notary",
|
|
202
|
-
"inspector",
|
|
203
|
-
];
|
|
204
|
-
|
|
205
193
|
/**
|
|
206
194
|
* Read-only metadata projection (no sessionMaterials).
|
|
207
195
|
* Distributed per PublicRoleRecord member so role↔field associations stay intact.
|