@akagilnc/pi-workflow-roles 0.1.4631 → 0.1.4641
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-host/production-host.js +731 -329
- package/dist/gatekeeper-role.js +76 -6
- package/dist/headless-host/production-host.js +708 -306
- package/dist/migrate-book-topology.js +9 -0
- package/dist/public-cli/countersign-run.js +31 -10
- package/dist/public-cli/main.js +401 -287
- package/dist/public-cli/settlement.js +95 -2
- package/dist/public-role-summons.js +19 -0
- package/dist/secretariat-contracts.js +4 -0
- package/package.json +1 -1
- package/src/archivist-record-entry.ts +2 -2
- package/src/audit-escalation.ts +1 -1
- package/src/gatekeeper-pass-envelope.ts +37 -5
- package/src/gatekeeper-role.ts +125 -20
- package/src/host-contracts.ts +22 -2
- package/src/pi/adapter.ts +5 -1
- package/src/public-cli/countersign-run.ts +50 -13
- package/src/public-cli/settlement.ts +117 -1
- package/src/public-role-summons.ts +30 -4
- package/src/role-envelope.ts +27 -10
- package/src/role-runtime.ts +142 -4
- package/src/secretariat-contracts.ts +18 -0
|
@@ -88,6 +88,22 @@ export type CountersignRunEnv = PostAdmissionEnv & {
|
|
|
88
88
|
* replace — never union. Production leaves unset outside that handoff.
|
|
89
89
|
*/
|
|
90
90
|
pendingCourtTicketNumbers?: readonly number[];
|
|
91
|
+
/**
|
|
92
|
+
* #969 gate path: plain-language re-ask when prior 给事中 reply was not three-state.
|
|
93
|
+
* Wins over gateReviewInstruction when both present (notary/inspector precedent).
|
|
94
|
+
*/
|
|
95
|
+
reviewReask?: string;
|
|
96
|
+
/**
|
|
97
|
+
* #969/#879 Secretariat submission-gate body: verbatim parent payload as dialogue
|
|
98
|
+
* content when reviewReask is absent. Binding stays ticket / argv instruction.
|
|
99
|
+
*/
|
|
100
|
+
gateReviewInstruction?: string;
|
|
101
|
+
/**
|
|
102
|
+
* #969 gate path: parent run durable board ticket handoff. Same-ticket resume
|
|
103
|
+
* key and identity 起居郎 bind under this key — never re-derived from receipt
|
|
104
|
+
* ticketNumber (optional / omit-able payload field).
|
|
105
|
+
*/
|
|
106
|
+
boundTicketNumber?: number;
|
|
91
107
|
};
|
|
92
108
|
|
|
93
109
|
/** Project admitted invocation onto the host-neutral turn request. */
|
|
@@ -470,6 +486,10 @@ export async function runPublicCountersign(
|
|
|
470
486
|
instruction: parsed.instruction,
|
|
471
487
|
projectRoot: admitted.projectRoot,
|
|
472
488
|
failureLabel: "unbound summons",
|
|
489
|
+
// #969: parent durable ticket is the resume/bind key (ADR 0079).
|
|
490
|
+
...(env.boundTicketNumber === undefined
|
|
491
|
+
? {}
|
|
492
|
+
: { boundTicketNumber: env.boundTicketNumber }),
|
|
473
493
|
},
|
|
474
494
|
env,
|
|
475
495
|
io,
|
|
@@ -528,16 +548,30 @@ export async function runPublicCountersign(
|
|
|
528
548
|
}
|
|
529
549
|
|
|
530
550
|
// Missing lawful 起居郎 terminal is not countersign body failure: leave
|
|
531
|
-
// unbound and continue (true-unbound face)
|
|
532
|
-
//
|
|
551
|
+
// unbound and continue (true-unbound face) — unless parent gate handoff
|
|
552
|
+
// already carries the durable board ticket (#969). Escalate / typed
|
|
553
|
+
// failure above; bound refresh still fails honest via station.
|
|
533
554
|
if (outcome.identity.kind === "ticket") {
|
|
534
|
-
|
|
535
|
-
|
|
555
|
+
// #969: parent durable handoff wins over 起居郎 re-assert when both
|
|
556
|
+
// present (receipt/assert mismatch → parent resume key).
|
|
557
|
+
typedTicket = isSafePositiveTicketNumber(env.boundTicketNumber)
|
|
558
|
+
? env.boundTicketNumber
|
|
559
|
+
: outcome.identity.ticketNumber;
|
|
536
560
|
// #871: identity may hand a co-review set; absent field defaults to [main].
|
|
537
561
|
typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
|
|
562
|
+
} else if (isSafePositiveTicketNumber(env.boundTicketNumber)) {
|
|
563
|
+
// Parent board handoff present; 起居郎 returned true-unbound — still
|
|
564
|
+
// resume/bind under the parent key (omitted receipt ticketNumber path).
|
|
565
|
+
typedTicket = env.boundTicketNumber;
|
|
566
|
+
}
|
|
567
|
+
if (typedTicket !== undefined) {
|
|
568
|
+
// #969/#879: gate re-ask / parent submission body share summons.instruction
|
|
569
|
+
// (reask wins; argv instruction remains the 起居郎 identity face above).
|
|
570
|
+
const resumeInstruction =
|
|
571
|
+
env.reviewReask ?? env.gateReviewInstruction ?? parsed.instruction;
|
|
538
572
|
const summons: SameTicketSummonsMaterials = {
|
|
539
|
-
instruction:
|
|
540
|
-
instructionEmpty:
|
|
573
|
+
instruction: resumeInstruction,
|
|
574
|
+
instructionEmpty: resumeInstruction.trim() === "",
|
|
541
575
|
};
|
|
542
576
|
const resumed = await tryResumeSameTicketSeatRun({
|
|
543
577
|
home: env.home,
|
|
@@ -641,13 +675,16 @@ export async function runPublicCountersign(
|
|
|
641
675
|
: { correlationId: env.correlationId }),
|
|
642
676
|
continuation: {
|
|
643
677
|
kind: "initial",
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
678
|
+
// #969/#879: gate path first mint carries parent payload as dialogue content;
|
|
679
|
+
// ordinary public entry keeps package transport prompt.
|
|
680
|
+
prompt: (env.reviewReask ?? env.gateReviewInstruction)
|
|
681
|
+
?? buildCountersignTransportPrompt(
|
|
682
|
+
admitted,
|
|
683
|
+
engineSessionMaterialFromOptions({
|
|
684
|
+
...pickEngineAxis(env),
|
|
685
|
+
packageRoot: env.packageRoot,
|
|
686
|
+
}),
|
|
687
|
+
),
|
|
651
688
|
},
|
|
652
689
|
};
|
|
653
690
|
// Mutable shell: ticket bind re-projects activation before executeTurn.
|
|
@@ -94,6 +94,8 @@ import {
|
|
|
94
94
|
DIARIST_OUTPUT_TOOL_NAME,
|
|
95
95
|
} from "../diarist-contracts.ts";
|
|
96
96
|
import {
|
|
97
|
+
SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY,
|
|
98
|
+
SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
|
|
97
99
|
SECRETARIAT_OUTPUT_TOOL_NAME,
|
|
98
100
|
} from "../secretariat-contracts.ts";
|
|
99
101
|
import {
|
|
@@ -3333,15 +3335,129 @@ export async function trySettleDiaristTerminalResult(
|
|
|
3333
3335
|
return settleLawfulDiaristTerminalResult(admitted, authority, scope);
|
|
3334
3336
|
}
|
|
3335
3337
|
|
|
3338
|
+
/**
|
|
3339
|
+
* #969 seat projection sole authority: 给事中 terminal (署|上呈) booked as a
|
|
3340
|
+
* durable custom entry (envelope-safe) — ledger accepted stays LLM params (#836).
|
|
3341
|
+
* Never scan toolResult rows: those are memory-only on headless/ACP (#617/#959).
|
|
3342
|
+
* Receipt bytes stay original; nested runId rides beside them.
|
|
3343
|
+
* Caller must only apply this when the terminal-defining submission actually
|
|
3344
|
+
* produced the officer entry (gate-bound converged, or 给事中 audit_escalation).
|
|
3345
|
+
*/
|
|
3346
|
+
function countersignTerminalFromEntries(
|
|
3347
|
+
entries: readonly {
|
|
3348
|
+
type?: string;
|
|
3349
|
+
customType?: string;
|
|
3350
|
+
data?: unknown;
|
|
3351
|
+
}[],
|
|
3352
|
+
): import("../secretariat-contracts.ts").SecretariatCountersignTerminalFact | undefined {
|
|
3353
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
3354
|
+
const entry = entries[index];
|
|
3355
|
+
if (entry?.type !== "custom") continue;
|
|
3356
|
+
if (entry.customType !== SECRETARIAT_GATE_OFFICER_ENTRY_TYPE) continue;
|
|
3357
|
+
const data =
|
|
3358
|
+
entry.data !== null && typeof entry.data === "object" && !Array.isArray(entry.data)
|
|
3359
|
+
? (entry.data as Record<string, unknown>)
|
|
3360
|
+
: undefined;
|
|
3361
|
+
if (data?.officer !== "countersign") continue;
|
|
3362
|
+
if (data.receipt === undefined) continue;
|
|
3363
|
+
const runId =
|
|
3364
|
+
typeof data.runId === "string" && data.runId.trim() !== ""
|
|
3365
|
+
? data.runId
|
|
3366
|
+
: undefined;
|
|
3367
|
+
return {
|
|
3368
|
+
receipt: data.receipt,
|
|
3369
|
+
...(runId === undefined ? {} : { runId }),
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
return undefined;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
function withCountersignTerminalFact(
|
|
3376
|
+
roleOutcome: Extract<
|
|
3377
|
+
import("./terminal.ts").TerminalRoleOutcome,
|
|
3378
|
+
{ kind: "accepted" | "audit_escalation" }
|
|
3379
|
+
>,
|
|
3380
|
+
officer: import("../secretariat-contracts.ts").SecretariatCountersignTerminalFact,
|
|
3381
|
+
): typeof roleOutcome {
|
|
3382
|
+
const prior =
|
|
3383
|
+
roleOutcome.decisiveFacts !== undefined && isRecord(roleOutcome.decisiveFacts)
|
|
3384
|
+
? roleOutcome.decisiveFacts
|
|
3385
|
+
: {};
|
|
3386
|
+
return {
|
|
3387
|
+
...roleOutcome,
|
|
3388
|
+
decisiveFacts: {
|
|
3389
|
+
...prior,
|
|
3390
|
+
[SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY]: officer,
|
|
3391
|
+
},
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
/**
|
|
3396
|
+
* Terminal-defining secretariat submission owns officer projection.
|
|
3397
|
+
* Last readable secretariatStatus on accepted payloads decides: only gate-bound
|
|
3398
|
+
* converged projects a prior durable officer entry. Parent escalate bypasses the
|
|
3399
|
+
* gate and must not inherit a stale pass entry from an earlier turn (#969).
|
|
3400
|
+
*/
|
|
3401
|
+
function acceptedSecretariatDefinesOfficerProjection(
|
|
3402
|
+
roleOutcome: Extract<
|
|
3403
|
+
import("./terminal.ts").TerminalRoleOutcome,
|
|
3404
|
+
{ kind: "accepted" }
|
|
3405
|
+
>,
|
|
3406
|
+
): boolean {
|
|
3407
|
+
const payloads = roleOutcome.payloads ?? [];
|
|
3408
|
+
for (let index = payloads.length - 1; index >= 0; index -= 1) {
|
|
3409
|
+
const payload = payloads[index];
|
|
3410
|
+
if (!isRecord(payload)) continue;
|
|
3411
|
+
const status = payload.secretariatStatus;
|
|
3412
|
+
if (typeof status !== "string") continue;
|
|
3413
|
+
return status === "converged";
|
|
3414
|
+
}
|
|
3415
|
+
return false;
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3336
3418
|
async function settleLawfulSecretariatTerminalResult(
|
|
3337
3419
|
admitted: AdmittedSecretariatInvocation,
|
|
3338
3420
|
authority: DurablePrincipalAuthority,
|
|
3339
3421
|
scope?: SettlementCourtScope,
|
|
3340
3422
|
): Promise<TerminalResult | undefined> {
|
|
3341
|
-
|
|
3423
|
+
const settled = await settleLawfulSeatAcceptedTerminalResult(admitted, authority, {
|
|
3342
3424
|
role: "secretariat",
|
|
3343
3425
|
toolName: SECRETARIAT_OUTPUT_TOOL_NAME,
|
|
3344
3426
|
}, scope);
|
|
3427
|
+
if (settled === undefined) return undefined;
|
|
3428
|
+
const coordinates = coordinatesFromAdmitted(authority, admitted);
|
|
3429
|
+
const entries = await readLawfulSettlementEntries(coordinates.sessionFile) ?? [];
|
|
3430
|
+
const officer = countersignTerminalFromEntries(entries);
|
|
3431
|
+
if (officer === undefined) return settled;
|
|
3432
|
+
|
|
3433
|
+
// 给事中上呈: public payloads = officer receipt; runId + receipt fact beside.
|
|
3434
|
+
// This kind is produced only when beforeAccept booked the officer entry.
|
|
3435
|
+
if (settled.roleOutcome.kind === "audit_escalation") {
|
|
3436
|
+
return {
|
|
3437
|
+
...settled,
|
|
3438
|
+
roleOutcome: withCountersignTerminalFact(
|
|
3439
|
+
{
|
|
3440
|
+
...settled.roleOutcome,
|
|
3441
|
+
payloads: [officer.receipt],
|
|
3442
|
+
},
|
|
3443
|
+
officer,
|
|
3444
|
+
),
|
|
3445
|
+
};
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
// Pass (署): keep 中书省 payloads; present 给事中 判词 + runId via decisiveFacts.
|
|
3449
|
+
// Parent escalate accepted terminal must not project a stale prior pass entry.
|
|
3450
|
+
if (settled.roleOutcome.kind === "accepted") {
|
|
3451
|
+
if (!acceptedSecretariatDefinesOfficerProjection(settled.roleOutcome)) {
|
|
3452
|
+
return settled;
|
|
3453
|
+
}
|
|
3454
|
+
return {
|
|
3455
|
+
...settled,
|
|
3456
|
+
roleOutcome: withCountersignTerminalFact(settled.roleOutcome, officer),
|
|
3457
|
+
};
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
return settled;
|
|
3345
3461
|
}
|
|
3346
3462
|
|
|
3347
3463
|
/** Try to settle a lawful Secretariat Terminal; undefined only for genuine absence. */
|
|
@@ -1040,9 +1040,9 @@ export async function summonParallelReviewerLenses(options: {
|
|
|
1040
1040
|
return results;
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
|
-
/** Gate officer summons: notary/auditor via --source-run; inspector via pointer
|
|
1043
|
+
/** Gate officer summons: notary/auditor via --source-run; inspector via pointer; countersign via ticket (#969). */
|
|
1044
1044
|
export async function summonGateOfficer(options: {
|
|
1045
|
-
readonly officer: "inspector" | "notary" | "auditor";
|
|
1045
|
+
readonly officer: "inspector" | "notary" | "auditor" | "countersign";
|
|
1046
1046
|
readonly sourceRunDirectory: string;
|
|
1047
1047
|
readonly cwd: string;
|
|
1048
1048
|
readonly home?: string;
|
|
@@ -1052,14 +1052,14 @@ export async function summonGateOfficer(options: {
|
|
|
1052
1052
|
readonly signal?: AbortSignal;
|
|
1053
1053
|
/**
|
|
1054
1054
|
* Plain-language re-ask when the prior officer reply was not three-state (#753 / #756).
|
|
1055
|
-
*
|
|
1055
|
+
* Officers: reviewReask → same-ticket resume summons.instruction.
|
|
1056
1056
|
* Never concatenated into argv (inspector parentRunPath is the pure 卷宗指针).
|
|
1057
1057
|
*/
|
|
1058
1058
|
readonly reask?: string;
|
|
1059
1059
|
/**
|
|
1060
1060
|
* In-flight parent 交卷 body (tool-call arguments). Relayed verbatim as officer
|
|
1061
1061
|
* dialogue content when reask is absent (#879). Binding pointer stays on
|
|
1062
|
-
* --source-run / 卷宗指针 argv — never a content substitute.
|
|
1062
|
+
* --source-run / 卷宗指针 / ticket argv — never a content substitute.
|
|
1063
1063
|
*/
|
|
1064
1064
|
readonly submission?: unknown;
|
|
1065
1065
|
/** Pi-adapter inject — forwarded to summonPublicRole (not a parent-host override). */
|
|
@@ -1121,6 +1121,32 @@ export async function summonGateOfficer(options: {
|
|
|
1121
1121
|
...common,
|
|
1122
1122
|
});
|
|
1123
1123
|
}
|
|
1124
|
+
if (options.officer === "countersign") {
|
|
1125
|
+
// #969: Secretariat submission gate → 给事中.
|
|
1126
|
+
// Dialogue / identity instruction = parent typed payload 原话 (ADR 0079) or reask;
|
|
1127
|
+
// no code-authored summons prose (#924). Ticket identity = parent run durable
|
|
1128
|
+
// board binding only (readBoardTicketNumber) — receipt ticketNumber is payload
|
|
1129
|
+
// content, never the sole identity source; parent true-unbound stays unbound.
|
|
1130
|
+
// correlationId only on this officer — notary/auditor/inspector stay untouched.
|
|
1131
|
+
const { runIdFromRunDirectory } = await import("./run-terminal-artifacts.ts");
|
|
1132
|
+
const correlationId = runIdFromRunDirectory(options.sourceRunDirectory);
|
|
1133
|
+
if (correlationId === undefined || correlationId.trim() === "") {
|
|
1134
|
+
throw new Error(
|
|
1135
|
+
`countersign gate summon requires parent runId from sourceRunDirectory: ${options.sourceRunDirectory}`,
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
const { readBoardTicketNumber } = await import("./run-ticket-number.ts");
|
|
1139
|
+
const parentTicket = await readBoardTicketNumber(options.sourceRunDirectory);
|
|
1140
|
+
// Argv instruction = reask or parent payload bytes only (never a fabricated line).
|
|
1141
|
+
const instruction = options.reask ?? gateReviewInstruction ?? "";
|
|
1142
|
+
return summonPublicRole({
|
|
1143
|
+
role: "countersign",
|
|
1144
|
+
argv: ["--project", options.cwd, "--", instruction],
|
|
1145
|
+
...common,
|
|
1146
|
+
correlationId,
|
|
1147
|
+
...(parentTicket === undefined ? {} : { boundTicketNumber: parentTicket }),
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1124
1150
|
// Inspector: argv stays pure 卷宗指针 (#747 parentRunPath lookup key).
|
|
1125
1151
|
// Content (body/reask) rides env → summons.instruction / first-mint prompt.
|
|
1126
1152
|
return summonPublicRole({
|
package/src/role-envelope.ts
CHANGED
|
@@ -9,7 +9,10 @@ import {
|
|
|
9
9
|
ENGINE_MODEL_FLAG_NAME,
|
|
10
10
|
normalizeEngineName,
|
|
11
11
|
} from "./engine-detour.ts";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
createDefaultGateOfficerSummon,
|
|
14
|
+
requireGatekeeperPass,
|
|
15
|
+
} from "./gatekeeper-pass-envelope.ts";
|
|
13
16
|
import type {
|
|
14
17
|
HostContext,
|
|
15
18
|
HostEventRegistration,
|
|
@@ -229,18 +232,32 @@ export async function prepareRoleEnvelope(options: {
|
|
|
229
232
|
// seat remains reachable through MCP.
|
|
230
233
|
setActiveTools(names) { preferredTools = [...names]; },
|
|
231
234
|
getActiveTools() { return [...preferredTools]; },
|
|
232
|
-
async requireGatekeeperPass(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
async requireGatekeeperPass(gateOptions) {
|
|
236
|
+
const packageRoot =
|
|
237
|
+
typeof options.dependencies.packageRoot === "string"
|
|
238
|
+
? options.dependencies.packageRoot
|
|
239
|
+
: undefined;
|
|
240
|
+
return requireGatekeeperPass({
|
|
241
|
+
context: gateOptions.context,
|
|
242
|
+
subject: gateOptions.subject,
|
|
243
|
+
...(gateOptions.signal === undefined ? {} : { signal: gateOptions.signal }),
|
|
237
244
|
hostActions: {
|
|
238
245
|
failInfrastructure: (error, _context, toolCallId) =>
|
|
239
|
-
|
|
240
|
-
bindSubmissionNonPass:
|
|
246
|
+
gateOptions.hostActions.failInfrastructure(error, gateOptions.context, toolCallId),
|
|
247
|
+
bindSubmissionNonPass: gateOptions.hostActions.bindSubmissionNonPass,
|
|
241
248
|
},
|
|
242
|
-
toolCallId:
|
|
243
|
-
...(
|
|
249
|
+
toolCallId: gateOptions.toolCallId,
|
|
250
|
+
...(gateOptions.submission === undefined ? {} : { submission: gateOptions.submission }),
|
|
251
|
+
// #969: package root + home reach nested 给事中/符宝郎 summons.
|
|
252
|
+
// hostAdapters (when set on deps) forward nested seat selection — production unset.
|
|
253
|
+
summonOfficer: createDefaultGateOfficerSummon({
|
|
254
|
+
cwd: gateOptions.context.cwd ?? request.cwd,
|
|
255
|
+
home: request.home,
|
|
256
|
+
...(packageRoot === undefined ? {} : { packageRoot }),
|
|
257
|
+
...(options.dependencies.hostAdapters === undefined
|
|
258
|
+
? {}
|
|
259
|
+
: { hostAdapters: options.dependencies.hostAdapters }),
|
|
260
|
+
}),
|
|
244
261
|
});
|
|
245
262
|
},
|
|
246
263
|
on(...registration: HostEventRegistration) {
|
package/src/role-runtime.ts
CHANGED
|
@@ -314,15 +314,23 @@ export {
|
|
|
314
314
|
writeToolExecutionObservationRecord,
|
|
315
315
|
} from "./tool-execution-observation.ts";
|
|
316
316
|
export type { ToolExecutionObservationRecord, ToolExecutionObservationWriter } from "./tool-execution-observation.ts";
|
|
317
|
-
|
|
317
|
+
import {
|
|
318
318
|
NOTARY_OUTPUT_TOOL,
|
|
319
319
|
INSPECTOR_OUTPUT_TOOL,
|
|
320
320
|
GatekeeperDecisionError,
|
|
321
321
|
createGatekeeperOutputTool,
|
|
322
322
|
runGatekeeper,
|
|
323
|
+
gateOfficerForSubject,
|
|
323
324
|
} from "./gatekeeper-role.ts";
|
|
325
|
+
export {
|
|
326
|
+
NOTARY_OUTPUT_TOOL,
|
|
327
|
+
INSPECTOR_OUTPUT_TOOL,
|
|
328
|
+
GatekeeperDecisionError,
|
|
329
|
+
createGatekeeperOutputTool,
|
|
330
|
+
runGatekeeper,
|
|
331
|
+
gateOfficerForSubject,
|
|
332
|
+
};
|
|
324
333
|
export type { GatekeeperResult, GatekeeperSubject, GatekeeperNonPassResult, GateOfficer, RunGatekeeperOptions } from "./gatekeeper-role.ts";
|
|
325
|
-
export { gateOfficerForSubject } from "./gatekeeper-role.ts";
|
|
326
334
|
import { ParentQueueReaskError } from "./submission-errors.ts";
|
|
327
335
|
|
|
328
336
|
export {
|
|
@@ -546,6 +554,13 @@ type NavigatorAttendanceDependency = Omit<NavigatorAttendance, "knownRoutePlaybo
|
|
|
546
554
|
export type RoleRuntimeDependencies = {
|
|
547
555
|
/** Package root for packaged engine-note resolution (#879). */
|
|
548
556
|
packageRoot?: string;
|
|
557
|
+
/**
|
|
558
|
+
* Composition-root adapter table for nested gate officer summons (#969).
|
|
559
|
+
* Production leaves unset (default pi + packaged externals). Tests inject
|
|
560
|
+
* faux nested hosts so prepareRoleEnvelope.requireGatekeeperPass is bitten
|
|
561
|
+
* without reimplementing the envelope summon closure.
|
|
562
|
+
*/
|
|
563
|
+
hostAdapters?: readonly import("./public-cli/role-turn-host-resolution.ts").NamedRoleTurnHostAdapter[];
|
|
549
564
|
/** Non-identity opening materials delivered in the ordinary role brief. */
|
|
550
565
|
loadRoleReferenceMaterials?(role: PackagedRole): Promise<string>;
|
|
551
566
|
loadJudgeSoul(): Promise<string>;
|
|
@@ -999,18 +1014,137 @@ const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]
|
|
|
999
1014
|
const COUNTERSIGN_STATUS_REASK =
|
|
1000
1015
|
"countersignStatus 不是 converged、continue、escalate 三态之一。请重新交卷,status 写明其一。" as const;
|
|
1001
1016
|
|
|
1017
|
+
/**
|
|
1018
|
+
* #969 authorized non-pi hosts: Secretariat converged is a candidate ticket —
|
|
1019
|
+
* AK-side submission gate summons 给事中. pi keeps mid-turn summon tool only.
|
|
1020
|
+
*/
|
|
1021
|
+
const SECRETARIAT_SUBMISSION_GATE_HOSTS = new Set(["codex", "claude", "grok-build"]);
|
|
1022
|
+
|
|
1023
|
+
/** Secretariat status words the non-pi submission gate reads (#969). */
|
|
1024
|
+
const SECRETARIAT_QUEUE_STATUSES = new Set(["converged", "escalate"]);
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Plain-language re-ask when non-pi secretariatStatus is not a known queue word.
|
|
1028
|
+
* Back to secretariat itself — 给事中 is not summoned (#969 / ADR 0055).
|
|
1029
|
+
*/
|
|
1030
|
+
const SECRETARIAT_STATUS_REASK =
|
|
1031
|
+
"secretariatStatus 不是 converged、escalate 之一。请重新交卷,status 写明其一。" as const;
|
|
1032
|
+
|
|
1002
1033
|
/**
|
|
1003
1034
|
* #924 Secretariat on the shared filed-officer envelope + non-terminating
|
|
1004
1035
|
* summon-countersign tool (auditor dossier extension pattern).
|
|
1005
1036
|
* Nested countersign lifecycle stays on summonPublicRole (ADR 0018).
|
|
1006
|
-
* #924 / 第 0
|
|
1037
|
+
* #924 / 第 0 条 (pi): output tool records the receipt as submitted — no status
|
|
1007
1038
|
* shape/value reject or reask; legality is content-layer / downstream.
|
|
1039
|
+
* #969 (codex/claude/grok-build): converged arms 既有交卷闸 → 给事中; escalate
|
|
1040
|
+
* skips the gate; other values reask secretariat (ADR 0055).
|
|
1008
1041
|
*/
|
|
1009
1042
|
export function createSecretariatRoleRuntime(
|
|
1010
1043
|
roleHost: RoleHost,
|
|
1011
1044
|
dependencies: SecretariatRuntimeDependencies,
|
|
1045
|
+
hostActions?: import("./host-contracts.ts").HostGatekeeperActions,
|
|
1012
1046
|
) {
|
|
1013
1047
|
let parentInstruction = "";
|
|
1048
|
+
// #969: non-pi submission gate only — pi mid-turn summon path stays untouched.
|
|
1049
|
+
const beforeAccept: FiledOfficerBeforeAccept | undefined =
|
|
1050
|
+
hostActions !== undefined && roleHost.requireGatekeeperPass !== undefined
|
|
1051
|
+
? async ({ toolCallId, parameters, signal, ctx }) => {
|
|
1052
|
+
const host =
|
|
1053
|
+
typeof ctx.host === "string" && ctx.host.trim() !== ""
|
|
1054
|
+
? ctx.host.trim()
|
|
1055
|
+
: undefined;
|
|
1056
|
+
if (host === undefined || !SECRETARIAT_SUBMISSION_GATE_HOSTS.has(host)) {
|
|
1057
|
+
return undefined;
|
|
1058
|
+
}
|
|
1059
|
+
const record =
|
|
1060
|
+
parameters !== null && typeof parameters === "object" && !Array.isArray(parameters)
|
|
1061
|
+
? (parameters as Record<string, unknown>)
|
|
1062
|
+
: undefined;
|
|
1063
|
+
const status =
|
|
1064
|
+
record !== undefined && typeof record.secretariatStatus === "string"
|
|
1065
|
+
? record.secretariatStatus
|
|
1066
|
+
: undefined;
|
|
1067
|
+
if (status === undefined || !SECRETARIAT_QUEUE_STATUSES.has(status)) {
|
|
1068
|
+
throw new ParentQueueReaskError(SECRETARIAT_STATUS_REASK);
|
|
1069
|
+
}
|
|
1070
|
+
if (status === "escalate") {
|
|
1071
|
+
// Parent escalate → throw to caller as-is; 给事中 does not attend (#969).
|
|
1072
|
+
return undefined;
|
|
1073
|
+
}
|
|
1074
|
+
try {
|
|
1075
|
+
const pass = await roleHost.requireGatekeeperPass!({
|
|
1076
|
+
context: ctx,
|
|
1077
|
+
subject: { kind: "secretariat_verdict" },
|
|
1078
|
+
...(signal === undefined ? {} : { signal }),
|
|
1079
|
+
hostActions,
|
|
1080
|
+
toolCallId,
|
|
1081
|
+
// #879: this-turn typed payload — identity-bound at submit site.
|
|
1082
|
+
submission: parameters,
|
|
1083
|
+
});
|
|
1084
|
+
// #969: book 给事中 署 snapshot for seat settlement projection (receipt + runId).
|
|
1085
|
+
// Ledger accepted stays LLM params (#836); public terminal reads this entry.
|
|
1086
|
+
if (
|
|
1087
|
+
pass !== undefined
|
|
1088
|
+
&& pass !== null
|
|
1089
|
+
&& typeof pass === "object"
|
|
1090
|
+
&& "receipt" in pass
|
|
1091
|
+
) {
|
|
1092
|
+
const {
|
|
1093
|
+
SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
|
|
1094
|
+
} = await import("./secretariat-contracts.ts");
|
|
1095
|
+
const runId =
|
|
1096
|
+
typeof pass.runId === "string" && pass.runId.trim() !== ""
|
|
1097
|
+
? pass.runId
|
|
1098
|
+
: undefined;
|
|
1099
|
+
ctx.sessionManager.appendCustomEntry?.(SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, {
|
|
1100
|
+
officer: "countersign",
|
|
1101
|
+
receipt: pass.receipt,
|
|
1102
|
+
...(runId === undefined ? {} : { runId }),
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
return undefined;
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
// #969: 给事中上呈 ends parent with officer receipt (no retry / 不擅改).
|
|
1108
|
+
if (
|
|
1109
|
+
error instanceof GatekeeperDecisionError
|
|
1110
|
+
&& error.result.status === "escalate"
|
|
1111
|
+
) {
|
|
1112
|
+
const receipt = error.result.receipt;
|
|
1113
|
+
const receiptRecord =
|
|
1114
|
+
receipt !== null && typeof receipt === "object" && !Array.isArray(receipt)
|
|
1115
|
+
? (receipt as Record<string, unknown>)
|
|
1116
|
+
: undefined;
|
|
1117
|
+
const runId =
|
|
1118
|
+
typeof error.result.runId === "string" && error.result.runId.trim() !== ""
|
|
1119
|
+
? error.result.runId
|
|
1120
|
+
: undefined;
|
|
1121
|
+
// Durable on every host: envelope persists custom entries only
|
|
1122
|
+
// (toolResult rows are memory-only on headless/ACP — #617/#959).
|
|
1123
|
+
const {
|
|
1124
|
+
SECRETARIAT_GATE_OFFICER_ENTRY_TYPE,
|
|
1125
|
+
} = await import("./secretariat-contracts.ts");
|
|
1126
|
+
ctx.sessionManager.appendCustomEntry?.(SECRETARIAT_GATE_OFFICER_ENTRY_TYPE, {
|
|
1127
|
+
officer: "countersign",
|
|
1128
|
+
receipt,
|
|
1129
|
+
...(runId === undefined ? {} : { runId }),
|
|
1130
|
+
});
|
|
1131
|
+
const { buildAuditEscalationResult } = await import("./audit-escalation.ts");
|
|
1132
|
+
return buildAuditEscalationResult(
|
|
1133
|
+
{
|
|
1134
|
+
status: "escalate",
|
|
1135
|
+
officer: "countersign",
|
|
1136
|
+
...(receiptRecord !== undefined
|
|
1137
|
+
&& Object.prototype.hasOwnProperty.call(receiptRecord, "decisionGate")
|
|
1138
|
+
? { decisionGate: receiptRecord.decisionGate }
|
|
1139
|
+
: {}),
|
|
1140
|
+
},
|
|
1141
|
+
receipt,
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
throw error;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
: undefined;
|
|
1014
1148
|
const base = createFiledOfficerRuntime(
|
|
1015
1149
|
roleHost,
|
|
1016
1150
|
{
|
|
@@ -1018,6 +1152,7 @@ export function createSecretariatRoleRuntime(
|
|
|
1018
1152
|
tool: SECRETARIAT_OUTPUT_TOOL_SPEC,
|
|
1019
1153
|
acceptedText: SECRETARIAT_ACCEPTED_TEXT,
|
|
1020
1154
|
soulTag: "secretariat",
|
|
1155
|
+
...(beforeAccept === undefined ? {} : { beforeAccept }),
|
|
1021
1156
|
},
|
|
1022
1157
|
dependencies,
|
|
1023
1158
|
);
|
|
@@ -1858,7 +1993,10 @@ export function createRoleRuntimeExtension(
|
|
|
1858
1993
|
...(dependencies.packageRoot === undefined
|
|
1859
1994
|
? {}
|
|
1860
1995
|
: { packageRoot: dependencies.packageRoot }),
|
|
1861
|
-
|
|
1996
|
+
...(dependencies.hostAdapters === undefined
|
|
1997
|
+
? {}
|
|
1998
|
+
: { hostAdapters: dependencies.hostAdapters }),
|
|
1999
|
+
}, hostActions);
|
|
1862
2000
|
const merger = createMergerRoleRuntime(roleHost, {
|
|
1863
2001
|
async loadSoul() { if (!dependencies.loadMergerSoul) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerSoul(); },
|
|
1864
2002
|
async loadInput(path) { if (!dependencies.loadMergerInput) throw new Error("Merger runtime dependencies are not configured"); return dependencies.loadMergerInput(path); },
|
|
@@ -11,6 +11,24 @@ export const SECRETARIAT_OUTPUT_TOOL_NAME = "ak_secretariat_output";
|
|
|
11
11
|
export const SECRETARIAT_SUMMON_COUNTERSIGN_TOOL_NAME =
|
|
12
12
|
"ak_secretariat_summon_countersign";
|
|
13
13
|
export const SECRETARIAT_ACCEPTED_TEXT = "中书省回执已接受";
|
|
14
|
+
/**
|
|
15
|
+
* #969 durable custom entry: 给事中 terminal (署|上呈) for seat settlement projection.
|
|
16
|
+
* Carries original receipt bytes + nested runId. Envelope persists custom entries
|
|
17
|
+
* (not toolResult rows) — headless/ACP safe. Single authority for public terminal
|
|
18
|
+
* officer projection (pass and escalate share this entry).
|
|
19
|
+
*/
|
|
20
|
+
export const SECRETARIAT_GATE_OFFICER_ENTRY_TYPE =
|
|
21
|
+
"ak-secretariat-gate-officer" as const;
|
|
22
|
+
|
|
23
|
+
/** Decisive-facts key for nested 给事中 terminal on public Secretariat settlement. */
|
|
24
|
+
export const SECRETARIAT_COUNTERSIGN_TERMINAL_FACT_KEY = "countersignTerminal" as const;
|
|
25
|
+
|
|
26
|
+
export type SecretariatCountersignTerminalFact = {
|
|
27
|
+
/** Officer receipt original bytes — never rewritten. */
|
|
28
|
+
readonly receipt: unknown;
|
|
29
|
+
/** Nested 给事中 runId when known. */
|
|
30
|
+
readonly runId?: string;
|
|
31
|
+
};
|
|
14
32
|
|
|
15
33
|
export type SecretariatVerdict =
|
|
16
34
|
| {
|