@akagilnc/pi-workflow-roles 0.1.5068 → 0.1.5079
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 +82 -83
- package/dist/compliance-transport.js +3 -2
- package/dist/countersign-contracts.js +2 -2
- package/dist/gatekeeper-role.js +12 -19
- package/dist/headless-host/production-host.js +82 -83
- package/dist/inspector-contracts.js +5 -2
- package/dist/migrate-book-topology.js +32 -20
- package/dist/open-tool-schema.js +4 -1
- package/dist/package-contracts/judge-output.js +2 -2
- package/dist/package-contracts/terminating-infrastructure.js +4 -1
- package/dist/public-cli/main.js +33 -20
- package/dist/review-submission.js +19 -8
- package/dist/submission-errors.js +17 -0
- package/dist/submission-ledger.js +18 -6
- package/package.json +1 -1
- package/src/compliance-transport.ts +3 -2
- package/src/countersign-contracts.ts +2 -2
- package/src/countersign-role.ts +3 -7
- package/src/gatekeeper-role.ts +14 -18
- package/src/inspector-contracts.ts +5 -2
- package/src/judge-role.ts +13 -21
- package/src/open-tool-schema.ts +9 -4
- package/src/package-contracts/judge-output.ts +2 -4
- package/src/package-contracts/terminating-infrastructure.ts +9 -4
- package/src/review-submission.ts +24 -10
- package/src/role-runtime.ts +9 -25
- package/src/submission-errors.ts +16 -0
- package/src/submission-gate.ts +2 -1
- package/src/submission-ledger.ts +23 -11
- package/src/worker-role.ts +4 -6
|
@@ -1,13 +1,24 @@
|
|
|
1
|
-
/** One shared terminating receipt contract for review officers (#1028). */
|
|
1
|
+
/** One shared terminating receipt contract for review officers (#1028 / #1055). */
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { openToolObject } from "./open-tool-schema.js";
|
|
4
3
|
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.js";
|
|
5
4
|
export const REVIEW_SUBMISSION_OUTPUT_TOOL_NAME = "ak_submission_output";
|
|
6
|
-
/**
|
|
7
|
-
export const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/** Single schema copy of the review queue words. Runtime membership reads this set. */
|
|
6
|
+
export const REVIEW_QUEUE_WORDS = ["converged", "continue", "escalate"];
|
|
7
|
+
export const REVIEW_QUEUE_STATUSES = new Set(REVIEW_QUEUE_WORDS);
|
|
8
|
+
/**
|
|
9
|
+
* One object root. Codex structured output rejects a root anyOf, and status
|
|
10
|
+
* stays a required three-state enum with no omitted-status failure branch.
|
|
11
|
+
* A review seat that can still submit records a real external-dependency
|
|
12
|
+
* failure as escalate plus the existing infrastructureFailure field.
|
|
13
|
+
*/
|
|
14
|
+
export const reviewSubmissionSchema = withTerminatingOutputDeclarations(Type.Object({
|
|
15
|
+
status: Type.Union([
|
|
16
|
+
Type.Literal(REVIEW_QUEUE_WORDS[0]),
|
|
17
|
+
Type.Literal(REVIEW_QUEUE_WORDS[1]),
|
|
18
|
+
Type.Literal(REVIEW_QUEUE_WORDS[2]),
|
|
19
|
+
], {
|
|
20
|
+
description: "审核队列三态枚举,必填。审核席仍能交卷而完成审核所需的外部依赖发生真实基础设施失败时,status 取 escalate,细节写入 infrastructureFailure。不得省略 status,不得用另两个枚举值充当放行或封驳。包侧不因格式拒收。",
|
|
21
|
+
}),
|
|
11
22
|
findings: Type.Optional(Type.Unknown({ description: "审核发现,原样留存" })),
|
|
12
23
|
reason: Type.Optional(Type.Unknown({ description: "上呈理由" })),
|
|
13
24
|
violations: Type.Optional(Type.Unknown({ description: "违规条目" })),
|
|
@@ -17,4 +28,4 @@ export const reviewSubmissionSchema = withTerminatingOutputDeclarations(openTool
|
|
|
17
28
|
note: Type.Optional(Type.Unknown({ description: "裁决附注" })),
|
|
18
29
|
evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
|
|
19
30
|
decisionGate: Type.Optional(Type.Unknown({ description: "需人权威处置的问题与选项;可说明 question、options,原样留存" })),
|
|
20
|
-
}))
|
|
31
|
+
}, { additionalProperties: true }));
|
|
@@ -50,6 +50,23 @@ export class WorkerUnfinishedReasonReminderError extends Error {
|
|
|
50
50
|
this.name = "WorkerUnfinishedReasonReminderError";
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Host constraint missed a routing discriminator (#1055).
|
|
55
|
+
* Names live only in the submission schema. This notice carries the field and the received value.
|
|
56
|
+
*/
|
|
57
|
+
export function unreadableDiscriminatorNotice(field, received) {
|
|
58
|
+
if (received === undefined)
|
|
59
|
+
return `读不出 ${field}`;
|
|
60
|
+
return `读不出 ${field}:${readableGateItem(received)}`;
|
|
61
|
+
}
|
|
62
|
+
/** The received discriminator only — never the rest of the receipt. */
|
|
63
|
+
export function receivedDiscriminator(receipt, field) {
|
|
64
|
+
if (receipt === null || typeof receipt !== "object" || Array.isArray(receipt))
|
|
65
|
+
return undefined;
|
|
66
|
+
if (!Object.hasOwn(receipt, field))
|
|
67
|
+
return undefined;
|
|
68
|
+
return receipt[field];
|
|
69
|
+
}
|
|
53
70
|
/**
|
|
54
71
|
* Parent seat status unreadable for queueing (#753).
|
|
55
72
|
* Correctable back to the parent itself — not a gate officer bounce, no officer field.
|
|
@@ -6,7 +6,8 @@ import { runIdFromRunDirectory } from "./run-terminal-artifacts.js";
|
|
|
6
6
|
import { readSitianRecords, resolveSitianRecordPathInLedger, sitianReport } from "./sitian-facade.js";
|
|
7
7
|
import { findRunDirectoryById } from "./public-cli/run-lifecycle.js";
|
|
8
8
|
import { isCorrectableExecuteError } from "./submission-correctable-error.js";
|
|
9
|
-
import { failOnInfrastructureFailureDeclaration } from "./package-contracts/terminating-infrastructure.js";
|
|
9
|
+
import { failOnInfrastructureFailureDeclaration, infrastructureFailureDiagnostic } from "./package-contracts/terminating-infrastructure.js";
|
|
10
|
+
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.js";
|
|
10
11
|
/**
|
|
11
12
|
* Admitted run identity for the ledger subject.
|
|
12
13
|
* Prefer AK_ROLE_RUN_DIR via sole runDirectory→runId parser (public-CLI correlation);
|
|
@@ -480,11 +481,22 @@ export function createSubmissionLedgerHost(host, outputTools, failInfrastructure
|
|
|
480
481
|
});
|
|
481
482
|
let result;
|
|
482
483
|
try {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
484
|
+
// A review escalate that carries the failure declaration is the
|
|
485
|
+
// seat's own submission. Every other declaration stays on the
|
|
486
|
+
// host-failure seam. Tool identity comes from the registry map.
|
|
487
|
+
const reviewEscalate = tool.name === REVIEW_SUBMISSION_OUTPUT_TOOL_NAME
|
|
488
|
+
&& infrastructureFailureDiagnostic(params) !== undefined
|
|
489
|
+
&& params !== null
|
|
490
|
+
&& typeof params === "object"
|
|
491
|
+
&& !Array.isArray(params)
|
|
492
|
+
&& params.status === "escalate";
|
|
493
|
+
if (!reviewEscalate) {
|
|
494
|
+
failOnInfrastructureFailureDeclaration(params, {
|
|
495
|
+
failInfrastructure(error, ctx) {
|
|
496
|
+
failInfrastructure(error, ctx);
|
|
497
|
+
},
|
|
498
|
+
}, context, toolCallId, tool.bounceInfrastructureDeclaration);
|
|
499
|
+
}
|
|
488
500
|
result = await tool.execute(toolCallId, params, signal, update, context);
|
|
489
501
|
}
|
|
490
502
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -4,7 +4,8 @@ import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
|
4
4
|
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
|
-
import {
|
|
7
|
+
import { officerConclusionReask } from "./gatekeeper-role.ts";
|
|
8
|
+
import { receivedDiscriminator } from "./submission-errors.ts";
|
|
8
9
|
import { coalesceSubmissionRows } from "./public-cli/terminal.ts";
|
|
9
10
|
import { readableGateItem } from "./readable-gate-item.ts";
|
|
10
11
|
|
|
@@ -226,7 +227,7 @@ export async function runComplianceAudit(options: RunComplianceAuditOptions): Pr
|
|
|
226
227
|
const summoned = await summon(subject, runDirectory, options.signal, reask, submission);
|
|
227
228
|
const decision = await projectAuditorTerminal(summoned);
|
|
228
229
|
if (decision.status === "received") {
|
|
229
|
-
reask =
|
|
230
|
+
reask = officerConclusionReask(receivedDiscriminator(decision.reply, "status"));
|
|
230
231
|
continue;
|
|
231
232
|
}
|
|
232
233
|
return decision;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* is defaulted, rewritten, or dropped (ADR 0055).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
8
|
+
import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
9
9
|
|
|
10
10
|
export const COUNTERSIGN_OUTPUT_TOOL_NAME = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
|
|
11
11
|
|
|
@@ -37,7 +37,7 @@ export function validateRecordedCountersignOutput(verdict: unknown): Countersign
|
|
|
37
37
|
if (typeof status !== "string") {
|
|
38
38
|
throw new Error("Countersign verdict has no execution discriminator");
|
|
39
39
|
}
|
|
40
|
-
if (
|
|
40
|
+
if (REVIEW_QUEUE_STATUSES.has(status)) {
|
|
41
41
|
return verdict as CountersignVerdict;
|
|
42
42
|
}
|
|
43
43
|
throw new Error("Countersign verdict has no execution discriminator");
|
package/src/countersign-role.ts
CHANGED
|
@@ -11,13 +11,9 @@ export type { CountersignVerdict };
|
|
|
11
11
|
// #836 r16 class 1: fix/note/decisionGate are LLM/human-read narrative content —
|
|
12
12
|
// no code branches on their length or nested presence. `status` alone is the
|
|
13
13
|
// machine discriminator the queue reads.
|
|
14
|
-
// #
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// (Type.Unknown) like every other gate-queue status field in this package
|
|
18
|
-
// (notary/auditor/gatekeeper/navigator); src/role-runtime.ts:997-1025 still
|
|
19
|
-
// classifies the recorded value and reasks the countersign itself when it
|
|
20
|
-
// isn't one of the three states — code, not the transport, does that work.
|
|
14
|
+
// #1055: the three-state field is an enum+required host generation constraint
|
|
15
|
+
// on reviewSubmissionSchema. Package execute still does not reject; an unreadable
|
|
16
|
+
// value reasks the countersign itself with the received value.
|
|
21
17
|
/** 给事中票庭审读五问的交卷形状(ADR 0074);形状指引,非 schema 闸。 */
|
|
22
18
|
export const countersignVerdictSchema = reviewSubmissionSchema;
|
|
23
19
|
export type CountersignVerdictParameters = import("./review-submission.ts").ReviewSubmission;
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -4,9 +4,9 @@ import type { HostContext } from "./host-contracts.ts";
|
|
|
4
4
|
import { auditorRunDirectory } from "./auditor-dossier-tool.ts";
|
|
5
5
|
import { packagedGateStageLabel } from "./packaged-role-registry.ts";
|
|
6
6
|
import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
7
|
-
import { GatekeeperDecisionError } from "./submission-errors.ts";
|
|
7
|
+
import { GatekeeperDecisionError, receivedDiscriminator, unreadableDiscriminatorNotice } from "./submission-errors.ts";
|
|
8
8
|
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
|
|
9
|
-
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
9
|
+
import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
10
10
|
import {
|
|
11
11
|
GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
12
12
|
gatekeeperDecisionSchema,
|
|
@@ -66,6 +66,8 @@ export type GatekeeperResult =
|
|
|
66
66
|
readonly status: "needs_reask";
|
|
67
67
|
readonly officer: GateOfficer;
|
|
68
68
|
readonly receipt: unknown;
|
|
69
|
+
/** Discriminator the queue already read. Not written back onto the receipt. */
|
|
70
|
+
readonly receivedStatus?: unknown;
|
|
69
71
|
readonly runId?: string;
|
|
70
72
|
}
|
|
71
73
|
| { readonly status: "no_receipt"; readonly stage: GateOfficer; readonly reason: string; readonly facts: NoReceiptLifecycleFacts }
|
|
@@ -210,12 +212,9 @@ function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
210
212
|
* `fallbackStatus` is the terminal outcome.status when the receipt body has no status key
|
|
211
213
|
* (keeps missing-args sentinel intact as the receipt).
|
|
212
214
|
*/
|
|
213
|
-
/** Map the shared review verdict onto the gate queue words (#1028). */
|
|
215
|
+
/** Map the shared review verdict onto the gate queue words (#1028 / #1055). */
|
|
214
216
|
function reviewQueueStatus(status: string): string | undefined {
|
|
215
|
-
|
|
216
|
-
if (status === "continue") return "continue";
|
|
217
|
-
if (status === "escalate") return "escalate";
|
|
218
|
-
return undefined;
|
|
217
|
+
return REVIEW_QUEUE_STATUSES.has(status) ? status : undefined;
|
|
219
218
|
}
|
|
220
219
|
|
|
221
220
|
function projectOfficerDecision(
|
|
@@ -235,7 +234,7 @@ function projectOfficerDecision(
|
|
|
235
234
|
if (queueStatus === "continue" || queueStatus === "escalate") {
|
|
236
235
|
return { status: queueStatus, officer, receipt };
|
|
237
236
|
}
|
|
238
|
-
return { status: "needs_reask", officer, receipt };
|
|
237
|
+
return { status: "needs_reask", officer, receipt, receivedStatus: status };
|
|
239
238
|
}
|
|
240
239
|
|
|
241
240
|
/**
|
|
@@ -371,12 +370,14 @@ function projectOfficerTerminal(
|
|
|
371
370
|
summoned,
|
|
372
371
|
);
|
|
373
372
|
}
|
|
373
|
+
const receipt = thisCourt.length > 0 ? thisCourt[thisCourt.length - 1] : retainedReceipt(outcome);
|
|
374
374
|
return withOfficerRunId(
|
|
375
375
|
{
|
|
376
376
|
status: "needs_reask",
|
|
377
377
|
officer,
|
|
378
378
|
// This-court receipt only (#879).
|
|
379
|
-
receipt
|
|
379
|
+
receipt,
|
|
380
|
+
receivedStatus: receivedDiscriminator(receipt, "status"),
|
|
380
381
|
},
|
|
381
382
|
summoned,
|
|
382
383
|
);
|
|
@@ -391,16 +392,11 @@ export type GatekeeperProjection = {
|
|
|
391
392
|
};
|
|
392
393
|
|
|
393
394
|
/**
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* Every review officer uses the same `status` field and three queue words (#1028).
|
|
395
|
+
* Officer conclusion was missing or not a queue word (#1055).
|
|
396
|
+
* `received` is the status value only, not the rest of the receipt.
|
|
397
397
|
*/
|
|
398
|
-
export
|
|
399
|
-
"
|
|
400
|
-
|
|
401
|
-
/** Pick the re-ask line for the officer under review. */
|
|
402
|
-
export function officerConclusionReask(officer: GateOfficer): string {
|
|
403
|
-
return OFFICER_CONCLUSION_REASK;
|
|
398
|
+
export function officerConclusionReask(received: unknown): string {
|
|
399
|
+
return unreadableDiscriminatorNotice("status", received);
|
|
404
400
|
}
|
|
405
401
|
|
|
406
402
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Dual path: gate-dispatched and independently callable (#568 / ADR 0074).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
7
|
+
import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
8
8
|
|
|
9
9
|
export const INSPECTOR_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
|
|
10
10
|
export const INSPECTOR_SOURCE_RUN_FLAG = {
|
|
@@ -30,7 +30,10 @@ export function validateRecordedInspectorOutput(value: unknown): InspectorOutput
|
|
|
30
30
|
} catch {
|
|
31
31
|
throw new Error("Inspector output has no execution discriminator");
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (typeof status !== "string") {
|
|
34
|
+
throw new Error("Inspector output has no execution discriminator");
|
|
35
|
+
}
|
|
36
|
+
if (REVIEW_QUEUE_STATUSES.has(status)) {
|
|
34
37
|
return value as InspectorOutput;
|
|
35
38
|
}
|
|
36
39
|
throw new Error("Inspector output has no execution discriminator");
|
package/src/judge-role.ts
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
import type { RoleHost, HostContext, HostToolResult, HostGatekeeperActions } from "./host-contracts.ts";
|
|
2
2
|
import type { Static } from "typebox";
|
|
3
|
-
import { reviewSubmissionSchema } from "./review-submission.ts";
|
|
4
|
-
import { GatekeeperDecisionError, ParentQueueReaskError } from "./submission-errors.ts";
|
|
3
|
+
import { REVIEW_QUEUE_STATUSES, reviewSubmissionSchema } from "./review-submission.ts";
|
|
4
|
+
import { GatekeeperDecisionError, ParentQueueReaskError, unreadableDiscriminatorNotice } from "./submission-errors.ts";
|
|
5
5
|
import { projectGatekeeperEscalation } from "./audit-escalation.ts";
|
|
6
6
|
|
|
7
7
|
import { readableGateItem } from "./readable-gate-item.ts";
|
|
8
8
|
import {
|
|
9
9
|
JUDGE_OUTPUT_TOOL_NAME,
|
|
10
|
-
validateAcceptedJudgeDetails,
|
|
11
10
|
type JudgeVerdict,
|
|
12
11
|
} from "./package-contracts/judge-output.ts";
|
|
13
12
|
|
|
14
|
-
const JUDGE_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
|
|
15
|
-
const JUDGE_STATUS_REASK =
|
|
16
|
-
"status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
|
|
17
13
|
|
|
18
14
|
export { JUDGE_OUTPUT_TOOL_NAME };
|
|
19
15
|
export type { JudgeVerdict };
|
|
@@ -25,8 +21,6 @@ export type { JudgeVerdict };
|
|
|
25
21
|
// The shared open schema preserves role-specific prose without adding another contract.
|
|
26
22
|
export const judgeVerdictSchema = reviewSubmissionSchema;
|
|
27
23
|
|
|
28
|
-
type JudgeVerdictParameters = Static<typeof judgeVerdictSchema>;
|
|
29
|
-
|
|
30
24
|
export type JudgeRoleDependencies = {
|
|
31
25
|
loadSoul(): Promise<string>;
|
|
32
26
|
};
|
|
@@ -36,10 +30,6 @@ export type JudgeRoleHostActions = HostGatekeeperActions & {
|
|
|
36
30
|
bindPriorGatePass(toolCallId: string, receipt: unknown): void;
|
|
37
31
|
};
|
|
38
32
|
|
|
39
|
-
export function validateVerdict(verdict: JudgeVerdictParameters): JudgeVerdict {
|
|
40
|
-
return validateAcceptedJudgeDetails(verdict);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
33
|
|
|
44
34
|
export function createJudgeRoleRuntime(
|
|
45
35
|
pi: RoleHost,
|
|
@@ -70,19 +60,21 @@ export function createJudgeRoleRuntime(
|
|
|
70
60
|
&& typeof (parameters as Record<string, unknown>).status === "string"
|
|
71
61
|
? (parameters as Record<string, unknown>).status as string
|
|
72
62
|
: undefined;
|
|
73
|
-
if (rawStatus === undefined || !
|
|
74
|
-
|
|
63
|
+
if (rawStatus === undefined || !REVIEW_QUEUE_STATUSES.has(rawStatus)) {
|
|
64
|
+
const received = parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
|
|
65
|
+
? (parameters as Record<string, unknown>).status
|
|
66
|
+
: undefined;
|
|
67
|
+
throw new ParentQueueReaskError(unreadableDiscriminatorNotice("status", received));
|
|
75
68
|
}
|
|
76
69
|
if (rawStatus === "escalate") {
|
|
77
70
|
// Parent escalate → throw to caller as-is; officers do not attend (#753 / #756).
|
|
78
|
-
const verdict = validateVerdict(parameters);
|
|
79
71
|
return {
|
|
80
72
|
content: [],
|
|
81
|
-
details:
|
|
73
|
+
details: parameters,
|
|
82
74
|
terminate: true as const,
|
|
83
75
|
};
|
|
84
76
|
}
|
|
85
|
-
|
|
77
|
+
// Queue membership above is the only three-state check. The receipt is not re-judged.
|
|
86
78
|
// Candidate verdict is already on the parent session books as this
|
|
87
79
|
// tool-call leaf (first-record-then-audit; run 019fea05 L61/L62).
|
|
88
80
|
// #753: 符宝郎内闸 — continue returns the raw receipt as a nonterminal tool result.
|
|
@@ -101,7 +93,7 @@ export function createJudgeRoleRuntime(
|
|
|
101
93
|
if (draftPass?.status === "continue") {
|
|
102
94
|
return {
|
|
103
95
|
content: [{ type: "text" as const, text: readableGateItem(draftPass.receipt) }],
|
|
104
|
-
details:
|
|
96
|
+
details: parameters,
|
|
105
97
|
terminate: false,
|
|
106
98
|
};
|
|
107
99
|
}
|
|
@@ -121,18 +113,18 @@ export function createJudgeRoleRuntime(
|
|
|
121
113
|
if (compliancePass?.status === "continue") {
|
|
122
114
|
return {
|
|
123
115
|
content: [draftPass, compliancePass].filter((pass) => pass !== undefined).map((pass) => ({ type: "text" as const, text: readableGateItem(pass.receipt) })),
|
|
124
|
-
details:
|
|
116
|
+
details: parameters,
|
|
125
117
|
terminate: false,
|
|
126
118
|
};
|
|
127
119
|
}
|
|
128
120
|
return {
|
|
129
121
|
content: [draftPass, compliancePass].filter((pass) => pass !== undefined).map((pass) => ({ type: "text" as const, text: readableGateItem(pass.receipt) })),
|
|
130
|
-
details:
|
|
122
|
+
details: parameters,
|
|
131
123
|
terminate: true as const,
|
|
132
124
|
};
|
|
133
125
|
} catch (error) {
|
|
134
126
|
if (error instanceof GatekeeperDecisionError && error.result.status === "escalate") {
|
|
135
|
-
return projectGatekeeperEscalation(error.result,
|
|
127
|
+
return projectGatekeeperEscalation(error.result, parameters);
|
|
136
128
|
}
|
|
137
129
|
throw error;
|
|
138
130
|
}
|
package/src/open-tool-schema.ts
CHANGED
|
@@ -12,7 +12,7 @@ function declarationIdentity(schema: TSchema): string {
|
|
|
12
12
|
return JSON.stringify(semantic);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/** Collapse transport variants into one provider-compatible
|
|
15
|
+
/** Collapse transport variants into one provider-compatible open object. Non-three-state tools stay zero-required. */
|
|
16
16
|
export function openToolObjectFromUnion(schema: UnionSchema): TSchema {
|
|
17
17
|
const declarations = new Map<string, TSchema[]>();
|
|
18
18
|
for (const variant of schema.anyOf) {
|
|
@@ -35,10 +35,15 @@ export function openToolObjectFromUnion(schema: UnionSchema): TSchema {
|
|
|
35
35
|
return object;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/** Open a pre-existing object transport schema
|
|
39
|
-
export function openToolObject(
|
|
38
|
+
/** Open a pre-existing object transport schema. Every field stays optional. */
|
|
39
|
+
export function openToolObject(
|
|
40
|
+
schema: TSchema & { properties: Record<string, TSchema> },
|
|
41
|
+
): TSchema {
|
|
40
42
|
const object = Type.Object(
|
|
41
|
-
Object.fromEntries(Object.entries(schema.properties).map(([name, declaration]) => [
|
|
43
|
+
Object.fromEntries(Object.entries(schema.properties).map(([name, declaration]) => [
|
|
44
|
+
name,
|
|
45
|
+
Type.Optional(described(name, declaration)),
|
|
46
|
+
])),
|
|
42
47
|
{ additionalProperties: true },
|
|
43
48
|
);
|
|
44
49
|
(object as unknown as { required: string[] }).required = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Package-owned Judge output leaf — no role registration surface. */
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
import { REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "../review-submission.ts";
|
|
4
|
+
import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "../review-submission.ts";
|
|
5
5
|
|
|
6
6
|
export const JUDGE_OUTPUT_TOOL_NAME: string = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
|
|
7
7
|
|
|
@@ -39,9 +39,7 @@ export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
|
|
|
39
39
|
if (typeof status !== "string") {
|
|
40
40
|
throw new Error("Judge verdict has no execution discriminator");
|
|
41
41
|
}
|
|
42
|
-
if (
|
|
43
|
-
["converged", "continue", "escalate"].includes(status)
|
|
44
|
-
) {
|
|
42
|
+
if (REVIEW_QUEUE_STATUSES.has(status)) {
|
|
45
43
|
return verdict as JudgeVerdict;
|
|
46
44
|
}
|
|
47
45
|
throw new Error("Judge verdict has no execution discriminator");
|
|
@@ -50,8 +50,9 @@ const infrastructureFailureDeclarationSchema = Type.Object(
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Compose declarations shared by terminating output tools: infrastructure failure
|
|
53
|
-
* and the optional role-asserted ticket identity. Returns an open object (additionalProperties: true
|
|
54
|
-
*
|
|
53
|
+
* and the optional role-asserted ticket identity. Returns an open object (additionalProperties: true)
|
|
54
|
+
* with the base schema's properties plus the shared declaration. Incoming required
|
|
55
|
+
* keys that still exist are kept; every other key stays optional.
|
|
55
56
|
* Static typing is preserved on the base (`as S`), so existing
|
|
56
57
|
* `Static<typeof ...>` derived parameter types are unchanged.
|
|
57
58
|
*/
|
|
@@ -78,7 +79,11 @@ export function withTerminatingOutputDeclarations<
|
|
|
78
79
|
],
|
|
79
80
|
};
|
|
80
81
|
const object = Type.Object(properties, { additionalProperties: true });
|
|
81
|
-
|
|
82
|
+
const incoming = (schema as { required?: unknown }).required;
|
|
83
|
+
const preserved = Array.isArray(incoming)
|
|
84
|
+
? incoming.filter((key): key is string => typeof key === "string" && Object.hasOwn(properties, key))
|
|
85
|
+
: [];
|
|
86
|
+
(object as unknown as { required: string[] }).required = preserved;
|
|
82
87
|
return object as unknown as S;
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -115,7 +120,7 @@ function isInfrastructureFailureDeclaration(
|
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
/** Non-empty trimmed diagnostic from the declaration, else undefined. */
|
|
118
|
-
function infrastructureFailureDiagnostic(
|
|
123
|
+
export function infrastructureFailureDiagnostic(
|
|
119
124
|
parameters: unknown,
|
|
120
125
|
): string | undefined {
|
|
121
126
|
if (!isInfrastructureFailureDeclaration(parameters)) return undefined;
|
package/src/review-submission.ts
CHANGED
|
@@ -1,18 +1,33 @@
|
|
|
1
|
-
/** One shared terminating receipt contract for review officers (#1028). */
|
|
1
|
+
/** One shared terminating receipt contract for review officers (#1028 / #1055). */
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
|
|
4
|
-
import { openToolObject } from "./open-tool-schema.ts";
|
|
5
4
|
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
6
5
|
|
|
7
6
|
export const REVIEW_SUBMISSION_OUTPUT_TOOL_NAME = "ak_submission_output" as const;
|
|
8
7
|
|
|
9
|
-
/**
|
|
8
|
+
/** Single schema copy of the review queue words. Runtime membership reads this set. */
|
|
9
|
+
export const REVIEW_QUEUE_WORDS = ["converged", "continue", "escalate"] as const;
|
|
10
|
+
export type ReviewQueueWord = (typeof REVIEW_QUEUE_WORDS)[number];
|
|
11
|
+
export const REVIEW_QUEUE_STATUSES: ReadonlySet<string> = new Set(REVIEW_QUEUE_WORDS);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One object root. Codex structured output rejects a root anyOf, and status
|
|
15
|
+
* stays a required three-state enum with no omitted-status failure branch.
|
|
16
|
+
* A review seat that can still submit records a real external-dependency
|
|
17
|
+
* failure as escalate plus the existing infrastructureFailure field.
|
|
18
|
+
*/
|
|
10
19
|
export const reviewSubmissionSchema = withTerminatingOutputDeclarations(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
Type.Object({
|
|
21
|
+
status: Type.Union(
|
|
22
|
+
[
|
|
23
|
+
Type.Literal(REVIEW_QUEUE_WORDS[0]),
|
|
24
|
+
Type.Literal(REVIEW_QUEUE_WORDS[1]),
|
|
25
|
+
Type.Literal(REVIEW_QUEUE_WORDS[2]),
|
|
26
|
+
],
|
|
27
|
+
{
|
|
28
|
+
description: "审核队列三态枚举,必填。审核席仍能交卷而完成审核所需的外部依赖发生真实基础设施失败时,status 取 escalate,细节写入 infrastructureFailure。不得省略 status,不得用另两个枚举值充当放行或封驳。包侧不因格式拒收。",
|
|
29
|
+
},
|
|
30
|
+
),
|
|
16
31
|
findings: Type.Optional(Type.Unknown({ description: "审核发现,原样留存" })),
|
|
17
32
|
reason: Type.Optional(Type.Unknown({ description: "上呈理由" })),
|
|
18
33
|
violations: Type.Optional(Type.Unknown({ description: "违规条目" })),
|
|
@@ -22,8 +37,7 @@ export const reviewSubmissionSchema = withTerminatingOutputDeclarations(
|
|
|
22
37
|
note: Type.Optional(Type.Unknown({ description: "裁决附注" })),
|
|
23
38
|
evidence: Type.Optional(Type.Unknown({ description: "留存证据" })),
|
|
24
39
|
decisionGate: Type.Optional(Type.Unknown({ description: "需人权威处置的问题与选项;可说明 question、options,原样留存" })),
|
|
25
|
-
}),
|
|
26
|
-
),
|
|
40
|
+
}, { additionalProperties: true }),
|
|
27
41
|
);
|
|
28
42
|
|
|
29
43
|
export type ReviewSubmission = { readonly status?: unknown; readonly [key: string]: unknown };
|
package/src/role-runtime.ts
CHANGED
|
@@ -314,7 +314,8 @@ export {
|
|
|
314
314
|
gateOfficerForSubject,
|
|
315
315
|
};
|
|
316
316
|
export type { GatekeeperResult, GatekeeperSubject, SubmissionGateNonPassResult, GateOfficer, RunGatekeeperOptions } from "./gatekeeper-role.ts";
|
|
317
|
-
import { ParentQueueReaskError } from "./submission-errors.ts";
|
|
317
|
+
import { ParentQueueReaskError, unreadableDiscriminatorNotice } from "./submission-errors.ts";
|
|
318
|
+
import { REVIEW_QUEUE_STATUSES } from "./review-submission.ts";
|
|
318
319
|
|
|
319
320
|
export {
|
|
320
321
|
DOCTOR_EVIDENCE_TOOL_NAME,
|
|
@@ -872,8 +873,6 @@ function readDiaristRunCoordinates(ctx: HostContext): {
|
|
|
872
873
|
const DIARIST_BOUNDS_REASK =
|
|
873
874
|
"边界无法使用。请重交 sessions:每卷 path + ranges,每端以原生 id 或本轮行号二选一指名。" as const;
|
|
874
875
|
const DIARIST_ROUTING_STATUSES = new Set(["completed", "escalate"]);
|
|
875
|
-
const DIARIST_STATUS_REASK =
|
|
876
|
-
"status 不是 completed、escalate 之一。请重新交卷,status 写明其一。" as const;
|
|
877
876
|
|
|
878
877
|
/**
|
|
879
878
|
* #708 / #779 / #901: 起居郎 public seat on the shared filed-officer envelope.
|
|
@@ -900,7 +899,7 @@ export function createDiaristRoleRuntime(
|
|
|
900
899
|
// before interpreting any other submitted field; caller-owned data stays open.
|
|
901
900
|
const status = submitted?.status;
|
|
902
901
|
if (typeof status !== "string" || !DIARIST_ROUTING_STATUSES.has(status)) {
|
|
903
|
-
throw new ParentQueueReaskError(
|
|
902
|
+
throw new ParentQueueReaskError(unreadableDiscriminatorNotice("status", submitted?.status));
|
|
904
903
|
}
|
|
905
904
|
if (status === "escalate") return parameters;
|
|
906
905
|
const assertion = readDiaristTicketAssertion(submitted);
|
|
@@ -943,26 +942,9 @@ export function createDiaristRoleRuntime(
|
|
|
943
942
|
);
|
|
944
943
|
}
|
|
945
944
|
|
|
946
|
-
/**
|
|
947
|
-
const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
|
|
948
|
-
|
|
949
|
-
/**
|
|
950
|
-
* Plain-language re-ask when status is not a known queue word.
|
|
951
|
-
* Back to countersign itself — notary is not summoned (#753).
|
|
952
|
-
*/
|
|
953
|
-
const COUNTERSIGN_STATUS_REASK =
|
|
954
|
-
"status 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
|
|
955
|
-
|
|
956
|
-
/** Secretariat status words the submission gate reads. */
|
|
945
|
+
/** Secretariat status words the submission gate reads. Not the review three-state field. */
|
|
957
946
|
const SECRETARIAT_QUEUE_STATUSES = new Set(["converged", "escalate"]);
|
|
958
947
|
|
|
959
|
-
/**
|
|
960
|
-
* Plain-language re-ask when secretariatStatus is not a known queue word.
|
|
961
|
-
* Back to secretariat itself — 给事中 is not summoned for escalation.
|
|
962
|
-
*/
|
|
963
|
-
const SECRETARIAT_STATUS_REASK =
|
|
964
|
-
"secretariatStatus 不是 converged、escalate 之一。请重新交卷,status 写明其一。" as const;
|
|
965
|
-
|
|
966
948
|
/**
|
|
967
949
|
* Secretariat output is terminal; converged submissions enter the shared
|
|
968
950
|
* submission gate on every host that mounts gatekeeper actions.
|
|
@@ -989,7 +971,9 @@ export function createSecretariatRoleRuntime(
|
|
|
989
971
|
? record.secretariatStatus
|
|
990
972
|
: undefined;
|
|
991
973
|
if (status === undefined || !SECRETARIAT_QUEUE_STATUSES.has(status)) {
|
|
992
|
-
throw new ParentQueueReaskError(
|
|
974
|
+
throw new ParentQueueReaskError(
|
|
975
|
+
unreadableDiscriminatorNotice("secretariatStatus", record?.secretariatStatus),
|
|
976
|
+
);
|
|
993
977
|
}
|
|
994
978
|
if (status === "escalate") {
|
|
995
979
|
// Parent escalate → throw to caller as-is; countersign does not attend.
|
|
@@ -1132,10 +1116,10 @@ export function createCountersignRoleRuntime(
|
|
|
1132
1116
|
record !== undefined && typeof record.status === "string"
|
|
1133
1117
|
? record.status
|
|
1134
1118
|
: undefined;
|
|
1135
|
-
if (status === undefined || !
|
|
1119
|
+
if (status === undefined || !REVIEW_QUEUE_STATUSES.has(status)) {
|
|
1136
1120
|
// Parent status unreadable → back to countersign itself; do not summon notary,
|
|
1137
1121
|
// do not forge an officer bounce face (#753).
|
|
1138
|
-
throw new ParentQueueReaskError(
|
|
1122
|
+
throw new ParentQueueReaskError(unreadableDiscriminatorNotice("status", record?.status));
|
|
1139
1123
|
}
|
|
1140
1124
|
if (status === "escalate") {
|
|
1141
1125
|
// Parent escalate → throw to caller as-is; notary does not attend (#753).
|
package/src/submission-errors.ts
CHANGED
|
@@ -58,6 +58,22 @@ export class WorkerUnfinishedReasonReminderError extends Error {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Host constraint missed a routing discriminator (#1055).
|
|
63
|
+
* Names live only in the submission schema. This notice carries the field and the received value.
|
|
64
|
+
*/
|
|
65
|
+
export function unreadableDiscriminatorNotice(field: string, received: unknown): string {
|
|
66
|
+
if (received === undefined) return `读不出 ${field}`;
|
|
67
|
+
return `读不出 ${field}:${readableGateItem(received)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The received discriminator only — never the rest of the receipt. */
|
|
71
|
+
export function receivedDiscriminator(receipt: unknown, field: string): unknown {
|
|
72
|
+
if (receipt === null || typeof receipt !== "object" || Array.isArray(receipt)) return undefined;
|
|
73
|
+
if (!Object.hasOwn(receipt, field)) return undefined;
|
|
74
|
+
return (receipt as Record<string, unknown>)[field];
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
/**
|
|
62
78
|
* Parent seat status unreadable for queueing (#753).
|
|
63
79
|
* Correctable back to the parent itself — not a gate officer bounce, no officer field.
|
package/src/submission-gate.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
type GateOfficerSummon,
|
|
28
28
|
} from "./gatekeeper-role.ts";
|
|
29
29
|
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
30
|
+
|
|
30
31
|
import { sessionFileFromPublicSummon } from "./session-assistant-usage.ts";
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -178,7 +179,7 @@ export async function requireSubmissionGate(options: {
|
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
if (gatekeeper.status === "needs_reask") {
|
|
181
|
-
reask = officerConclusionReask(
|
|
182
|
+
reask = officerConclusionReask(gatekeeper.receivedStatus);
|
|
182
183
|
continue;
|
|
183
184
|
}
|
|
184
185
|
if (gatekeeper.status === "transport_failure") {
|