@evomap/evolver-proxy 2.0.0-beta.2 → 2.0.0-beta.22

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 (69) hide show
  1. package/dist/bin/evolver-llm-proxy.js +0 -0
  2. package/dist/bin/evolver-proxy.d.ts +105 -7
  3. package/dist/bin/evolver-proxy.js +877 -121
  4. package/dist/daemon/atpConsent.js +5 -2
  5. package/dist/daemon/collaborationFacade.js +26 -16
  6. package/dist/daemon/proxyDaemon.d.ts +65 -0
  7. package/dist/daemon/proxyDaemon.js +1384 -29
  8. package/dist/daemon/publishRecallVerifier.d.ts +114 -0
  9. package/dist/daemon/publishRecallVerifier.js +495 -0
  10. package/dist/daemon/selectHub.js +5 -3
  11. package/dist/daemon/systemdNotifier.d.ts +48 -0
  12. package/dist/daemon/systemdNotifier.js +163 -0
  13. package/dist/index.d.ts +4 -1
  14. package/dist/index.js +4 -1
  15. package/dist/lifecycle/claimNudge.d.ts +20 -0
  16. package/dist/lifecycle/claimNudge.js +124 -0
  17. package/dist/lifecycle/legacyNodeId.d.ts +11 -13
  18. package/dist/lifecycle/legacyNodeId.js +35 -20
  19. package/dist/lifecycle/manager.d.ts +4 -0
  20. package/dist/lifecycle/manager.js +15 -2
  21. package/dist/llm/server.js +24 -4
  22. package/dist/llm/traceControl.js +1 -1
  23. package/dist/llm/upstream.d.ts +5 -1
  24. package/dist/llm/upstream.js +72 -2
  25. package/dist/private/accountAssetCompatibility.d.ts +29 -0
  26. package/dist/private/accountAssetCompatibility.js +196 -0
  27. package/dist/private/adapterLoader.d.ts +21 -1
  28. package/dist/private/adapterLoader.js +242 -7
  29. package/dist/private/nodeCredentialStore.d.ts +23 -0
  30. package/dist/private/nodeCredentialStore.js +210 -0
  31. package/dist/router/messagesRoute.js +9 -3
  32. package/dist/router/providerRoutes.js +7 -3
  33. package/dist/selfUpdate/bootstrap.d.ts +162 -0
  34. package/dist/selfUpdate/bootstrap.js +3524 -0
  35. package/dist/selfUpdate/bootstrapReadiness.d.ts +9 -0
  36. package/dist/selfUpdate/bootstrapReadiness.js +153 -0
  37. package/dist/selfUpdate/builtinKey.d.ts +4 -0
  38. package/dist/selfUpdate/builtinKey.js +16 -0
  39. package/dist/selfUpdate/controllerLifecycleAuthority.d.ts +45 -0
  40. package/dist/selfUpdate/controllerLifecycleAuthority.js +61 -0
  41. package/dist/selfUpdate/executor.d.ts +27 -11
  42. package/dist/selfUpdate/executor.js +233 -58
  43. package/dist/selfUpdate/failureCodes.d.ts +10 -0
  44. package/dist/selfUpdate/failureCodes.js +13 -0
  45. package/dist/selfUpdate/index.d.ts +5 -1
  46. package/dist/selfUpdate/index.js +5 -1
  47. package/dist/selfUpdate/lastUpdate.d.ts +3 -1
  48. package/dist/selfUpdate/lastUpdate.js +37 -6
  49. package/dist/selfUpdate/migration.d.ts +158 -0
  50. package/dist/selfUpdate/migration.js +2672 -0
  51. package/dist/selfUpdate/policy.d.ts +19 -2
  52. package/dist/selfUpdate/policy.js +76 -2
  53. package/dist/selfUpdate/recoveryChildStartGate.d.ts +29 -0
  54. package/dist/selfUpdate/recoveryChildStartGate.js +319 -0
  55. package/dist/selfUpdate/releaseBinary.d.ts +13 -0
  56. package/dist/selfUpdate/releaseBinary.js +93 -10
  57. package/dist/selfUpdate/transaction.d.ts +117 -0
  58. package/dist/selfUpdate/transaction.js +1322 -0
  59. package/dist/selfUpdate/unixController.d.ts +23 -0
  60. package/dist/selfUpdate/unixController.js +514 -0
  61. package/dist/selfUpdate/version.d.ts +6 -2
  62. package/dist/selfUpdate/version.js +5 -3
  63. package/dist/selfUpdate/windowsController.d.ts +35 -0
  64. package/dist/selfUpdate/windowsController.js +655 -0
  65. package/dist/selfUpdate/windowsUpdater.d.ts +104 -0
  66. package/dist/selfUpdate/windowsUpdater.js +882 -0
  67. package/dist/sync/engine.d.ts +12 -0
  68. package/dist/sync/engine.js +255 -64
  69. package/package.json +10 -3
@@ -0,0 +1,114 @@
1
+ import { assetstore, mailbox } from '@evomap/evolver-core';
2
+ export type PublishRecallOutcomeKind = 'ok' | 'missing' | 'mismatch' | 'error' | 'skipped';
3
+ export type PublishRecallSkipReason = 'feature_disabled' | 'sampled_out' | 'fetch_unavailable' | 'missing_asset_id' | 'invalid_asset_id' | 'already_queued' | 'queue_full';
4
+ export interface PublishRecallConfig {
5
+ enabled: boolean;
6
+ sampleRate: number;
7
+ queueMax: number;
8
+ outcomeMax: number;
9
+ initialWaitMs: number;
10
+ pollMs: number;
11
+ fetchTimeoutMs: number;
12
+ maxAttempts: number;
13
+ backoffMs: readonly number[];
14
+ }
15
+ export interface PublishRecallQueueEntry {
16
+ assetId: string;
17
+ assetType?: assetstore.AssetKind;
18
+ publishedAt: number;
19
+ attempts: number;
20
+ nextAttemptAt: number;
21
+ }
22
+ export interface PublishRecallOutcome {
23
+ assetId: string;
24
+ assetType?: assetstore.AssetKind;
25
+ outcome: PublishRecallOutcomeKind;
26
+ reason?: string;
27
+ attempts: number;
28
+ at: number;
29
+ latencyMs: number;
30
+ ageMs?: number;
31
+ recalledAssetId?: string;
32
+ computedAssetId?: string;
33
+ }
34
+ export interface PublishRecallState {
35
+ version: 1;
36
+ queue: PublishRecallQueueEntry[];
37
+ outcomes: PublishRecallOutcome[];
38
+ counts: Record<PublishRecallOutcomeKind, number>;
39
+ }
40
+ export interface PublishRecallStatus {
41
+ enabled: boolean;
42
+ fetchAvailable: boolean;
43
+ queued: number;
44
+ counts: Record<PublishRecallOutcomeKind, number>;
45
+ lastOutcome: PublishRecallOutcome | null;
46
+ persistenceHealthy: boolean;
47
+ }
48
+ export interface PublishRecallStateStore {
49
+ getState(key: string): string | undefined;
50
+ setState(key: string, value: string): void;
51
+ }
52
+ export interface PublishRecallTimers {
53
+ setTimeout(callback: () => void, delayMs: number): unknown;
54
+ clearTimeout(handle: unknown): void;
55
+ }
56
+ export interface PublishRecallVerifierPort {
57
+ start(): void;
58
+ stop(): void | Promise<void>;
59
+ observeAcceptedPublish(envelope: mailbox.Envelope, result: unknown): number;
60
+ status(): PublishRecallStatus;
61
+ }
62
+ export interface PublishRecallVerifierOptions {
63
+ store: PublishRecallStateStore;
64
+ fetchAssetById?: (assetId: string) => Promise<assetstore.AssetRecord | null>;
65
+ config: PublishRecallConfig;
66
+ now?: () => number;
67
+ random?: () => number;
68
+ timers?: PublishRecallTimers;
69
+ stateKey?: string;
70
+ }
71
+ export declare function resolvePublishRecallConfig(env?: Readonly<Record<string, string | undefined>>): PublishRecallConfig;
72
+ export declare class PublishRecallVerifier implements PublishRecallVerifierPort {
73
+ private readonly opts;
74
+ private readonly now;
75
+ private readonly random;
76
+ private readonly timers;
77
+ private readonly stateKey;
78
+ private state;
79
+ private timer;
80
+ private started;
81
+ private stopping;
82
+ private running;
83
+ private activeEntry;
84
+ private persistenceHealthy;
85
+ constructor(opts: PublishRecallVerifierOptions);
86
+ start(): void;
87
+ stop(): Promise<void>;
88
+ enqueue(input: {
89
+ assetId: string;
90
+ assetType?: assetstore.AssetKind;
91
+ publishedAt?: number;
92
+ }): {
93
+ enqueued: boolean;
94
+ reason?: PublishRecallSkipReason;
95
+ };
96
+ observeAcceptedPublish(envelope: mailbox.Envelope, result: unknown): number;
97
+ runDue(): Promise<number>;
98
+ private processDue;
99
+ inspect(): PublishRecallState;
100
+ status(): PublishRecallStatus;
101
+ private process;
102
+ private deferFromNow;
103
+ private fetchWithTimeout;
104
+ private finish;
105
+ private skip;
106
+ private recordOutcome;
107
+ private remove;
108
+ private elapsed;
109
+ private backoffForAttempt;
110
+ private schedule;
111
+ private clearTimer;
112
+ private loadState;
113
+ private persist;
114
+ }
@@ -0,0 +1,495 @@
1
+ import { assetstore, mailbox, wire } from '@evomap/evolver-core';
2
+ const DEFAULT_STATE_KEY = 'publish_recall_verifier:v1';
3
+ const DEFAULT_BACKOFF_MS = [5_000, 15_000, 60_000];
4
+ const OUTCOME_KINDS = ['ok', 'missing', 'mismatch', 'error', 'skipped'];
5
+ const FETCH_TIMEOUT = Symbol('publish_recall_fetch_timeout');
6
+ // Public Hub fetches retain these delivery diagnostics on the unwrapped canonical asset.
7
+ const RECALL_HASH_EXCLUDED_FIELDS = [
8
+ 'asset_id',
9
+ 'gdi_score',
10
+ 'success_rate',
11
+ 'reuse_count',
12
+ 'source_node_id',
13
+ 'payload_backfill_reason',
14
+ ];
15
+ const defaultTimers = {
16
+ setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
17
+ clearTimeout: (handle) => clearTimeout(handle),
18
+ };
19
+ export function resolvePublishRecallConfig(env = process.env) {
20
+ return {
21
+ enabled: env['EVOLVE_RECALL_VERIFY'] === '1',
22
+ sampleRate: sampleRate(env['EVOLVE_RECALL_VERIFY_SAMPLE_RATE']),
23
+ queueMax: boundedInt(env['EVOLVE_RECALL_VERIFY_QUEUE_MAX'], 256, 1, 4_096),
24
+ outcomeMax: boundedInt(env['EVOLVE_RECALL_VERIFY_OUTCOME_MAX'], 256, 1, 4_096),
25
+ initialWaitMs: boundedInt(env['EVOLVE_RECALL_VERIFY_INITIAL_WAIT_MS'], 5_000, 0, 24 * 60 * 60_000),
26
+ pollMs: boundedInt(env['EVOLVE_RECALL_VERIFY_POLL_MS'], 5_000, 10, 60 * 60_000),
27
+ fetchTimeoutMs: boundedInt(env['EVOLVE_RECALL_VERIFY_FETCH_TIMEOUT_MS'], 8_000, 1, 10 * 60_000),
28
+ maxAttempts: boundedInt(env['EVOLVE_RECALL_VERIFY_ATTEMPTS'], 3, 1, 20),
29
+ backoffMs: DEFAULT_BACKOFF_MS,
30
+ };
31
+ }
32
+ export class PublishRecallVerifier {
33
+ opts;
34
+ now;
35
+ random;
36
+ timers;
37
+ stateKey;
38
+ state;
39
+ timer;
40
+ started = false;
41
+ stopping = false;
42
+ running;
43
+ activeEntry;
44
+ persistenceHealthy = true;
45
+ constructor(opts) {
46
+ this.opts = opts;
47
+ this.now = opts.now ?? (() => Date.now());
48
+ this.random = opts.random ?? Math.random;
49
+ this.timers = opts.timers ?? defaultTimers;
50
+ this.stateKey = opts.stateKey ?? DEFAULT_STATE_KEY;
51
+ this.state = this.loadState();
52
+ }
53
+ start() {
54
+ if (this.started)
55
+ return;
56
+ this.stopping = false;
57
+ this.started = true;
58
+ this.schedule();
59
+ }
60
+ async stop() {
61
+ this.started = false;
62
+ this.stopping = true;
63
+ this.clearTimer();
64
+ const active = this.running;
65
+ if (active)
66
+ await active.catch(() => undefined);
67
+ }
68
+ enqueue(input) {
69
+ if (!this.opts.config.enabled)
70
+ return { enqueued: false, reason: 'feature_disabled' };
71
+ if (!this.opts.fetchAssetById)
72
+ return { enqueued: false, reason: 'fetch_unavailable' };
73
+ const assetId = input.assetId.trim();
74
+ const publishedAt = finiteTimestamp(input.publishedAt, this.now());
75
+ const base = {
76
+ assetId,
77
+ ...(input.assetType ? { assetType: input.assetType } : {}),
78
+ attempts: 0,
79
+ };
80
+ if (!assetId)
81
+ return this.skip(base, 'missing_asset_id');
82
+ if (!isContentAssetId(assetId))
83
+ return this.skip(base, 'invalid_asset_id');
84
+ if (this.opts.config.sampleRate < 1 && this.random() >= this.opts.config.sampleRate) {
85
+ return this.skip(base, 'sampled_out');
86
+ }
87
+ if (this.state.queue.some((entry) => entry.assetId === assetId)) {
88
+ return { enqueued: false, reason: 'already_queued' };
89
+ }
90
+ while (this.state.queue.length >= this.opts.config.queueMax) {
91
+ const dropIndex = this.state.queue.findIndex((entry) => entry !== this.activeEntry);
92
+ if (dropIndex < 0)
93
+ return this.skip(base, 'queue_full');
94
+ const [dropped] = this.state.queue.splice(dropIndex, 1);
95
+ if (dropped)
96
+ this.recordOutcome({
97
+ assetId: dropped.assetId,
98
+ ...(dropped.assetType ? { assetType: dropped.assetType } : {}),
99
+ outcome: 'skipped',
100
+ reason: 'queue_full',
101
+ attempts: dropped.attempts,
102
+ at: this.now(),
103
+ latencyMs: 0,
104
+ ageMs: Math.max(0, this.now() - dropped.publishedAt),
105
+ }, false);
106
+ }
107
+ this.state.queue.push({
108
+ assetId,
109
+ ...(input.assetType ? { assetType: input.assetType } : {}),
110
+ publishedAt,
111
+ attempts: 0,
112
+ nextAttemptAt: publishedAt + this.opts.config.initialWaitMs,
113
+ });
114
+ this.persist();
115
+ this.schedule();
116
+ return { enqueued: true };
117
+ }
118
+ observeAcceptedPublish(envelope, result) {
119
+ try {
120
+ if (!this.opts.config.enabled || !this.opts.fetchAssetById)
121
+ return 0;
122
+ if (envelope.type !== 'asset_submit')
123
+ return 0;
124
+ const receipt = asRecord(result);
125
+ if (receipt['status'] !== 'accepted')
126
+ return 0;
127
+ const assets = assetsFromEnvelope(envelope);
128
+ const submittedIds = optionalStringArray(receipt['submittedAssetIds']);
129
+ const receiptIds = optionalStringArray(receipt['assetIds']);
130
+ const singleReceiptId = stringValue(receipt['assetId']);
131
+ const refs = assets.map((asset, index) => ({
132
+ assetId: submittedIds[index]
133
+ ?? receiptIds[index]
134
+ ?? (index === 0 ? singleReceiptId : undefined)
135
+ ?? verifiedEnvelopeAssetId(asset)
136
+ ?? '',
137
+ assetType: asset.type,
138
+ }));
139
+ let enqueued = 0;
140
+ for (const ref of refs) {
141
+ if (this.enqueue(ref).enqueued)
142
+ enqueued += 1;
143
+ }
144
+ return enqueued;
145
+ }
146
+ catch {
147
+ return 0;
148
+ }
149
+ }
150
+ runDue() {
151
+ if (this.stopping || !this.opts.config.enabled || !this.opts.fetchAssetById || this.running)
152
+ return Promise.resolve(0);
153
+ const run = this.processDue();
154
+ this.running = run;
155
+ return run.finally(() => {
156
+ if (this.running === run)
157
+ this.running = undefined;
158
+ this.schedule();
159
+ });
160
+ }
161
+ async processDue() {
162
+ let processed = 0;
163
+ const due = this.state.queue.filter((entry) => entry.nextAttemptAt <= this.now());
164
+ for (const entry of due) {
165
+ if (this.stopping)
166
+ break;
167
+ if (!this.state.queue.includes(entry))
168
+ continue;
169
+ this.activeEntry = entry;
170
+ try {
171
+ await this.process(entry);
172
+ }
173
+ finally {
174
+ if (this.activeEntry === entry)
175
+ this.activeEntry = undefined;
176
+ }
177
+ processed += 1;
178
+ }
179
+ return processed;
180
+ }
181
+ inspect() {
182
+ return {
183
+ version: 1,
184
+ queue: this.state.queue.map((entry) => ({ ...entry })),
185
+ outcomes: this.state.outcomes.map((outcome) => ({ ...outcome })),
186
+ counts: { ...this.state.counts },
187
+ };
188
+ }
189
+ status() {
190
+ return {
191
+ enabled: this.opts.config.enabled,
192
+ fetchAvailable: Boolean(this.opts.fetchAssetById),
193
+ queued: this.state.queue.length,
194
+ counts: { ...this.state.counts },
195
+ lastOutcome: this.state.outcomes.length > 0 ? { ...this.state.outcomes[this.state.outcomes.length - 1] } : null,
196
+ persistenceHealthy: this.persistenceHealthy,
197
+ };
198
+ }
199
+ async process(entry) {
200
+ if (entry.attempts >= this.opts.config.maxAttempts) {
201
+ this.finish(entry, 'error', 'retry_exhausted', 0);
202
+ return;
203
+ }
204
+ const startedAt = this.now();
205
+ entry.attempts += 1;
206
+ // If this process exits mid-fetch, do not let a restarted verifier immediately overlap the old request.
207
+ entry.nextAttemptAt = startedAt
208
+ + this.opts.config.fetchTimeoutMs
209
+ + this.backoffForAttempt(entry.attempts);
210
+ this.persist();
211
+ let recalled;
212
+ try {
213
+ recalled = await this.fetchWithTimeout(entry.assetId);
214
+ }
215
+ catch (error) {
216
+ const reason = error === FETCH_TIMEOUT ? 'fetch_timeout' : 'fetch_error';
217
+ // The fetch seam has no cancellation contract. Retrying a timed-out request could overlap the still-running
218
+ // operation, so timeout is terminal; ordinary failures remain retryable from their completion time.
219
+ if (error === FETCH_TIMEOUT || entry.attempts >= this.opts.config.maxAttempts) {
220
+ this.finish(entry, 'error', reason, this.elapsed(startedAt));
221
+ }
222
+ else {
223
+ this.deferFromNow(entry);
224
+ }
225
+ return;
226
+ }
227
+ if (!recalled) {
228
+ if (entry.attempts < this.opts.config.maxAttempts)
229
+ this.deferFromNow(entry);
230
+ else
231
+ this.finish(entry, 'missing', 'not_found', this.elapsed(startedAt));
232
+ return;
233
+ }
234
+ let computedAssetId;
235
+ try {
236
+ computedAssetId = wire.computeAssetId(recalled, RECALL_HASH_EXCLUDED_FIELDS);
237
+ }
238
+ catch {
239
+ this.finish(entry, 'error', 'hash_recompute_failed', this.elapsed(startedAt), recalled.asset_id);
240
+ return;
241
+ }
242
+ if (!computedAssetId) {
243
+ this.finish(entry, 'error', 'hash_recompute_failed', this.elapsed(startedAt), recalled.asset_id);
244
+ return;
245
+ }
246
+ if (recalled.asset_id !== entry.assetId || computedAssetId !== entry.assetId) {
247
+ this.finish(entry, 'mismatch', 'asset_id_mismatch', this.elapsed(startedAt), recalled.asset_id, computedAssetId ?? undefined);
248
+ return;
249
+ }
250
+ this.finish(entry, 'ok', undefined, this.elapsed(startedAt), recalled.asset_id, computedAssetId);
251
+ }
252
+ deferFromNow(entry) {
253
+ entry.nextAttemptAt = this.now() + this.backoffForAttempt(entry.attempts);
254
+ this.persist();
255
+ }
256
+ async fetchWithTimeout(assetId) {
257
+ let timeoutHandle;
258
+ const timeout = new Promise((_resolve, reject) => {
259
+ timeoutHandle = this.timers.setTimeout(() => reject(FETCH_TIMEOUT), this.opts.config.fetchTimeoutMs);
260
+ const handle = timeoutHandle;
261
+ handle?.unref?.();
262
+ });
263
+ try {
264
+ return await Promise.race([this.opts.fetchAssetById(assetId), timeout]);
265
+ }
266
+ finally {
267
+ if (timeoutHandle !== undefined) {
268
+ try {
269
+ this.timers.clearTimeout(timeoutHandle);
270
+ }
271
+ catch { /* best-effort timeout cleanup */ }
272
+ }
273
+ }
274
+ }
275
+ finish(entry, outcome, reason, latencyMs, recalledAssetId, computedAssetId) {
276
+ this.remove(entry);
277
+ const at = this.now();
278
+ this.recordOutcome({
279
+ assetId: entry.assetId,
280
+ ...(entry.assetType ? { assetType: entry.assetType } : {}),
281
+ outcome,
282
+ ...(reason ? { reason } : {}),
283
+ attempts: entry.attempts,
284
+ at,
285
+ latencyMs,
286
+ ageMs: Math.max(0, at - entry.publishedAt),
287
+ ...(recalledAssetId ? { recalledAssetId } : {}),
288
+ ...(computedAssetId ? { computedAssetId } : {}),
289
+ });
290
+ }
291
+ skip(base, reason) {
292
+ this.recordOutcome({
293
+ assetId: base.assetId,
294
+ ...(base.assetType ? { assetType: base.assetType } : {}),
295
+ outcome: 'skipped',
296
+ reason,
297
+ attempts: base.attempts,
298
+ at: this.now(),
299
+ latencyMs: 0,
300
+ });
301
+ return { enqueued: false, reason };
302
+ }
303
+ recordOutcome(outcome, persist = true) {
304
+ this.state.outcomes.push(outcome);
305
+ while (this.state.outcomes.length > this.opts.config.outcomeMax)
306
+ this.state.outcomes.shift();
307
+ this.state.counts[outcome.outcome] += 1;
308
+ if (persist)
309
+ this.persist();
310
+ }
311
+ remove(entry) {
312
+ const index = this.state.queue.indexOf(entry);
313
+ if (index >= 0)
314
+ this.state.queue.splice(index, 1);
315
+ }
316
+ elapsed(startedAt) {
317
+ return Math.max(0, this.now() - startedAt);
318
+ }
319
+ backoffForAttempt(attempt) {
320
+ const values = this.opts.config.backoffMs.length > 0 ? this.opts.config.backoffMs : DEFAULT_BACKOFF_MS;
321
+ return Math.max(0, values[Math.min(attempt - 1, values.length - 1)] ?? 0);
322
+ }
323
+ schedule() {
324
+ if (!this.started || !this.opts.config.enabled || !this.opts.fetchAssetById || this.running)
325
+ return;
326
+ this.clearTimer();
327
+ if (this.state.queue.length === 0)
328
+ return;
329
+ const earliest = Math.min(...this.state.queue.map((entry) => entry.nextAttemptAt));
330
+ const delay = Math.max(0, Math.min(this.opts.config.pollMs, earliest - this.now()));
331
+ this.timer = this.timers.setTimeout(() => {
332
+ this.timer = undefined;
333
+ void this.runDue().catch(() => { this.schedule(); });
334
+ }, delay);
335
+ const handle = this.timer;
336
+ handle?.unref?.();
337
+ }
338
+ clearTimer() {
339
+ if (this.timer === undefined)
340
+ return;
341
+ try {
342
+ this.timers.clearTimeout(this.timer);
343
+ }
344
+ catch { /* best-effort timer cleanup */ }
345
+ this.timer = undefined;
346
+ }
347
+ loadState() {
348
+ try {
349
+ const raw = this.opts.store.getState(this.stateKey);
350
+ if (!raw)
351
+ return emptyState();
352
+ const parsed = JSON.parse(raw);
353
+ const restored = restoreState(parsed, this.opts.config.queueMax, this.opts.config.outcomeMax);
354
+ if (restored)
355
+ return restored;
356
+ this.persistenceHealthy = false;
357
+ return emptyState();
358
+ }
359
+ catch {
360
+ this.persistenceHealthy = false;
361
+ return emptyState();
362
+ }
363
+ }
364
+ persist() {
365
+ try {
366
+ this.opts.store.setState(this.stateKey, JSON.stringify(this.state));
367
+ this.persistenceHealthy = true;
368
+ }
369
+ catch {
370
+ this.persistenceHealthy = false;
371
+ }
372
+ }
373
+ }
374
+ function emptyState() {
375
+ return {
376
+ version: 1,
377
+ queue: [],
378
+ outcomes: [],
379
+ counts: { ok: 0, missing: 0, mismatch: 0, error: 0, skipped: 0 },
380
+ };
381
+ }
382
+ function restoreState(value, queueMax, outcomeMax) {
383
+ const record = asRecord(value);
384
+ if (record['version'] !== 1 || !Array.isArray(record['queue']) || !Array.isArray(record['outcomes']))
385
+ return null;
386
+ const queue = record['queue'].map(parseQueueEntry).filter((entry) => Boolean(entry)).slice(-queueMax);
387
+ const outcomes = record['outcomes'].map(parseOutcome).filter((entry) => Boolean(entry)).slice(-outcomeMax);
388
+ const countsRecord = asRecord(record['counts']);
389
+ const counts = emptyState().counts;
390
+ for (const kind of OUTCOME_KINDS)
391
+ counts[kind] = finiteCount(countsRecord[kind]);
392
+ return { version: 1, queue, outcomes, counts };
393
+ }
394
+ function parseQueueEntry(value) {
395
+ const record = asRecord(value);
396
+ const assetId = stringValue(record['assetId']);
397
+ const publishedAt = finiteNumber(record['publishedAt']);
398
+ const attempts = finiteNumber(record['attempts']);
399
+ const nextAttemptAt = finiteNumber(record['nextAttemptAt']);
400
+ if (!assetId || publishedAt === undefined || attempts === undefined || nextAttemptAt === undefined)
401
+ return null;
402
+ const assetType = assetKind(record['assetType']);
403
+ return {
404
+ assetId,
405
+ ...(assetType ? { assetType } : {}),
406
+ publishedAt,
407
+ attempts: Math.max(0, Math.floor(attempts)),
408
+ nextAttemptAt,
409
+ };
410
+ }
411
+ function parseOutcome(value) {
412
+ const record = asRecord(value);
413
+ const assetId = stringValue(record['assetId']);
414
+ const outcome = OUTCOME_KINDS.find((kind) => kind === record['outcome']);
415
+ const attempts = finiteNumber(record['attempts']);
416
+ const at = finiteNumber(record['at']);
417
+ const latencyMs = finiteNumber(record['latencyMs']);
418
+ const ageMs = record['ageMs'] === undefined ? undefined : finiteNumber(record['ageMs']);
419
+ if (assetId === undefined || !outcome || attempts === undefined || at === undefined || latencyMs === undefined
420
+ || (record['ageMs'] !== undefined && ageMs === undefined))
421
+ return null;
422
+ const assetType = assetKind(record['assetType']);
423
+ return {
424
+ assetId,
425
+ ...(assetType ? { assetType } : {}),
426
+ outcome,
427
+ ...(stringValue(record['reason']) ? { reason: stringValue(record['reason']) } : {}),
428
+ attempts: Math.max(0, Math.floor(attempts)),
429
+ at,
430
+ latencyMs: Math.max(0, latencyMs),
431
+ ...(ageMs !== undefined ? { ageMs: Math.max(0, ageMs) } : {}),
432
+ ...(stringValue(record['recalledAssetId']) ? { recalledAssetId: stringValue(record['recalledAssetId']) } : {}),
433
+ ...(stringValue(record['computedAssetId']) ? { computedAssetId: stringValue(record['computedAssetId']) } : {}),
434
+ };
435
+ }
436
+ function assetsFromEnvelope(envelope) {
437
+ const payload = asRecord(envelope.payload);
438
+ if (Array.isArray(payload['assets']))
439
+ return payload['assets'].filter(isAssetRecord);
440
+ return isAssetRecord(envelope.payload) ? [envelope.payload] : [];
441
+ }
442
+ function isAssetRecord(value) {
443
+ const record = asRecord(value);
444
+ return Boolean(assetKind(record['type']));
445
+ }
446
+ function assetKind(value) {
447
+ return value === 'Gene' || value === 'Capsule' || value === 'EvolutionEvent' || value === 'AntiGene'
448
+ ? value
449
+ : undefined;
450
+ }
451
+ function asRecord(value) {
452
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
453
+ }
454
+ function optionalStringArray(value) {
455
+ return Array.isArray(value) ? value.map(stringValue) : [];
456
+ }
457
+ function stringValue(value) {
458
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
459
+ }
460
+ function isContentAssetId(value) {
461
+ return /^sha256:[a-f0-9]{64}$/i.test(value);
462
+ }
463
+ function verifiedEnvelopeAssetId(asset) {
464
+ const declared = stringValue(asset.asset_id);
465
+ if (!declared || !isContentAssetId(declared))
466
+ return undefined;
467
+ try {
468
+ return wire.computeAssetId(asset) === declared ? declared : undefined;
469
+ }
470
+ catch {
471
+ return undefined;
472
+ }
473
+ }
474
+ function finiteNumber(value) {
475
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
476
+ }
477
+ function finiteTimestamp(value, fallback) {
478
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
479
+ }
480
+ function finiteCount(value) {
481
+ const count = finiteNumber(value);
482
+ return count === undefined ? 0 : Math.max(0, Math.floor(count));
483
+ }
484
+ function boundedInt(value, fallback, min, max) {
485
+ if (!value?.trim())
486
+ return fallback;
487
+ const parsed = Number(value);
488
+ return Number.isFinite(parsed) ? Math.max(min, Math.min(max, Math.floor(parsed))) : fallback;
489
+ }
490
+ function sampleRate(value) {
491
+ if (!value?.trim())
492
+ return 1;
493
+ const parsed = Number(value);
494
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? parsed : 1;
495
+ }
@@ -12,10 +12,12 @@ export function resolveHubUrl(env) {
12
12
  return resolvePublicHubUrl(env);
13
13
  }
14
14
  function resolvePrivateHubUrl(env) {
15
- return trimmed(env['EVOMAP_HUB_URL'])
15
+ const url = trimmed(env['EVOMAP_HUB_URL'])
16
16
  ?? trimmed(env['A2A_HUB_URL'])
17
- ?? trimmed(env['EVOLVER_DEFAULT_HUB_URL'])
18
- ?? resolvePublicHubUrl({});
17
+ ?? trimmed(env['EVOLVER_DEFAULT_HUB_URL']);
18
+ if (!url)
19
+ throw new Error('private Hub URL is not configured');
20
+ return url;
19
21
  }
20
22
  function trimmed(value) {
21
23
  const v = value?.trim();
@@ -0,0 +1,48 @@
1
+ export interface SystemdNotifyHealth {
2
+ running: boolean;
3
+ ipcListening: boolean;
4
+ lifecycleArmed: boolean;
5
+ lastTickAt?: number;
6
+ nextTickDueAt?: number;
7
+ consecutiveFailures: number;
8
+ }
9
+ export type SystemdNotifyExec = (command: string, args: readonly string[], options: {
10
+ env: NodeJS.ProcessEnv;
11
+ timeout: number;
12
+ windowsHide: boolean;
13
+ }, callback: (error: Error | null) => void) => void;
14
+ interface SystemdNotifierOptions {
15
+ env?: NodeJS.ProcessEnv;
16
+ platform?: NodeJS.Platform;
17
+ now?: () => number;
18
+ health: () => SystemdNotifyHealth;
19
+ execFile?: SystemdNotifyExec;
20
+ readyRetryDelaysMs?: readonly number[];
21
+ sleep?: (delayMs: number) => Promise<void>;
22
+ notifyCommand?: string;
23
+ }
24
+ export declare function systemdWatchdogIntervalMs(env?: NodeJS.ProcessEnv): number;
25
+ export declare class SystemdNotifier {
26
+ private readonly options;
27
+ private readonly env;
28
+ private readonly platform;
29
+ private readonly now;
30
+ private readonly execFile;
31
+ private readonly readyRetryDelaysMs;
32
+ private readonly sleep;
33
+ private readonly notifyCommand;
34
+ private timer;
35
+ private readySent;
36
+ private readyInFlight;
37
+ constructor(options: SystemdNotifierOptions);
38
+ ready(): Promise<boolean>;
39
+ readyOrThrow(): Promise<void>;
40
+ stop(): void;
41
+ private active;
42
+ private startWatchdog;
43
+ private pingWatchdog;
44
+ private announceReady;
45
+ private readHealth;
46
+ private notify;
47
+ }
48
+ export {};