@akagilnc/pi-workflow-roles 0.1.4321 → 0.1.4387
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 +4 -1
- package/README.zh-CN.md +5 -1
- package/dist/acp-host/production-host.js +1308 -587
- package/dist/doctor-contracts.js +2 -2
- package/dist/engine-detour-tool.js +2 -8
- package/dist/gatekeeper-role.js +4 -19
- package/dist/headless-host/production-host.js +2613 -1892
- package/dist/merger-contracts.js +2 -2
- package/dist/migrate-book-topology.js +34 -9
- package/dist/notary-contracts.js +2 -2
- package/dist/package-contracts/auditor-output.js +2 -2
- package/dist/package-contracts/fixer-output.js +2 -2
- package/dist/package-contracts/gatekeeper-output.js +2 -2
- package/dist/package-contracts/navigator-output.js +2 -2
- package/dist/package-contracts/terminating-infrastructure.js +7 -2
- package/dist/package-contracts/terminating-tools.js +6 -0
- package/dist/packaged-role-registry.js +20 -1
- package/dist/public-cli/case-dossier-delivery.js +45 -28
- package/dist/public-cli/countersign-run.js +439 -0
- package/dist/public-cli/invocation.js +10 -0
- package/dist/public-cli/main.js +388 -101
- package/dist/public-cli/option-definitions.js +14 -0
- package/dist/public-cli/post-admission.js +44 -49
- package/dist/public-cli/run-lifecycle.js +13 -1
- package/dist/public-cli/settlement.js +14 -53
- package/dist/public-role-summons.js +14 -0
- package/dist/secretariat-contracts.js +8 -0
- package/dist/sitian-appender.js +21 -4
- package/extensions/role-runtime.ts +1 -0
- package/package.json +1 -1
- package/scripts/build-package.mjs +1 -0
- package/souls/secretariat.md +26 -0
- package/souls/ticket-law.md +9 -0
- package/src/collector-tool-schemas.ts +2 -2
- package/src/countersign-role.ts +2 -2
- package/src/diarist-role.ts +2 -2
- package/src/doctor-contracts.ts +2 -2
- package/src/engine-detour-tool.ts +3 -9
- package/src/gatekeeper-pass-envelope.ts +35 -2
- package/src/gatekeeper-role.ts +8 -31
- package/src/gleaner-left-role.ts +2 -2
- package/src/host-contracts.ts +2 -1
- package/src/inspector-role.ts +2 -2
- package/src/judge-role.ts +2 -2
- package/src/merger-contracts.ts +2 -2
- package/src/notary-contracts.ts +2 -2
- package/src/package-contracts/auditor-output.ts +2 -2
- package/src/package-contracts/fixer-output.ts +2 -2
- package/src/package-contracts/gatekeeper-output.ts +2 -2
- package/src/package-contracts/navigator-output.ts +2 -2
- package/src/package-contracts/terminating-infrastructure.ts +13 -3
- package/src/package-contracts/terminating-tools.ts +9 -1
- package/src/packaged-role-registry.ts +20 -1
- package/src/public-cli/case-dossier-delivery.ts +48 -29
- package/src/public-cli/cli.ts +10 -0
- package/src/public-cli/countersign-run.ts +29 -5
- package/src/public-cli/invocation.ts +28 -2
- package/src/public-cli/option-definitions.ts +17 -0
- package/src/public-cli/post-admission.ts +45 -58
- package/src/public-cli/run-lifecycle.ts +29 -2
- package/src/public-cli/secretariat-run.ts +266 -0
- package/src/public-cli/settlement.ts +31 -55
- package/src/public-cli/terminal.ts +2 -1
- package/src/public-role-summons.ts +23 -1
- package/src/reviewer-role.ts +2 -2
- package/src/role-runtime-dependencies.ts +1 -0
- package/src/role-runtime.ts +203 -18
- package/src/secretariat-contracts.ts +27 -0
- package/src/secretariat-role.ts +210 -0
- package/src/sitian-appender.ts +36 -3
- package/src/worker-role.ts +2 -2
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.js";
|
|
2
|
+
import { CliUsageError } from "./cli-errors.js";
|
|
3
|
+
import { projectCourtTicketNumbers, } from "../diarist-contracts.js";
|
|
4
|
+
import { isSafePositiveTicketNumber } from "../run-ticket-number.js";
|
|
5
|
+
import { admitCountersignInvocation, bindAdmittedTicketNumber, bindCourtTicketNumbersOnAdmitted, buildCountersignTransportPrompt, relocateAdmittedRunToTicket, } from "./invocation.js";
|
|
6
|
+
import { prepareSummonsResumeMaterials, presentControlledFailure, runPostAdmissionOneShot, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, StationChildExhaustedError, } from "./post-admission.js";
|
|
7
|
+
import { loadResumableCountersignRun, markRunAdmitted, markRunTerminal, } from "./run-lifecycle.js";
|
|
8
|
+
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
|
|
9
|
+
import { presentStructuralRejection, trySettleCountersignTerminalResult, } from "./settlement.js";
|
|
10
|
+
import { projectRoleTurnRequest, } from "./turn-request.js";
|
|
11
|
+
/** Project admitted invocation onto the host-neutral turn request. */
|
|
12
|
+
export function buildCountersignTurnRequest(admitted, options) {
|
|
13
|
+
return projectRoleTurnRequest(admitted, {
|
|
14
|
+
activation: {
|
|
15
|
+
role: "countersign",
|
|
16
|
+
// Admitted typed binding rides the turn activation seam to the Notary gate.
|
|
17
|
+
...(admitted.ticketNumber === undefined
|
|
18
|
+
? {}
|
|
19
|
+
: { ticketNumber: admitted.ticketNumber }),
|
|
20
|
+
},
|
|
21
|
+
}, options);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read #871 set from preserved diarist payloads.
|
|
25
|
+
* - Field absent / null / non-array → undefined (no new set; resume keeps store).
|
|
26
|
+
* - Explicit [] → single-ticket [main] whole-set replace (signed empty-set contract).
|
|
27
|
+
* - Non-empty array with zero lawful typed members after projection → not a new set
|
|
28
|
+
* (do not impersonate explicit empty and wipe a stored multi-ticket fact).
|
|
29
|
+
* - Non-empty array with ≥1 lawful member → sole type/dedupe projection + principal guarantee.
|
|
30
|
+
* - Multiple submissions: last qualifying set wins.
|
|
31
|
+
* Live path never shape-rejects the role turn; durable damage is a separate seam.
|
|
32
|
+
*/
|
|
33
|
+
function courtTicketNumbersFromOutcome(roleOutcome, principalTicket) {
|
|
34
|
+
if (roleOutcome === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const payloads = roleOutcome.kind === "accepted" ||
|
|
37
|
+
roleOutcome.kind === "audit_escalation" ||
|
|
38
|
+
roleOutcome.kind === "failure"
|
|
39
|
+
? roleOutcome.payloads ?? []
|
|
40
|
+
: [];
|
|
41
|
+
let latest;
|
|
42
|
+
for (const payload of payloads) {
|
|
43
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const record = payload;
|
|
47
|
+
if (!Object.hasOwn(record, "courtTicketNumbers"))
|
|
48
|
+
continue;
|
|
49
|
+
const raw = record.courtTicketNumbers;
|
|
50
|
+
// Only a real array is a candidate set. Non-array is not explicit empty.
|
|
51
|
+
if (!Array.isArray(raw))
|
|
52
|
+
continue;
|
|
53
|
+
// Members only — do not inject principal yet, or all-invalid non-empty becomes [main].
|
|
54
|
+
const members = projectCourtTicketNumbers(raw);
|
|
55
|
+
if (members === null)
|
|
56
|
+
continue;
|
|
57
|
+
if (members.length === 0) {
|
|
58
|
+
// Literal [] is the intentional principal-only replace; non-empty garbage is not.
|
|
59
|
+
if (raw.length === 0) {
|
|
60
|
+
latest = projectCourtTicketNumbers([], { principalTicket }) ?? undefined;
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
latest =
|
|
65
|
+
projectCourtTicketNumbers(raw, { principalTicket }) ?? undefined;
|
|
66
|
+
}
|
|
67
|
+
return latest;
|
|
68
|
+
}
|
|
69
|
+
/** Routing boolean over the child's own typed sequence — does not pick or rewrite a sole row. */
|
|
70
|
+
function courtDiaristEscalated(roleOutcome) {
|
|
71
|
+
if (roleOutcome === undefined)
|
|
72
|
+
return false;
|
|
73
|
+
if (roleOutcome.kind === "audit_escalation")
|
|
74
|
+
return true;
|
|
75
|
+
if (roleOutcome.kind !== "accepted")
|
|
76
|
+
return false;
|
|
77
|
+
return (roleOutcome.payloads ?? []).some((payload) => {
|
|
78
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload))
|
|
79
|
+
return false;
|
|
80
|
+
const record = payload;
|
|
81
|
+
return record.status === "escalate" || record.countersignStatus === "escalate";
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Invoke public 起居郎 under the court-pipeline quiet face.
|
|
86
|
+
* Returns typed identity: ticket assertion, true-unbound, or escalate.
|
|
87
|
+
* Non-zero exit without escalate status is not rethrown here — caller decides
|
|
88
|
+
* whether refresh must fail or first-entry settles controlled failure.
|
|
89
|
+
* When `boundTicketNumber` is set (typed handoff from countersign), diarist
|
|
90
|
+
* binds under that key before the turn — never mechanical recognition from prose.
|
|
91
|
+
*/
|
|
92
|
+
export async function invokeCourtDiarist(input, env, io) {
|
|
93
|
+
// Quiet face: the countersign caller must not see diarist CLI chatter.
|
|
94
|
+
const quietIo = {
|
|
95
|
+
stdout() { },
|
|
96
|
+
stderr(text) {
|
|
97
|
+
// Surface nested failures onto the parent stderr only; no success noise.
|
|
98
|
+
if (text.trim() !== "")
|
|
99
|
+
io.stderr(text);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const { summonPublicRole } = await import("../public-role-summons.js");
|
|
103
|
+
const result = await summonPublicRole({
|
|
104
|
+
role: "diarist",
|
|
105
|
+
argv: [
|
|
106
|
+
"--project",
|
|
107
|
+
input.projectRoot,
|
|
108
|
+
...(input.attachmentPaths ?? []).flatMap((path) => ["--attach", path]),
|
|
109
|
+
"--",
|
|
110
|
+
input.instruction,
|
|
111
|
+
],
|
|
112
|
+
cwd: env.cwd,
|
|
113
|
+
home: env.home,
|
|
114
|
+
agentDir: env.agentDir,
|
|
115
|
+
packageRoot: env.packageRoot,
|
|
116
|
+
io: quietIo,
|
|
117
|
+
...(env.credentials === undefined ? {} : { credentials: env.credentials }),
|
|
118
|
+
...(env.signal === undefined ? {} : { signal: env.signal }),
|
|
119
|
+
...(input.correlationId === undefined ? {} : { correlationId: input.correlationId }),
|
|
120
|
+
...(input.boundTicketNumber === undefined
|
|
121
|
+
? {}
|
|
122
|
+
: { boundTicketNumber: input.boundTicketNumber }),
|
|
123
|
+
// Child seat selects from the composition-root table. Do not pass the
|
|
124
|
+
// already-selected parent adapter (#840 / ADR 0082: --host 旗标>席位配置>缺省 pi).
|
|
125
|
+
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
126
|
+
});
|
|
127
|
+
const roleOutcome = result.terminal?.roleOutcome;
|
|
128
|
+
// Escalate routing is a boolean over the preserved sequence (#881). Reasons and
|
|
129
|
+
// payload bodies stay on roleOutcome — never rewritten into a sole identity reason.
|
|
130
|
+
if (courtDiaristEscalated(roleOutcome)) {
|
|
131
|
+
return {
|
|
132
|
+
identity: { kind: "escalate" },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (result.exitCode !== 0) {
|
|
136
|
+
const diagnostic = roleOutcome?.kind === "failure"
|
|
137
|
+
? roleOutcome.diagnostic
|
|
138
|
+
: result.stderr?.trim() || `exit ${result.exitCode}`;
|
|
139
|
+
return {
|
|
140
|
+
identity: { kind: "unbound" },
|
|
141
|
+
failedWithoutEscalate: {
|
|
142
|
+
diagnostic: `court diarist station failed for ${input.failureLabel}: ${diagnostic}`,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const asserted = result.admitted?.ticketNumber;
|
|
147
|
+
if (isSafePositiveTicketNumber(asserted)) {
|
|
148
|
+
const courtTicketNumbers = courtTicketNumbersFromOutcome(roleOutcome, asserted);
|
|
149
|
+
return {
|
|
150
|
+
identity: {
|
|
151
|
+
kind: "ticket",
|
|
152
|
+
ticketNumber: asserted,
|
|
153
|
+
...(courtTicketNumbers === undefined ? {} : { courtTicketNumbers }),
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
identity: { kind: "unbound" },
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Court-pipeline prior station: refresh this ticket's 起居录 before the
|
|
163
|
+
* countersign body turn when already bound (ADR 0075 每次过庭都跑)。
|
|
164
|
+
* Caller-invisible — no diarist argv on the countersign command line.
|
|
165
|
+
*
|
|
166
|
+
* Missing ticketNumber (true-unbound / identity deferred) skips the refresh
|
|
167
|
+
* station — no diary is minted for a true-unbound run. First-entry identity
|
|
168
|
+
* lives on `runPublicCountersign` (typed 起居郎 key for bind + same-ticket
|
|
169
|
+
* resume). Bound refresh: 起居郎 failure propagates (失败诚实).
|
|
170
|
+
* Path delivery onto materials is not this station's job — post-admission owns it.
|
|
171
|
+
*/
|
|
172
|
+
export async function runCountersignCourtDiaristStation(admitted, env, io) {
|
|
173
|
+
if (env.runCourtDiaristStation !== undefined) {
|
|
174
|
+
await env.runCourtDiaristStation(admitted);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Production court refresh (ADR 0075: 每次过庭都跑是调用者用法) only under a known ticket identity.
|
|
178
|
+
// First-entry unbound identity is owned by runPublicCountersign.
|
|
179
|
+
if (admitted.ticketNumber === undefined)
|
|
180
|
+
return;
|
|
181
|
+
// #871: refresh every member of the typed co-review set; single-ticket face
|
|
182
|
+
// is just the set [main]. Present-but-empty / missing-principal is damage —
|
|
183
|
+
// never silently fall back to main-only (legacy absent field still may).
|
|
184
|
+
let refreshTickets;
|
|
185
|
+
if (admitted.courtTicketNumbers !== undefined) {
|
|
186
|
+
if (admitted.courtTicketNumbers.length === 0 ||
|
|
187
|
+
!admitted.courtTicketNumbers.includes(admitted.ticketNumber)) {
|
|
188
|
+
throw new StationChildExhaustedError(`court diarist station: courtTicketNumbers is damaged (empty or missing principal #${admitted.ticketNumber})`);
|
|
189
|
+
}
|
|
190
|
+
refreshTickets = admitted.courtTicketNumbers;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
refreshTickets = [admitted.ticketNumber];
|
|
194
|
+
}
|
|
195
|
+
for (const ticketNumber of refreshTickets) {
|
|
196
|
+
const outcome = await invokeCourtDiarist({
|
|
197
|
+
instruction: `整理 #${ticketNumber} 的本案依据。`,
|
|
198
|
+
projectRoot: admitted.projectRoot,
|
|
199
|
+
failureLabel: `ticket #${ticketNumber}`,
|
|
200
|
+
correlationId: admitted.runId,
|
|
201
|
+
// Refresh holds a typed key — hand it off so identity is bound before turn.
|
|
202
|
+
boundTicketNumber: ticketNumber,
|
|
203
|
+
}, env, io);
|
|
204
|
+
if (outcome.identity.kind === "escalate") {
|
|
205
|
+
throw new StationChildExhaustedError("court diarist station escalated (cannot identify court target)");
|
|
206
|
+
}
|
|
207
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
208
|
+
throw new StationChildExhaustedError(outcome.failedWithoutEscalate.diagnostic);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export async function runPublicCountersign(argv, env, io, parseCountersignArgv) {
|
|
213
|
+
let parsed;
|
|
214
|
+
try {
|
|
215
|
+
parsed = parseCountersignArgv(argv);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (error instanceof CliUsageError) {
|
|
219
|
+
presentStructuralRejection(error, io);
|
|
220
|
+
return { exitCode: 2 };
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
let admitted;
|
|
225
|
+
try {
|
|
226
|
+
admitted = await admitCountersignInvocation({
|
|
227
|
+
home: env.home,
|
|
228
|
+
principalAuthority: env.principalAuthority,
|
|
229
|
+
cwd: env.cwd,
|
|
230
|
+
instruction: parsed.instruction,
|
|
231
|
+
attachmentPaths: parsed.attachmentPaths,
|
|
232
|
+
...(parsed.project === undefined ? {} : { project: parsed.project }),
|
|
233
|
+
...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
|
|
234
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
235
|
+
...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (error instanceof CliUsageError) {
|
|
240
|
+
presentStructuralRejection(error, io);
|
|
241
|
+
return { exitCode: 2 };
|
|
242
|
+
}
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
await markRunAdmitted(admitted, env.principalAuthority);
|
|
246
|
+
// #637 / #771 / ADR 0079: ticket identity is the 起居郎 LLM typed assertion
|
|
247
|
+
// (never mechanical matching of summons text). Resolve that typed key after
|
|
248
|
+
// admit so the countersign run page exists first; same-ticket re-summons
|
|
249
|
+
// resume the prior run on the typed key (unused mint is abandoned). The test
|
|
250
|
+
// seam `runCourtDiaristStation` defers identity to beforeDispatch; generic
|
|
251
|
+
// hook failures stay on the parent call-local budget, exhausted nested
|
|
252
|
+
// station children still skip parent auto-resume (#840 父子不层叠).
|
|
253
|
+
let typedTicket;
|
|
254
|
+
let typedCourtTicketNumbers;
|
|
255
|
+
let identityDiaristRan = false;
|
|
256
|
+
if (env.runCourtDiaristStation === undefined) {
|
|
257
|
+
const outcome = await invokeCourtDiarist({
|
|
258
|
+
instruction: parsed.instruction,
|
|
259
|
+
projectRoot: admitted.projectRoot,
|
|
260
|
+
failureLabel: "unbound summons",
|
|
261
|
+
correlationId: admitted.runId,
|
|
262
|
+
}, env, io);
|
|
263
|
+
identityDiaristRan = true;
|
|
264
|
+
if (outcome.identity.kind === "escalate") {
|
|
265
|
+
// 御批: 识别不了就上抛 — settle on the admitted countersign run.
|
|
266
|
+
return await presentControlledFailure(admitted, {
|
|
267
|
+
timedOut: false,
|
|
268
|
+
code: null,
|
|
269
|
+
stderr: "",
|
|
270
|
+
thrown: new Error("court diarist station escalated (cannot identify court target)"),
|
|
271
|
+
}, countersignAdapters(), env.principalAuthority, io);
|
|
272
|
+
}
|
|
273
|
+
// Typed failure terminal (verification / infra / non-zero without escalate)
|
|
274
|
+
// is not 真无票 — settle controlled failure on the admitted run (失败诚实).
|
|
275
|
+
// Only a true missing lawful typed terminal keeps the r5 unbound-continue.
|
|
276
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
277
|
+
return await presentControlledFailure(admitted, {
|
|
278
|
+
timedOut: false,
|
|
279
|
+
code: null,
|
|
280
|
+
stderr: "",
|
|
281
|
+
thrown: new Error(outcome.failedWithoutEscalate.diagnostic),
|
|
282
|
+
}, countersignAdapters(), env.principalAuthority, io);
|
|
283
|
+
}
|
|
284
|
+
// Missing lawful 起居郎 terminal is not countersign body failure: leave
|
|
285
|
+
// unbound and continue (true-unbound face). Escalate / typed failure above;
|
|
286
|
+
// bound refresh still fails honest via runCountersignCourtDiaristStation.
|
|
287
|
+
if (outcome.identity.kind === "ticket") {
|
|
288
|
+
const assertedTicket = outcome.identity.ticketNumber;
|
|
289
|
+
typedTicket = assertedTicket;
|
|
290
|
+
// #871: identity may hand a co-review set; absent field defaults to [main].
|
|
291
|
+
typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
|
|
292
|
+
const summons = {
|
|
293
|
+
instruction: parsed.instruction,
|
|
294
|
+
instructionEmpty: parsed.instruction.trim() === "",
|
|
295
|
+
attachmentPaths: parsed.attachmentPaths,
|
|
296
|
+
};
|
|
297
|
+
const resumed = await tryResumeSameTicketSeatRun({
|
|
298
|
+
home: env.home,
|
|
299
|
+
projectRoot: admitted.projectRoot,
|
|
300
|
+
role: "countersign",
|
|
301
|
+
ticketNumber: typedTicket,
|
|
302
|
+
freshSummons: env.freshSummons,
|
|
303
|
+
summons,
|
|
304
|
+
resume: async (runId, materials) => {
|
|
305
|
+
// Resume selected: file the provisional run under the asserted ticket
|
|
306
|
+
// before abandoning it, so typed identity never leaves an unbound row.
|
|
307
|
+
await bindAdmittedTicketNumber(admitted, assertedTicket);
|
|
308
|
+
await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
|
|
309
|
+
await markRunTerminal(admitted.runDirectory);
|
|
310
|
+
// Identity 起居郎 asserted unbound (no issue face). Resume still runs
|
|
311
|
+
// the bound refresh station under the typed key (ADR 0075: 每次过庭都跑是调用者用法).
|
|
312
|
+
// #871: hand the identity set so resume can whole-replace the run fact
|
|
313
|
+
// when this summons produced a new typed set (never union).
|
|
314
|
+
return await runPublicCountersignResume({
|
|
315
|
+
runId,
|
|
316
|
+
...(materials === undefined ? {} : { summons: materials }),
|
|
317
|
+
}, {
|
|
318
|
+
...env,
|
|
319
|
+
...(typedCourtTicketNumbers === undefined
|
|
320
|
+
? {}
|
|
321
|
+
: { pendingCourtTicketNumbers: typedCourtTicketNumbers }),
|
|
322
|
+
}, io);
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
if (resumed !== undefined) {
|
|
326
|
+
return resumed;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (identityDiaristRan && typedTicket !== undefined) {
|
|
331
|
+
try {
|
|
332
|
+
await bindAdmittedTicketNumber(admitted, typedTicket);
|
|
333
|
+
await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
|
|
334
|
+
// First court: explicit set wins; omitted field → single-ticket face [main].
|
|
335
|
+
// Set persistence is outside beforeDispatch — route write failures into the
|
|
336
|
+
// same controlled-failure settlement as station children (#871 B7).
|
|
337
|
+
await bindCourtTicketNumbersOnAdmitted(admitted, typedCourtTicketNumbers ?? [typedTicket]);
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
return await presentControlledFailure(admitted, {
|
|
341
|
+
timedOut: false,
|
|
342
|
+
code: null,
|
|
343
|
+
stderr: "",
|
|
344
|
+
thrown: error,
|
|
345
|
+
}, countersignAdapters(), env.principalAuthority, io);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const turnProjection = {
|
|
349
|
+
packageRoot: env.packageRoot,
|
|
350
|
+
home: env.home,
|
|
351
|
+
agentDir: env.agentDir,
|
|
352
|
+
...(env.model === undefined ? {} : { model: env.model }),
|
|
353
|
+
...pickEngineAxis(env),
|
|
354
|
+
...(env.timeoutMs === undefined ? {} : { timeoutMs: env.timeoutMs }),
|
|
355
|
+
...(env.correlationId === undefined || env.correlationId.trim() === ""
|
|
356
|
+
? {}
|
|
357
|
+
: { correlationId: env.correlationId }),
|
|
358
|
+
continuation: {
|
|
359
|
+
kind: "initial",
|
|
360
|
+
prompt: buildCountersignTransportPrompt(admitted, engineSessionMaterialFromOptions({
|
|
361
|
+
...pickEngineAxis(env),
|
|
362
|
+
packageRoot: env.packageRoot,
|
|
363
|
+
})),
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
// Mutable shell: ticket bind re-projects activation before executeTurn.
|
|
367
|
+
const turnRequest = buildCountersignTurnRequest(admitted, turnProjection);
|
|
368
|
+
const result = await runPostAdmissionOneShot({
|
|
369
|
+
admitted,
|
|
370
|
+
env,
|
|
371
|
+
io,
|
|
372
|
+
request: turnRequest,
|
|
373
|
+
adapters: countersignAdapters({
|
|
374
|
+
beforeDispatch: async (admittedSeat, lease) => {
|
|
375
|
+
// Dossier pointer delivery rides post-admission after this hook (#709).
|
|
376
|
+
if (!identityDiaristRan) {
|
|
377
|
+
// Test seam (or any deferred identity): station owns assert + bind.
|
|
378
|
+
await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
379
|
+
}
|
|
380
|
+
else if (typedTicket !== undefined) {
|
|
381
|
+
await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
382
|
+
}
|
|
383
|
+
await relocateAdmittedRunToTicket(admittedSeat, env.principalAuthority, lease);
|
|
384
|
+
Object.assign(turnRequest, buildCountersignTurnRequest(admittedSeat, turnProjection));
|
|
385
|
+
},
|
|
386
|
+
}),
|
|
387
|
+
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
388
|
+
});
|
|
389
|
+
await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
|
|
390
|
+
return result;
|
|
391
|
+
}
|
|
392
|
+
function countersignAdapters(options) {
|
|
393
|
+
return {
|
|
394
|
+
trySettle: (admitted, authority, scope) => trySettleCountersignTerminalResult(admitted, authority, scope),
|
|
395
|
+
// Accepted receipts and failure terminals both present via shared path.
|
|
396
|
+
shouldPresentSettled: () => true,
|
|
397
|
+
...(options?.beforeDispatch === undefined
|
|
398
|
+
? {}
|
|
399
|
+
: { beforeDispatch: options.beforeDispatch }),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Resume a previously admitted Countersign run (#599 / DK-3 / #637).
|
|
404
|
+
* Restores role/ticket/session identity. Bound court re-entry runs the diarist
|
|
405
|
+
* refresh station first (ADR 0075 每次过庭都跑); unbound skips refresh.
|
|
406
|
+
* Same-ticket summons deliver this turn's instruction + frozen attachments on
|
|
407
|
+
* the resume prompt; manual resume keeps package-envelope / caller-message
|
|
408
|
+
* semantics and birth attachments. 起居录 path delivery remains post-admission's
|
|
409
|
+
* single mount (#709).
|
|
410
|
+
*/
|
|
411
|
+
export async function runPublicCountersignResume(request, env, io) {
|
|
412
|
+
return await runPostAdmissionSeatResume({
|
|
413
|
+
request,
|
|
414
|
+
env,
|
|
415
|
+
io,
|
|
416
|
+
load: (effective) => loadResumableCountersignRun(env.home, effective.runId, env.principalAuthority),
|
|
417
|
+
buildTurnRequest: async (admitted, effective) => {
|
|
418
|
+
const summonsPrepared = await prepareSummonsResumeMaterials(admitted.runDirectory, effective.summons);
|
|
419
|
+
return buildCountersignTurnRequest(admitted, resumeTurnRequestProjectionOptions(admitted, effective, env, summonsPrepared));
|
|
420
|
+
},
|
|
421
|
+
adapters: countersignAdapters({
|
|
422
|
+
beforeDispatch: async (admitted) => {
|
|
423
|
+
// #871 B7: durable set damage already identified at load — settle as
|
|
424
|
+
// station-child exhausted → presentControlledFailure (not structural exit 2).
|
|
425
|
+
if (admitted.courtTicketNumbersDamage !== undefined) {
|
|
426
|
+
throw new StationChildExhaustedError(admitted.courtTicketNumbersDamage);
|
|
427
|
+
}
|
|
428
|
+
// #871: same-ticket re-summons may hand a fresh typed set from identity.
|
|
429
|
+
// Whole-set replace onto this run fact; no new set → keep stored set.
|
|
430
|
+
// Manual resume (no pending) keeps the durable set and never invents one.
|
|
431
|
+
if (env.pendingCourtTicketNumbers !== undefined) {
|
|
432
|
+
await bindCourtTicketNumbersOnAdmitted(admitted, env.pendingCourtTicketNumbers);
|
|
433
|
+
}
|
|
434
|
+
await runCountersignCourtDiaristStation(admitted, env, io);
|
|
435
|
+
},
|
|
436
|
+
}),
|
|
437
|
+
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
@@ -445,6 +445,9 @@ export function parseAuditorArgv(args) {
|
|
|
445
445
|
export function parseDiaristArgv(args) {
|
|
446
446
|
return parseInstructionArgv(args, "diarist");
|
|
447
447
|
}
|
|
448
|
+
export function parseSecretariatArgv(args) {
|
|
449
|
+
return parseInstructionArgv(args, "secretariat");
|
|
450
|
+
}
|
|
448
451
|
/**
|
|
449
452
|
* Parse Coder-specific argv after the `coder` token.
|
|
450
453
|
* Phase defaults to apply; spellings from PUBLIC_OPTION_TABLE.coder (#342).
|
|
@@ -706,6 +709,10 @@ export async function admitAuditorInvocation(options) {
|
|
|
706
709
|
export async function admitDiaristInvocation(options) {
|
|
707
710
|
return admitStandardMaterialInvocation("diarist", options);
|
|
708
711
|
}
|
|
712
|
+
/** Admit a public Secretariat (中书省) run (#924). */
|
|
713
|
+
export async function admitSecretariatInvocation(options) {
|
|
714
|
+
return admitStandardMaterialInvocation("secretariat", options);
|
|
715
|
+
}
|
|
709
716
|
/** Shared prompt transport for instruction-seat roles (judge/countersign/inspector). */
|
|
710
717
|
export function buildInstructionTransportPrompt(admitted, engineMaterial) {
|
|
711
718
|
const lines = [admitted.instructionEmpty ? "" : admitted.instruction];
|
|
@@ -722,6 +729,9 @@ export function buildInstructionTransportPrompt(admitted, engineMaterial) {
|
|
|
722
729
|
export function buildJudgeTransportPrompt(admitted, engineMaterial) {
|
|
723
730
|
return buildInstructionTransportPrompt(admitted, engineMaterial);
|
|
724
731
|
}
|
|
732
|
+
export function buildSecretariatTransportPrompt(admitted, engineMaterial) {
|
|
733
|
+
return buildInstructionTransportPrompt(admitted, engineMaterial);
|
|
734
|
+
}
|
|
725
735
|
export function buildInspectorTransportPrompt(admitted, engineMaterial) {
|
|
726
736
|
return buildInstructionTransportPrompt(admitted, engineMaterial);
|
|
727
737
|
}
|