@akagilnc/pi-workflow-roles 0.1.5918 → 0.1.5926

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 CHANGED
@@ -140,10 +140,10 @@ ak-role --model <provider/model[:thinking]> resume <runId> "<ruling>"
140
140
 
141
141
  ## Tests
142
142
 
143
- During the [ADR 0090](docs/adr/0090-tests-run-on-vitest-fixtures-and-helpers-from-the-platform.md) expand phase, `pnpm run test:vitest` runs the migrated examples from all three projects; CI runs it alongside the unchanged `test:all`. For a focused run:
143
+ During the [ADR 0090](docs/adr/0090-tests-run-on-vitest-fixtures-and-helpers-from-the-platform.md) migration, `pnpm test` runs all unit/contract tests (including root-level tests) with Vitest. `test:vitest` also runs the migrated factory-board timer tests and the integration/package expand examples. `test:all` runs these first, then the remaining Node tests; CI retains both entries until migration closes. `test:integration` uses the same Vitest batch, including the package expand example, followed by the remaining integration tests. For a focused run:
144
144
 
145
145
  ```bash
146
- pnpm exec vitest run --project unit test/unit/package-home-seam.test.ts
146
+ pnpm exec vitest run --project unit-contract
147
147
  pnpm exec vitest run --project integration test/integration/public-cli-bin-startup.test.ts
148
148
  pnpm exec vitest run --project package test/package/published-dist-import-closure.test.ts
149
149
  ```
@@ -22648,7 +22648,7 @@ async function executeActivationStage(role, stage, infrastructure) {
22648
22648
  role,
22649
22649
  stageId: stage.id,
22650
22650
  status: "failed",
22651
- timestamp: infrastructure.clock(),
22651
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22652
22652
  cause: namedActivationCause(activationError)
22653
22653
  });
22654
22654
  } catch (traceError) {
@@ -23459,7 +23459,6 @@ function createRoleRuntimeExtension(dependencies, deliveryRequestLimit) {
23459
23459
  activeCollector = await collectorBusiness.activate(context);
23460
23460
  }
23461
23461
  };
23462
- const clock = dependencies.activationClock ?? (() => (/* @__PURE__ */ new Date()).toISOString());
23463
23462
  const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
23464
23463
  roleHost.on("session_start", async (event, ctx) => {
23465
23464
  admitted = false;
@@ -23566,7 +23565,7 @@ function createRoleRuntimeExtension(dependencies, deliveryRequestLimit) {
23566
23565
  });
23567
23566
  navigatorAttendance.prepare();
23568
23567
  }
23569
- await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { clock, writeTrace });
23568
+ await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { writeTrace });
23570
23569
  registerReportTicketTool(roleHost, hostActions);
23571
23570
  roleHost.setActiveTools([
23572
23571
  .../* @__PURE__ */ new Set([...roleHost.getActiveTools(), REPORT_TICKET_TOOL_NAME])
@@ -1360,13 +1360,6 @@ export async function writeFactoryBoardPage(input) {
1360
1360
  const outputPath = await writePageOutsideLedgers(gate.ledgerRoots, gate.outputAbsolute, html, BOARD_OUTPUT_REFUSAL);
1361
1361
  return { outputPath, html };
1362
1362
  }
1363
- const defaultBoardScheduler = {
1364
- every(ms, tick) {
1365
- const timer = setInterval(tick, ms);
1366
- timer.unref?.();
1367
- return () => clearInterval(timer);
1368
- },
1369
- };
1370
1363
  /**
1371
1364
  * Production board lifecycle: write immediately, then regenerate on the declared
1372
1365
  * refresh boundary so the same viewing surface observes new runs / generated-at.
@@ -1382,8 +1375,6 @@ export function startFactoryBoardPage(input) {
1382
1375
  if (input.view === undefined && input.loadView === undefined) {
1383
1376
  throw new Error("view or loadView is required");
1384
1377
  }
1385
- const clock = input.clock ?? (() => new Date());
1386
- const scheduler = input.scheduler ?? defaultBoardScheduler;
1387
1378
  let stopped = false;
1388
1379
  let cancel;
1389
1380
  let inFlight;
@@ -1424,7 +1415,7 @@ export function startFactoryBoardPage(input) {
1424
1415
  return writeFactoryBoardPage({
1425
1416
  books: input.books,
1426
1417
  view,
1427
- now: clock(),
1418
+ now: new Date(),
1428
1419
  outputPath: input.outputPath,
1429
1420
  refreshBoundarySeconds,
1430
1421
  });
@@ -1448,9 +1439,9 @@ export function startFactoryBoardPage(input) {
1448
1439
  if (stopped || lastRejection !== undefined)
1449
1440
  return first;
1450
1441
  const intervalMs = Math.max(1, Math.round(refreshBoundarySeconds * 1000));
1451
- cancel = scheduler.every(intervalMs, () => {
1452
- queueWrite();
1453
- });
1442
+ const timer = setInterval(queueWrite, intervalMs);
1443
+ timer.unref?.();
1444
+ cancel = () => clearInterval(timer);
1454
1445
  return first;
1455
1446
  })
1456
1447
  .catch((error) => {
@@ -22615,7 +22615,7 @@ async function executeActivationStage(role, stage, infrastructure) {
22615
22615
  role,
22616
22616
  stageId: stage.id,
22617
22617
  status: "failed",
22618
- timestamp: infrastructure.clock(),
22618
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22619
22619
  cause: namedActivationCause(activationError)
22620
22620
  });
22621
22621
  } catch (traceError) {
@@ -23426,7 +23426,6 @@ function createRoleRuntimeExtension(dependencies, deliveryRequestLimit) {
23426
23426
  activeCollector = await collectorBusiness.activate(context);
23427
23427
  }
23428
23428
  };
23429
- const clock = dependencies.activationClock ?? (() => (/* @__PURE__ */ new Date()).toISOString());
23430
23429
  const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
23431
23430
  roleHost.on("session_start", async (event, ctx) => {
23432
23431
  admitted = false;
@@ -23533,7 +23532,7 @@ function createRoleRuntimeExtension(dependencies, deliveryRequestLimit) {
23533
23532
  });
23534
23533
  navigatorAttendance.prepare();
23535
23534
  }
23536
- await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { clock, writeTrace });
23535
+ await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { writeTrace });
23537
23536
  registerReportTicketTool(roleHost, hostActions);
23538
23537
  roleHost.setActiveTools([
23539
23538
  .../* @__PURE__ */ new Set([...roleHost.getActiveTools(), REPORT_TICKET_TOOL_NAME])
@@ -44,14 +44,6 @@ export function readOAuthKeepaliveProviders(path = oauthKeepaliveSettingPath())
44
44
  throw error;
45
45
  }
46
46
  }
47
- const defaultScheduler = {
48
- every(ms, tick) {
49
- const timer = setInterval(tick, ms);
50
- // Allow the process to exit if a caller forgets stop (same discipline as trajectory).
51
- timer.unref?.();
52
- return () => clearInterval(timer);
53
- },
54
- };
55
47
  function errorClassName(error) {
56
48
  if (error instanceof Error && error.name.trim() !== "")
57
49
  return error.name;
@@ -92,7 +84,6 @@ function emitWarning(ctx, providerId, error) {
92
84
  export function createOAuthKeepalive(options = {}) {
93
85
  const providers = Object.freeze((options.providers ?? DEFAULT_OAUTH_KEEPALIVE_PROVIDERS).map((id) => id));
94
86
  const intervalMs = options.intervalMs ?? OAUTH_KEEPALIVE_INTERVAL_MS;
95
- const scheduler = options.scheduler ?? defaultScheduler;
96
87
  let cancel;
97
88
  let abortController;
98
89
  /** Bare single-flight gate; survives stop/start until the in-flight call settles. */
@@ -140,9 +131,9 @@ export function createOAuthKeepalive(options = {}) {
140
131
  // Reload / re-start: tear down any prior interval before hanging a new one.
141
132
  stop();
142
133
  abortController = new AbortController();
143
- cancel = scheduler.every(intervalMs, () => {
144
- void runTick(ctx);
145
- });
134
+ const timer = setInterval(() => { void runTick(ctx); }, intervalMs);
135
+ timer.unref?.();
136
+ cancel = () => clearInterval(timer);
146
137
  },
147
138
  stop,
148
139
  };
@@ -211,7 +211,7 @@ async function executeActivationStage(role, stage, infrastructure) {
211
211
  role,
212
212
  stageId: stage.id,
213
213
  status: "failed",
214
- timestamp: infrastructure.clock(),
214
+ timestamp: new Date().toISOString(),
215
215
  cause: namedActivationCause(activationError),
216
216
  });
217
217
  }
@@ -1141,7 +1141,6 @@ deliveryRequestLimit) {
1141
1141
  activeCollector = await collectorBusiness.activate(context);
1142
1142
  },
1143
1143
  };
1144
- const clock = dependencies.activationClock ?? (() => new Date().toISOString());
1145
1144
  const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
1146
1145
  roleHost.on("session_start", async (event, ctx) => {
1147
1146
  admitted = false;
@@ -1267,7 +1266,7 @@ deliveryRequestLimit) {
1267
1266
  // The lookup starts with the parent role (#1160 / #1192).
1268
1267
  navigatorAttendance.prepare();
1269
1268
  }
1270
- await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { clock, writeTrace });
1269
+ await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { writeTrace });
1271
1270
  // #1171: same registration mouth as submission tools; when-to-call is on the tool itself.
1272
1271
  registerReportTicketTool(roleHost, hostActions);
1273
1272
  roleHost.setActiveTools([
@@ -726,14 +726,6 @@ export async function writeTicketTrajectoryPage(input) {
726
726
  const outputPath = await writePageOutsideLedgers([gate.ledgerRoot], gate.outputAbsolute, html, TRAJECTORY_OUTPUT_REFUSAL);
727
727
  return { outputPath, html };
728
728
  }
729
- const defaultScheduler = {
730
- every(ms, tick) {
731
- const timer = setInterval(tick, ms);
732
- // Allow the process to exit naturally if the caller forgets stop in scripts.
733
- timer.unref?.();
734
- return () => clearInterval(timer);
735
- },
736
- };
737
729
  /**
738
730
  * Production page lifecycle: write immediately, then regenerate on the declared
739
731
  * refresh boundary so the same viewing surface observes new runs / generated-at.
@@ -745,8 +737,6 @@ export function startTicketTrajectoryPage(input) {
745
737
  if (!(refreshBoundarySeconds > 0) || !Number.isFinite(refreshBoundarySeconds)) {
746
738
  throw new Error("refreshBoundarySeconds must be a positive finite number");
747
739
  }
748
- const clock = input.clock ?? (() => new Date());
749
- const scheduler = input.scheduler ?? defaultScheduler;
750
740
  let stopped = false;
751
741
  let cancel;
752
742
  let inFlight;
@@ -787,7 +777,7 @@ export function startTicketTrajectoryPage(input) {
787
777
  return writeTicketTrajectoryPage({
788
778
  ledgerDir: input.ledgerDir,
789
779
  ticketSnapshot: input.ticketSnapshot,
790
- now: clock(),
780
+ now: new Date(),
791
781
  outputPath: input.outputPath,
792
782
  refreshBoundarySeconds,
793
783
  });
@@ -811,9 +801,9 @@ export function startTicketTrajectoryPage(input) {
811
801
  if (stopped || lastRejection !== undefined)
812
802
  return first;
813
803
  const intervalMs = Math.max(1, Math.round(refreshBoundarySeconds * 1000));
814
- cancel = scheduler.every(intervalMs, () => {
815
- queueWrite();
816
- });
804
+ const timer = setInterval(queueWrite, intervalMs);
805
+ timer.unref?.();
806
+ cancel = () => clearInterval(timer);
817
807
  return first;
818
808
  })
819
809
  .catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5918",
3
+ "version": "0.1.5926",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -44,11 +44,11 @@
44
44
  "scripts": {
45
45
  "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json && node scripts/build-package.mjs",
46
46
  "prepack": "npm run build",
47
- "test": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
48
- "test:fast": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-timeout=300000 --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/unit/**/*.test.ts test/contract/**/*.test.ts",
49
- "test:integration": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/unit/**/*.test.ts test/contract/**/*.test.ts test/integration/**/*.test.ts",
50
- "test:all": "node scripts/run-test-all.mjs",
51
- "test:vitest": "vitest run test/unit/package-home-seam.test.ts test/integration/public-cli-bin-startup.test.ts test/package/published-dist-import-closure.test.ts",
47
+ "test": "vitest run --project unit-contract",
48
+ "test:fast": "pnpm test",
49
+ "test:integration": "pnpm run test:vitest && node scripts/run-test-all.mjs integration",
50
+ "test:all": "pnpm run test:vitest && node scripts/run-test-all.mjs",
51
+ "test:vitest": "vitest run test/unit/ test/contract/ test/primary-aware-cleanup.test.ts test/integration/factory-board.test.ts test/integration/public-cli-bin-startup.test.ts test/integration/test-user-profile-preload.test.ts test/package/published-dist-import-closure.test.ts",
52
52
  "test:adjudication": "node --import tsx --import ./scripts/test-process-env-preload.mjs --test-reporter=dot --test-reporter-destination=stdout --test-reporter=spec --test-reporter-destination=node-test-report.txt --test test/adjudication/**/*.test.ts",
53
53
  "typecheck": "tsc --noEmit",
54
54
  "migrate:book-topology": "node dist/migrate-book-topology.js"
@@ -80,6 +80,7 @@
80
80
  "@earendil-works/pi-coding-agent": "latest",
81
81
  "@types/node": "latest",
82
82
  "esbuild": "^0.28.1",
83
+ "execa": "^10.2.0",
83
84
  "tsx": "latest",
84
85
  "typescript": "latest",
85
86
  "vitest": "^5.0.5"
@@ -35,8 +35,6 @@ import {
35
35
  renderTicketTrajectoryStationHtml,
36
36
  type TicketTrajectoryBookIndex,
37
37
  type TicketTrajectoryRun,
38
- type TrajectoryClock,
39
- type TrajectoryScheduler,
40
38
  type UnboundTrajectoryRun,
41
39
  } from "./ticket-trajectory.ts";
42
40
  import type { BoardSnapshot, SnapshotTicket, TicketIssueState } from "./ticket-snapshot.ts";
@@ -50,10 +48,6 @@ export const UNACCEPTED_WATCH_MS = 15 * 60 * 1000;
50
48
  /** Same declared refresh bound as the S1 trajectory surface (seconds). */
51
49
  export { DEFAULT_REFRESH_BOUNDARY_SECONDS };
52
50
 
53
- /** Injected clock + scheduler for the production board lifecycle. */
54
- export type FactoryBoardClock = TrajectoryClock;
55
- export type FactoryBoardScheduler = TrajectoryScheduler;
56
-
57
51
  export type FactoryBoardPageHandle = {
58
52
  readonly outputPath: string;
59
53
  /** Settles when the first page write finishes (or rejects on first failure). */
@@ -1680,14 +1674,6 @@ export async function writeFactoryBoardPage(input: {
1680
1674
  return { outputPath, html };
1681
1675
  }
1682
1676
 
1683
- const defaultBoardScheduler: FactoryBoardScheduler = {
1684
- every(ms, tick) {
1685
- const timer = setInterval(tick, ms);
1686
- timer.unref?.();
1687
- return () => clearInterval(timer);
1688
- },
1689
- };
1690
-
1691
1677
  /**
1692
1678
  * Production board lifecycle: write immediately, then regenerate on the declared
1693
1679
  * refresh boundary so the same viewing surface observes new runs / generated-at.
@@ -1706,8 +1692,6 @@ export function startFactoryBoardPage(input: {
1706
1692
  loadView?: () => Promise<FactoryBoardView>;
1707
1693
  outputPath: string;
1708
1694
  refreshBoundarySeconds?: number;
1709
- clock?: FactoryBoardClock;
1710
- scheduler?: FactoryBoardScheduler;
1711
1695
  }): FactoryBoardPageHandle {
1712
1696
  const refreshBoundarySeconds = input.refreshBoundarySeconds ?? DEFAULT_REFRESH_BOUNDARY_SECONDS;
1713
1697
  if (!(refreshBoundarySeconds > 0) || !Number.isFinite(refreshBoundarySeconds)) {
@@ -1716,8 +1700,6 @@ export function startFactoryBoardPage(input: {
1716
1700
  if (input.view === undefined && input.loadView === undefined) {
1717
1701
  throw new Error("view or loadView is required");
1718
1702
  }
1719
- const clock = input.clock ?? (() => new Date());
1720
- const scheduler = input.scheduler ?? defaultBoardScheduler;
1721
1703
 
1722
1704
  let stopped = false;
1723
1705
  let cancel: (() => void) | undefined;
@@ -1759,7 +1741,7 @@ export function startFactoryBoardPage(input: {
1759
1741
  return writeFactoryBoardPage({
1760
1742
  books: input.books,
1761
1743
  view,
1762
- now: clock(),
1744
+ now: new Date(),
1763
1745
  outputPath: input.outputPath,
1764
1746
  refreshBoundarySeconds,
1765
1747
  });
@@ -1781,9 +1763,9 @@ export function startFactoryBoardPage(input: {
1781
1763
  .then((first) => {
1782
1764
  if (stopped || lastRejection !== undefined) return first;
1783
1765
  const intervalMs = Math.max(1, Math.round(refreshBoundarySeconds * 1000));
1784
- cancel = scheduler.every(intervalMs, () => {
1785
- queueWrite();
1786
- });
1766
+ const timer = setInterval(queueWrite, intervalMs);
1767
+ timer.unref?.();
1768
+ cancel = () => clearInterval(timer);
1787
1769
  return first;
1788
1770
  })
1789
1771
  .catch((error) => {
@@ -59,19 +59,11 @@ export function readOAuthKeepaliveProviders(
59
59
  }
60
60
  }
61
61
 
62
- /** Injected interval scheduler (same shape as TrajectoryScheduler). */
63
- export type OAuthKeepaliveScheduler = {
64
- /** Schedule `tick` every `ms` milliseconds; return a cancel function. */
65
- every: (ms: number, tick: () => void) => () => void;
66
- };
67
-
68
62
  export type OAuthKeepaliveOptions = {
69
63
  /** Static provider id list; frozen at construction. Default: ["kimi-coding"]. */
70
64
  providers?: readonly string[];
71
65
  /** Interval between ticks in ms. Default: 60_000. First tick is after one interval. */
72
66
  intervalMs?: number;
73
- /** Injectable scheduler for tests. Default: setInterval + unref. */
74
- scheduler?: OAuthKeepaliveScheduler;
75
67
  };
76
68
 
77
69
  /** Minimal context surface used by keepalive (public extension facade only). */
@@ -97,15 +89,6 @@ export type OAuthKeepalive = {
97
89
  stop(): void;
98
90
  };
99
91
 
100
- const defaultScheduler: OAuthKeepaliveScheduler = {
101
- every(ms, tick) {
102
- const timer = setInterval(tick, ms);
103
- // Allow the process to exit if a caller forgets stop (same discipline as trajectory).
104
- timer.unref?.();
105
- return () => clearInterval(timer);
106
- },
107
- };
108
-
109
92
  function errorClassName(error: unknown): string {
110
93
  if (error instanceof Error && error.name.trim() !== "") return error.name;
111
94
  if (error !== null && typeof error === "object" && "name" in error) {
@@ -148,8 +131,6 @@ export function createOAuthKeepalive(options: OAuthKeepaliveOptions = {}): OAuth
148
131
  (options.providers ?? DEFAULT_OAUTH_KEEPALIVE_PROVIDERS).map((id) => id),
149
132
  ) as readonly string[];
150
133
  const intervalMs = options.intervalMs ?? OAUTH_KEEPALIVE_INTERVAL_MS;
151
- const scheduler = options.scheduler ?? defaultScheduler;
152
-
153
134
  let cancel: (() => void) | undefined;
154
135
  let abortController: AbortController | undefined;
155
136
  /** Bare single-flight gate; survives stop/start until the in-flight call settles. */
@@ -194,9 +175,9 @@ export function createOAuthKeepalive(options: OAuthKeepaliveOptions = {}): OAuth
194
175
  // Reload / re-start: tear down any prior interval before hanging a new one.
195
176
  stop();
196
177
  abortController = new AbortController();
197
- cancel = scheduler.every(intervalMs, () => {
198
- void runTick(ctx);
199
- });
178
+ const timer = setInterval(() => { void runTick(ctx); }, intervalMs);
179
+ timer.unref?.();
180
+ cancel = () => clearInterval(timer);
200
181
  },
201
182
  stop,
202
183
  };
@@ -405,7 +405,7 @@ async function emitActivationTrace(
405
405
  async function executeActivationStage(
406
406
  role: string,
407
407
  stage: { id: string; run(): Promise<void> },
408
- infrastructure: { clock(): string; writeTrace(record: ActivationTraceRecord): void | Promise<void> },
408
+ infrastructure: { writeTrace(record: ActivationTraceRecord): void | Promise<void> },
409
409
  ): Promise<void> {
410
410
  try {
411
411
  await stage.run();
@@ -415,7 +415,7 @@ async function executeActivationStage(
415
415
  role,
416
416
  stageId: stage.id,
417
417
  status: "failed",
418
- timestamp: infrastructure.clock(),
418
+ timestamp: new Date().toISOString(),
419
419
  cause: namedActivationCause(activationError),
420
420
  });
421
421
  } catch (traceError) {
@@ -469,7 +469,6 @@ export type RoleRuntimeDependencies = {
469
469
  loadDoctorCase?(path: string): Promise<import("./doctor-contracts.ts").DoctorCase>;
470
470
  loadMergerInput?(path: string): Promise<unknown>;
471
471
  createNavigatorAttendance?(options: { context: HostContext; role: string; phase: NavigatorPhase; subjectKey: string; invocationId: string; onEvent: NavigatorAttendanceOptions["onEvent"] }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
472
- activationClock?(): string;
473
472
  activationTraceWriter?: (record: ActivationTraceRecord) => void | Promise<void>;
474
473
  };
475
474
 
@@ -1499,7 +1498,6 @@ export function createRoleRuntimeExtension(
1499
1498
  },
1500
1499
  };
1501
1500
 
1502
- const clock = dependencies.activationClock ?? (() => new Date().toISOString());
1503
1501
  const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
1504
1502
 
1505
1503
  roleHost.on("session_start", async (event, ctx) => {
@@ -1631,7 +1629,7 @@ export function createRoleRuntimeExtension(
1631
1629
  navigatorAttendance.prepare();
1632
1630
  }
1633
1631
 
1634
- await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { clock, writeTrace });
1632
+ await executeActivationStage(entry.role, activationStage(entry.role, activateByRole), { writeTrace });
1635
1633
  // #1171: same registration mouth as submission tools; when-to-call is on the tool itself.
1636
1634
  registerReportTicketTool(roleHost, hostActions);
1637
1635
  roleHost.setActiveTools([
@@ -66,13 +66,6 @@ export type TicketSnapshot = {
66
66
 
67
67
  export type StationSource = "tool" | "invocation" | "name" | "unknown";
68
68
 
69
- /** Injected clock + scheduler so tests drive the production lifecycle without wall sleep. */
70
- export type TrajectoryClock = () => Date;
71
- export type TrajectoryScheduler = {
72
- /** Schedule `tick` every `ms` milliseconds; return a cancel function. */
73
- every: (ms: number, tick: () => void) => () => void;
74
- };
75
-
76
69
  export type TicketTrajectoryPageHandle = {
77
70
  readonly outputPath: string;
78
71
  /** Settles when the first page write finishes (or rejects on first failure). */
@@ -937,15 +930,6 @@ export async function writeTicketTrajectoryPage(input: {
937
930
  return { outputPath, html };
938
931
  }
939
932
 
940
- const defaultScheduler: TrajectoryScheduler = {
941
- every(ms, tick) {
942
- const timer = setInterval(tick, ms);
943
- // Allow the process to exit naturally if the caller forgets stop in scripts.
944
- timer.unref?.();
945
- return () => clearInterval(timer);
946
- },
947
- };
948
-
949
933
  /**
950
934
  * Production page lifecycle: write immediately, then regenerate on the declared
951
935
  * refresh boundary so the same viewing surface observes new runs / generated-at.
@@ -957,15 +941,11 @@ export function startTicketTrajectoryPage(input: {
957
941
  ticketSnapshot: TicketSnapshot;
958
942
  outputPath: string;
959
943
  refreshBoundarySeconds?: number;
960
- clock?: TrajectoryClock;
961
- scheduler?: TrajectoryScheduler;
962
944
  }): TicketTrajectoryPageHandle {
963
945
  const refreshBoundarySeconds = input.refreshBoundarySeconds ?? DEFAULT_REFRESH_BOUNDARY_SECONDS;
964
946
  if (!(refreshBoundarySeconds > 0) || !Number.isFinite(refreshBoundarySeconds)) {
965
947
  throw new Error("refreshBoundarySeconds must be a positive finite number");
966
948
  }
967
- const clock = input.clock ?? (() => new Date());
968
- const scheduler = input.scheduler ?? defaultScheduler;
969
949
 
970
950
  let stopped = false;
971
951
  let cancel: (() => void) | undefined;
@@ -1007,7 +987,7 @@ export function startTicketTrajectoryPage(input: {
1007
987
  return writeTicketTrajectoryPage({
1008
988
  ledgerDir: input.ledgerDir,
1009
989
  ticketSnapshot: input.ticketSnapshot,
1010
- now: clock(),
990
+ now: new Date(),
1011
991
  outputPath: input.outputPath,
1012
992
  refreshBoundarySeconds,
1013
993
  });
@@ -1029,9 +1009,9 @@ export function startTicketTrajectoryPage(input: {
1029
1009
  .then((first) => {
1030
1010
  if (stopped || lastRejection !== undefined) return first;
1031
1011
  const intervalMs = Math.max(1, Math.round(refreshBoundarySeconds * 1000));
1032
- cancel = scheduler.every(intervalMs, () => {
1033
- queueWrite();
1034
- });
1012
+ const timer = setInterval(queueWrite, intervalMs);
1013
+ timer.unref?.();
1014
+ cancel = () => clearInterval(timer);
1035
1015
  return first;
1036
1016
  })
1037
1017
  .catch((error) => {