@cat-factory/node-server 0.92.7 → 0.92.8

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,9 +1,9 @@
1
1
  import type { Clock } from '@cat-factory/kernel';
2
2
  import type { Logger, ServerContainer } from '@cat-factory/server';
3
3
  /**
4
- * Start the periodic environment TTL sweep. Runs once immediately then on a timer.
5
- * Best-effort: a failed sweep is logged and retried next tick, never thrown. Returns a
6
- * stop function that clears the timer.
4
+ * Start the periodic environment TTL sweep. Runs once immediately then on the interval,
5
+ * non-overlapping + best-effort (see {@link startSweeper}). Returns a stop function that
6
+ * clears the timer.
7
7
  */
8
8
  export declare function startEnvironmentSweeper(container: ServerContainer, clock: Clock, log: Logger): () => void;
9
9
  //# sourceMappingURL=environments.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"environments.d.ts","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAUlE;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAkBZ"}
1
+ {"version":3,"file":"environments.d.ts","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAWlE;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAaZ"}
@@ -1,3 +1,4 @@
1
+ import { startSweeper } from './sweeper.js';
1
2
  // Periodic ephemeral-environment TTL teardown for the Node facade — the analogue of
2
3
  // the Worker's every-2-min cron call to `sweepExpiredEnvironments`. The Node service
3
4
  // has no cron, so a timer destroys environments whose expiry has elapsed. No-op when
@@ -5,27 +6,24 @@
5
6
  /** How often the Node service tears down expired environments. */
6
7
  const ENVIRONMENT_SWEEP_INTERVAL_MS = 2 * 60 * 1000;
7
8
  /**
8
- * Start the periodic environment TTL sweep. Runs once immediately then on a timer.
9
- * Best-effort: a failed sweep is logged and retried next tick, never thrown. Returns a
10
- * stop function that clears the timer.
9
+ * Start the periodic environment TTL sweep. Runs once immediately then on the interval,
10
+ * non-overlapping + best-effort (see {@link startSweeper}). Returns a stop function that
11
+ * clears the timer.
11
12
  */
12
13
  export function startEnvironmentSweeper(container, clock, log) {
13
14
  const environments = container.environments;
14
15
  if (!environments)
15
16
  return () => { };
16
- const tick = async () => {
17
- try {
17
+ return startSweeper({
18
+ name: 'environment-ttl',
19
+ intervalMs: ENVIRONMENT_SWEEP_INTERVAL_MS,
20
+ log,
21
+ failureMessage: 'environment TTL sweep failed',
22
+ tick: async () => {
18
23
  const torn = await environments.teardownService.sweepExpired(clock.now());
19
24
  if (torn > 0)
20
25
  log.info({ torn }, 'tore down expired environments');
21
- }
22
- catch (error) {
23
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'environment TTL sweep failed');
24
- }
25
- };
26
- void tick();
27
- const timer = setInterval(() => void tick(), ENVIRONMENT_SWEEP_INTERVAL_MS);
28
- timer.unref?.();
29
- return () => clearInterval(timer);
26
+ },
27
+ });
30
28
  }
31
29
  //# sourceMappingURL=environments.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"environments.js","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAGA,oFAAoF;AACpF,qFAAqF;AACrF,qFAAqF;AACrF,yFAAyF;AAEzF,kEAAkE;AAClE,MAAM,6BAA6B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAEnD;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAA;IAC3C,IAAI,CAAC,YAAY;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,eAAe,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YACzE,IAAI,IAAI,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,gCAAgC,CAAC,CAAA;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CACP,EAAE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC/D,8BAA8B,CAC/B,CAAA;QACH,CAAC;IACH,CAAC,CAAA;IACD,KAAK,IAAI,EAAE,CAAA;IACX,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,6BAA6B,CAAC,CAAA;IAC3E,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;IACf,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC"}
1
+ {"version":3,"file":"environments.js","sourceRoot":"","sources":["../src/environments.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,oFAAoF;AACpF,qFAAqF;AACrF,qFAAqF;AACrF,yFAAyF;AAEzF,kEAAkE;AAClE,MAAM,6BAA6B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAEnD;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAA;IAC3C,IAAI,CAAC,YAAY;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAClC,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,iBAAiB;QACvB,UAAU,EAAE,6BAA6B;QACzC,GAAG;QACH,cAAc,EAAE,8BAA8B;QAC9C,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,eAAe,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YACzE,IAAI,IAAI,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,gCAAgC,CAAC,CAAA;QACpE,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
@@ -1,24 +1,9 @@
1
- import type { Clock, GitHubInstallationRepository, RepoProjectionRepository } from '@cat-factory/kernel';
2
- import type { Logger } from '@cat-factory/server';
3
- /** A projection not synced for this long is considered stale (matches the Worker). */
4
- export declare const GITHUB_RECONCILE_STALE_MS: number;
5
- /** The seams the reconcile pass drives (kept narrow so it is unit-testable with fakes). */
6
- export interface GitHubReconcileDeps {
7
- repoProjectionRepository: Pick<RepoProjectionRepository, 'listStale'>;
8
- installationRepository: Pick<GitHubInstallationRepository, 'softDelete'>;
9
- /** The GitHub module's incremental per-repo resync (`GitHubSyncService.syncRepoById`). */
10
- syncRepoById: (workspaceId: string, repoGithubId: number) => Promise<void>;
11
- }
12
- /**
13
- * One reconcile pass: re-sync every stale repo projection, tombstoning installations
14
- * that are gone. Best-effort per repo — one failure must not abort the rest. Returns
15
- * the number of repos successfully re-synced.
16
- */
17
- export declare function reconcileStaleRepos(deps: GitHubReconcileDeps, clock: Clock, staleMs: number, log: Logger): Promise<number>;
1
+ import type { Clock } from '@cat-factory/kernel';
2
+ import { type GitHubReconcileDeps, type Logger } from '@cat-factory/server';
18
3
  /**
19
4
  * Start the periodic reconcile sweep. Runs once immediately then on the two-minute
20
- * timer. Best-effort: a failed pass is logged and retried next tick, never thrown.
21
- * Returns a stop function that clears the timer.
5
+ * timer, non-overlapping + best-effort (see {@link startSweeper}). Returns a stop
6
+ * function that clears the timer.
22
7
  */
23
8
  export declare function startGitHubReconcileSweeper(deps: GitHubReconcileDeps, clock: Clock, log: Logger): () => void;
24
9
  //# sourceMappingURL=githubReconcile.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"githubReconcile.d.ts","sourceRoot":"","sources":["../src/githubReconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,4BAA4B,EAC5B,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAcjD,sFAAsF;AACtF,eAAO,MAAM,yBAAyB,QAAiB,CAAA;AAEvD,2FAA2F;AAC3F,MAAM,WAAW,mBAAmB;IAClC,wBAAwB,EAAE,IAAI,CAAC,wBAAwB,EAAE,WAAW,CAAC,CAAA;IACrE,sBAAsB,EAAE,IAAI,CAAC,4BAA4B,EAAE,YAAY,CAAC,CAAA;IACxE,0FAA0F;IAC1F,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3E;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CA4CjB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAuBZ"}
1
+ {"version":3,"file":"githubReconcile.d.ts","sourceRoot":"","sources":["../src/githubReconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,MAAM,EAEZ,MAAM,qBAAqB,CAAA;AAc5B;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAWZ"}
@@ -1,115 +1,30 @@
1
+ import { GITHUB_RECONCILE_STALE_MS, reconcileStaleRepos, } from '@cat-factory/server';
2
+ import { startSweeper } from './sweeper.js';
1
3
  // Periodic GitHub reconciliation for the Node facade — the analogue of the Worker's
2
- // every-2-min `github-reconcile` cron (`infrastructure/github/sync-consumer.ts`'s
3
- // `reconcileStaleRepos`). Webhooks are the primary sync path but can be missed (a
4
- // delivery outage, a restart mid-delivery), so this backstop re-syncs every tracked
5
- // repo whose projection has gone stale — without it a dropped webhook leaves the
6
- // branch/PR/issue/check-run projections stale forever. It also tombstones an
7
- // installation whose uninstall webhook never arrived, so its repos stop being swept.
8
- // The Worker enqueues resyncs when a queue is bound; Node has no queue, so it uses
9
- // the Worker's direct-sync fallback path (`syncService.syncRepoById`) inline.
4
+ // every-2-min `github-reconcile` cron. Webhooks are the primary sync path but can be
5
+ // missed (a delivery outage, a restart mid-delivery), so this backstop re-syncs every
6
+ // tracked repo whose projection has gone stale. The reconcile pass itself lives in the
7
+ // shared `@cat-factory/server` `reconcileStaleRepos` (so it can't drift from the Worker);
8
+ // this file supplies only the Node driver — a direct inline resync (Node has no queue).
9
+ // The pass itself is unit-tested in `@cat-factory/server` (one test for one implementation).
10
10
  /** How often the reconcile pass runs (matches the Worker's frequent cron). */
11
11
  const GITHUB_RECONCILE_INTERVAL_MS = 2 * 60 * 1000;
12
- /** A projection not synced for this long is considered stale (matches the Worker). */
13
- export const GITHUB_RECONCILE_STALE_MS = 30 * 60 * 1000;
14
- /**
15
- * One reconcile pass: re-sync every stale repo projection, tombstoning installations
16
- * that are gone. Best-effort per repo — one failure must not abort the rest. Returns
17
- * the number of repos successfully re-synced.
18
- */
19
- export async function reconcileStaleRepos(deps, clock, staleMs, log) {
20
- // `listStale` already excludes repos whose installation is tombstoned, so a dead
21
- // installation stops being swept once it is known-gone; the handling below tombstones
22
- // one the webhook never told us about (a missed uninstall), so it stops next pass.
23
- const stale = await deps.repoProjectionRepository.listStale(clock.now() - staleMs);
24
- let synced = 0;
25
- for (const repo of stale) {
26
- try {
27
- await deps.syncRepoById(repo.workspaceId, repo.githubId);
28
- synced += 1;
29
- }
30
- catch (error) {
31
- // Best-effort pass (webhooks are the primary path): a gone/forbidden GitHub App
32
- // installation (uninstalled or revoked → 401/404 when minting its token) is an
33
- // expected operational state for a stale projection, so log it at warn; any
34
- // other fault is a real error. Either way, continue with the next repo.
35
- const gone = isInstallationGoneError(error);
36
- // A token-mint 404/410 means the installation itself is gone — uninstalled or
37
- // revoked without us receiving the webhook. Tombstone it so this and every future
38
- // pass skip ALL its repos until it is reinstalled (the `unsuspend`/reinstall
39
- // webhook clears the tombstone). Scoped to the mint error (not a repo-level 404,
40
- // which means a single deleted repo) and to 404/410 (never 401, which can be a
41
- // transient app-JWT fault hitting everything).
42
- if (isInstallationTokenGoneError(error)) {
43
- try {
44
- await deps.installationRepository.softDelete(repo.installationId, clock.now());
45
- }
46
- catch {
47
- // Best-effort: a failed tombstone just means we retry (and warn) next pass.
48
- }
49
- }
50
- log[gone ? 'warn' : 'error']({
51
- sweeper: 'github-reconcile',
52
- workspaceId: repo.workspaceId,
53
- repoGithubId: repo.githubId,
54
- installationId: repo.installationId,
55
- err: error instanceof Error ? error.message : String(error),
56
- }, gone
57
- ? 'skipping stale repo whose GitHub App installation is gone (uninstalled/revoked); reinstall the app to re-enable it'
58
- : 'repo resync failed');
59
- }
60
- }
61
- return synced;
62
- }
63
12
  /**
64
13
  * Start the periodic reconcile sweep. Runs once immediately then on the two-minute
65
- * timer. Best-effort: a failed pass is logged and retried next tick, never thrown.
66
- * Returns a stop function that clears the timer.
14
+ * timer, non-overlapping + best-effort (see {@link startSweeper}). Returns a stop
15
+ * function that clears the timer.
67
16
  */
68
17
  export function startGitHubReconcileSweeper(deps, clock, log) {
69
- let running = false;
70
- const tick = async () => {
71
- // Skip if the previous pass is still in flight: a pass re-syncing many repos can
72
- // outlast the interval, and setInterval would otherwise stack overlapping passes.
73
- if (running)
74
- return;
75
- running = true;
76
- try {
18
+ return startSweeper({
19
+ name: 'github-reconcile',
20
+ intervalMs: GITHUB_RECONCILE_INTERVAL_MS,
21
+ log,
22
+ failureMessage: 'github reconcile sweep failed',
23
+ tick: async () => {
77
24
  const synced = await reconcileStaleRepos(deps, clock, GITHUB_RECONCILE_STALE_MS, log);
78
25
  if (synced > 0)
79
26
  log.info({ synced }, 'reconciled stale github repo projections');
80
- }
81
- catch (error) {
82
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'github reconcile sweep failed');
83
- }
84
- finally {
85
- running = false;
86
- }
87
- };
88
- void tick();
89
- const timer = setInterval(() => void tick(), GITHUB_RECONCILE_INTERVAL_MS);
90
- timer.unref?.();
91
- return () => clearInterval(timer);
92
- }
93
- /**
94
- * Whether a sync error is a *gone/forbidden GitHub App installation* rather than a
95
- * transient fault: minting an installation token for an uninstalled or revoked
96
- * installation returns 401/404 (and a deleted repo 404/410). These are not worth
97
- * an error-level log or a retry storm — the connection needs human action.
98
- * (Mirrors the Worker's `sync-consumer.ts` classification.)
99
- */
100
- function isInstallationGoneError(error) {
101
- const message = error instanceof Error ? error.message : String(error);
102
- return /\(HTTP (401|404|410)\)/.test(message);
103
- }
104
- /**
105
- * Whether the error is specifically a *token mint* returning 404/410 — i.e. the
106
- * installation itself is gone (uninstalled/revoked), not merely a single repo
107
- * being inaccessible. Matches the App registry's mint-failure message. Excludes 401
108
- * (a transient app-JWT/clock fault would mint-fail for every installation, and must
109
- * not tombstone a healthy connection).
110
- */
111
- function isInstallationTokenGoneError(error) {
112
- const message = error instanceof Error ? error.message : String(error);
113
- return /Failed to mint installation token .*\(HTTP (404|410)\)/i.test(message);
27
+ },
28
+ });
114
29
  }
115
30
  //# sourceMappingURL=githubReconcile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"githubReconcile.js","sourceRoot":"","sources":["../src/githubReconcile.ts"],"names":[],"mappings":"AAOA,oFAAoF;AACpF,kFAAkF;AAClF,kFAAkF;AAClF,oFAAoF;AACpF,iFAAiF;AACjF,6EAA6E;AAC7E,qFAAqF;AACrF,mFAAmF;AACnF,8EAA8E;AAE9E,8EAA8E;AAC9E,MAAM,4BAA4B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAClD,sFAAsF;AACtF,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAUvD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAyB,EACzB,KAAY,EACZ,OAAe,EACf,GAAW;IAEX,iFAAiF;IACjF,sFAAsF;IACtF,mFAAmF;IACnF,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,CAAA;IAClF,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;YACxD,MAAM,IAAI,CAAC,CAAA;QACb,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gFAAgF;YAChF,+EAA+E;YAC/E,4EAA4E;YAC5E,wEAAwE;YACxE,MAAM,IAAI,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAA;YAC3C,8EAA8E;YAC9E,kFAAkF;YAClF,6EAA6E;YAC7E,iFAAiF;YACjF,+EAA+E;YAC/E,+CAA+C;YAC/C,IAAI,4BAA4B,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,sBAAsB,CAAC,UAAU,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;gBAChF,CAAC;gBAAC,MAAM,CAAC;oBACP,4EAA4E;gBAC9E,CAAC;YACH,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAC1B;gBACE,OAAO,EAAE,kBAAkB;gBAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,YAAY,EAAE,IAAI,CAAC,QAAQ;gBAC3B,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC5D,EACD,IAAI;gBACF,CAAC,CAAC,oHAAoH;gBACtH,CAAC,CAAC,oBAAoB,CACzB,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,IAAyB,EACzB,KAAY,EACZ,GAAW;IAEX,IAAI,OAAO,GAAG,KAAK,CAAA;IACnB,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,iFAAiF;QACjF,kFAAkF;QAClF,IAAI,OAAO;YAAE,OAAM;QACnB,OAAO,GAAG,IAAI,CAAA;QACd,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,yBAAyB,EAAE,GAAG,CAAC,CAAA;YACrF,IAAI,MAAM,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,0CAA0C,CAAC,CAAA;QAClF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CACP,EAAE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC/D,+BAA+B,CAChC,CAAA;QACH,CAAC;gBAAS,CAAC;YACT,OAAO,GAAG,KAAK,CAAA;QACjB,CAAC;IACH,CAAC,CAAA;IACD,KAAK,IAAI,EAAE,CAAA;IACX,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,4BAA4B,CAAC,CAAA;IAC1E,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;IACf,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,uBAAuB,CAAC,KAAc;IAC7C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACtE,OAAO,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,4BAA4B,CAAC,KAAc;IAClD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACtE,OAAO,yDAAyD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;AAChF,CAAC"}
1
+ {"version":3,"file":"githubReconcile.js","sourceRoot":"","sources":["../src/githubReconcile.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EAGzB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,oFAAoF;AACpF,qFAAqF;AACrF,sFAAsF;AACtF,uFAAuF;AACvF,0FAA0F;AAC1F,wFAAwF;AACxF,6FAA6F;AAE7F,8EAA8E;AAC9E,MAAM,4BAA4B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAElD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,IAAyB,EACzB,KAAY,EACZ,GAAW;IAEX,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,kBAAkB;QACxB,UAAU,EAAE,4BAA4B;QACxC,GAAG;QACH,cAAc,EAAE,+BAA+B;QAC/C,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,yBAAyB,EAAE,GAAG,CAAC,CAAA;YACrF,IAAI,MAAM,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,0CAA0C,CAAC,CAAA;QAClF,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
@@ -15,8 +15,10 @@ import type { Logger, ServerContainer } from '@cat-factory/server';
15
15
  export declare function resolveSweepInterval(env?: NodeJS.ProcessEnv): number;
16
16
  /**
17
17
  * Start the periodic initiative-loop sweep. Runs once immediately then on the resolved interval
18
- * (default one minute; see {@link resolveSweepInterval}). Best-effort: a failed sweep is logged
19
- * and retried next tick, never thrown. Returns a stop function that clears the timer.
18
+ * (default one minute; see {@link resolveSweepInterval}), non-overlapping + best-effort (see
19
+ * {@link startSweeper}). The non-overlap guard matters most here: `runDue` reconciles spawned
20
+ * tasks and spawns the next wave, so two concurrent passes could both observe "no active run"
21
+ * and double-spawn. Returns a stop function that clears the timer.
20
22
  */
21
23
  export declare function startInitiativeLoopSweeper(container: ServerContainer, clock: Clock, log: Logger, intervalMs?: number): () => void;
22
24
  //# sourceMappingURL=initiativeLoop.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"initiativeLoop.d.ts","sourceRoot":"","sources":["../src/initiativeLoop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAWlE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAIjF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAA+B,GAC1C,MAAM,IAAI,CAoBZ"}
1
+ {"version":3,"file":"initiativeLoop.d.ts","sourceRoot":"","sources":["../src/initiativeLoop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAYlE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAIjF;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAA+B,GAC1C,MAAM,IAAI,CAeZ"}
@@ -1,3 +1,4 @@
1
+ import { startSweeper } from './sweeper.js';
1
2
  // Periodic initiative-execution-loop sweep for the Node facade — the analogue of the Worker's
2
3
  // every-2-min cron call to `initiatives.loop.runDue`. The Node service has no cron, so a timer
3
4
  // ticks every executing initiative (reconcile spawned tasks + spawn the next wave). Terminal
@@ -24,27 +25,26 @@ export function resolveSweepInterval(env = process.env) {
24
25
  }
25
26
  /**
26
27
  * Start the periodic initiative-loop sweep. Runs once immediately then on the resolved interval
27
- * (default one minute; see {@link resolveSweepInterval}). Best-effort: a failed sweep is logged
28
- * and retried next tick, never thrown. Returns a stop function that clears the timer.
28
+ * (default one minute; see {@link resolveSweepInterval}), non-overlapping + best-effort (see
29
+ * {@link startSweeper}). The non-overlap guard matters most here: `runDue` reconciles spawned
30
+ * tasks and spawns the next wave, so two concurrent passes could both observe "no active run"
31
+ * and double-spawn. Returns a stop function that clears the timer.
29
32
  */
30
33
  export function startInitiativeLoopSweeper(container, clock, log, intervalMs = resolveSweepInterval()) {
31
34
  const initiatives = container.initiatives;
32
35
  if (!initiatives)
33
36
  return () => { };
34
- const tick = async () => {
35
- try {
37
+ return startSweeper({
38
+ name: 'initiative-loop',
39
+ intervalMs,
40
+ log,
41
+ failureMessage: 'initiative-loop sweep failed',
42
+ tick: async () => {
36
43
  const { spawned, completed } = await initiatives.loop.runDue(clock.now());
37
44
  if (spawned > 0 || completed > 0) {
38
45
  log.info({ spawned, completed }, 'ticked initiative loop');
39
46
  }
40
- }
41
- catch (error) {
42
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'initiative-loop sweep failed');
43
- }
44
- };
45
- void tick();
46
- const timer = setInterval(() => void tick(), intervalMs);
47
- timer.unref?.();
48
- return () => clearInterval(timer);
47
+ },
48
+ });
49
49
  }
50
50
  //# sourceMappingURL=initiativeLoop.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"initiativeLoop.js","sourceRoot":"","sources":["../src/initiativeLoop.ts"],"names":[],"mappings":"AAGA,8FAA8F;AAC9F,+FAA+F;AAC/F,6FAA6F;AAC7F,2FAA2F;AAC3F,kCAAkC;AAElC,4FAA4F;AAC5F,MAAM,iCAAiC,GAAG,EAAE,GAAG,IAAI,CAAA;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IACvE,MAAM,GAAG,GAAG,GAAG,CAAC,2BAA2B,CAAA;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;IAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iCAAiC,CAAA;AAC3F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,SAA0B,EAC1B,KAAY,EACZ,GAAW,EACX,UAAU,GAAW,oBAAoB,EAAE;IAE3C,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,CAAA;IACzC,IAAI,CAAC,WAAW;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IACjC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YACzE,IAAI,OAAO,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;gBACjC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,wBAAwB,CAAC,CAAA;YAC5D,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CACP,EAAE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC/D,8BAA8B,CAC/B,CAAA;QACH,CAAC;IACH,CAAC,CAAA;IACD,KAAK,IAAI,EAAE,CAAA;IACX,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,UAAU,CAAC,CAAA;IACxD,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;IACf,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC"}
1
+ {"version":3,"file":"initiativeLoop.js","sourceRoot":"","sources":["../src/initiativeLoop.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,8FAA8F;AAC9F,+FAA+F;AAC/F,6FAA6F;AAC7F,2FAA2F;AAC3F,kCAAkC;AAElC,4FAA4F;AAC5F,MAAM,iCAAiC,GAAG,EAAE,GAAG,IAAI,CAAA;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IACvE,MAAM,GAAG,GAAG,GAAG,CAAC,2BAA2B,CAAA;IAC3C,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAA;IAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iCAAiC,CAAA;AAC3F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CACxC,SAA0B,EAC1B,KAAY,EACZ,GAAW,EACX,UAAU,GAAW,oBAAoB,EAAE;IAE3C,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,CAAA;IACzC,IAAI,CAAC,WAAW;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IACjC,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,iBAAiB;QACvB,UAAU;QACV,GAAG;QACH,cAAc,EAAE,8BAA8B;QAC9C,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YACzE,IAAI,OAAO,GAAG,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;gBACjC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,wBAAwB,CAAC,CAAA;YAC5D,CAAC;QACH,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
package/dist/kaizen.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { Clock } from '@cat-factory/kernel';
2
2
  import type { Logger, ServerContainer } from '@cat-factory/server';
3
3
  /**
4
- * Start the periodic Kaizen grading sweep. Runs once immediately then on a one-minute
5
- * timer. Best-effort: a failed pass is logged and retried next tick, never thrown.
6
- * Returns a stop function that clears the timer.
4
+ * Start the periodic Kaizen grading sweep. Runs once immediately then on the interval,
5
+ * non-overlapping + best-effort (see {@link startSweeper}). Returns a stop function that
6
+ * clears the timer.
7
7
  */
8
8
  export declare function startKaizenSweeper(container: ServerContainer, clock: Clock, log: Logger): () => void;
9
9
  //# sourceMappingURL=kaizen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"kaizen.d.ts","sourceRoot":"","sources":["../src/kaizen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAclE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CA4BZ"}
1
+ {"version":3,"file":"kaizen.d.ts","sourceRoot":"","sources":["../src/kaizen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAelE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAgBZ"}
package/dist/kaizen.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { startSweeper } from './sweeper.js';
1
2
  // Periodic Kaizen grading sweep for the Node facade — the analogue of the Worker's
2
3
  // every-2-min cron call to `kaizen.service.runPending`. The engine only inserts
3
4
  // `scheduled` rows at run completion, so this timer does the actual LLM grading (and
@@ -9,36 +10,24 @@ const KAIZEN_STALE_MS = 10 * 60 * 1000;
9
10
  /** Max gradings to run per pass (each is an LLM call; keep the batch small). */
10
11
  const KAIZEN_SWEEP_BATCH = 5;
11
12
  /**
12
- * Start the periodic Kaizen grading sweep. Runs once immediately then on a one-minute
13
- * timer. Best-effort: a failed pass is logged and retried next tick, never thrown.
14
- * Returns a stop function that clears the timer.
13
+ * Start the periodic Kaizen grading sweep. Runs once immediately then on the interval,
14
+ * non-overlapping + best-effort (see {@link startSweeper}). Returns a stop function that
15
+ * clears the timer.
15
16
  */
16
17
  export function startKaizenSweeper(container, clock, log) {
17
18
  const kaizen = container.kaizen;
18
19
  if (!kaizen)
19
20
  return () => { };
20
- let running = false;
21
- const tick = async () => {
22
- // Skip if the previous pass is still in flight: a batch of LLM gradings can outlast the
23
- // interval, and setInterval would otherwise stack overlapping passes against the same rows.
24
- if (running)
25
- return;
26
- running = true;
27
- try {
21
+ return startSweeper({
22
+ name: 'kaizen',
23
+ intervalMs: KAIZEN_SWEEP_INTERVAL_MS,
24
+ log,
25
+ failureMessage: 'kaizen sweep failed',
26
+ tick: async () => {
28
27
  const processed = await kaizen.service.runPending(clock.now() - KAIZEN_STALE_MS, KAIZEN_SWEEP_BATCH);
29
28
  if (processed > 0)
30
29
  log.info({ processed }, 'ran pending kaizen gradings');
31
- }
32
- catch (error) {
33
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'kaizen sweep failed');
34
- }
35
- finally {
36
- running = false;
37
- }
38
- };
39
- void tick();
40
- const timer = setInterval(() => void tick(), KAIZEN_SWEEP_INTERVAL_MS);
41
- timer.unref?.();
42
- return () => clearInterval(timer);
30
+ },
31
+ });
43
32
  }
44
33
  //# sourceMappingURL=kaizen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"kaizen.js","sourceRoot":"","sources":["../src/kaizen.ts"],"names":[],"mappings":"AAGA,mFAAmF;AACnF,gFAAgF;AAChF,qFAAqF;AACrF,wFAAwF;AAExF,2FAA2F;AAC3F,MAAM,wBAAwB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAC9C,uFAAuF;AACvF,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AACtC,gFAAgF;AAChF,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAE5B;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;IAC/B,IAAI,CAAC,MAAM;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAC5B,IAAI,OAAO,GAAG,KAAK,CAAA;IACnB,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,wFAAwF;QACxF,4FAA4F;QAC5F,IAAI,OAAO;YAAE,OAAM;QACnB,OAAO,GAAG,IAAI,CAAA;QACd,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAC/C,KAAK,CAAC,GAAG,EAAE,GAAG,eAAe,EAC7B,kBAAkB,CACnB,CAAA;YACD,IAAI,SAAS,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,6BAA6B,CAAC,CAAA;QAC3E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CACP,EAAE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC/D,qBAAqB,CACtB,CAAA;QACH,CAAC;gBAAS,CAAC;YACT,OAAO,GAAG,KAAK,CAAA;QACjB,CAAC;IACH,CAAC,CAAA;IACD,KAAK,IAAI,EAAE,CAAA;IACX,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,wBAAwB,CAAC,CAAA;IACtE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;IACf,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC"}
1
+ {"version":3,"file":"kaizen.js","sourceRoot":"","sources":["../src/kaizen.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,mFAAmF;AACnF,gFAAgF;AAChF,qFAAqF;AACrF,wFAAwF;AAExF,2FAA2F;AAC3F,MAAM,wBAAwB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAA;AAC9C,uFAAuF;AACvF,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AACtC,gFAAgF;AAChF,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAE5B;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;IAC/B,IAAI,CAAC,MAAM;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAC5B,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,wBAAwB;QACpC,GAAG;QACH,cAAc,EAAE,qBAAqB;QACrC,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAC/C,KAAK,CAAC,GAAG,EAAE,GAAG,eAAe,EAC7B,kBAAkB,CACnB,CAAA;YACD,IAAI,SAAS,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,6BAA6B,CAAC,CAAA;QAC3E,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import type { Clock } from '@cat-factory/kernel';
2
2
  import { type Logger, type ServerContainer } from '@cat-factory/server';
3
3
  /**
4
- * Start the periodic notification-escalation sweep. Runs once immediately then on a
5
- * one-minute timer. Best-effort: a failed sweep is logged and retried next tick, never
6
- * thrown. Returns a stop function that clears the timer.
4
+ * Start the periodic notification-escalation sweep. Runs once immediately then on the
5
+ * interval, non-overlapping + best-effort (see {@link startSweeper}). Returns a stop
6
+ * function that clears the timer.
7
7
  */
8
8
  export declare function startNotificationEscalationSweeper(container: ServerContainer, clock: Clock, log: Logger): () => void;
9
9
  //# sourceMappingURL=notifications.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAA8B,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAWnG;;;;GAIG;AACH,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAiBZ"}
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAA8B,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAYnG;;;;GAIG;AACH,wBAAgB,kCAAkC,CAChD,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAYZ"}
@@ -1,4 +1,5 @@
1
1
  import { escalateStaleNotifications } from '@cat-factory/server';
2
+ import { startSweeper } from './sweeper.js';
2
3
  // Periodic notification-escalation sweep for the Node facade — the analogue of the
3
4
  // Worker's every-2-min cron call to `escalateStaleNotifications`. Runs no longer time out
4
5
  // waiting for a human, so a notification that has waited past its workspace's
@@ -7,26 +8,23 @@ import { escalateStaleNotifications } from '@cat-factory/server';
7
8
  /** How often the Node service escalates long-waiting notifications. */
8
9
  const NOTIFICATION_ESCALATION_INTERVAL_MS = 60 * 1000;
9
10
  /**
10
- * Start the periodic notification-escalation sweep. Runs once immediately then on a
11
- * one-minute timer. Best-effort: a failed sweep is logged and retried next tick, never
12
- * thrown. Returns a stop function that clears the timer.
11
+ * Start the periodic notification-escalation sweep. Runs once immediately then on the
12
+ * interval, non-overlapping + best-effort (see {@link startSweeper}). Returns a stop
13
+ * function that clears the timer.
13
14
  */
14
15
  export function startNotificationEscalationSweeper(container, clock, log) {
15
16
  if (!container.notifications)
16
17
  return () => { };
17
- const tick = async () => {
18
- try {
18
+ return startSweeper({
19
+ name: 'notification-escalation',
20
+ intervalMs: NOTIFICATION_ESCALATION_INTERVAL_MS,
21
+ log,
22
+ failureMessage: 'notification escalation failed',
23
+ tick: async () => {
19
24
  const escalated = await escalateStaleNotifications(container, clock.now());
20
25
  if (escalated > 0)
21
26
  log.info({ escalated }, 'escalated notifications');
22
- }
23
- catch (error) {
24
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'notification escalation failed');
25
- }
26
- };
27
- void tick();
28
- const timer = setInterval(() => void tick(), NOTIFICATION_ESCALATION_INTERVAL_MS);
29
- timer.unref?.();
30
- return () => clearInterval(timer);
27
+ },
28
+ });
31
29
  }
32
30
  //# sourceMappingURL=notifications.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAqC,MAAM,qBAAqB,CAAA;AAEnG,mFAAmF;AACnF,0FAA0F;AAC1F,8EAA8E;AAC9E,0FAA0F;AAC1F,kFAAkF;AAElF,uEAAuE;AACvE,MAAM,mCAAmC,GAAG,EAAE,GAAG,IAAI,CAAA;AAErD;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAChD,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,IAAI,CAAC,SAAS,CAAC,aAAa;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,0BAA0B,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YAC1E,IAAI,SAAS,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,yBAAyB,CAAC,CAAA;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CACP,EAAE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC/D,gCAAgC,CACjC,CAAA;QACH,CAAC;IACH,CAAC,CAAA;IACD,KAAK,IAAI,EAAE,CAAA;IACX,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,mCAAmC,CAAC,CAAA;IACjF,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;IACf,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC"}
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAqC,MAAM,qBAAqB,CAAA;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,mFAAmF;AACnF,0FAA0F;AAC1F,8EAA8E;AAC9E,0FAA0F;AAC1F,kFAAkF;AAElF,uEAAuE;AACvE,MAAM,mCAAmC,GAAG,EAAE,GAAG,IAAI,CAAA;AAErD;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAChD,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,IAAI,CAAC,SAAS,CAAC,aAAa;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAC7C,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,yBAAyB;QAC/B,UAAU,EAAE,mCAAmC;QAC/C,GAAG;QACH,cAAc,EAAE,gCAAgC;QAChD,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,SAAS,GAAG,MAAM,0BAA0B,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YAC1E,IAAI,SAAS,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,EAAE,yBAAyB,CAAC,CAAA;QACvE,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import type { Clock } from '@cat-factory/kernel';
2
2
  import type { Logger, ServerContainer } from '@cat-factory/server';
3
3
  /**
4
- * Start the periodic schedule sweep. Runs once immediately then on a one-minute
5
- * timer. Best-effort: a failed sweep is logged and retried next tick, never thrown.
6
- * Returns a stop function that clears the timer.
4
+ * Start the periodic schedule sweep. Runs once immediately then on the interval,
5
+ * non-overlapping + best-effort (see {@link startSweeper}). Returns a stop function that
6
+ * clears the timer.
7
7
  */
8
8
  export declare function startScheduleSweeper(container: ServerContainer, clock: Clock, log: Logger): () => void;
9
9
  //# sourceMappingURL=recurring.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recurring.d.ts","sourceRoot":"","sources":["../src/recurring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAWlE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAoBZ"}
1
+ {"version":3,"file":"recurring.d.ts","sourceRoot":"","sources":["../src/recurring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAYlE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAeZ"}
package/dist/recurring.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { startSweeper } from './sweeper.js';
1
2
  // Periodic recurring-pipeline sweep for the Node facade — the analogue of the
2
3
  // Worker's every-2-min cron call to `runDue`. The Node service has no cron, so a
3
4
  // timer fires every due schedule (the actual cadence is hours; a frequent tick just
@@ -6,28 +7,25 @@
6
7
  /** How often the Node service checks for due schedules. */
7
8
  const SCHEDULE_SWEEP_INTERVAL_MS = 60 * 1000;
8
9
  /**
9
- * Start the periodic schedule sweep. Runs once immediately then on a one-minute
10
- * timer. Best-effort: a failed sweep is logged and retried next tick, never thrown.
11
- * Returns a stop function that clears the timer.
10
+ * Start the periodic schedule sweep. Runs once immediately then on the interval,
11
+ * non-overlapping + best-effort (see {@link startSweeper}). Returns a stop function that
12
+ * clears the timer.
12
13
  */
13
14
  export function startScheduleSweeper(container, clock, log) {
14
15
  const recurring = container.recurring;
15
16
  if (!recurring)
16
17
  return () => { };
17
- const tick = async () => {
18
- try {
18
+ return startSweeper({
19
+ name: 'recurring-pipelines',
20
+ intervalMs: SCHEDULE_SWEEP_INTERVAL_MS,
21
+ log,
22
+ failureMessage: 'recurring-pipeline sweep failed',
23
+ tick: async () => {
19
24
  const { fired, skipped } = await recurring.service.runDue(clock.now());
20
25
  if (fired > 0 || skipped > 0) {
21
26
  log.info({ fired, skipped }, 'fired recurring pipelines');
22
27
  }
23
- }
24
- catch (error) {
25
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'recurring-pipeline sweep failed');
26
- }
27
- };
28
- void tick();
29
- const timer = setInterval(() => void tick(), SCHEDULE_SWEEP_INTERVAL_MS);
30
- timer.unref?.();
31
- return () => clearInterval(timer);
28
+ },
29
+ });
32
30
  }
33
31
  //# sourceMappingURL=recurring.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"recurring.js","sourceRoot":"","sources":["../src/recurring.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,iFAAiF;AACjF,oFAAoF;AACpF,mFAAmF;AACnF,eAAe;AAEf,2DAA2D;AAC3D,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,CAAA;AAE5C;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;IACrC,IAAI,CAAC,SAAS;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAC/B,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YACtE,IAAI,KAAK,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,2BAA2B,CAAC,CAAA;YAC3D,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CACP,EAAE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAC/D,iCAAiC,CAClC,CAAA;QACH,CAAC;IACH,CAAC,CAAA;IACD,KAAK,IAAI,EAAE,CAAA;IACX,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,EAAE,0BAA0B,CAAC,CAAA;IACxE,KAAK,CAAC,KAAK,EAAE,EAAE,CAAA;IACf,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;AACnC,CAAC"}
1
+ {"version":3,"file":"recurring.js","sourceRoot":"","sources":["../src/recurring.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,8EAA8E;AAC9E,iFAAiF;AACjF,oFAAoF;AACpF,mFAAmF;AACnF,eAAe;AAEf,2DAA2D;AAC3D,MAAM,0BAA0B,GAAG,EAAE,GAAG,IAAI,CAAA;AAE5C;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAA0B,EAC1B,KAAY,EACZ,GAAW;IAEX,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;IACrC,IAAI,CAAC,SAAS;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAC/B,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,qBAAqB;QAC3B,UAAU,EAAE,0BAA0B;QACtC,GAAG;QACH,cAAc,EAAE,iCAAiC;QACjD,IAAI,EAAE,KAAK,IAAI,EAAE;YACf,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAA;YACtE,IAAI,KAAK,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;gBAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,2BAA2B,CAAC,CAAA;YAC3D,CAAC;QACH,CAAC;KACF,CAAC,CAAA;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAC9B,0BAA0B,EAC1B,0BAA0B,EAC1B,KAAK,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,gCAAgC,EAChC,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAuBlE,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,EAAE,iBAAiB,CAAC,CAAA;IACnE,uBAAuB,EAAE,IAAI,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,CAAA;IAEzE,8BAA8B,EAAE,IAAI,CAAC,8BAA8B,EAAE,iBAAiB,CAAC,CAAA;IAEvF,0BAA0B,EAAE,IAAI,CAAC,0BAA0B,EAAE,iBAAiB,CAAC,CAAA;IAC/E,0BAA0B,EAAE,IAAI,CAAC,0BAA0B,EAAE,iBAAiB,CAAC,CAAA;IAG/E,gCAAgC,EAAE,IAAI,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAA;IAEzF,gCAAgC,EAAE,IAAI,CAAC,gCAAgC,EAAE,iBAAiB,CAAC,CAAA;IAE3F,yBAAyB,EAAE,IAAI,CAAC,yBAAyB,EAAE,iBAAiB,CAAC,CAAA;IAE7E,4BAA4B,EAAE,IAAI,CAAC,4BAA4B,EAAE,eAAe,CAAC,CAAA;IAKjF,gBAAgB,EAAE,IAAI,CAAC,0BAA0B,EAAE,iBAAiB,CAAC,CAAA;IAGrE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,CAAA;CAChF;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,eAAe,EAAE,MAAM,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;CACtB;AAmBD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,eAAe,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,eAAe,CAAC,CAqC1B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAkBZ;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,YAAY,EAAE,0BAA0B,EACxC,mBAAmB,EAAE,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7D,kBAAkB,EAAE,IAAI,CAAC,2BAA2B,EAAE,KAAK,CAAC,EAC5D,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CA4BZ"}
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAC9B,0BAA0B,EAC1B,0BAA0B,EAC1B,KAAK,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,gCAAgC,EAChC,oBAAoB,EACpB,mBAAmB,EACnB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAwBlE,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,EAAE,iBAAiB,CAAC,CAAA;IACnE,uBAAuB,EAAE,IAAI,CAAC,uBAAuB,EAAE,iBAAiB,CAAC,CAAA;IAEzE,8BAA8B,EAAE,IAAI,CAAC,8BAA8B,EAAE,iBAAiB,CAAC,CAAA;IAEvF,0BAA0B,EAAE,IAAI,CAAC,0BAA0B,EAAE,iBAAiB,CAAC,CAAA;IAC/E,0BAA0B,EAAE,IAAI,CAAC,0BAA0B,EAAE,iBAAiB,CAAC,CAAA;IAG/E,gCAAgC,EAAE,IAAI,CAAC,gCAAgC,EAAE,eAAe,CAAC,CAAA;IAEzF,gCAAgC,EAAE,IAAI,CAAC,gCAAgC,EAAE,iBAAiB,CAAC,CAAA;IAE3F,yBAAyB,EAAE,IAAI,CAAC,yBAAyB,EAAE,iBAAiB,CAAC,CAAA;IAE7E,4BAA4B,EAAE,IAAI,CAAC,4BAA4B,EAAE,eAAe,CAAC,CAAA;IAKjF,gBAAgB,EAAE,IAAI,CAAC,0BAA0B,EAAE,iBAAiB,CAAC,CAAA;IAGrE,sBAAsB,EAAE,IAAI,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,CAAA;CAChF;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,qBAAqB,EAAE,MAAM,CAAA;IAC7B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,eAAe,EAAE,MAAM,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;CACtB;AAmBD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,eAAe,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,eAAe,CAAC,CAqC1B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAaZ;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,YAAY,EAAE,0BAA0B,EACxC,mBAAmB,EAAE,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7D,kBAAkB,EAAE,IAAI,CAAC,2BAA2B,EAAE,KAAK,CAAC,EAC5D,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,MAAM,IAAI,CAuBZ"}
package/dist/retention.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_WORKSPACE_SETTINGS } from '@cat-factory/kernel';
2
2
  import { sweepBinaryArtifactRetention } from '@cat-factory/orchestration';
3
+ import { startSweeper } from './sweeper.js';
3
4
  /** Recurring-pipeline run history is kept ~1 week (the inspector's window). */
4
5
  const SCHEDULE_RUN_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
5
6
  /**
@@ -55,21 +56,18 @@ export async function sweepRetention(repos, retention, now) {
55
56
  * a stop function that clears the timer.
56
57
  */
57
58
  export function startRetentionSweeper(repos, retention, clock, log) {
58
- const tick = async () => {
59
- try {
59
+ return startSweeper({
60
+ name: 'retention',
61
+ intervalMs: RETENTION_SWEEP_INTERVAL_MS,
62
+ log,
63
+ failureMessage: 'retention sweep failed',
64
+ tick: async () => {
60
65
  const reclaimed = await sweepRetention(repos, retention, clock.now());
61
66
  if (Object.values(reclaimed).some((n) => n > 0)) {
62
67
  log.info(reclaimed, 'retention sweep reclaimed rows');
63
68
  }
64
- }
65
- catch (error) {
66
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'retention sweep failed');
67
- }
68
- };
69
- void tick();
70
- const timer = setInterval(() => void tick(), RETENTION_SWEEP_INTERVAL_MS);
71
- timer.unref?.(); // never keep the process alive on the sweep timer alone
72
- return () => clearInterval(timer);
69
+ },
70
+ });
73
71
  }
74
72
  /**
75
73
  * Start the per-workspace binary-artifact retention sweep (the Node analogue of the Worker's
@@ -79,8 +77,12 @@ export function startRetentionSweeper(repos, retention, clock, log) {
79
77
  * once immediately then hourly; best-effort. Returns a stop function.
80
78
  */
81
79
  export function startArtifactRetentionSweeper(resolveStore, workspaceRepository, settingsRepository, clock, log) {
82
- const tick = async () => {
83
- try {
80
+ return startSweeper({
81
+ name: 'artifact-retention',
82
+ intervalMs: RETENTION_SWEEP_INTERVAL_MS,
83
+ log,
84
+ failureMessage: 'artifact retention sweep failed',
85
+ tick: async () => {
84
86
  const removed = await sweepBinaryArtifactRetention({
85
87
  resolveStore,
86
88
  listWorkspaceIds: () => workspaceRepository.listVisible(null).then((ws) => ws.map((w) => w.id)),
@@ -91,14 +93,7 @@ export function startArtifactRetentionSweeper(resolveStore, workspaceRepository,
91
93
  });
92
94
  if (removed > 0)
93
95
  log.info({ binaryArtifacts: removed }, 'artifact retention sweep reclaimed rows');
94
- }
95
- catch (error) {
96
- log.error({ err: error instanceof Error ? error.message : String(error) }, 'artifact retention sweep failed');
97
- }
98
- };
99
- void tick();
100
- const timer = setInterval(() => void tick(), RETENTION_SWEEP_INTERVAL_MS);
101
- timer.unref?.();
102
- return () => clearInterval(timer);
96
+ },
97
+ });
103
98
  }
104
99
  //# sourceMappingURL=retention.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"retention.js","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Logger } from '@cat-factory/server';
2
+ /** A best-effort periodic sweep started via {@link startSweeper}. */
3
+ export interface SweeperOptions {
4
+ /** Short sweep name, used as the toad-scheduler task id (so a scheduler-surfaced error names its sweep). */
5
+ name: string;
6
+ /** How often to run the sweep. */
7
+ intervalMs: number;
8
+ /** Logger for the best-effort failure line. */
9
+ log: Logger;
10
+ /** The message logged (with the error) when a pass throws. */
11
+ failureMessage: string;
12
+ /** One sweep pass. Any success logging lives inside it; throws are caught + logged. */
13
+ tick: () => Promise<void>;
14
+ }
15
+ /**
16
+ * Start a periodic, non-overlapping, best-effort sweep. Runs `tick` once immediately then
17
+ * on the interval; skips a tick while the previous pass is still in flight
18
+ * (`preventOverrun`); logs (never throws) a failed pass; never keeps the process alive on
19
+ * the sweep timer alone (`unref`, matching the hand-rolled `setInterval(...).unref()`
20
+ * timers this replaced). Returns a stop function that halts the job — still call it on
21
+ * shutdown so a long pass in flight can't outlive the rest of the teardown.
22
+ */
23
+ export declare function startSweeper(options: SweeperOptions): () => void;
24
+ //# sourceMappingURL=sweeper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweeper.d.ts","sourceRoot":"","sources":["../src/sweeper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAiBjD,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,4GAA4G;IAC5G,IAAI,EAAE,MAAM,CAAA;IACZ,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAA;IACX,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAA;IACtB,uFAAuF;IACvF,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CAYhE"}
@@ -0,0 +1,23 @@
1
+ import { AsyncTask, SimpleIntervalJob, ToadScheduler } from 'toad-scheduler';
2
+ /**
3
+ * Start a periodic, non-overlapping, best-effort sweep. Runs `tick` once immediately then
4
+ * on the interval; skips a tick while the previous pass is still in flight
5
+ * (`preventOverrun`); logs (never throws) a failed pass; never keeps the process alive on
6
+ * the sweep timer alone (`unref`, matching the hand-rolled `setInterval(...).unref()`
7
+ * timers this replaced). Returns a stop function that halts the job — still call it on
8
+ * shutdown so a long pass in flight can't outlive the rest of the teardown.
9
+ */
10
+ export function startSweeper(options) {
11
+ const { name, intervalMs, log, failureMessage, tick } = options;
12
+ const scheduler = new ToadScheduler();
13
+ const task = new AsyncTask(name, tick, (error) => {
14
+ log.error({ err: error instanceof Error ? error.message : String(error) }, failureMessage);
15
+ });
16
+ const job = new SimpleIntervalJob({ milliseconds: intervalMs, runImmediately: true }, task, {
17
+ preventOverrun: true,
18
+ unref: true,
19
+ });
20
+ scheduler.addSimpleIntervalJob(job);
21
+ return () => scheduler.stop();
22
+ }
23
+ //# sourceMappingURL=sweeper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweeper.js","sourceRoot":"","sources":["../src/sweeper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AA8B5E;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAuB;IAClD,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IAC/D,MAAM,SAAS,GAAG,IAAI,aAAa,EAAE,CAAA;IACrC,MAAM,IAAI,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;QAC/C,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAA;IAC5F,CAAC,CAAC,CAAA;IACF,MAAM,GAAG,GAAG,IAAI,iBAAiB,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE;QAC1F,cAAc,EAAE,IAAI;QACpB,KAAK,EAAE,IAAI;KACZ,CAAC,CAAA;IACF,SAAS,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAA;IACnC,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;AAC/B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/node-server",
3
- "version": "0.92.7",
3
+ "version": "0.92.8",
4
4
  "description": "Node.js runtime facade for the Agent Architecture Board: serves the shared @cat-factory/server Hono app via @hono/node-server, wiring Node implementations of the runtime ports (persistence, gateways, model provisioning).",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,6 +31,7 @@
31
31
  "layered-loader": "^14.5.3",
32
32
  "pg": "^8.22.0",
33
33
  "pg-boss": "^12.25.1",
34
+ "toad-scheduler": "^4.1.0",
34
35
  "ws": "^8.21.0",
35
36
  "@cat-factory/agents": "0.53.6",
36
37
  "@cat-factory/caching": "0.6.34",
@@ -47,7 +48,7 @@
47
48
  "@cat-factory/provider-bedrock": "0.7.205",
48
49
  "@cat-factory/provider-cloudflare": "0.7.206",
49
50
  "@cat-factory/provider-s3": "0.2.139",
50
- "@cat-factory/server": "0.110.5",
51
+ "@cat-factory/server": "0.111.0",
51
52
  "@cat-factory/spend": "0.12.15"
52
53
  },
53
54
  "devDependencies": {
@@ -57,7 +58,7 @@
57
58
  "drizzle-kit": "1.0.0-rc.4",
58
59
  "typescript": "7.0.1-rc",
59
60
  "vitest": "^4.1.9",
60
- "@cat-factory/conformance": "0.10.97"
61
+ "@cat-factory/conformance": "0.10.98"
61
62
  },
62
63
  "optionalDependencies": {
63
64
  "ioredis": "^5.11.1"