@akagilnc/pi-workflow-roles 0.1.5013 → 0.1.5037
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 +116 -101
- package/dist/headless-host/production-host.js +116 -101
- package/dist/migrate-book-topology.js +3 -2
- package/dist/packaged-role-registry.js +3 -2
- package/dist/public-cli/cli.js +17 -2
- package/dist/public-cli/countersign-run.js +13 -8
- package/dist/public-cli/instruction-seat-run.js +63 -67
- package/dist/public-cli/invocation.js +13 -32
- package/dist/public-cli/main.js +172 -101
- package/dist/public-cli/post-admission.js +8 -5
- package/dist/public-cli/run-lifecycle.js +12 -15
- package/dist/public-cli/seat-ticket-binding.js +5 -8
- package/dist/public-role-summons.js +8 -3
- package/package.json +1 -1
- package/resources/countersign-ticket-issue.md +5 -0
- package/resources/diarist-collect.md +1 -0
- package/resources/secretariat-ticket-identity.md +15 -0
- package/souls/audit-law.md +1 -1
- package/src/packaged-role-registry.ts +3 -2
- package/src/public-cli/cli.ts +18 -2
- package/src/public-cli/countersign-run.ts +17 -10
- package/src/public-cli/instruction-seat-run.ts +66 -63
- package/src/public-cli/invocation.ts +10 -38
- package/src/public-cli/post-admission.ts +10 -6
- package/src/public-cli/run-lifecycle.ts +12 -13
- package/src/public-cli/seat-ticket-binding.ts +5 -8
- package/src/public-role-summons.ts +16 -3
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared same-parent resume seam for gate / officer seats (#635 / #637 / #747 / #987).
|
|
3
3
|
*
|
|
4
|
-
* Lookup
|
|
5
|
-
*
|
|
6
|
-
* `ak-role resume <runId>`.
|
|
7
|
-
* keep this seam. Lookup/resume failures propagate (失败诚实) — never wash into
|
|
4
|
+
* Lookup uses the parent run path; a typed ticket narrows same-ticket gate
|
|
5
|
+
* re-summons, never creates an issue identity. Callers can also use explicit
|
|
6
|
+
* `ak-role resume <runId>`. Lookup/resume failures propagate (失败诚实) — never wash into
|
|
8
7
|
* a fresh mint. Returns undefined when the caller declared an explicit fresh
|
|
9
8
|
* summons (`ak-role new`) or when no prior run exists; both mint new.
|
|
10
9
|
* freshSummons is required so no seat can drift back into its own skip branch.
|
|
@@ -13,7 +12,7 @@ import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
|
13
12
|
import { findLatestRunIdForSeatTicket, } from "./run-lifecycle.js";
|
|
14
13
|
/**
|
|
15
14
|
* Sole same-seat → resume decision by parent run path (#637 / #724 / #747 / #987).
|
|
16
|
-
* A typed ticket
|
|
15
|
+
* A supplied typed ticket only narrows the prior-run search. When a prior run is
|
|
17
16
|
* found, resume carries this summons' materials. Lookup/resume failures
|
|
18
17
|
* propagate (失败诚实) — never wash into a fresh mint. Returns undefined when
|
|
19
18
|
* the caller declared an explicit fresh summons (`ak-role new`), the parent
|
|
@@ -30,9 +29,7 @@ export async function tryResumeSameTicketSeatRun(input) {
|
|
|
30
29
|
bookKey: resolveBookKeyFromGit(input.projectRoot),
|
|
31
30
|
role: input.role,
|
|
32
31
|
parentRunPath: input.parentRunPath,
|
|
33
|
-
...(input.ticketNumber === undefined
|
|
34
|
-
? {}
|
|
35
|
-
: { ticketNumber: input.ticketNumber }),
|
|
32
|
+
...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
|
|
36
33
|
});
|
|
37
34
|
if (previousRunId === undefined)
|
|
38
35
|
return undefined;
|
|
@@ -274,7 +274,10 @@ async function summonPublicRole(options) {
|
|
|
274
274
|
result = stepped.ok;
|
|
275
275
|
}
|
|
276
276
|
const stderr = captured?.stderrText();
|
|
277
|
-
const runDirectory = result.
|
|
277
|
+
const runDirectory = result.terminal !== void 0 && result.terminal.roleOutcome.role !== options.role && typeof result.terminal.runId === "string" ? await (await import("./public-cli/run-lifecycle.js")).findRunDirectoryById(
|
|
278
|
+
home,
|
|
279
|
+
result.terminal.runId
|
|
280
|
+
) : result.admitted?.runDirectory;
|
|
278
281
|
return {
|
|
279
282
|
exitCode: result.exitCode,
|
|
280
283
|
...result.terminal === void 0 ? {} : { terminal: result.terminal },
|
|
@@ -478,8 +481,10 @@ async function summonGateOfficer(options) {
|
|
|
478
481
|
`countersign gate summon requires parent runId from sourceRunDirectory: ${options.sourceRunDirectory}`
|
|
479
482
|
);
|
|
480
483
|
}
|
|
481
|
-
const { readBoardTicketNumber } = await import("./run-ticket-number.js");
|
|
484
|
+
const { isSafePositiveTicketNumber, readBoardTicketNumber } = await import("./run-ticket-number.js");
|
|
482
485
|
const parentTicket = await readBoardTicketNumber(options.sourceRunDirectory);
|
|
486
|
+
const submittedTicket = options.submission !== null && typeof options.submission === "object" && !Array.isArray(options.submission) ? options.submission.ticketNumber : void 0;
|
|
487
|
+
const courtTicket = isSafePositiveTicketNumber(submittedTicket) ? submittedTicket : parentTicket;
|
|
483
488
|
const instruction = options.reask ?? gateReviewInstruction ?? "";
|
|
484
489
|
return summonPublicRole({
|
|
485
490
|
role: options.officer,
|
|
@@ -487,7 +492,7 @@ async function summonGateOfficer(options) {
|
|
|
487
492
|
...common,
|
|
488
493
|
correlationId,
|
|
489
494
|
parentRunPath: options.sourceRunDirectory,
|
|
490
|
-
...
|
|
495
|
+
...courtTicket === void 0 ? {} : { boundTicketNumber: courtTicket }
|
|
491
496
|
});
|
|
492
497
|
}
|
|
493
498
|
return summonPublicRole({
|
package/package.json
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# 给事中票面审读方法
|
|
2
|
+
|
|
3
|
+
中书省交卷时,以交卷原文所指的实际 GitHub issue 为审读对象;传召附带的绑定票号只用于绑卷,不替中书省决定票号。先在当前项目的 GitHub 仓库用宿主已有 CLI 读取该 issue 的现行正文(例如 `gh issue view <票号> --json body`);不得只看中书省交卷文字或本地草稿,就声称已审 GitHub 票面。对照原交卷和实际 issue 正文,按《票面法》审读。
|
|
4
|
+
|
|
5
|
+
中书省以 `converged` 交卷却没有可审的实际 GitHub issue 身份时,封驳退回中书省补齐;不得以无号草稿署票。此类封驳走现有重交通道,不由代码按回执形状拒收。issue 不存在或正文读取失败时,如实说明现场失败,不把未读到的票面判作已审。交卷与绑卷票号冲突也须说明,不猜测替代票号。
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
- 认得出本庭对象:`status: completed` 且 `ticketNumber: N`,并交边界。
|
|
35
35
|
- 真无票对象(方案/派单/处置案等):`status: completed` 且 `ticketNumber: null`(或省略)。真无票是合法结果,不入录。
|
|
36
|
+
- 本庭明确要新建票、但 issue 尚未建号:不是认票失败。以 `status: completed`、`ticketNumber: null` 交出已辨明的本案对话边界;起居录先随无号 run 暂存,建号后由既有归卷流程补归该号。不要借用旁票号。
|
|
36
37
|
- 认不出本庭对象是哪一张:`status: escalate` 并写明 `reason`。识别不了就上抛,不得省略票号假装无录,也不得猜一个号。
|
|
37
38
|
- 不得从旁及的他票或任何猜测里挑一个号顶替认不出的身份。
|
|
38
39
|
- 传召文自然会提轮次、决定编号、commit sha、邻票号;你认的是本庭对象,不是文中出现的每一个数字。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 中书省票身份与落票方法
|
|
2
|
+
|
|
3
|
+
本方法适用于公开中书省调用。票面范围、审读职责和交卷形状仍以各自法源为准。
|
|
4
|
+
|
|
5
|
+
## 先辨票,再改票
|
|
6
|
+
|
|
7
|
+
调用者可以给票号,也可以不给。给了票号,先读取并核对该 issue,再在原 issue 上更新票面;不要另建一票。没有给票号,不等于新票:先结合本次对话、本票起居录和当前 GitHub 仓库中的 issue 判断是否已有对应票。找到已有票就更新那张;只有确认没有对应票,才在当前仓库创建 issue。不得仅凭内容相似就认定已有票,也不得因没找到就忽略搜索范围或不确定性。
|
|
8
|
+
|
|
9
|
+
票面改定后,应将实际要执行的正文落到对应 GitHub issue。不能确认已有票还是新票、无法读取或更新目标 issue,或创建 issue 失败时,不得把草稿当成已落票结果送庭;可上呈或重交,并说明现场观察到的真实失败原因。若信息不足以区分多个候选 issue,可上呈请裁定,不猜票号、不另建重复票。
|
|
10
|
+
|
|
11
|
+
## 落地后送庭
|
|
12
|
+
|
|
13
|
+
只有确认目标 issue 存在且正文已更新为本票最终票面后,才以 `converged` 交卷,并携带该 issue 的实际票号。沿用既有交卷闸,将交卷原文交给给事中审这张有票号 issue;不以无号草稿替代 issue 审读。不另造传召或记账机制。
|
|
14
|
+
|
|
15
|
+
执行 GitHub 操作时使用当前宿主实际提供的 CLI / 能力;不在包内另造 GitHub 适配层。记录足以核对身份和落地结果的证据,不把模型自报冒充为现场观察。
|
package/souls/audit-law.md
CHANGED
|
@@ -19,7 +19,7 @@ finding 提**类别**;实例只作证据。一次找齐全,不逐轮抽样
|
|
|
19
19
|
|
|
20
20
|
判断不收敛(同类问题反复、修理只增不减)可上呈;你的打回与上一轮相同、对方重交只换措辞,须上呈,不得再打回。修理 diff 自身按同尺复核。
|
|
21
21
|
|
|
22
|
-
衙门不是需求来源。弹章、施工方的注释与测试、你造的探针,都只是线索。判一条 finding
|
|
22
|
+
衙门不是需求来源。弹章、施工方的注释与测试、你造的探针,都只是线索。判一条 finding 成立、开一条约束,判词须写明要它的那一句法,先看那句讲的是不是这一条;指不出的,按过度防御或加戏驳回。判词缺这一样,审刑院打回,符宝郎驳。对方按你处方加的东西,下轮同审。
|
|
23
23
|
|
|
24
24
|
判例:#1025 御史台据一句注释造场景,大理寺引「上呈暂停」判成立,审刑院、符宝郎皆署;那句法没要过「谁有资格唤醒」,场景也从未发生。
|
|
25
25
|
|
|
@@ -391,7 +391,7 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
391
391
|
activationStage: "load-and-install",
|
|
392
392
|
receiptStatusKey: "status",
|
|
393
393
|
// #924: 公用《票面法》三席同装
|
|
394
|
-
sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md"],
|
|
394
|
+
sessionMaterials: ["CLAUDE.md", "souls/countersign.md", "souls/ticket-law.md", "resources/countersign-ticket-issue.md"],
|
|
395
395
|
},
|
|
396
396
|
{
|
|
397
397
|
role: "secretariat",
|
|
@@ -399,7 +399,7 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
399
399
|
presentSettled: "always",
|
|
400
400
|
summonResume: false,
|
|
401
401
|
admission: "instruction",
|
|
402
|
-
sameParent: "
|
|
402
|
+
sameParent: "none",
|
|
403
403
|
phases: [null],
|
|
404
404
|
outputTool: SECRETARIAT_OUTPUT_TOOL_NAME,
|
|
405
405
|
settlement: "accepted",
|
|
@@ -415,6 +415,7 @@ export const PUBLIC_ROLE_RECORDS = [
|
|
|
415
415
|
"CLAUDE.md",
|
|
416
416
|
"souls/secretariat.md",
|
|
417
417
|
"souls/ticket-law.md",
|
|
418
|
+
"resources/secretariat-ticket-identity.md",
|
|
418
419
|
"souls/countersign.md",
|
|
419
420
|
"souls/notary.md",
|
|
420
421
|
],
|
package/src/public-cli/cli.ts
CHANGED
|
@@ -77,7 +77,8 @@ import {
|
|
|
77
77
|
type PublicRoleOptionOwner,
|
|
78
78
|
type TypedOptionConsumer,
|
|
79
79
|
} from "./option-definitions.ts";
|
|
80
|
-
import { runPublicInstructionSeat, runPublicInstructionSeatResume } from "./instruction-seat-run.ts";
|
|
80
|
+
import { continueParentAfterChild, runPublicInstructionSeat, runPublicInstructionSeatResume } from "./instruction-seat-run.ts";
|
|
81
|
+
import { courtDiaristEscalated } from "./countersign-run.ts";
|
|
81
82
|
import { runPublicAnalyst } from "./analyst-run.ts";
|
|
82
83
|
import {
|
|
83
84
|
AUTO_RESUME_LIMIT,
|
|
@@ -1229,7 +1230,22 @@ export async function runAkRole(
|
|
|
1229
1230
|
}),
|
|
1230
1231
|
io,
|
|
1231
1232
|
);
|
|
1232
|
-
|
|
1233
|
+
let current = result;
|
|
1234
|
+
while (
|
|
1235
|
+
current.exitCode === 0 && current.admitted?.correlationId !== undefined
|
|
1236
|
+
&& current.terminal?.roleOutcome.kind === "accepted"
|
|
1237
|
+
&& !courtDiaristEscalated(current.terminal.roleOutcome)
|
|
1238
|
+
) {
|
|
1239
|
+
const parentRunId = current.admitted.correlationId;
|
|
1240
|
+
const parentRole = await peekRoleRunRole(home, parentRunId);
|
|
1241
|
+
if (parentRole === undefined) break;
|
|
1242
|
+
const parentSeat = resolveEffectiveSeat(config, parentRole, credentials, invocationFromParsed(parsed));
|
|
1243
|
+
const parentEnv = createRoleEnvironment(env, {
|
|
1244
|
+
role: parentRole, home, agentDir, cwd, credentials, seat: parentSeat, config,
|
|
1245
|
+
});
|
|
1246
|
+
current = await continueParentAfterChild(parentRunId, current.admitted, parentEnv, io);
|
|
1247
|
+
}
|
|
1248
|
+
return cliResultFromRoleRun(current);
|
|
1233
1249
|
}
|
|
1234
1250
|
|
|
1235
1251
|
if (
|
|
@@ -45,8 +45,8 @@ export type CountersignRunEnv = PostAdmissionEnv & {
|
|
|
45
45
|
*/
|
|
46
46
|
gateReviewInstruction?: string;
|
|
47
47
|
/**
|
|
48
|
-
* #969 / #987 gate path: parent run durable board ticket handoff for bind
|
|
49
|
-
*
|
|
48
|
+
* #969 / #987 gate path: parent run durable board ticket handoff for bind.
|
|
49
|
+
* Same-ticket resume lookup narrows the parent path by this typed number.
|
|
50
50
|
*/
|
|
51
51
|
boundTicketNumber?: number;
|
|
52
52
|
/**
|
|
@@ -79,6 +79,7 @@ export type CourtDiaristIdentity =
|
|
|
79
79
|
export type CourtDiaristInvocationResult = {
|
|
80
80
|
readonly identity: CourtDiaristIdentity;
|
|
81
81
|
readonly admitted?: import("./invocation.ts").AdmittedRoleInvocation;
|
|
82
|
+
readonly terminal?: import("./terminal.ts").TerminalResult;
|
|
82
83
|
readonly failedWithoutEscalate?: { readonly diagnostic: string };
|
|
83
84
|
};
|
|
84
85
|
|
|
@@ -161,7 +162,7 @@ export type CourtDiaristSummonEnv = Pick<
|
|
|
161
162
|
* - Multiple submissions: last qualifying set wins.
|
|
162
163
|
* Live path never shape-rejects the role turn; durable damage is a separate seam.
|
|
163
164
|
*/
|
|
164
|
-
function courtTicketNumbersFromOutcome(
|
|
165
|
+
export function courtTicketNumbersFromOutcome(
|
|
165
166
|
roleOutcome: TerminalRoleOutcome | undefined,
|
|
166
167
|
principalTicket: number,
|
|
167
168
|
submissions?: readonly unknown[],
|
|
@@ -199,7 +200,7 @@ function courtTicketNumbersFromOutcome(
|
|
|
199
200
|
}
|
|
200
201
|
|
|
201
202
|
/** Routing boolean over the child's own typed sequence — does not pick or rewrite a sole row. */
|
|
202
|
-
function courtDiaristEscalated(
|
|
203
|
+
export function courtDiaristEscalated(
|
|
203
204
|
roleOutcome: TerminalRoleOutcome | undefined,
|
|
204
205
|
): boolean {
|
|
205
206
|
if (roleOutcome === undefined) return false;
|
|
@@ -280,6 +281,7 @@ export async function invokeCourtDiarist(
|
|
|
280
281
|
diagnostic: courtDiaristEscalateDiagnostic(roleOutcome, submissions),
|
|
281
282
|
},
|
|
282
283
|
...(result.admitted === undefined ? {} : { admitted: result.admitted }),
|
|
284
|
+
...(result.terminal === undefined ? {} : { terminal: result.terminal }),
|
|
283
285
|
};
|
|
284
286
|
}
|
|
285
287
|
|
|
@@ -328,14 +330,15 @@ export async function invokeCourtDiarist(
|
|
|
328
330
|
* Missing ticketNumber (true-unbound / identity deferred) skips the refresh
|
|
329
331
|
* station — no diary is minted for a true-unbound run. First-entry identity
|
|
330
332
|
* lives on the shared countersign entry (typed 起居郎 key for bind). Bound refresh:
|
|
331
|
-
*
|
|
333
|
+
* technical failure propagates; LLM escalation remains a recorded conclusion.
|
|
332
334
|
* Path delivery onto materials is not this station's job — post-admission owns it.
|
|
333
335
|
*/
|
|
334
336
|
export async function runCountersignCourtDiaristStation(
|
|
335
337
|
admitted: AdmittedCountersignInvocation,
|
|
336
338
|
env: CountersignRunEnv,
|
|
337
339
|
io: CliIo,
|
|
338
|
-
|
|
340
|
+
afterTicketNumber?: number,
|
|
341
|
+
): Promise<{ readonly terminal: import("./terminal.ts").TerminalResult; readonly childRunId?: string } | undefined> {
|
|
339
342
|
if (env.runCourtDiaristStation !== undefined) {
|
|
340
343
|
await env.runCourtDiaristStation(admitted);
|
|
341
344
|
return;
|
|
@@ -362,7 +365,8 @@ export async function runCountersignCourtDiaristStation(
|
|
|
362
365
|
refreshTickets = [admitted.ticketNumber];
|
|
363
366
|
}
|
|
364
367
|
|
|
365
|
-
|
|
368
|
+
const completedIndex = afterTicketNumber === undefined ? -1 : refreshTickets.indexOf(afterTicketNumber);
|
|
369
|
+
for (const ticketNumber of refreshTickets.slice(completedIndex + 1)) {
|
|
366
370
|
const outcome = await invokeCourtDiarist(
|
|
367
371
|
{
|
|
368
372
|
instruction: `整理 #${ticketNumber} 的本案依据。`,
|
|
@@ -376,13 +380,16 @@ export async function runCountersignCourtDiaristStation(
|
|
|
376
380
|
io,
|
|
377
381
|
);
|
|
378
382
|
|
|
379
|
-
if (outcome.identity.kind === "escalate") {
|
|
380
|
-
throw new StationChildExhaustedError(outcome.identity.diagnostic);
|
|
381
|
-
}
|
|
382
383
|
if (outcome.failedWithoutEscalate !== undefined) {
|
|
383
384
|
throw new StationChildExhaustedError(
|
|
384
385
|
outcome.failedWithoutEscalate.diagnostic,
|
|
385
386
|
);
|
|
386
387
|
}
|
|
388
|
+
if (outcome.identity.kind === "escalate" && outcome.terminal !== undefined) {
|
|
389
|
+
return {
|
|
390
|
+
terminal: outcome.terminal,
|
|
391
|
+
...(outcome.admitted === undefined ? {} : { childRunId: outcome.admitted.runId }),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
387
394
|
}
|
|
388
395
|
}
|
|
@@ -388,9 +388,7 @@ async function runCountersignBody(
|
|
|
388
388
|
projectRoot: resolve(parsed.project ?? env.cwd),
|
|
389
389
|
role: "countersign",
|
|
390
390
|
parentRunPath: gateParentRunPath,
|
|
391
|
-
...(env.boundTicketNumber
|
|
392
|
-
? {}
|
|
393
|
-
: { ticketNumber: env.boundTicketNumber }),
|
|
391
|
+
...(isSafePositiveTicketNumber(env.boundTicketNumber) ? { ticketNumber: env.boundTicketNumber } : {}),
|
|
394
392
|
freshSummons: env.freshSummons,
|
|
395
393
|
summons: {
|
|
396
394
|
sourceRunPath: gateParentRunPath,
|
|
@@ -430,15 +428,17 @@ async function runCountersignBody(
|
|
|
430
428
|
let typedTicket: number | undefined;
|
|
431
429
|
let typedCourtTicketNumbers: readonly number[] | undefined;
|
|
432
430
|
let identityDiaristRan = false;
|
|
433
|
-
let
|
|
431
|
+
let childDiaristRunId: string | undefined;
|
|
432
|
+
let pausedDiaristTerminal: TerminalResult | undefined;
|
|
434
433
|
|
|
435
|
-
if (env.runCourtDiaristStation === undefined) {
|
|
434
|
+
if (gateParentRunPath === undefined && env.runCourtDiaristStation === undefined) {
|
|
436
435
|
let outcome: Awaited<ReturnType<typeof invokeCourtDiarist>>;
|
|
437
436
|
try {
|
|
438
437
|
outcome = await invokeCourtDiarist({
|
|
439
438
|
instruction: parsed.instruction ?? "",
|
|
440
439
|
projectRoot: admitted.projectRoot,
|
|
441
440
|
failureLabel: "unbound summons",
|
|
441
|
+
correlationId: admitted.runId,
|
|
442
442
|
...(env.boundTicketNumber === undefined ? {} : { boundTicketNumber: env.boundTicketNumber }),
|
|
443
443
|
}, env, io);
|
|
444
444
|
} catch (error) {
|
|
@@ -454,17 +454,16 @@ async function runCountersignBody(
|
|
|
454
454
|
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
455
455
|
}
|
|
456
456
|
identityDiaristRan = true;
|
|
457
|
+
if (outcome.identity.kind === "escalate") pausedDiaristTerminal = outcome.terminal;
|
|
457
458
|
if (outcome.admitted?.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) {
|
|
458
|
-
|
|
459
|
+
childDiaristRunId = outcome.admitted.runId;
|
|
459
460
|
}
|
|
460
|
-
if (outcome.
|
|
461
|
-
const diagnostic = outcome.
|
|
462
|
-
? outcome.identity.diagnostic
|
|
463
|
-
: outcome.failedWithoutEscalate?.diagnostic ?? "";
|
|
461
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
462
|
+
const diagnostic = outcome.failedWithoutEscalate.diagnostic;
|
|
464
463
|
await materializeAdmission(
|
|
465
464
|
isSafePositiveTicketNumber(env.boundTicketNumber) ? env.boundTicketNumber : undefined,
|
|
466
465
|
);
|
|
467
|
-
if (
|
|
466
|
+
if (childDiaristRunId !== undefined) await recordChildDiaristRun(admitted, childDiaristRunId);
|
|
468
467
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
469
468
|
return await presentControlledFailure(admitted, {
|
|
470
469
|
timedOut: false,
|
|
@@ -474,19 +473,20 @@ async function runCountersignBody(
|
|
|
474
473
|
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
475
474
|
}
|
|
476
475
|
if (outcome.identity.kind === "ticket") {
|
|
477
|
-
typedTicket =
|
|
478
|
-
? env.boundTicketNumber
|
|
479
|
-
: outcome.identity.ticketNumber;
|
|
476
|
+
typedTicket = outcome.identity.ticketNumber;
|
|
480
477
|
typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
|
|
481
|
-
} else if (isSafePositiveTicketNumber(env.boundTicketNumber)) {
|
|
482
|
-
typedTicket = env.boundTicketNumber;
|
|
483
478
|
}
|
|
484
479
|
}
|
|
485
480
|
|
|
486
481
|
await materializeAdmission(typedTicket);
|
|
487
482
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
488
|
-
if (
|
|
489
|
-
await recordChildDiaristRun(admitted,
|
|
483
|
+
if (childDiaristRunId !== undefined) {
|
|
484
|
+
await recordChildDiaristRun(admitted, childDiaristRunId);
|
|
485
|
+
}
|
|
486
|
+
if (pausedDiaristTerminal !== undefined) {
|
|
487
|
+
// The child already submitted its own pause; no parent turn exists yet.
|
|
488
|
+
io.stdout(formatTerminalResult(pausedDiaristTerminal));
|
|
489
|
+
return { exitCode: 0, admitted, terminal: pausedDiaristTerminal };
|
|
490
490
|
}
|
|
491
491
|
if (gateParentRunPath !== undefined) {
|
|
492
492
|
await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
|
|
@@ -540,7 +540,11 @@ async function runCountersignBody(
|
|
|
540
540
|
beforeDispatch: async (admittedSeat, lease) => {
|
|
541
541
|
if (!packagedAdmitsCountersign(admittedSeat.role)) return;
|
|
542
542
|
if (!identityDiaristRan || typedTicket !== undefined) {
|
|
543
|
-
await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
543
|
+
const refresh = await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
544
|
+
if (refresh !== undefined) {
|
|
545
|
+
io.stdout(formatTerminalResult(refresh.terminal));
|
|
546
|
+
return refresh.terminal;
|
|
547
|
+
}
|
|
544
548
|
}
|
|
545
549
|
await relocateAdmittedRunToTicket(admittedSeat, env.principalAuthority, lease);
|
|
546
550
|
Object.assign(turnRequest, buildInstructionSeatTurnRequest(admittedSeat, turnProjection));
|
|
@@ -628,7 +632,6 @@ export async function runPublicInstructionSeat(
|
|
|
628
632
|
projectRoot,
|
|
629
633
|
role,
|
|
630
634
|
parentRunPath: sourceDirectory,
|
|
631
|
-
...(auditorTicket === undefined ? {} : { ticketNumber: auditorTicket }),
|
|
632
635
|
freshSummons: env.freshSummons,
|
|
633
636
|
summons,
|
|
634
637
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -657,7 +660,6 @@ export async function runPublicInstructionSeat(
|
|
|
657
660
|
projectRoot,
|
|
658
661
|
role,
|
|
659
662
|
parentRunPath,
|
|
660
|
-
...(env.boundTicketNumber === undefined ? {} : { ticketNumber: env.boundTicketNumber }),
|
|
661
663
|
freshSummons: env.freshSummons,
|
|
662
664
|
summons,
|
|
663
665
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -686,7 +688,6 @@ export async function runPublicInstructionSeat(
|
|
|
686
688
|
throw error;
|
|
687
689
|
}
|
|
688
690
|
const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
|
|
689
|
-
const knownTicket = (await readBoardTicketNumber(source.runDirectory)) ?? env.boundTicketNumber;
|
|
690
691
|
const summons: SameTicketSummonsMaterials = {
|
|
691
692
|
sourceRunPath: source.runDirectory,
|
|
692
693
|
sourceRun: source,
|
|
@@ -697,7 +698,6 @@ export async function runPublicInstructionSeat(
|
|
|
697
698
|
projectRoot,
|
|
698
699
|
role,
|
|
699
700
|
parentRunPath: source.runDirectory,
|
|
700
|
-
...(knownTicket === undefined ? {} : { ticketNumber: knownTicket }),
|
|
701
701
|
freshSummons: env.freshSummons,
|
|
702
702
|
summons,
|
|
703
703
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -737,52 +737,33 @@ export async function runPublicInstructionSeat(
|
|
|
737
737
|
|
|
738
738
|
const runAdmitted = async (): Promise<SeatRunResult> => {
|
|
739
739
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
756
|
-
}, io);
|
|
740
|
+
if (role === "secretariat") {
|
|
741
|
+
let outcome: Awaited<ReturnType<typeof invokeCourtDiarist>>;
|
|
742
|
+
try {
|
|
743
|
+
outcome = await invokeCourtDiarist({
|
|
744
|
+
instruction: parsed.instruction ?? "",
|
|
745
|
+
projectRoot: admitted.projectRoot,
|
|
746
|
+
failureLabel: "secretariat unbound summons",
|
|
747
|
+
attachmentPaths: admitted.attachments.map((attachment) => attachment.frozenPath),
|
|
748
|
+
correlationId: admitted.runId,
|
|
749
|
+
}, env, io);
|
|
750
|
+
} catch (error) {
|
|
751
|
+
return await presentControlledFailure(admitted, {
|
|
752
|
+
timedOut: false, code: null, stderr: "", thrown: error,
|
|
753
|
+
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
754
|
+
}
|
|
757
755
|
if (outcome.admitted?.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) {
|
|
758
756
|
await recordChildDiaristRun(admitted, outcome.admitted.runId);
|
|
759
757
|
}
|
|
760
|
-
if (outcome.
|
|
761
|
-
const diagnostic = outcome.identity.kind === "escalate"
|
|
762
|
-
? outcome.identity.diagnostic
|
|
763
|
-
: outcome.failedWithoutEscalate?.diagnostic ?? "";
|
|
758
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
764
759
|
return await presentControlledFailure(admitted, {
|
|
765
|
-
timedOut: false,
|
|
766
|
-
|
|
767
|
-
stderr: "",
|
|
768
|
-
thrown: new Error(diagnostic),
|
|
760
|
+
timedOut: false, code: null, stderr: "",
|
|
761
|
+
thrown: new Error(outcome.failedWithoutEscalate.diagnostic),
|
|
769
762
|
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
770
763
|
}
|
|
771
|
-
if (outcome.identity.kind === "
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
await bindAdmittedTicketNumber(outcome.admitted, outcome.identity.ticketNumber);
|
|
775
|
-
await relocateAdmittedRunToTicket(outcome.admitted, env.principalAuthority);
|
|
776
|
-
await bindAdmittedTicketNumber(admitted, outcome.identity.ticketNumber);
|
|
777
|
-
await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
|
|
778
|
-
} catch (error) {
|
|
779
|
-
return await presentControlledFailure(admitted, {
|
|
780
|
-
timedOut: false,
|
|
781
|
-
code: null,
|
|
782
|
-
stderr: "",
|
|
783
|
-
thrown: error,
|
|
784
|
-
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
785
|
-
}
|
|
764
|
+
if (outcome.identity.kind === "escalate" && outcome.terminal !== undefined) {
|
|
765
|
+
io.stdout(formatTerminalResult(outcome.terminal));
|
|
766
|
+
return { exitCode: 0, admitted, terminal: outcome.terminal };
|
|
786
767
|
}
|
|
787
768
|
}
|
|
788
769
|
return dispatchAdmitted(admitted, env, io);
|
|
@@ -856,3 +837,25 @@ export async function runPublicInstructionSeatResume(
|
|
|
856
837
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
857
838
|
});
|
|
858
839
|
}
|
|
840
|
+
|
|
841
|
+
/** Continue the parent once any escalated child has submitted. */
|
|
842
|
+
export async function continueParentAfterChild(
|
|
843
|
+
parentRunId: string,
|
|
844
|
+
child: AdmittedRoleInvocation,
|
|
845
|
+
env: InstructionSeatRunEnv,
|
|
846
|
+
io: CliIo,
|
|
847
|
+
): Promise<SeatRunResult> {
|
|
848
|
+
const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority, true);
|
|
849
|
+
if (loaded.run.state !== "admitted") {
|
|
850
|
+
return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
|
|
851
|
+
}
|
|
852
|
+
const admitted = loaded.admitted;
|
|
853
|
+
if (admitted.role === "countersign" && admitted.ticketNumber !== undefined) {
|
|
854
|
+
const refresh = await runCountersignCourtDiaristStation(admitted, env, io, child.ticketNumber);
|
|
855
|
+
if (refresh !== undefined) {
|
|
856
|
+
io.stdout(formatTerminalResult(refresh.terminal));
|
|
857
|
+
return { exitCode: 0, admitted, terminal: refresh.terminal };
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return dispatchAdmitted(admitted, env, io);
|
|
861
|
+
}
|
|
@@ -27,6 +27,7 @@ import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
|
|
|
27
27
|
import {
|
|
28
28
|
ensureRoleRunDirectory,
|
|
29
29
|
ensureRoleRunPlacement,
|
|
30
|
+
listBookRunDirectories,
|
|
30
31
|
roleRunArtifactsDirectory,
|
|
31
32
|
roleRunPlacement,
|
|
32
33
|
type RoleRunSubject,
|
|
@@ -539,19 +540,13 @@ export async function persistAdmittedSourceRunPath(
|
|
|
539
540
|
/**
|
|
540
541
|
* Bind a post-admission resolved ticketNumber onto the in-memory admitted
|
|
541
542
|
* object and both durable pages (invocation.json + admitted-request.json).
|
|
542
|
-
*
|
|
543
|
-
* Never clears an existing binding.
|
|
543
|
+
* A later typed role receipt replaces an earlier assertion (#1025).
|
|
544
544
|
*/
|
|
545
545
|
export async function bindAdmittedTicketNumber(
|
|
546
546
|
admitted: AdmittedRoleInvocation,
|
|
547
547
|
ticketNumber: number,
|
|
548
548
|
): Promise<void> {
|
|
549
|
-
if (admitted.ticketNumber
|
|
550
|
-
if (admitted.ticketNumber === ticketNumber) return;
|
|
551
|
-
throw new Error(
|
|
552
|
-
`bindAdmittedTicketNumber refuses to replace existing ticket #${admitted.ticketNumber} with #${ticketNumber}`,
|
|
553
|
-
);
|
|
554
|
-
}
|
|
549
|
+
if (admitted.ticketNumber === ticketNumber) return;
|
|
555
550
|
await bindTicketNumberOnRunDirectory(admitted.runDirectory, ticketNumber);
|
|
556
551
|
(admitted as { ticketNumber?: number }).ticketNumber = ticketNumber;
|
|
557
552
|
}
|
|
@@ -642,7 +637,7 @@ export async function bindCourtTicketNumbersOnAdmitted(
|
|
|
642
637
|
admitted.courtTicketNumbers = frozen;
|
|
643
638
|
}
|
|
644
639
|
|
|
645
|
-
/**
|
|
640
|
+
/** File a role run under its latest typed ticket identity. */
|
|
646
641
|
export async function relocateAdmittedRunToTicket(
|
|
647
642
|
admitted: AdmittedRoleInvocation,
|
|
648
643
|
authority: DurablePrincipalAuthority,
|
|
@@ -668,6 +663,8 @@ export async function relocateAdmittedRunToTicket(
|
|
|
668
663
|
for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
|
|
669
664
|
if (typeof childRunId !== "string") continue;
|
|
670
665
|
const childDirectory = join(dirname(oldRunDirectory), `${childRunId}@diarist`);
|
|
666
|
+
// A child that already filed under a ticket keeps its own assertion.
|
|
667
|
+
if (!existsSync(childDirectory)) continue;
|
|
671
668
|
const childTarget = roleRunPlacement(ledgerHome, {
|
|
672
669
|
bookKey: admitted.bookKey,
|
|
673
670
|
subject: { ticketNumber: admitted.ticketNumber },
|
|
@@ -675,7 +672,6 @@ export async function relocateAdmittedRunToTicket(
|
|
|
675
672
|
role: "diarist",
|
|
676
673
|
});
|
|
677
674
|
authority.seal(childTarget);
|
|
678
|
-
if (!existsSync(childDirectory) && existsSync(childTarget.runDirectory)) continue;
|
|
679
675
|
await bindTicketNumberOnRunDirectory(childDirectory, admitted.ticketNumber);
|
|
680
676
|
await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
681
677
|
ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
|
|
@@ -683,14 +679,13 @@ export async function relocateAdmittedRunToTicket(
|
|
|
683
679
|
}
|
|
684
680
|
}
|
|
685
681
|
|
|
686
|
-
//
|
|
687
|
-
// own bind and the caller's later bind use this existing relocation seam.
|
|
682
|
+
// The first ticket assignment moves only an unbound diarist's own diary.
|
|
688
683
|
if (admitted.role === "diarist") {
|
|
689
684
|
await rehomeUnboundTicketProvenance(oldRunDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
690
685
|
}
|
|
691
686
|
|
|
692
|
-
// Rename commits the run placement. Diary assignment above is
|
|
693
|
-
//
|
|
687
|
+
// Rename commits the run placement. Diary assignment above is a Sitian append
|
|
688
|
+
// outside the atomic directory move.
|
|
694
689
|
// Persisted paths are resolved from typed run identity on read.
|
|
695
690
|
await rename(oldRunDirectory, target.runDirectory);
|
|
696
691
|
|
|
@@ -752,34 +747,11 @@ export async function bindTicketNumberOnRunDirectory(
|
|
|
752
747
|
"bindTicketNumberOnRunDirectory",
|
|
753
748
|
);
|
|
754
749
|
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
755
|
-
const invocationPath = join(runDirectory, "invocation.json");
|
|
756
750
|
const admitted = JSON.parse(await readFile(admittedPath, "utf8")) as Record<
|
|
757
751
|
string,
|
|
758
752
|
unknown
|
|
759
753
|
>;
|
|
760
|
-
|
|
761
|
-
if (typeof existing === "number") {
|
|
762
|
-
if (existing === ticketNumber) return;
|
|
763
|
-
throw new Error(
|
|
764
|
-
`bindTicketNumberOnRunDirectory refuses to replace existing ticket #${existing} with #${ticketNumber}`,
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
// Conflict guard before any write: crash window of bindAdmittedTicketNumber
|
|
768
|
-
// can leave invocation bound while admitted-request is still unbound — refuse
|
|
769
|
-
// silent rebind. Read-before-merge; never check the page just overwritten.
|
|
770
|
-
if (existsSync(invocationPath)) {
|
|
771
|
-
const invocation = JSON.parse(
|
|
772
|
-
await readFile(invocationPath, "utf8"),
|
|
773
|
-
) as Record<string, unknown>;
|
|
774
|
-
if (
|
|
775
|
-
typeof invocation.ticketNumber === "number" &&
|
|
776
|
-
invocation.ticketNumber !== ticketNumber
|
|
777
|
-
) {
|
|
778
|
-
throw new Error(
|
|
779
|
-
`bindTicketNumberOnRunDirectory refuses to replace invocation ticket #${invocation.ticketNumber} with #${ticketNumber}`,
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
754
|
+
if (admitted.ticketNumber === ticketNumber) return;
|
|
783
755
|
await writeFile(
|
|
784
756
|
admittedPath,
|
|
785
757
|
`${JSON.stringify({ ...admitted, ticketNumber }, null, 2)}\n`,
|
|
@@ -223,8 +223,9 @@ function withOnceSuccessfulBeforeDispatch<
|
|
|
223
223
|
...adapters,
|
|
224
224
|
beforeDispatch: async (admitted, lease) => {
|
|
225
225
|
if (succeeded) return;
|
|
226
|
-
await hook(admitted, lease);
|
|
226
|
+
const result = await hook(admitted, lease);
|
|
227
227
|
succeeded = true;
|
|
228
|
+
return result;
|
|
228
229
|
},
|
|
229
230
|
};
|
|
230
231
|
}
|
|
@@ -352,7 +353,8 @@ export type PostAdmissionAdapters<
|
|
|
352
353
|
result: RoleTurnResult;
|
|
353
354
|
sessionFile: string;
|
|
354
355
|
}) => Promise<RoleTurnKnownFailure | undefined>;
|
|
355
|
-
|
|
356
|
+
/** A child office may pause this run with its unchanged terminal before a host turn starts. */
|
|
357
|
+
beforeDispatch?: (admitted: A, lease?: RunWriterLease) => Promise<T | void> | T | void;
|
|
356
358
|
/**
|
|
357
359
|
* After the host turn settles and before any held writer lease is released.
|
|
358
360
|
* Post-turn bind/relocate (e.g. Diarist first assert) must run here so a
|
|
@@ -709,9 +711,8 @@ export async function dispatchPostAdmissionTurn<
|
|
|
709
711
|
};
|
|
710
712
|
}
|
|
711
713
|
try {
|
|
712
|
-
// #858:
|
|
713
|
-
//
|
|
714
|
-
// or reject missing/malformed declarations. An existing binding wins.
|
|
714
|
+
// #858: the first typed assertion files an unbound run. Later receipts
|
|
715
|
+
// remain untouched; this seam does not adjudicate a ticket change.
|
|
715
716
|
if (admitted.ticketNumber === undefined) {
|
|
716
717
|
const rows = await readRecordedSubmissionRows(
|
|
717
718
|
admitted.projectRoot,
|
|
@@ -825,7 +826,10 @@ export async function dispatchPostAdmissionTurn<
|
|
|
825
826
|
// (#840 父子不层叠).
|
|
826
827
|
if (adapters.beforeDispatch !== undefined) {
|
|
827
828
|
try {
|
|
828
|
-
await adapters.beforeDispatch(admitted, lease);
|
|
829
|
+
const paused = await adapters.beforeDispatch(admitted, lease);
|
|
830
|
+
if (paused !== undefined) {
|
|
831
|
+
return { exitCode: 0, admitted, terminal: paused, skipAutoResume: true as const };
|
|
832
|
+
}
|
|
829
833
|
} catch (error) {
|
|
830
834
|
const settled = (await presentControlledFailure(
|
|
831
835
|
admitted,
|