@akagilnc/pi-workflow-roles 0.1.4062 → 0.1.4196

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.
Files changed (72) hide show
  1. package/dist/acp-host/production-host.js +2075 -1088
  2. package/dist/archivist-record-entry.js +204 -20
  3. package/dist/archivist-record-topology.js +36 -16
  4. package/dist/auditor-dossier-tool.js +2 -1
  5. package/dist/diarist-contracts.js +96 -0
  6. package/dist/doctor-evidence.js +3 -2
  7. package/dist/headless-host/production-host.js +2215 -1228
  8. package/dist/migrate-book-topology.js +278917 -0
  9. package/dist/navigator-public-session.js +22 -25
  10. package/dist/notary-source-run.js +7 -4
  11. package/dist/pi/durable-principal.js +9 -6
  12. package/dist/public-cli/auto-resume.js +2 -5
  13. package/dist/public-cli/diarist-run.js +27 -25
  14. package/dist/public-cli/instruction-seat-run.js +8 -11
  15. package/dist/public-cli/invocation.js +127 -63
  16. package/dist/public-cli/main.js +1507 -645
  17. package/dist/public-cli/option-definitions.js +2 -2
  18. package/dist/public-cli/post-admission.js +47 -12
  19. package/dist/public-cli/run-lifecycle.js +206 -58
  20. package/dist/public-cli/seat-ticket-binding.js +3 -6
  21. package/dist/public-cli/settlement.js +5 -3
  22. package/dist/role-run-placement.js +84 -0
  23. package/dist/role-run-relocation.js +358 -0
  24. package/dist/run-terminal-artifacts.js +2 -1
  25. package/dist/run-ticket-number.js +51 -32
  26. package/dist/sitian-appender.js +11 -26
  27. package/dist/submission-ledger.js +78 -19
  28. package/dist/ticket-provenance.js +3 -1
  29. package/package.json +3 -2
  30. package/scripts/build-package.mjs +34 -2
  31. package/souls/countersign.md +2 -2
  32. package/souls/fixer.md +3 -2
  33. package/src/analyst-ledger.ts +14 -15
  34. package/src/archivist-record-entry.ts +285 -38
  35. package/src/archivist-record-topology.ts +43 -18
  36. package/src/auditor-dossier-tool.ts +2 -1
  37. package/src/book-topology-migration-placement.ts +493 -0
  38. package/src/book-topology-migration.ts +220 -0
  39. package/src/book-topology-migrators-t11.ts +856 -0
  40. package/src/book-topology-mixed-volume-migrators.ts +470 -0
  41. package/src/book-topology-partition-migrators.ts +25 -0
  42. package/src/book-topology-record-class-migrators.ts +779 -0
  43. package/src/book-topology-runs-migrator.ts +244 -0
  44. package/src/diarist-contracts.ts +116 -0
  45. package/src/diarist-role.ts +6 -0
  46. package/src/diarist.ts +2 -0
  47. package/src/doctor-evidence.ts +4 -1
  48. package/src/doctor-role.ts +1 -1
  49. package/src/navigator-public-session.ts +33 -30
  50. package/src/notary-source-run.ts +9 -4
  51. package/src/pi/durable-principal.ts +9 -13
  52. package/src/public-cli/auto-resume.ts +2 -6
  53. package/src/public-cli/countersign-run.ts +182 -32
  54. package/src/public-cli/diarist-run.ts +34 -26
  55. package/src/public-cli/instruction-seat-run.ts +7 -11
  56. package/src/public-cli/invocation.ts +192 -58
  57. package/src/public-cli/option-definitions.ts +2 -2
  58. package/src/public-cli/post-admission.ts +73 -13
  59. package/src/public-cli/run-lifecycle.ts +255 -65
  60. package/src/public-cli/seat-ticket-binding.ts +3 -6
  61. package/src/public-cli/settlement.ts +6 -4
  62. package/src/role-run-placement.ts +128 -0
  63. package/src/role-run-relocation.ts +491 -0
  64. package/src/role-runtime.ts +11 -5
  65. package/src/run-terminal-artifacts.ts +3 -1
  66. package/src/run-ticket-number.ts +80 -20
  67. package/src/sitian-appender.ts +25 -28
  68. package/src/submission-ledger.ts +102 -19
  69. package/src/ticket-provenance.ts +4 -1
  70. package/src/ticket-trajectory.ts +22 -35
  71. package/src/worker-role.ts +4 -4
  72. package/src/worker-submission-gates.ts +6 -6
@@ -25,10 +25,16 @@
25
25
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
26
26
  import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.ts";
27
27
  import { CliUsageError } from "./cli-errors.ts";
28
+ import {
29
+ projectCourtTicketNumbers,
30
+ } from "../diarist-contracts.ts";
31
+ import { isSafePositiveTicketNumber } from "../run-ticket-number.ts";
28
32
  import {
29
33
  admitCountersignInvocation,
30
34
  bindAdmittedTicketNumber,
35
+ bindCourtTicketNumbersOnAdmitted,
31
36
  buildCountersignTransportPrompt,
37
+ relocateAdmittedRunToTicket,
32
38
  type AdmittedCountersignInvocation,
33
39
  type ParseCountersignArgvResult,
34
40
  } from "./invocation.ts";
@@ -46,6 +52,7 @@ import {
46
52
  markRunAdmitted,
47
53
  markRunTerminal,
48
54
  type PublicResumeRequest,
55
+ type RunWriterLease,
49
56
  type SameTicketSummonsMaterials,
50
57
  } from "./run-lifecycle.ts";
51
58
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
@@ -70,6 +77,12 @@ export type CountersignRunEnv = PostAdmissionEnv & {
70
77
  runCourtDiaristStation?: (
71
78
  admitted: AdmittedCountersignInvocation,
72
79
  ) => Promise<void>;
80
+ /**
81
+ * #871: typed co-review set from the identity 起居郎 turn, applied onto the
82
+ * resumed run before bound refresh (same-ticket re-summons path). Whole-set
83
+ * replace — never union. Production leaves unset outside that handoff.
84
+ */
85
+ pendingCourtTicketNumbers?: readonly number[];
73
86
  };
74
87
 
75
88
  /** Project admitted invocation onto the host-neutral turn request. */
@@ -94,7 +107,16 @@ export function buildCountersignTurnRequest(
94
107
 
95
108
  /** 起居郎 identity outcome — escalate stays distinct from missing terminal. */
96
109
  type CourtDiaristIdentity =
97
- | { readonly kind: "ticket"; readonly ticketNumber: number }
110
+ | {
111
+ readonly kind: "ticket";
112
+ readonly ticketNumber: number;
113
+ /**
114
+ * #871 typed co-review set when the LLM explicitly submitted one.
115
+ * Absent means "no new set this turn" — resume must keep the stored run fact
116
+ * (never silently degrade a multi-ticket set to [main]).
117
+ */
118
+ readonly courtTicketNumbers?: readonly number[];
119
+ }
98
120
  | { readonly kind: "unbound" }
99
121
  | { readonly kind: "escalate" };
100
122
 
@@ -103,6 +125,53 @@ type CourtDiaristInvocationResult = {
103
125
  readonly failedWithoutEscalate?: { readonly diagnostic: string };
104
126
  };
105
127
 
128
+ /**
129
+ * Read #871 set from preserved diarist payloads.
130
+ * - Field absent / null / non-array → undefined (no new set; resume keeps store).
131
+ * - Explicit [] → single-ticket [main] whole-set replace (signed empty-set contract).
132
+ * - Non-empty array with zero lawful typed members after projection → not a new set
133
+ * (do not impersonate explicit empty and wipe a stored multi-ticket fact).
134
+ * - Non-empty array with ≥1 lawful member → sole type/dedupe projection + principal guarantee.
135
+ * - Multiple submissions: last qualifying set wins.
136
+ * Live path never shape-rejects the role turn; durable damage is a separate seam.
137
+ */
138
+ function courtTicketNumbersFromOutcome(
139
+ roleOutcome: TerminalRoleOutcome | undefined,
140
+ principalTicket: number,
141
+ ): readonly number[] | undefined {
142
+ if (roleOutcome === undefined) return undefined;
143
+ const payloads =
144
+ roleOutcome.kind === "accepted" ||
145
+ roleOutcome.kind === "audit_escalation" ||
146
+ roleOutcome.kind === "failure"
147
+ ? roleOutcome.payloads ?? []
148
+ : [];
149
+ let latest: readonly number[] | undefined;
150
+ for (const payload of payloads) {
151
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
152
+ continue;
153
+ }
154
+ const record = payload as Record<string, unknown>;
155
+ if (!Object.hasOwn(record, "courtTicketNumbers")) continue;
156
+ const raw = record.courtTicketNumbers;
157
+ // Only a real array is a candidate set. Non-array is not explicit empty.
158
+ if (!Array.isArray(raw)) continue;
159
+ // Members only — do not inject principal yet, or all-invalid non-empty becomes [main].
160
+ const members = projectCourtTicketNumbers(raw);
161
+ if (members === null) continue;
162
+ if (members.length === 0) {
163
+ // Literal [] is the intentional principal-only replace; non-empty garbage is not.
164
+ if (raw.length === 0) {
165
+ latest = projectCourtTicketNumbers([], { principalTicket }) ?? undefined;
166
+ }
167
+ continue;
168
+ }
169
+ latest =
170
+ projectCourtTicketNumbers(raw, { principalTicket }) ?? undefined;
171
+ }
172
+ return latest;
173
+ }
174
+
106
175
  /** Routing boolean over the child's own typed sequence — does not pick or rewrite a sole row. */
107
176
  function courtDiaristEscalated(roleOutcome: TerminalRoleOutcome | undefined): boolean {
108
177
  if (roleOutcome === undefined) return false;
@@ -181,13 +250,14 @@ async function invokeCourtDiarist(input: {
181
250
  }
182
251
 
183
252
  const asserted = (result.admitted as { ticketNumber?: number } | undefined)?.ticketNumber;
184
- if (
185
- typeof asserted === "number" &&
186
- Number.isSafeInteger(asserted) &&
187
- asserted >= 1
188
- ) {
253
+ if (isSafePositiveTicketNumber(asserted)) {
254
+ const courtTicketNumbers = courtTicketNumbersFromOutcome(roleOutcome, asserted);
189
255
  return {
190
- identity: { kind: "ticket", ticketNumber: asserted },
256
+ identity: {
257
+ kind: "ticket",
258
+ ticketNumber: asserted,
259
+ ...(courtTicketNumbers === undefined ? {} : { courtTicketNumbers }),
260
+ },
191
261
  };
192
262
  }
193
263
  return {
@@ -219,25 +289,45 @@ export async function runCountersignCourtDiaristStation(
219
289
  // First-entry unbound identity is owned by runPublicCountersign.
220
290
  if (admitted.ticketNumber === undefined) return;
221
291
 
222
- const outcome = await invokeCourtDiarist(
223
- {
224
- instruction: `整理 #${admitted.ticketNumber} 的本案依据。`,
225
- projectRoot: admitted.projectRoot,
226
- failureLabel: `ticket #${admitted.ticketNumber}`,
227
- // Refresh holds a typed key — hand it off so identity is bound before turn.
228
- boundTicketNumber: admitted.ticketNumber,
229
- },
230
- env,
231
- io,
232
- );
292
+ // #871: refresh every member of the typed co-review set; single-ticket face
293
+ // is just the set [main]. Present-but-empty / missing-principal is damage —
294
+ // never silently fall back to main-only (legacy absent field still may).
295
+ let refreshTickets: readonly number[];
296
+ if (admitted.courtTicketNumbers !== undefined) {
297
+ if (
298
+ admitted.courtTicketNumbers.length === 0 ||
299
+ !admitted.courtTicketNumbers.includes(admitted.ticketNumber)
300
+ ) {
301
+ throw new StationChildExhaustedError(
302
+ `court diarist station: courtTicketNumbers is damaged (empty or missing principal #${admitted.ticketNumber})`,
303
+ );
304
+ }
305
+ refreshTickets = admitted.courtTicketNumbers;
306
+ } else {
307
+ refreshTickets = [admitted.ticketNumber];
308
+ }
233
309
 
234
- if (outcome.identity.kind === "escalate") {
235
- throw new StationChildExhaustedError(
236
- "court diarist station escalated (cannot identify court target)",
310
+ for (const ticketNumber of refreshTickets) {
311
+ const outcome = await invokeCourtDiarist(
312
+ {
313
+ instruction: `整理 #${ticketNumber} 的本案依据。`,
314
+ projectRoot: admitted.projectRoot,
315
+ failureLabel: `ticket #${ticketNumber}`,
316
+ // Refresh holds a typed key — hand it off so identity is bound before turn.
317
+ boundTicketNumber: ticketNumber,
318
+ },
319
+ env,
320
+ io,
237
321
  );
238
- }
239
- if (outcome.failedWithoutEscalate !== undefined) {
240
- throw new StationChildExhaustedError(outcome.failedWithoutEscalate.diagnostic);
322
+
323
+ if (outcome.identity.kind === "escalate") {
324
+ throw new StationChildExhaustedError(
325
+ "court diarist station escalated (cannot identify court target)",
326
+ );
327
+ }
328
+ if (outcome.failedWithoutEscalate !== undefined) {
329
+ throw new StationChildExhaustedError(outcome.failedWithoutEscalate.diagnostic);
330
+ }
241
331
  }
242
332
  }
243
333
 
@@ -293,6 +383,7 @@ export async function runPublicCountersign(
293
383
  // hook failures stay on the parent call-local budget, exhausted nested
294
384
  // station children still skip parent auto-resume (#840 父子不层叠).
295
385
  let typedTicket: number | undefined;
386
+ let typedCourtTicketNumbers: readonly number[] | undefined;
296
387
  let identityDiaristRan = false;
297
388
 
298
389
  if (env.runCourtDiaristStation === undefined) {
@@ -347,7 +438,10 @@ export async function runPublicCountersign(
347
438
  // unbound and continue (true-unbound face). Escalate / typed failure above;
348
439
  // bound refresh still fails honest via runCountersignCourtDiaristStation.
349
440
  if (outcome.identity.kind === "ticket") {
350
- typedTicket = outcome.identity.ticketNumber;
441
+ const assertedTicket = outcome.identity.ticketNumber;
442
+ typedTicket = assertedTicket;
443
+ // #871: identity may hand a co-review set; absent field defaults to [main].
444
+ typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
351
445
  const summons: SameTicketSummonsMaterials = {
352
446
  instruction: parsed.instruction,
353
447
  instructionEmpty: parsed.instruction.trim() === "",
@@ -361,16 +455,26 @@ export async function runPublicCountersign(
361
455
  freshSummons: env.freshSummons,
362
456
  summons,
363
457
  resume: async (runId, materials) => {
364
- // Resume selected: abandon mint first so a throw cannot leave it admitted.
458
+ // Resume selected: file the provisional run under the asserted ticket
459
+ // before abandoning it, so typed identity never leaves an unbound row.
460
+ await bindAdmittedTicketNumber(admitted, assertedTicket);
461
+ await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
365
462
  await markRunTerminal(admitted.runDirectory);
366
463
  // Identity 起居郎 asserted unbound (no issue face). Resume still runs
367
464
  // the bound refresh station under the typed key (refresh-every-court).
465
+ // #871: hand the identity set so resume can whole-replace the run fact
466
+ // when this summons produced a new typed set (never union).
368
467
  return await runPublicCountersignResume(
369
468
  {
370
469
  runId,
371
470
  ...(materials === undefined ? {} : { summons: materials }),
372
471
  },
373
- env,
472
+ {
473
+ ...env,
474
+ ...(typedCourtTicketNumbers === undefined
475
+ ? {}
476
+ : { pendingCourtTicketNumbers: typedCourtTicketNumbers }),
477
+ },
374
478
  io,
375
479
  );
376
480
  },
@@ -381,6 +485,33 @@ export async function runPublicCountersign(
381
485
  }
382
486
  }
383
487
 
488
+ if (identityDiaristRan && typedTicket !== undefined) {
489
+ try {
490
+ await bindAdmittedTicketNumber(admitted, typedTicket);
491
+ await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
492
+ // First court: explicit set wins; omitted field → single-ticket face [main].
493
+ // Set persistence is outside beforeDispatch — route write failures into the
494
+ // same controlled-failure settlement as station children (#871 B7).
495
+ await bindCourtTicketNumbersOnAdmitted(
496
+ admitted,
497
+ typedCourtTicketNumbers ?? [typedTicket],
498
+ );
499
+ } catch (error) {
500
+ return await presentControlledFailure(
501
+ admitted,
502
+ {
503
+ timedOut: false,
504
+ code: null,
505
+ stderr: "",
506
+ thrown: error,
507
+ },
508
+ countersignAdapters(),
509
+ env.principalAuthority,
510
+ io,
511
+ );
512
+ }
513
+ }
514
+
384
515
  const turnProjection: RoleTurnRequestProjectionOptions = {
385
516
  packageRoot: env.packageRoot,
386
517
  home: env.home,
@@ -405,23 +536,25 @@ export async function runPublicCountersign(
405
536
  // Mutable shell: ticket bind re-projects activation before executeTurn.
406
537
  const turnRequest = buildCountersignTurnRequest(admitted, turnProjection);
407
538
 
408
- return await runPostAdmissionOneShot({
539
+ const result = await runPostAdmissionOneShot({
409
540
  admitted,
410
541
  env,
411
542
  io,
412
543
  request: turnRequest,
413
544
  adapters: countersignAdapters({
414
- beforeDispatch: async (admittedSeat) => {
545
+ beforeDispatch: async (admittedSeat, lease) => {
415
546
  // Dossier pointer delivery rides post-admission after this hook (#709).
416
547
  if (!identityDiaristRan) {
417
548
  // Test seam (or any deferred identity): station owns assert + bind.
418
549
  await runCountersignCourtDiaristStation(admittedSeat, env, io);
419
550
  } else if (typedTicket !== undefined) {
420
- // Production identity asserted unbound; bind typed key, then bound
421
- // refresh so freeze loads issue face (typed handoff, not prose match).
422
- await bindAdmittedTicketNumber(admittedSeat, typedTicket);
423
551
  await runCountersignCourtDiaristStation(admittedSeat, env, io);
424
552
  }
553
+ await relocateAdmittedRunToTicket(
554
+ admittedSeat,
555
+ env.principalAuthority,
556
+ lease,
557
+ );
425
558
  Object.assign(
426
559
  turnRequest,
427
560
  buildCountersignTurnRequest(admittedSeat, turnProjection),
@@ -430,11 +563,14 @@ export async function runPublicCountersign(
430
563
  }),
431
564
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
432
565
  });
566
+ await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
567
+ return result;
433
568
  }
434
569
 
435
570
  function countersignAdapters(options?: {
436
571
  beforeDispatch?: (
437
572
  admitted: AdmittedCountersignInvocation,
573
+ lease: RunWriterLease,
438
574
  ) => void | Promise<void>;
439
575
  }) {
440
576
  return {
@@ -496,6 +632,20 @@ export async function runPublicCountersignResume(
496
632
  },
497
633
  adapters: countersignAdapters({
498
634
  beforeDispatch: async (admitted) => {
635
+ // #871 B7: durable set damage already identified at load — settle as
636
+ // station-child exhausted → presentControlledFailure (not structural exit 2).
637
+ if (admitted.courtTicketNumbersDamage !== undefined) {
638
+ throw new StationChildExhaustedError(admitted.courtTicketNumbersDamage);
639
+ }
640
+ // #871: same-ticket re-summons may hand a fresh typed set from identity.
641
+ // Whole-set replace onto this run fact; no new set → keep stored set.
642
+ // Manual resume (no pending) keeps the durable set and never invents one.
643
+ if (env.pendingCourtTicketNumbers !== undefined) {
644
+ await bindCourtTicketNumbersOnAdmitted(
645
+ admitted,
646
+ env.pendingCourtTicketNumbers,
647
+ );
648
+ }
499
649
  await runCountersignCourtDiaristStation(admitted, env, io);
500
650
  },
501
651
  }),
@@ -12,6 +12,7 @@ import {
12
12
  admitDiaristInvocation,
13
13
  bindAdmittedTicketNumber,
14
14
  buildInstructionTransportPrompt,
15
+ relocateAdmittedRunToTicket,
15
16
  type AdmittedDiaristInvocation,
16
17
  type ParseDiaristArgvResult,
17
18
  } from "./invocation.ts";
@@ -27,6 +28,7 @@ import {
27
28
  loadResumableDiaristRun,
28
29
  markRunAdmitted,
29
30
  type PublicResumeRequest,
31
+ type RunWriterLease,
30
32
  type SameTicketSummonsMaterials,
31
33
  } from "./run-lifecycle.ts";
32
34
  import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.ts";
@@ -40,7 +42,7 @@ import {
40
42
  projectRoleTurnRequest,
41
43
  type RoleTurnRequestProjectionOptions,
42
44
  } from "./turn-request.ts";
43
- import { readRunTicketNumber } from "../run-ticket-number.ts";
45
+ import { readBoardTicketNumber } from "../run-ticket-number.ts";
44
46
 
45
47
  export type DiaristRunEnv = PostAdmissionEnv & {
46
48
  principalAuthority: DurablePrincipalAuthority;
@@ -66,11 +68,38 @@ export function buildDiaristTurnRequest(
66
68
  );
67
69
  }
68
70
 
69
- function diaristAdapters(): PostAdmissionAdapters<AdmittedDiaristInvocation> {
71
+ /**
72
+ * Board-bound ticket on pages → memory bind + unbound→ticket relocate under lease.
73
+ * Shared by resume (interrupted board-bound unbound) and post-turn first assert.
74
+ */
75
+ async function bindAndRelocateDiaristIfBoardBound(
76
+ admitted: AdmittedDiaristInvocation,
77
+ authority: DiaristRunEnv["principalAuthority"],
78
+ lease: RunWriterLease,
79
+ ): Promise<void> {
80
+ const boardTicket = await readBoardTicketNumber(admitted.runDirectory);
81
+ if (boardTicket === undefined) return;
82
+ if (admitted.ticketNumber === undefined) {
83
+ await bindAdmittedTicketNumber(admitted, boardTicket);
84
+ }
85
+ await relocateAdmittedRunToTicket(admitted, authority, lease);
86
+ }
87
+
88
+ function diaristAdapters(
89
+ env: DiaristRunEnv,
90
+ ): PostAdmissionAdapters<AdmittedDiaristInvocation> {
70
91
  return {
71
92
  trySettle: (admitted, authority, scope) =>
72
93
  trySettleDiaristTerminalResult(admitted, authority, scope),
73
94
  shouldPresentSettled: () => true,
95
+ // Resume interrupted state: board ticket already on pages, run still under unbound/.
96
+ beforeDispatch: async (admitted, lease) => {
97
+ await bindAndRelocateDiaristIfBoardBound(admitted, env.principalAuthority, lease);
98
+ },
99
+ // First post-turn assert: accept hook wrote board pages; relocate before lease release.
100
+ afterDispatch: async (admitted, lease) => {
101
+ await bindAndRelocateDiaristIfBoardBound(admitted, env.principalAuthority, lease);
102
+ },
74
103
  };
75
104
  }
76
105
 
@@ -141,6 +170,7 @@ export async function runPublicDiarist(
141
170
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
142
171
  ...(env.model === undefined ? {} : { model: env.model }),
143
172
  ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
173
+ ...(handoffTicket === undefined ? {} : { assertedTicketNumber: handoffTicket }),
144
174
  });
145
175
  } catch (error) {
146
176
  if (error instanceof CliUsageError) {
@@ -152,14 +182,6 @@ export async function runPublicDiarist(
152
182
 
153
183
  await markRunAdmitted(admitted, env.principalAuthority);
154
184
 
155
- // Typed handoff: bind before the turn so identity is on durable pages.
156
- if (
157
- typeof handoffTicket === "number" &&
158
- Number.isSafeInteger(handoffTicket) &&
159
- handoffTicket >= 1
160
- ) {
161
- await bindAdmittedTicketNumber(admitted, handoffTicket);
162
- }
163
185
 
164
186
  const turnProjection: RoleTurnRequestProjectionOptions = {
165
187
  packageRoot: env.packageRoot,
@@ -189,22 +211,8 @@ export async function runPublicDiarist(
189
211
  env,
190
212
  io,
191
213
  request: turnRequest,
192
- adapters: diaristAdapters(),
214
+ adapters: diaristAdapters(env),
193
215
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
194
- }).then(async (result) => {
195
- // Accept hook binds ticket onto durable pages (#771). Mirror that page fact
196
- // onto the caller-visible admitted object — never pick a ticketNumber out of
197
- // the payload sequence (#881 sole-collapse ban on ticket/escalate).
198
- if (admitted.ticketNumber === undefined && result.admitted !== undefined) {
199
- const fromPages = await readRunTicketNumber(admitted.runDirectory);
200
- if (fromPages !== undefined) {
201
- await bindAdmittedTicketNumber(admitted, fromPages);
202
- if (result.admitted.ticketNumber === undefined) {
203
- (result.admitted as { ticketNumber?: number }).ticketNumber = fromPages;
204
- }
205
- }
206
- }
207
- return result;
208
216
  });
209
217
  }
210
218
 
@@ -242,7 +250,7 @@ export async function runPublicDiaristResume(
242
250
  ),
243
251
  );
244
252
  },
245
- adapters: diaristAdapters(),
253
+ adapters: diaristAdapters(env),
246
254
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
247
255
  });
248
256
  }
@@ -7,13 +7,12 @@
7
7
  */
8
8
  import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contracts.ts";
9
9
  import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.ts";
10
- import { readRunTicketNumber } from "../run-ticket-number.ts";
10
+ import { readBoardTicketNumber } from "../run-ticket-number.ts";
11
11
  import { CliUsageError } from "./cli-errors.ts";
12
12
  import {
13
13
  admitAuditorInvocation,
14
14
  admitGatekeeperInvocation,
15
15
  admitNavigatorInvocation,
16
- bindAdmittedTicketNumber,
17
16
  buildInstructionTransportPrompt,
18
17
  persistAdmittedSourceRunPath,
19
18
  type AdmittedAuditorInvocation,
@@ -286,7 +285,8 @@ export async function runPublicInstructionSeat(
286
285
  home: env.home,
287
286
  });
288
287
  auditorSourceRun = resolved.runDirectory;
289
- auditorSourceTicket = await readRunTicketNumber(resolved.runDirectory);
288
+ // Auditor inherits board identity only — migration-derived is display, not admission.
289
+ auditorSourceTicket = await readBoardTicketNumber(resolved.runDirectory);
290
290
  } catch (error) {
291
291
  presentStructuralRejection(
292
292
  new CliUsageError(error instanceof Error ? error.message : String(error)),
@@ -345,6 +345,9 @@ export async function runPublicInstructionSeat(
345
345
  ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
346
346
  ...(env.model === undefined ? {} : { model: env.model }),
347
347
  ...(env.correlationId === undefined ? {} : { correlationId: env.correlationId }),
348
+ ...(auditorSourceTicket === undefined
349
+ ? {}
350
+ : { assertedTicketNumber: auditorSourceTicket }),
348
351
  });
349
352
  } catch (error) {
350
353
  if (error instanceof CliUsageError) {
@@ -395,14 +398,7 @@ export async function runPublicInstructionSeat(
395
398
  env,
396
399
  io,
397
400
  request: turnRequest,
398
- adapters: instructionSeatAdapters({
399
- beforeDispatch: async (admittedSeat) => {
400
- // Auditor inherits source-run ticket (notary face) — typed, not prose match.
401
- if (auditorSourceTicket !== undefined) {
402
- await bindAdmittedTicketNumber(admittedSeat, auditorSourceTicket);
403
- }
404
- },
405
- }),
401
+ adapters: instructionSeatAdapters(),
406
402
  ...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
407
403
  });
408
404
  },