@akagilnc/pi-workflow-roles 0.1.5289 → 0.1.5301

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.
@@ -1,40 +1,17 @@
1
1
  /**
2
- * Court diarist station for the shared countersign entry (#572 / ADR 0074 /
3
- * ADR 0075 / #742 / #771). The public seat itself admits and settles through
4
- * instruction-seat-run. This module only summons 起居郎 and refreshes a bound
5
- * court diary.
6
- *
7
- * Court admission auto-runs 起居郎 so the 起居郎 LLM asserts the court target;
8
- * mechanical layer only verifies; countersign reuses that typed identity for bind
9
- * (ADR 0075 / 0081). Code never matches instruction text against book-known
10
- * numbers. Who may call 起居郎 and in what order is not written into law
11
- * (ADR 0075 不规定谁调用起居郎、顺序归调用者). #1092: code no longer freezes or
12
- * delivers 起居录 paths — roles locate records by ticket. Bound refresh hands the
13
- * typed key to 起居郎 so freeze loads issue face (ADR 0075: 每次过庭都跑是调用者用法 /
14
- * typed handoff).
2
+ * Secretariat's pre-ticket diarist summons. Countersign no longer summons
3
+ * the diarist; callers refresh the diary before court when needed (ADR 0081).
15
4
  */
16
5
  import type { DurablePrincipalAuthority } from "../host-contracts.ts";
17
- import { projectCourtTicketNumbers } from "../diarist-contracts.ts";
18
6
  import { readableGateItem } from "../readable-gate-item.ts";
19
- import { isSafePositiveTicketNumber } from "../run-ticket-number.ts";
20
7
  import type { AdmittedCountersignInvocation } from "./invocation.ts";
21
- import {
22
- type PostAdmissionEnv,
23
- StationChildExhaustedError,
24
- } from "./post-admission.ts";
8
+ import type { PostAdmissionEnv } from "./post-admission.ts";
25
9
  import type { CliIo } from "./cli-io.ts";
26
10
  import type { TerminalRoleOutcome } from "./terminal.ts";
27
11
 
28
12
  export type CountersignRunEnv = PostAdmissionEnv & {
29
13
  principalAuthority: DurablePrincipalAuthority;
30
14
  createRunId?: () => string;
31
- /**
32
- * Test seam: replace the court-pipeline 起居郎 station.
33
- * Production leaves this unset and runs the public diarist seat.
34
- */
35
- runCourtDiaristStation?: (
36
- admitted: AdmittedCountersignInvocation,
37
- ) => Promise<void>;
38
15
  /**
39
16
  * #969 gate path: plain-language re-ask when prior 给事中 reply was not three-state.
40
17
  * Wins over gateReviewInstruction when both present (notary/inspector precedent).
@@ -60,16 +37,6 @@ export type CountersignRunEnv = PostAdmissionEnv & {
60
37
 
61
38
  /** 起居郎 identity outcome — escalate stays distinct from missing terminal. */
62
39
  export type CourtDiaristIdentity =
63
- | {
64
- readonly kind: "ticket";
65
- readonly ticketNumber: number;
66
- /**
67
- * #871 typed co-review set when the LLM explicitly submitted one.
68
- * Absent means "no new set this turn" — resume must keep the stored run fact
69
- * (never silently degrade a multi-ticket set to [main]).
70
- */
71
- readonly courtTicketNumbers?: readonly number[];
72
- }
73
40
  | { readonly kind: "unbound" }
74
41
  | {
75
42
  readonly kind: "escalate";
@@ -141,7 +108,7 @@ function courtDiaristEscalateDiagnostic(
141
108
  })}`;
142
109
  }
143
110
 
144
- /** Env slice shared by court diarist identity summons (countersign / secretariat). */
111
+ /** Env slice for the secretariat's pre-ticket diarist summons. */
145
112
  export type CourtDiaristSummonEnv = Pick<
146
113
  CountersignRunEnv,
147
114
  | "cwd"
@@ -153,53 +120,6 @@ export type CourtDiaristSummonEnv = Pick<
153
120
  | "hostAdapters"
154
121
  >;
155
122
 
156
- /**
157
- * Read #871 set from preserved diarist payloads.
158
- * - Field absent / null / non-array → undefined (no new set; resume keeps store).
159
- * - Explicit [] → single-ticket [main] whole-set replace (signed empty-set contract).
160
- * - Non-empty array with zero lawful typed members after projection → not a new set
161
- * (do not impersonate explicit empty and wipe a stored multi-ticket fact).
162
- * - Non-empty array with ≥1 lawful member → sole type/dedupe projection + principal guarantee.
163
- * - Multiple submissions: last qualifying set wins.
164
- * Live path never shape-rejects the role turn; durable damage is a separate seam.
165
- */
166
- export function courtTicketNumbersFromOutcome(
167
- roleOutcome: TerminalRoleOutcome | undefined,
168
- principalTicket: number,
169
- submissions?: readonly unknown[],
170
- ): readonly number[] | undefined {
171
- if (roleOutcome === undefined) return undefined;
172
- const payloads = courtDiaristPayloadRows(roleOutcome, submissions);
173
- let latest: readonly number[] | undefined;
174
- for (const payload of payloads) {
175
- if (
176
- typeof payload !== "object" ||
177
- payload === null ||
178
- Array.isArray(payload)
179
- ) {
180
- continue;
181
- }
182
- const record = payload as Record<string, unknown>;
183
- if (!Object.hasOwn(record, "courtTicketNumbers")) continue;
184
- const raw = record.courtTicketNumbers;
185
- // Only a real array is a candidate set. Non-array is not explicit empty.
186
- if (!Array.isArray(raw)) continue;
187
- // Members only — do not inject principal yet, or all-invalid non-empty becomes [main].
188
- const members = projectCourtTicketNumbers(raw);
189
- if (members === null) continue;
190
- if (members.length === 0) {
191
- // Literal [] is the intentional principal-only replace; non-empty garbage is not.
192
- if (raw.length === 0) {
193
- latest =
194
- projectCourtTicketNumbers([], { principalTicket }) ?? undefined;
195
- }
196
- continue;
197
- }
198
- latest = projectCourtTicketNumbers(raw, { principalTicket }) ?? undefined;
199
- }
200
- return latest;
201
- }
202
-
203
123
  /** Latest payload only. An earlier escalate must not block a later non-escalate submission. */
204
124
  export function latestPayloadEscalated(
205
125
  roleOutcome: TerminalRoleOutcome | undefined,
@@ -223,11 +143,8 @@ export function courtDiaristEscalated(
223
143
 
224
144
  /**
225
145
  * Invoke public 起居郎 under the court-pipeline quiet face.
226
- * Returns typed identity: ticket assertion, true-unbound, or escalate.
227
- * Non-zero exit without escalate status is not rethrown here — caller decides
228
- * whether refresh must fail or first-entry settles controlled failure.
229
- * When `boundTicketNumber` is set (typed handoff from countersign), diarist
230
- * binds under that key before the turn — never mechanical recognition from prose.
146
+ * Returns escalate or unbound; the secretariat needs no ticket handoff.
147
+ * Non-zero exit without escalate status is not rethrown here — caller settles controlled failure.
231
148
  */
232
149
  export async function invokeCourtDiarist(
233
150
  input: {
@@ -237,13 +154,11 @@ export async function invokeCourtDiarist(
237
154
  readonly attachmentPaths?: readonly string[];
238
155
  /** Direct parent run id for durable child lineage (ADR 0010). */
239
156
  readonly correlationId?: string;
240
- /** Already-verified typed key from countersign (refresh / post-assert handoff). */
241
- readonly boundTicketNumber?: number;
242
157
  },
243
158
  env: CourtDiaristSummonEnv,
244
159
  io: CliIo,
245
160
  ): Promise<CourtDiaristInvocationResult> {
246
- // Quiet face: the countersign caller must not see diarist CLI chatter.
161
+ // Quiet face: the secretariat caller must not see diarist CLI chatter.
247
162
  const quietIo: CliIo = {
248
163
  stdout() {},
249
164
  stderr(text: string) {
@@ -272,9 +187,6 @@ export async function invokeCourtDiarist(
272
187
  ...(input.correlationId === undefined
273
188
  ? {}
274
189
  : { correlationId: input.correlationId }),
275
- ...(input.boundTicketNumber === undefined
276
- ? {}
277
- : { boundTicketNumber: input.boundTicketNumber }),
278
190
  // Child seat selects from the composition-root table. Do not pass the
279
191
  // already-selected parent adapter (#840 / ADR 0082: --host 旗标>席位配置>缺省 pi).
280
192
  ...(env.hostAdapters === undefined
@@ -311,97 +223,8 @@ export async function invokeCourtDiarist(
311
223
  };
312
224
  }
313
225
 
314
- const asserted = (result.admitted as { ticketNumber?: number } | undefined)
315
- ?.ticketNumber;
316
- if (isSafePositiveTicketNumber(asserted)) {
317
- const courtTicketNumbers = courtTicketNumbersFromOutcome(
318
- roleOutcome,
319
- asserted,
320
- submissions,
321
- );
322
- return {
323
- identity: {
324
- kind: "ticket",
325
- ticketNumber: asserted,
326
- ...(courtTicketNumbers === undefined ? {} : { courtTicketNumbers }),
327
- },
328
- ...(result.admitted === undefined ? {} : { admitted: result.admitted }),
329
- };
330
- }
331
226
  return {
332
227
  identity: { kind: "unbound" },
333
228
  ...(result.admitted === undefined ? {} : { admitted: result.admitted }),
334
229
  };
335
230
  }
336
-
337
- /**
338
- * Court-pipeline prior station: refresh this ticket's 起居录 before the
339
- * countersign body turn when already bound (ADR 0075 每次过庭都跑)。
340
- * Caller-invisible — no diarist argv on the countersign command line.
341
- *
342
- * Missing ticketNumber (true-unbound / identity deferred) skips the refresh
343
- * station — no diary is minted for a true-unbound run. First-entry identity
344
- * lives on the shared countersign entry (typed 起居郎 key for bind). Bound refresh:
345
- * technical failure propagates; LLM escalation remains a recorded conclusion.
346
- * Path delivery onto materials is not this station's job — post-admission owns it.
347
- */
348
- export async function runCountersignCourtDiaristStation(
349
- admitted: AdmittedCountersignInvocation,
350
- env: CountersignRunEnv,
351
- io: CliIo,
352
- afterTicketNumber?: number,
353
- ): Promise<{ readonly terminal: import("./terminal.ts").TerminalResult; readonly childRunId?: string } | undefined> {
354
- if (env.runCourtDiaristStation !== undefined) {
355
- await env.runCourtDiaristStation(admitted);
356
- return;
357
- }
358
- // Production court refresh (ADR 0075: 每次过庭都跑是调用者用法) only under a known ticket identity.
359
- // First-entry unbound identity is owned by the shared countersign entry.
360
- if (admitted.ticketNumber === undefined) return;
361
-
362
- // #871: refresh every member of the typed co-review set; single-ticket face
363
- // is just the set [main]. Present-but-empty / missing-principal is damage —
364
- // never silently fall back to main-only (legacy absent field still may).
365
- let refreshTickets: readonly number[];
366
- if (admitted.courtTicketNumbers !== undefined) {
367
- if (
368
- admitted.courtTicketNumbers.length === 0 ||
369
- !admitted.courtTicketNumbers.includes(admitted.ticketNumber)
370
- ) {
371
- throw new StationChildExhaustedError(
372
- `court diarist station: courtTicketNumbers is damaged (empty or missing principal #${admitted.ticketNumber})`,
373
- );
374
- }
375
- refreshTickets = admitted.courtTicketNumbers;
376
- } else {
377
- refreshTickets = [admitted.ticketNumber];
378
- }
379
-
380
- const completedIndex = afterTicketNumber === undefined ? -1 : refreshTickets.indexOf(afterTicketNumber);
381
- for (const ticketNumber of refreshTickets.slice(completedIndex + 1)) {
382
- const outcome = await invokeCourtDiarist(
383
- {
384
- instruction: admitted.instruction,
385
- projectRoot: admitted.projectRoot,
386
- failureLabel: `ticket #${ticketNumber}`,
387
- correlationId: admitted.runId,
388
- // Refresh holds a typed key — hand it off so identity is bound before turn.
389
- boundTicketNumber: ticketNumber,
390
- },
391
- env,
392
- io,
393
- );
394
-
395
- if (outcome.failedWithoutEscalate !== undefined) {
396
- throw new StationChildExhaustedError(
397
- outcome.failedWithoutEscalate.diagnostic,
398
- );
399
- }
400
- if (outcome.identity.kind === "escalate" && outcome.terminal !== undefined) {
401
- return {
402
- terminal: outcome.terminal,
403
- ...(outcome.admitted === undefined ? {} : { childRunId: outcome.admitted.runId }),
404
- };
405
- }
406
- }
407
- }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * One public role run: admit → turn request → post-admission → settle.
3
3
  * Seat differences are composition-root fields. An omitted reviewer lens
4
- * starts two ordinary single-axis runs here. Countersign keeps deferred
5
- * identity on this entry; the court diarist station stays in countersign-run.
4
+ * starts two ordinary single-axis runs here.
6
5
  */
7
6
  import { dirname, resolve, sep } from "node:path";
8
7
  import { AUDITOR_DOSSIER_PROMPT } from "../compliance-transport.ts";
@@ -39,7 +38,6 @@ import { CliUsageError } from "./cli-errors.ts";
39
38
  import {
40
39
  admitPublicRole,
41
40
  bindAdmittedTicketNumber,
42
- bindCourtTicketNumbersOnAdmitted,
43
41
  buildInstructionTransportPrompt,
44
42
  materializeCountersignInvocation,
45
43
  persistAdmittedSourceRunPath,
@@ -98,12 +96,11 @@ import {
98
96
  import {
99
97
  invokeCourtDiarist,
100
98
  latestPayloadEscalated,
101
- runCountersignCourtDiaristStation,
102
99
  type CountersignRunEnv,
103
100
  } from "./countersign-run.ts";
104
101
  export type InstructionSeatRunEnv = PostAdmissionEnv & Pick<
105
102
  CountersignRunEnv,
106
- "reviewReask" | "gateReviewInstruction" | "parentRunPath" | "runCourtDiaristStation"
103
+ "reviewReask" | "gateReviewInstruction" | "parentRunPath"
107
104
  >;
108
105
 
109
106
  type SeatRunResult = {
@@ -460,8 +457,7 @@ async function runOmittedLensBatch(
460
457
 
461
458
  /**
462
459
  * Countersign on the shared entry: gate parent resume, deferred materialization,
463
- * 起居郎 identity, then the same one-shot settlement as every other seat.
464
- * Court refresh stays on runCountersignCourtDiaristStation.
460
+ * then the same one-shot settlement as every other seat.
465
461
  */
466
462
  async function runCountersignBody(
467
463
  parsed: PublicSeatParse,
@@ -516,88 +512,14 @@ async function runCountersignBody(
516
512
  });
517
513
  };
518
514
 
519
- let typedTicket: number | undefined;
520
- let typedCourtTicketNumbers: readonly number[] | undefined;
521
- let identityDiaristRan = false;
522
- let childDiaristRunId: string | undefined;
523
- let pausedDiaristTerminal: TerminalResult | undefined;
524
-
525
- if (gateParentRunPath === undefined && env.runCourtDiaristStation === undefined) {
526
- let outcome: Awaited<ReturnType<typeof invokeCourtDiarist>>;
527
- try {
528
- outcome = await invokeCourtDiarist({
529
- instruction: parsed.instruction ?? "",
530
- projectRoot: admitted.projectRoot,
531
- failureLabel: "unbound summons",
532
- correlationId: admitted.runId,
533
- ...(env.boundTicketNumber === undefined ? {} : { boundTicketNumber: env.boundTicketNumber }),
534
- }, env, io);
535
- } catch (error) {
536
- await materializeAdmission(
537
- isSafePositiveTicketNumber(env.boundTicketNumber) ? env.boundTicketNumber : undefined,
538
- );
539
- await markRunAdmitted(admitted, env.principalAuthority);
540
- return await presentControlledFailure(admitted, {
541
- timedOut: false,
542
- code: null,
543
- stderr: "",
544
- thrown: error,
545
- }, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
546
- }
547
- identityDiaristRan = true;
548
- if (outcome.identity.kind === "escalate") pausedDiaristTerminal = outcome.terminal;
549
- if (outcome.admitted?.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) {
550
- childDiaristRunId = outcome.admitted.runId;
551
- }
552
- if (outcome.failedWithoutEscalate !== undefined) {
553
- const diagnostic = outcome.failedWithoutEscalate.diagnostic;
554
- await materializeAdmission(
555
- isSafePositiveTicketNumber(env.boundTicketNumber) ? env.boundTicketNumber : undefined,
556
- );
557
- if (childDiaristRunId !== undefined) await recordChildDiaristRun(admitted, childDiaristRunId);
558
- await markRunAdmitted(admitted, env.principalAuthority);
559
- return await presentControlledFailure(admitted, {
560
- timedOut: false,
561
- code: null,
562
- stderr: "",
563
- thrown: new Error(diagnostic),
564
- }, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
565
- }
566
- if (outcome.identity.kind === "ticket") {
567
- typedTicket = outcome.identity.ticketNumber;
568
- typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
569
- }
570
- }
571
-
572
- await materializeAdmission(typedTicket);
515
+ await materializeAdmission(
516
+ isSafePositiveTicketNumber(env.boundTicketNumber) ? env.boundTicketNumber : undefined,
517
+ );
573
518
  await markRunAdmitted(admitted, env.principalAuthority);
574
- if (childDiaristRunId !== undefined) {
575
- await recordChildDiaristRun(admitted, childDiaristRunId);
576
- }
577
- if (pausedDiaristTerminal !== undefined) {
578
- // The child already submitted its own pause; no parent turn exists yet.
579
- io.stdout(formatTerminalResult(pausedDiaristTerminal));
580
- return { exitCode: 0, admitted, terminal: pausedDiaristTerminal };
581
- }
582
519
  if (gateParentRunPath !== undefined) {
583
520
  await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
584
521
  admitted = { ...admitted, sourceRunPath: gateParentRunPath };
585
522
  }
586
- if (identityDiaristRan && typedTicket !== undefined) {
587
- try {
588
- await bindAdmittedTicketNumber(admitted, typedTicket);
589
- await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
590
- await bindCourtTicketNumbersOnAdmitted(admitted, typedCourtTicketNumbers ?? [typedTicket]);
591
- } catch (error) {
592
- return await presentControlledFailure(admitted, {
593
- timedOut: false,
594
- code: null,
595
- stderr: "",
596
- thrown: error,
597
- }, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
598
- }
599
- }
600
-
601
523
  const turnProjection: RoleTurnRequestProjectionOptions = {
602
524
  packageRoot: env.packageRoot,
603
525
  home: env.home,
@@ -630,13 +552,6 @@ async function runCountersignBody(
630
552
  ...seatAdapters(admitted, env),
631
553
  beforeDispatch: async (admittedSeat, lease) => {
632
554
  if (!packagedAdmitsCountersign(admittedSeat.role)) return;
633
- if (!identityDiaristRan || typedTicket !== undefined) {
634
- const refresh = await runCountersignCourtDiaristStation(admittedSeat, env, io);
635
- if (refresh !== undefined) {
636
- io.stdout(formatTerminalResult(refresh.terminal));
637
- return refresh.terminal;
638
- }
639
- }
640
555
  await relocateAdmittedRunToTicket(admittedSeat, env.principalAuthority, lease);
641
556
  Object.assign(turnRequest, buildInstructionSeatTurnRequest(admittedSeat, turnProjection));
642
557
  },
@@ -1019,16 +934,6 @@ export async function continueParentAfterChild(
1019
934
  return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
1020
935
  }
1021
936
  const admitted = loaded.admitted;
1022
- // Court diarist refresh advances only after a diarist child; gate officers
1023
- // (notary/auditor/…) resume straight into queueConclusionFromChild (#1057).
1024
- if (admitted.role === "countersign" && admitted.ticketNumber !== undefined
1025
- && child.role === "diarist") {
1026
- const refresh = await runCountersignCourtDiaristStation(admitted, env, io, child.ticketNumber);
1027
- if (refresh !== undefined) {
1028
- io.stdout(formatTerminalResult(refresh.terminal));
1029
- return { exitCode: 0, admitted, terminal: refresh.terminal };
1030
- }
1031
- }
1032
937
  const resolved = await queueConclusionFromChild(child, env, io);
1033
938
  if (resolved !== undefined && "stop" in resolved) return resolved.stop;
1034
939
  if (resolved !== undefined && resolved.status === "escalate") {
@@ -60,7 +60,6 @@ import {
60
60
  import {
61
61
  loadDoctorCase,
62
62
  } from "../doctor-evidence.ts";
63
- import { projectCourtTicketNumbers } from "../diarist-contracts.ts";
64
63
  import type { DoctorCaseIdentity } from "../doctor-contracts.ts";
65
64
  import {
66
65
  emptyCollectorManifest,
@@ -156,17 +155,6 @@ export type AdmittedJudgeInvocation = AdmittedRoleInvocationBase & {
156
155
 
157
156
  export type AdmittedCountersignInvocation = AdmittedRoleInvocationBase & {
158
157
  readonly role: "countersign";
159
- /**
160
- * #871 typed co-review set for this countersign run (durable run fact).
161
- * Main ticket remains `ticketNumber`; this set drives per-ticket court diarist refresh.
162
- * Whole-set replace on new typed submission — never union with history.
163
- */
164
- courtTicketNumbers?: readonly number[];
165
- /**
166
- * Load-time durable set damage diagnostic (#871 B7). Resume must settle as
167
- * countersign controlled failure with this text — never structural exit 2 or main-only.
168
- */
169
- courtTicketNumbersDamage?: string;
170
158
  /**
171
159
  * Parent run directory (#747 / #987 gate same-parent resume). Persisted on first
172
160
  * mint when gate supplies parentRunPath; independent of ticket-number lookup.
@@ -338,7 +326,7 @@ export function issueAdmissionPlacement(
338
326
  readonly cwd: string;
339
327
  readonly runId: string;
340
328
  readonly role: AdmittedRoleInvocation["role"];
341
- /** Identity already asserted by 起居郎; never derive this from CLI parameters. */
329
+ /** Typed identity, when supplied; never derive it from CLI prose. */
342
330
  readonly subject: RoleRunSubject;
343
331
  readonly home?: string;
344
332
  /** Placement may be computed before a same-ticket lookup without touching disk. */
@@ -594,56 +582,6 @@ export async function recordAdmittedCorrelation(
594
582
  mutable.correlationIds = correlationIds;
595
583
  }
596
584
 
597
- /**
598
- * #871: persist the typed co-review set as a countersign run fact (admitted-request +
599
- * invocation.json). Whole-set replace — never union with a prior set. Main ticket
600
- * binding stays on `ticketNumber` alone. Projection reuses the sole contract helper;
601
- * principal membership is mandatory.
602
- */
603
- export async function bindCourtTicketNumbersOnAdmitted(
604
- admitted: AdmittedCountersignInvocation,
605
- courtTicketNumbers: readonly number[],
606
- ): Promise<void> {
607
- if (admitted.ticketNumber === undefined) {
608
- throw new Error(
609
- "bindCourtTicketNumbersOnAdmitted requires a bound principal ticketNumber",
610
- );
611
- }
612
- const principal = requireSafePositiveTicketNumber(
613
- admitted.ticketNumber,
614
- "bindCourtTicketNumbersOnAdmitted principal ticketNumber",
615
- );
616
- // Strict write path: every member must already be a lawful number (no soft filter).
617
- for (const item of courtTicketNumbers) {
618
- if (!isSafePositiveTicketNumber(item)) {
619
- throw new Error(
620
- `bindCourtTicketNumbersOnAdmitted requires safe positive integers, got ${String(item)}`,
621
- );
622
- }
623
- }
624
- const projected = projectCourtTicketNumbers(courtTicketNumbers, {
625
- principalTicket: principal,
626
- });
627
- if (projected === null || projected.length === 0) {
628
- throw new Error("bindCourtTicketNumbersOnAdmitted requires a non-empty ticket set");
629
- }
630
- const frozen = Object.freeze([...projected]);
631
- const admittedPath = admitted.admittedRequestPath;
632
- const current = JSON.parse(await readFile(admittedPath, "utf8")) as Record<
633
- string,
634
- unknown
635
- >;
636
- await writeFile(
637
- admittedPath,
638
- `${JSON.stringify({ ...current, courtTicketNumbers: frozen }, null, 2)}\n`,
639
- "utf8",
640
- );
641
- await mergeInvocationIdentityPage(admitted.runDirectory, {
642
- courtTicketNumbers: frozen,
643
- });
644
- admitted.courtTicketNumbers = frozen;
645
- }
646
-
647
585
  /** File a role run under its latest typed ticket identity. */
648
586
  export async function relocateAdmittedRunToTicket(
649
587
  admitted: AdmittedRoleInvocation,
@@ -1904,7 +1842,7 @@ export type AdmitJudgeInvocationOptions = {
1904
1842
 
1905
1843
  export type AdmitInspectorInvocationOptions = AdmitJudgeInvocationOptions & {
1906
1844
  correlationId?: string;
1907
- /** Typed identity handed off by 起居郎 or inherited from an already-bound run. */
1845
+ /** Typed identity from the caller or an already-bound run. */
1908
1846
  assertedTicketNumber?: number;
1909
1847
  };
1910
1848
 
@@ -211,15 +211,6 @@ async function settleProcessCancelAfterTurn<A extends AdmittedRoleInvocation, T
211
211
  };
212
212
  }
213
213
 
214
- /**
215
- * Nested station-child role already finished its own call-local loop.
216
- * Parent records that failure once and must not auto-resume into a re-summon
217
- * (#840 父子不层叠). Other beforeDispatch failures keep the shared #416 budget.
218
- */
219
- export class StationChildExhaustedError extends Error {
220
- override readonly name = "StationChildExhaustedError";
221
- }
222
-
223
214
  function withOnceSuccessfulBeforeDispatch<
224
215
  A extends AdmittedRoleInvocation,
225
216
  T extends TerminalResult = TerminalResult,
@@ -825,16 +816,12 @@ export async function dispatchPostAdmissionTurn<
825
816
  io,
826
817
  persistRunState,
827
818
  )) as { exitCode: number; admitted: A; terminal: T };
828
- if (error instanceof StationChildExhaustedError) {
829
- return { ...settled, skipAutoResume: true as const, ...deferredPersist };
830
- }
831
819
  return { ...settled, ...deferredPersist };
832
820
  }
833
821
  }
834
822
 
835
- // Turn request is assembled after beforeDispatch so this turn sees whatever it
836
- // settled — the seat's ticket bind re-projection and any court diarist station
837
- // writes (#742). Dialogue continuation stays caller/peer opaque. #1092: no
823
+ // Turn request is assembled after beforeDispatch so this turn sees any
824
+ // seat-specific admission changes. Dialogue continuation stays caller/peer opaque. #1092: no
838
825
  // code-side 起居录 path freeze or readingMaterial inject — roles read by ticket.
839
826
  // No package-resume parallel face or typed resume identity.
840
827
  // #990: one-shot / auto-resume may freeze principal+runDirectory before