@evomap/evolver-core 2.0.0-beta.17 → 2.0.0-beta.19

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 (71) hide show
  1. package/dist/algo/candidateAssembly.js +21 -2
  2. package/dist/algo/cycleEngine.d.ts +12 -0
  3. package/dist/algo/cycleEngine.js +36 -4
  4. package/dist/algo/geneHealth.d.ts +2 -2
  5. package/dist/algo/geneHealth.js +5 -4
  6. package/dist/algo/geneSelection.d.ts +1 -1
  7. package/dist/algo/orchestrator.js +9 -2
  8. package/dist/assetstore/assetSidecarRecords.js +4 -0
  9. package/dist/assetstore/assetStoreHealth.js +41 -24
  10. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  11. package/dist/assetstore/assetStoreStorage.js +16 -7
  12. package/dist/assetstore/localJsonl.d.ts +2 -1
  13. package/dist/assetstore/localJsonl.js +54 -10
  14. package/dist/assetstore/provenance.d.ts +24 -0
  15. package/dist/assetstore/provenance.js +219 -12
  16. package/dist/assetstore/provider.d.ts +20 -1
  17. package/dist/assetstore/provider.js +34 -1
  18. package/dist/bootstrap/index.d.ts +2 -1
  19. package/dist/bootstrap/index.js +2 -1
  20. package/dist/bootstrap/v1EnvCompat.d.ts +110 -0
  21. package/dist/bootstrap/v1EnvCompat.js +256 -0
  22. package/dist/events/public.d.ts +1 -1
  23. package/dist/events/public.js +1 -1
  24. package/dist/events/reports.d.ts +2 -0
  25. package/dist/events/reports.js +4 -0
  26. package/dist/exec/autoExec.d.ts +18 -1
  27. package/dist/exec/autoExec.js +24 -9
  28. package/dist/exec/autonomousCycle.d.ts +19 -4
  29. package/dist/exec/autonomousCycle.js +63 -13
  30. package/dist/exec/claudeBridge.d.ts +25 -7
  31. package/dist/exec/claudeBridge.js +264 -29
  32. package/dist/exec/prompt.js +5 -1
  33. package/dist/exec/runnerRegistry.d.ts +68 -26
  34. package/dist/exec/runnerRegistry.js +307 -72
  35. package/dist/exec/selfPr.js +1 -7
  36. package/dist/feedback/envelope.d.ts +61 -0
  37. package/dist/feedback/envelope.js +168 -0
  38. package/dist/feedback/index.d.ts +1 -0
  39. package/dist/feedback/index.js +1 -0
  40. package/dist/hub/assetCallLog.d.ts +35 -1
  41. package/dist/hub/assetCallLog.js +124 -1
  42. package/dist/hub/bindings.d.ts +8 -1
  43. package/dist/hub/bindings.js +17 -6
  44. package/dist/hub/capability.d.ts +11 -1
  45. package/dist/hub/fake.d.ts +2 -2
  46. package/dist/hub/fake.js +1 -1
  47. package/dist/index.d.ts +3 -1
  48. package/dist/index.js +4 -1
  49. package/dist/mailbox/dispatch.d.ts +1 -1
  50. package/dist/mailbox/dispatch.js +22 -6
  51. package/dist/mailbox/envelope.d.ts +7 -1
  52. package/dist/mailbox/envelope.js +9 -2
  53. package/dist/mailbox/ipcServer.d.ts +10 -2
  54. package/dist/mailbox/ipcServer.js +163 -13
  55. package/dist/mailbox/store.d.ts +38 -2
  56. package/dist/mailbox/store.js +416 -27
  57. package/dist/signals/curriculum.d.ts +55 -0
  58. package/dist/signals/curriculum.js +202 -0
  59. package/dist/signals/expand.js +17 -6
  60. package/dist/signals/index.d.ts +2 -1
  61. package/dist/signals/index.js +2 -1
  62. package/dist/strategy/constraintAblation.js +115 -369
  63. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  64. package/dist/strategy/constraintAblationPredicates.js +339 -0
  65. package/dist/trace/index.d.ts +2 -1
  66. package/dist/trace/index.js +2 -1
  67. package/dist/trace/proxyTurns.d.ts +31 -0
  68. package/dist/trace/proxyTurns.js +137 -0
  69. package/dist/verify/validation.d.ts +11 -1
  70. package/dist/verify/validation.js +31 -0
  71. package/package.json +4 -1
@@ -1,11 +1,13 @@
1
1
  import { ulid as makeUlid } from 'ulid';
2
2
  import { specForType, assertKnownType } from './catalog.js';
3
- export const ENVELOPE_SCHEMA_VERSION = '1.0.0';
3
+ export const PRIORITIES = Object.freeze(['high', 'normal', 'low']);
4
+ export const ENVELOPE_SCHEMA_VERSION = '1.1.0';
4
5
  /** 固定 envelope 字段集 (schema-snapshot 锁; 增删字段必须改此处). */
5
6
  export const ENVELOPE_FIELDS = [
6
7
  'id', 'type', 'direction', 'status', 'handler', 'payload', 'correlationId', 'replyTo',
7
8
  'receiptId', 'idempotencyKey', 'sourceAgent', 'targetAgent', 'runtimeNamespace',
8
- 'attempts', 'nextRetryAt', 'ttlAt', 'createdAt', 'updatedAt', 'schemaVersion', 'feedsMaterial',
9
+ 'priority', 'attempts', 'nextRetryAt', 'ttlAt', 'createdAt', 'updatedAt', 'schemaVersion', 'feedsMaterial',
10
+ 'lastError',
9
11
  ];
10
12
  const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
11
13
  /** 防原型污染 (v1 GHSA). */
@@ -22,6 +24,9 @@ export function sanitizePayload(v) {
22
24
  return v;
23
25
  }
24
26
  const TTL_MS = { control: 60 * 60 * 1000, default: 7 * 24 * 60 * 60 * 1000 };
27
+ export function normalizePriority(value) {
28
+ return value === 'high' || value === 'low' ? value : 'normal';
29
+ }
25
30
  export function createEnvelope(input) {
26
31
  const spec = assertKnownType(input.type);
27
32
  const id = input.id ?? makeUlid();
@@ -40,6 +45,7 @@ export function createEnvelope(input) {
40
45
  sourceAgent: input.sourceAgent ?? '',
41
46
  targetAgent: input.targetAgent ?? '',
42
47
  runtimeNamespace: input.runtimeNamespace ?? 'default',
48
+ priority: normalizePriority(input.priority),
43
49
  attempts: 0,
44
50
  nextRetryAt: null,
45
51
  ttlAt: now + TTL_MS[spec.ttlClass],
@@ -47,6 +53,7 @@ export function createEnvelope(input) {
47
53
  updatedAt: now,
48
54
  schemaVersion: ENVELOPE_SCHEMA_VERSION,
49
55
  feedsMaterial: spec.feedsMaterial,
56
+ lastError: null,
50
57
  };
51
58
  }
52
59
  export { specForType };
@@ -1,11 +1,13 @@
1
1
  import { type IncomingMessage, type ServerResponse } from 'node:http';
2
2
  import { type Envelope } from './envelope.js';
3
- import type { MailboxStore } from './store.js';
3
+ import { type MailboxStore } from './store.js';
4
4
  export interface IpcServerOptions {
5
5
  store: MailboxStore;
6
6
  token: string;
7
7
  host?: string;
8
8
  now?: () => number;
9
+ /** When set, every mailbox route is pinned to this daemon namespace. */
10
+ runtimeNamespace?: string;
9
11
  extraRoutes?: IpcRouteHandler[];
10
12
  onSend?: (envelope: Envelope, result: {
11
13
  receiptId: string;
@@ -34,6 +36,7 @@ export declare class MailboxIpcServer {
34
36
  private readonly token;
35
37
  private readonly host;
36
38
  private readonly now;
39
+ private readonly runtimeNamespace;
37
40
  private readonly extraRoutes;
38
41
  private readonly onSend;
39
42
  private readonly onAuthFailure;
@@ -42,5 +45,10 @@ export declare class MailboxIpcServer {
42
45
  close(): Promise<void>;
43
46
  private handle;
44
47
  private readJson;
48
+ private resolveSendNamespace;
49
+ private resolveRuntimeNamespace;
50
+ private messageInRuntime;
51
+ private canMutateMessage;
45
52
  private json;
46
- }
53
+ }
54
+ export declare function legacyMailboxMessage(message: Envelope): Record<string, unknown>;
@@ -1,8 +1,33 @@
1
1
  import { createServer } from 'node:http';
2
2
  import { timingSafeEqual } from 'node:crypto';
3
3
  import { createEnvelope } from './envelope.js';
4
+ import { mailboxClaimOwner } from './store.js';
4
5
  const MAX_BODY = 256 * 1024; // 256KB 上限(防本机注入超大体)
5
6
  const LOOPBACK = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
7
+ const STATUSES = new Set(['pending', 'in_flight', 'done', 'failed', 'expired']);
8
+ const DIRECTIONS = new Set(['outbound', 'inbound', 'local']);
9
+ const PRIORITIES = new Set(['high', 'normal', 'low']);
10
+ function serializeClaimedMessage(message) {
11
+ const claimToken = mailboxClaimOwner(message);
12
+ if (!claimToken)
13
+ throw new Error('claimed mailbox message is missing its claim token');
14
+ return { ...message, claimToken };
15
+ }
16
+ function requiredClaimToken(value) {
17
+ if (typeof value !== 'string' || value.length === 0)
18
+ throw new Error('claimToken must be a non-empty string');
19
+ return value;
20
+ }
21
+ function optionalQueryInteger(url, name) {
22
+ const raw = url.searchParams.get(name);
23
+ if (raw === null)
24
+ return undefined;
25
+ const value = Number(raw);
26
+ if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {
27
+ throw new Error(`${name} must be a non-negative integer`);
28
+ }
29
+ return value;
30
+ }
6
31
  /** 定长常量时间比较(防 token 计时侧信道). */
7
32
  function tokenEq(a, b) {
8
33
  const ba = Buffer.from(a), bb = Buffer.from(b);
@@ -20,6 +45,7 @@ export class MailboxIpcServer {
20
45
  token;
21
46
  host;
22
47
  now;
48
+ runtimeNamespace;
23
49
  extraRoutes;
24
50
  onSend;
25
51
  onAuthFailure;
@@ -28,6 +54,7 @@ export class MailboxIpcServer {
28
54
  this.token = opts.token;
29
55
  this.host = opts.host ?? '127.0.0.1';
30
56
  this.now = opts.now ?? (() => Date.now());
57
+ this.runtimeNamespace = opts.runtimeNamespace;
31
58
  this.extraRoutes = opts.extraRoutes ?? [];
32
59
  this.onSend = opts.onSend;
33
60
  this.onAuthFailure = opts.onAuthFailure;
@@ -90,41 +117,98 @@ export class MailboxIpcServer {
90
117
  }
91
118
  if (route === 'POST /mailbox/send') {
92
119
  const body = (await this.readJson(req));
93
- const env = createEnvelope({ ...body, now });
120
+ if (body.priority !== undefined && !PRIORITIES.has(body.priority)) {
121
+ throw new Error('priority must be high, normal, or low');
122
+ }
123
+ const legacyRef = typeof body.ref_id === 'string' && body.ref_id ? body.ref_id : undefined;
124
+ const runtimeNamespace = this.resolveSendNamespace(body);
125
+ let env = createEnvelope({
126
+ ...body,
127
+ runtimeNamespace,
128
+ correlationId: body.correlationId ?? legacyRef,
129
+ now,
130
+ });
131
+ if (body.expires_at !== undefined) {
132
+ if (typeof body.expires_at !== 'number' || !Number.isFinite(body.expires_at)) {
133
+ throw new Error('expires_at must be a finite epoch-millisecond number');
134
+ }
135
+ env = { ...env, ttlAt: body.expires_at };
136
+ }
94
137
  const r = this.store.send(env);
95
138
  if (r.stored)
96
139
  this.onSend?.(env, r);
97
- return this.json(res, 200, { id: env.id, receiptId: r.receiptId, stored: r.stored, correlationId: env.correlationId });
140
+ return this.json(res, 200, {
141
+ id: env.id,
142
+ receiptId: r.receiptId,
143
+ stored: r.stored,
144
+ correlationId: env.correlationId,
145
+ message_id: env.id,
146
+ status: env.status,
147
+ });
98
148
  }
99
149
  if (route === 'POST /mailbox/claim') {
100
150
  const b = (await this.readJson(req));
101
- const got = this.store.claim(b.handler, b.limit ?? 16, b.leaseMs ?? 30_000, now, b.runtimeNamespace);
102
- return this.json(res, 200, { messages: got });
151
+ const runtimeNamespace = this.resolveRuntimeNamespace(b.runtimeNamespace);
152
+ const got = this.store.claim(b.handler, b.limit ?? 16, b.leaseMs ?? 30_000, now, runtimeNamespace);
153
+ return this.json(res, 200, { messages: got.map(serializeClaimedMessage) });
103
154
  }
104
155
  if (route === 'POST /mailbox/complete') {
105
156
  const b = (await this.readJson(req));
106
- this.store.complete(b.id, now);
157
+ if (!this.canMutateMessage(b.id))
158
+ return this.json(res, 404, { error: 'not found' });
159
+ const claimToken = requiredClaimToken(b.claimToken);
160
+ if (!this.store.completeClaimed(b.id, now, claimToken)) {
161
+ return this.json(res, 409, { error: 'mailbox claim is no longer owned by this worker', code: 'claim_conflict' });
162
+ }
107
163
  return this.json(res, 200, { ok: true });
108
164
  }
109
165
  if (route === 'POST /mailbox/fail') {
110
166
  const b = (await this.readJson(req));
111
- this.store.fail(b.id, b.error ?? 'ipc fail', now);
167
+ if (!this.canMutateMessage(b.id))
168
+ return this.json(res, 404, { error: 'not found' });
169
+ const claimToken = requiredClaimToken(b.claimToken);
170
+ if (!this.store.failClaimed(b.id, b.error ?? 'ipc fail', now, claimToken)) {
171
+ return this.json(res, 409, { error: 'mailbox claim is no longer owned by this worker', code: 'claim_conflict' });
172
+ }
112
173
  return this.json(res, 200, { ok: true });
113
174
  }
114
- if (req.method === 'GET' && url.pathname === '/mailbox/status') {
115
- const id = url.searchParams.get('id') ?? '';
175
+ const statusPathMatch = req.method === 'GET' ? /^\/mailbox\/status\/([^/]+)$/.exec(url.pathname) : null;
176
+ if (req.method === 'GET' && (url.pathname === '/mailbox/status' || statusPathMatch)) {
177
+ const id = statusPathMatch ? decodeURIComponent(statusPathMatch[1] ?? '') : url.searchParams.get('id') ?? '';
178
+ const message = this.messageInRuntime(id);
179
+ if (!message)
180
+ return this.json(res, 404, { error: 'not found' });
181
+ if (statusPathMatch) {
182
+ return this.json(res, 200, legacyMailboxMessage(message));
183
+ }
116
184
  const s = this.store.getStatus(id);
117
185
  return s ? this.json(res, 200, s) : this.json(res, 404, { error: 'not found' });
118
186
  }
119
187
  if (req.method === 'GET' && url.pathname === '/mailbox/list') {
120
- const status = url.searchParams.get('status') ?? undefined;
188
+ const type = url.searchParams.get('type') ?? undefined;
189
+ const legacy = type !== undefined;
190
+ const rawStatus = url.searchParams.get('status') ?? undefined;
191
+ const statusValue = legacyMailboxStatus(rawStatus);
192
+ if (rawStatus !== undefined && statusValue === undefined)
193
+ throw new Error('invalid status');
194
+ const directionValue = url.searchParams.get('direction') ?? undefined;
195
+ if (directionValue !== undefined && !DIRECTIONS.has(directionValue)) {
196
+ throw new Error('invalid direction');
197
+ }
121
198
  const got = this.store.list({
122
- status: status,
199
+ status: statusValue,
123
200
  handler: url.searchParams.get('handler') ?? undefined,
124
- runtimeNamespace: url.searchParams.get('runtimeNamespace') ?? undefined,
125
- limit: Number(url.searchParams.get('limit') ?? 200),
201
+ runtimeNamespace: this.resolveRuntimeNamespace(url.searchParams.get('runtimeNamespace') ?? undefined),
202
+ type,
203
+ direction: directionValue,
204
+ newestFirst: legacy,
205
+ offset: optionalQueryInteger(url, 'offset'),
206
+ limit: optionalQueryInteger(url, 'limit') ?? 200,
207
+ });
208
+ return this.json(res, 200, {
209
+ messages: legacy ? got.map(legacyMailboxMessage) : got,
210
+ count: got.length,
126
211
  });
127
- return this.json(res, 200, { messages: got });
128
212
  }
129
213
  return this.json(res, 404, { error: 'unknown route' });
130
214
  }
@@ -161,8 +245,74 @@ export class MailboxIpcServer {
161
245
  req.on('error', reject);
162
246
  });
163
247
  }
248
+ resolveSendNamespace(body) {
249
+ const bound = this.runtimeNamespace;
250
+ if (bound === undefined)
251
+ return body.runtimeNamespace;
252
+ const requested = body.runtimeNamespace;
253
+ if (requested !== undefined && (typeof requested !== 'string' || requested !== bound)) {
254
+ throw new Error('runtimeNamespace does not match the IPC server namespace');
255
+ }
256
+ const channel = body.channel;
257
+ if (channel !== undefined && (typeof channel !== 'string'
258
+ || (channel !== 'evomap-hub' && channel !== bound))) {
259
+ throw new Error('channel does not match the IPC server namespace');
260
+ }
261
+ return bound;
262
+ }
263
+ resolveRuntimeNamespace(requested) {
264
+ if (requested !== undefined && typeof requested !== 'string') {
265
+ throw new Error('runtimeNamespace must be a string');
266
+ }
267
+ const bound = this.runtimeNamespace;
268
+ if (bound === undefined)
269
+ return requested;
270
+ if (requested !== undefined && requested !== bound) {
271
+ throw new Error('runtimeNamespace does not match the IPC server namespace');
272
+ }
273
+ return bound;
274
+ }
275
+ messageInRuntime(id) {
276
+ const message = this.store.getById(id);
277
+ if (this.runtimeNamespace !== undefined && message?.runtimeNamespace !== this.runtimeNamespace)
278
+ return undefined;
279
+ return message;
280
+ }
281
+ canMutateMessage(id) {
282
+ return this.messageInRuntime(id) !== undefined;
283
+ }
164
284
  json(res, code, body) {
165
285
  res.writeHead(code, { 'content-type': 'application/json' });
166
286
  res.end(JSON.stringify(body));
167
287
  }
288
+ }
289
+ export function legacyMailboxMessage(message) {
290
+ return {
291
+ id: message.id,
292
+ message_id: message.id,
293
+ channel: message.runtimeNamespace === 'default' ? 'evomap-hub' : message.runtimeNamespace,
294
+ direction: message.direction,
295
+ type: message.type,
296
+ status: message.status === 'done' ? (message.direction === 'inbound' ? 'delivered' : 'synced') : message.status,
297
+ payload: message.payload,
298
+ priority: message.priority,
299
+ ref_id: message.replyTo ?? message.correlationId,
300
+ created_at: message.createdAt,
301
+ synced_at: message.status === 'done' ? message.updatedAt : null,
302
+ expires_at: message.ttlAt,
303
+ retry_count: message.attempts,
304
+ next_retry_at: message.nextRetryAt,
305
+ error: message.lastError,
306
+ };
307
+ }
308
+ function legacyMailboxStatus(value) {
309
+ if (value === undefined)
310
+ return undefined;
311
+ if (STATUSES.has(value))
312
+ return value;
313
+ if (value === 'delivered' || value === 'synced' || value === 'acked')
314
+ return 'done';
315
+ if (value === 'rejected')
316
+ return 'failed';
317
+ return undefined;
168
318
  }
@@ -1,7 +1,14 @@
1
1
  import { type Envelope, type Status, type CreateEnvelopeInput } from './envelope.js';
2
2
  export declare const MAX_ATTEMPTS = 5;
3
3
  export declare const DEFAULT_IMPORT_JSONL_MAX_LINE_BYTES: number;
4
+ export declare const MAILBOX_CLAIM_OWNER: unique symbol;
5
+ export type ClaimedEnvelope = Envelope & {
6
+ readonly [MAILBOX_CLAIM_OWNER]: string;
7
+ };
8
+ export declare function mailboxClaimOwner(envelope: Envelope): string | undefined;
4
9
  export declare function expBackoffMs(attempt: number): number;
10
+ /** Read one mailbox state value without creating, migrating, or taking write ownership of the database. */
11
+ export declare function readMailboxState(path: string, key: string): string | undefined;
5
12
  /** mailbox sqlite 引擎 (WAL + busy_timeout). 状态机 pending→in_flight→done/failed/expired + 租约 + 重试/DLQ. */
6
13
  export declare class MailboxStore {
7
14
  private readonly db;
@@ -47,12 +54,31 @@ export declare class MailboxStore {
47
54
  hasMessageWithIdempotencyKey(idempotencyKey: string): boolean;
48
55
  hasMessageWithPayload(type: string, payload: unknown): boolean;
49
56
  /** 原子 claim: pending(或租约过期的 in_flight) → in_flight + 租约. 可按 runtimeNamespace 分区. */
50
- claim(handler: string, limit: number, leaseMs: number, now: number, runtimeNamespace?: string): Envelope[];
57
+ claim(handler: string, limit: number, leaseMs: number, now: number, runtimeNamespace?: string): ClaimedEnvelope[];
51
58
  complete(id: string, now: number): void;
52
59
  /** 失败: attempts<N→退避回 pending; ≥N→DLQ(不自动丢). */
53
60
  fail(id: string, err: string, now: number, maxAttempts?: number): void;
54
61
  /** 暂缓: transient upstream outage 不消耗 attempts, 仅设置下次可 claim 时间. */
55
62
  defer(id: string, err: string, now: number, retryAfterMs: number): void;
63
+ deferUnlessProcessed(id: string, processedKey: string, err: string, now: number, retryAfterMs: number): boolean;
64
+ failUnlessProcessed(id: string, processedKey: string, err: string, now: number, maxAttempts?: number): boolean;
65
+ completeClaimed(id: string, now: number, claimOwner: string): boolean;
66
+ /** Atomically complete the current worker's lease and persist its idempotency result. */
67
+ completeClaimedAndMarkProcessed(id: string, processedKey: string, result: unknown, now: number, claimOwner: string): boolean;
68
+ renewClaim(id: string, now: number, leaseMs: number, claimOwner: string): boolean;
69
+ setClaimedCheckpoint(id: string, checkpoint: unknown, now: number, claimOwner: string): boolean;
70
+ getClaimedCheckpoint(id: string): unknown | undefined;
71
+ deferClaimed(id: string, err: string, now: number, retryAfterMs: number, claimOwner: string): boolean;
72
+ failClaimed(id: string, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
73
+ /** Transition only the row held by the current worker, unless a concurrent success marker already exists. */
74
+ deferClaimedUnlessProcessed(id: string, processedKey: string, err: string, now: number, retryAfterMs: number, claimOwner: string): boolean;
75
+ /** Transition only the row held by the current worker, unless a concurrent success marker already exists. */
76
+ failClaimedUnlessProcessed(id: string, processedKey: string, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
77
+ /** Atomically fail a non-leased intent and persist the replayable terminal result. */
78
+ failAndMarkProcessedUnlessProcessed(id: string, blockingProcessedKeys: readonly string[], resultKey: string, result: unknown, err: string, now: number, maxAttempts?: number): boolean;
79
+ /** Atomically fail the current worker's leased intent and persist the replayable terminal result. */
80
+ failClaimedAndMarkProcessedUnlessProcessed(id: string, blockingProcessedKeys: readonly string[], resultKey: string, result: unknown, err: string, now: number, claimOwner: string, maxAttempts?: number): boolean;
81
+ private transitionUnlessProcessed;
56
82
  /** DLQ 重放(人工/agent 显式, 不自动丢). */
57
83
  replayDlq(id: string, now: number): void;
58
84
  dlq(): Envelope[];
@@ -73,16 +99,26 @@ export declare class MailboxStore {
73
99
  id: string;
74
100
  type: string;
75
101
  status: Status;
102
+ priority: Envelope['priority'];
76
103
  attempts: number;
77
104
  nextRetryAt: number | null;
78
105
  ttlAt: number | null;
79
106
  dlq: boolean;
107
+ lastError: string | null;
80
108
  } | undefined;
81
109
  /** 幂等(A13): 副作用 handler 用 idempotencyKey 去重; 命中返缓存不重跑. */
82
110
  isProcessed(key: string): boolean;
83
111
  getProcessed(key: string): unknown;
84
112
  markProcessed(key: string, result: unknown, now: number): void;
113
+ replaceProcessed(key: string, result: unknown, now: number): void;
114
+ /** Persist a monotonic outcome and its lightweight concurrency marker in one SQLite transaction. */
115
+ replaceProcessedWithMarker(key: string, result: unknown, markerKey: string, markerResult: unknown, now: number): void;
116
+ /** Backfill a concurrency marker only when the durable source result still satisfies the caller's predicate. */
117
+ markProcessedIf(sourceKey: string, markerKey: string, markerResult: unknown, now: number, matches: (sourceResult: unknown) => boolean): boolean;
118
+ deleteProcessed(keys: readonly string[]): void;
119
+ private messageClaimOwner;
85
120
  /** v1 messages.jsonl → sqlite 迁移(幂等可重入). */
86
- importJsonl(path: string): number;
121
+ importJsonl(source: string | number): number;
122
+ private applyImportedV1Update;
87
123
  close(): void;
88
124
  }