@axiom-lattice/local-stores 3.1.24 → 3.2.0

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,5 +1,5 @@
1
1
 
2
- > @axiom-lattice/local-stores@3.1.24 build /home/runner/work/agentic/agentic/packages/local-stores
2
+ > @axiom-lattice/local-stores@3.2.0 build /home/runner/work/agentic/agentic/packages/local-stores
3
3
  > tsup src/index.ts --format cjs,esm --dts --sourcemap
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,13 +8,13 @@
8
8
  CLI Target: es2020
9
9
  CJS Build start
10
10
  ESM Build start
11
- CJS dist/index.js 239.26 KB
12
- CJS dist/index.js.map 434.80 KB
13
- CJS ⚡️ Build success in 587ms
14
- ESM dist/index.mjs 234.63 KB
15
- ESM dist/index.mjs.map 431.04 KB
16
- ESM ⚡️ Build success in 599ms
11
+ ESM dist/index.mjs 237.70 KB
12
+ ESM dist/index.mjs.map 436.10 KB
13
+ ESM ⚡️ Build success in 568ms
14
+ CJS dist/index.js 242.32 KB
15
+ CJS dist/index.js.map 439.84 KB
16
+ CJS ⚡️ Build success in 569ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 12512ms
19
- DTS dist/index.d.ts 44.04 KB
20
- DTS dist/index.d.mts 44.04 KB
18
+ DTS ⚡️ Build success in 12893ms
19
+ DTS dist/index.d.ts 44.67 KB
20
+ DTS dist/index.d.mts 44.67 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # @axiom-lattice/local-stores
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - eafaac8: Multi-gateway (PostgreSQL-only) coordination primitives — Plans 1 & 2.
8
+
9
+ **Plan 1 — replica identity + distributed leases**
10
+
11
+ - `getReplicaId()` stable per-process replica identity (`AXIOM_REPLICA_ID` or `hostname:pid`).
12
+ - `LeaseStore` / `LeaseRecord` protocol, `InMemoryLeaseStore` (registered as the default `"lease"` store), and `PostgreSQLLeaseStore` (migration v185 `lattice_leases`; atomic self-expiring upsert).
13
+ - `withLease(store, key, ownerLabel, fn)` — distributed critical-section helper with a unique per-invocation holder, bounded acquisition, heartbeat, and release-in-finally.
14
+
15
+ **Plan 2 — atomic thread-message-queue claim + lease**
16
+
17
+ - `ClaimMessagesOptions` and `IMessageQueueStore.claimPendingMessages` / `reclaimExpiredProcessing`.
18
+ - PostgreSQL migration v186 adds `locked_by` / `locked_at` / `lease_expires_at`; claim is an atomic `UPDATE … WHERE id IN (SELECT … FOR UPDATE SKIP LOCKED)` so concurrent Gateway replicas receive disjoint sets.
19
+ - In-memory and local SQLite implementations match the same semantics.
20
+
21
+ No existing behavior changes: the legacy `markProcessing` path is untouched; `Agent` is not yet rewired to the new claim (that is a follow-up plan).
22
+
23
+ - 6080389: Scheduler exactly-once across replicas (claim-before-execute).
24
+
25
+ - `ScheduleStorage.claimScheduledRun(taskId)` atomically transitions `pending -> running` (Memory/Local/PostgreSQL implementations).
26
+ - `DefaultScheduleClient.executeTask` claims before running the handler, so a fire armed on multiple replicas executes only once (losers skip). Retry/CRON-rearm paths re-set `PENDING`, so later fires are claimable. See ADR-158.
27
+
28
+ - 7ecbd01: Recover scheduled tasks stuck `running` after a crash.
29
+
30
+ - `ScheduleStorage.requeueStaleRunning(olderThanMs)` requeues `running` tasks older than a threshold back to `pending` (Memory/Local/PostgreSQL).
31
+ - `DefaultScheduleClient.restore()` requeues stale `running` schedules (30-minute heuristic) before re-arming, so a CRON schedule is no longer permanently stalled by a crash that left an unsettled claim. See ADR-158.
32
+
33
+ ### Patch Changes
34
+
35
+ - Updated dependencies [6addbbf]
36
+ - Updated dependencies [66e6080]
37
+ - Updated dependencies [360f9e9]
38
+ - Updated dependencies [eafaac8]
39
+ - Updated dependencies [b8c4c92]
40
+ - Updated dependencies [4c99955]
41
+ - Updated dependencies [167620b]
42
+ - Updated dependencies [d3cd2d5]
43
+ - Updated dependencies [396ad50]
44
+ - Updated dependencies [eec9db7]
45
+ - Updated dependencies [d4429f1]
46
+ - Updated dependencies [6080389]
47
+ - Updated dependencies [7ecbd01]
48
+ - Updated dependencies [e5e7b0f]
49
+ - Updated dependencies [00b179d]
50
+ - Updated dependencies [5f3d14b]
51
+ - @axiom-lattice/core@6.3.0
52
+ - @axiom-lattice/protocols@4.7.0
53
+
3
54
  ## 3.1.24
4
55
 
5
56
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SqlJsStatic, Database } from 'sql.js';
2
2
  import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
3
3
  import { ThreadStore, Thread, CreateThreadRequest, AssistantStore, Assistant, CreateAssistantRequest, WorkspaceStore, Workspace, CreateWorkspaceRequest, UpdateWorkspaceRequest, ProjectStore, ProjectFilter, Project, CreateProjectRequest, UpdateProjectRequest, UpdateProjectCapabilityBundlesResult, ProjectRoomStore, ProjectRoom, ProjectMembershipStore, ProjectMembership, ProjectHumanRole, ProjectMembershipMutationResult, ProjectBotMembershipStore, ProjectBotMembership, ProjectRoomMessageStore, ProjectRoomMessage, ProjectRoomReadStateStore, ProjectRoomReadState, UserStore, User, CreateUserRequest, UpdateUserRequest, TenantStore, Tenant, CreateTenantRequest, UpdateTenantRequest, UserTenantLinkStore, UserTenantLink, CreateUserTenantLinkRequest, UpdateUserTenantLinkRequest, DatabaseConfigStore, DatabaseConfigEntry, CreateDatabaseConfigRequest, UpdateDatabaseConfigRequest, ConnectionStore, ConnectionEntry, MetricsServerConfigStore, MetricsServerConfigEntry, CreateMetricsServerConfigRequest, UpdateMetricsServerConfigRequest, McpServerConfigStore, McpServerConfigEntry, CreateMcpServerConfigRequest, UpdateMcpServerConfigRequest, ModelProviderStore, ModelProviderEntry, CreateModelProviderRequest, UpdateModelProviderRequest, ModelProviderStatus, ModelProviderModelEntry, ProviderModelInput, WorkflowTrackingStore, CreateWorkflowRunRequest, WorkflowRun, UpdateWorkflowRunRequest, QueryWorkflowRunsOptions, QueryWorkflowRunsResult, CreateRunStepRequest, RunStep, UpdateRunStepRequest, StepType, EvalStore, EvalProject, CreateEvalProjectRequest, EvalSuite, CreateEvalSuiteRequest, EvalCase, CreateEvalCaseRequest, EvalRun, CreateEvalRunRequest, EvalRunResult, EvalProjectReport, BindingRegistry, Binding, CreateBindingInput, BindingMutablePatch, BindingListParams, ChannelInstallationStore, ChannelInstallation, ChannelInstallationType, CreateChannelInstallationInput, UpdateChannelInstallationRequest, A2AApiKeyStore, A2AApiKeyRecord, CreateA2AApiKeyInput, UpdateA2AApiKeyInput, A2AApiKeyEntry, OpenAuditStore, OpenAuditQuery, OpenAuditRecord, OpenAuditAppendInput, SkillStore, SkillStoreContext, Skill, CreateSkillRequest, ScheduleStorage, ScheduledTaskDefinition, ScheduledTaskStatus, ScheduleExecutionType, TaskStore, CreateTaskRequest, TaskItem, TaskListFilter, TaskDependentListQuery, UpdateTaskRequest, TaskMutationSnapshot, TaskWorkItemStore, CreateWorkItemRequest, TaskWorkItem, CreateWorkItemIfAbsentRequest, TaskWorkItemListFilter, ProjectLifecycleEventQuery, CapabilityBundleStore, CapabilityBundle, InternalCreateCapabilityBundleInput, InternalUpdateCapabilityBundleInput, CapabilityBundleDeleteResult, IConversationStore, ConversationRecord, CreateConversationInput } from '@axiom-lattice/protocols';
4
- import { IMessageQueueStore, AddMessageParams, PendingMessage, QueueScope, ThreadInfo } from '@axiom-lattice/core';
4
+ import { IMessageQueueStore, AddMessageParams, PendingMessage, QueueScope, ClaimMessagesOptions, ThreadInfo } from '@axiom-lattice/core';
5
5
 
6
6
  /**
7
7
  * Shared SQLite database connection for local stores using sql.js (WASM).
@@ -720,6 +720,8 @@ declare class LocalThreadMessageQueueStore implements IMessageQueueStore {
720
720
  addMessageAtHead(params: AddMessageParams): Promise<PendingMessage>;
721
721
  getPendingMessages(threadId: string, scope?: QueueScope): Promise<PendingMessage[]>;
722
722
  getProcessingMessages(threadId: string, scope?: QueueScope): Promise<PendingMessage[]>;
723
+ claimPendingMessages(threadId: string, scope: QueueScope | undefined, options: ClaimMessagesOptions): Promise<PendingMessage[]>;
724
+ reclaimExpiredProcessing(threadId: string, scope?: QueueScope): Promise<number>;
723
725
  getQueueSize(threadId: string, scope?: QueueScope): Promise<number>;
724
726
  getThreadsWithPendingMessages(): Promise<ThreadInfo[]>;
725
727
  removeMessage(messageId: string, scope?: QueueScope): Promise<boolean>;
@@ -760,6 +762,15 @@ declare class LocalScheduleStorage implements ScheduleStorage {
760
762
  save(task: ScheduledTaskDefinition): Promise<void>;
761
763
  get(taskId: string): Promise<ScheduledTaskDefinition | null>;
762
764
  update(taskId: string, updates: Partial<ScheduledTaskDefinition>): Promise<void>;
765
+ claimScheduledRun(taskId: string): Promise<boolean>;
766
+ /**
767
+ * Requeues `running` tasks whose `updated_at` is older than `olderThanMs` back
768
+ * to `pending` (recovery after a crash that left a claim unsettled).
769
+ *
770
+ * @param olderThanMs Age threshold in milliseconds.
771
+ * @returns The number of tasks requeued.
772
+ */
773
+ requeueStaleRunning(olderThanMs: number): Promise<number>;
763
774
  delete(taskId: string): Promise<void>;
764
775
  getActiveTasks(): Promise<ScheduledTaskDefinition[]>;
765
776
  getTasksByType(taskType: string): Promise<ScheduledTaskDefinition[]>;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SqlJsStatic, Database } from 'sql.js';
2
2
  import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
3
3
  import { ThreadStore, Thread, CreateThreadRequest, AssistantStore, Assistant, CreateAssistantRequest, WorkspaceStore, Workspace, CreateWorkspaceRequest, UpdateWorkspaceRequest, ProjectStore, ProjectFilter, Project, CreateProjectRequest, UpdateProjectRequest, UpdateProjectCapabilityBundlesResult, ProjectRoomStore, ProjectRoom, ProjectMembershipStore, ProjectMembership, ProjectHumanRole, ProjectMembershipMutationResult, ProjectBotMembershipStore, ProjectBotMembership, ProjectRoomMessageStore, ProjectRoomMessage, ProjectRoomReadStateStore, ProjectRoomReadState, UserStore, User, CreateUserRequest, UpdateUserRequest, TenantStore, Tenant, CreateTenantRequest, UpdateTenantRequest, UserTenantLinkStore, UserTenantLink, CreateUserTenantLinkRequest, UpdateUserTenantLinkRequest, DatabaseConfigStore, DatabaseConfigEntry, CreateDatabaseConfigRequest, UpdateDatabaseConfigRequest, ConnectionStore, ConnectionEntry, MetricsServerConfigStore, MetricsServerConfigEntry, CreateMetricsServerConfigRequest, UpdateMetricsServerConfigRequest, McpServerConfigStore, McpServerConfigEntry, CreateMcpServerConfigRequest, UpdateMcpServerConfigRequest, ModelProviderStore, ModelProviderEntry, CreateModelProviderRequest, UpdateModelProviderRequest, ModelProviderStatus, ModelProviderModelEntry, ProviderModelInput, WorkflowTrackingStore, CreateWorkflowRunRequest, WorkflowRun, UpdateWorkflowRunRequest, QueryWorkflowRunsOptions, QueryWorkflowRunsResult, CreateRunStepRequest, RunStep, UpdateRunStepRequest, StepType, EvalStore, EvalProject, CreateEvalProjectRequest, EvalSuite, CreateEvalSuiteRequest, EvalCase, CreateEvalCaseRequest, EvalRun, CreateEvalRunRequest, EvalRunResult, EvalProjectReport, BindingRegistry, Binding, CreateBindingInput, BindingMutablePatch, BindingListParams, ChannelInstallationStore, ChannelInstallation, ChannelInstallationType, CreateChannelInstallationInput, UpdateChannelInstallationRequest, A2AApiKeyStore, A2AApiKeyRecord, CreateA2AApiKeyInput, UpdateA2AApiKeyInput, A2AApiKeyEntry, OpenAuditStore, OpenAuditQuery, OpenAuditRecord, OpenAuditAppendInput, SkillStore, SkillStoreContext, Skill, CreateSkillRequest, ScheduleStorage, ScheduledTaskDefinition, ScheduledTaskStatus, ScheduleExecutionType, TaskStore, CreateTaskRequest, TaskItem, TaskListFilter, TaskDependentListQuery, UpdateTaskRequest, TaskMutationSnapshot, TaskWorkItemStore, CreateWorkItemRequest, TaskWorkItem, CreateWorkItemIfAbsentRequest, TaskWorkItemListFilter, ProjectLifecycleEventQuery, CapabilityBundleStore, CapabilityBundle, InternalCreateCapabilityBundleInput, InternalUpdateCapabilityBundleInput, CapabilityBundleDeleteResult, IConversationStore, ConversationRecord, CreateConversationInput } from '@axiom-lattice/protocols';
4
- import { IMessageQueueStore, AddMessageParams, PendingMessage, QueueScope, ThreadInfo } from '@axiom-lattice/core';
4
+ import { IMessageQueueStore, AddMessageParams, PendingMessage, QueueScope, ClaimMessagesOptions, ThreadInfo } from '@axiom-lattice/core';
5
5
 
6
6
  /**
7
7
  * Shared SQLite database connection for local stores using sql.js (WASM).
@@ -720,6 +720,8 @@ declare class LocalThreadMessageQueueStore implements IMessageQueueStore {
720
720
  addMessageAtHead(params: AddMessageParams): Promise<PendingMessage>;
721
721
  getPendingMessages(threadId: string, scope?: QueueScope): Promise<PendingMessage[]>;
722
722
  getProcessingMessages(threadId: string, scope?: QueueScope): Promise<PendingMessage[]>;
723
+ claimPendingMessages(threadId: string, scope: QueueScope | undefined, options: ClaimMessagesOptions): Promise<PendingMessage[]>;
724
+ reclaimExpiredProcessing(threadId: string, scope?: QueueScope): Promise<number>;
723
725
  getQueueSize(threadId: string, scope?: QueueScope): Promise<number>;
724
726
  getThreadsWithPendingMessages(): Promise<ThreadInfo[]>;
725
727
  removeMessage(messageId: string, scope?: QueueScope): Promise<boolean>;
@@ -760,6 +762,15 @@ declare class LocalScheduleStorage implements ScheduleStorage {
760
762
  save(task: ScheduledTaskDefinition): Promise<void>;
761
763
  get(taskId: string): Promise<ScheduledTaskDefinition | null>;
762
764
  update(taskId: string, updates: Partial<ScheduledTaskDefinition>): Promise<void>;
765
+ claimScheduledRun(taskId: string): Promise<boolean>;
766
+ /**
767
+ * Requeues `running` tasks whose `updated_at` is older than `olderThanMs` back
768
+ * to `pending` (recovery after a crash that left a claim unsettled).
769
+ *
770
+ * @param olderThanMs Age threshold in milliseconds.
771
+ * @returns The number of tasks requeued.
772
+ */
773
+ requeueStaleRunning(olderThanMs: number): Promise<number>;
763
774
  delete(taskId: string): Promise<void>;
764
775
  getActiveTasks(): Promise<ScheduledTaskDefinition[]>;
765
776
  getTasksByType(taskType: string): Promise<ScheduledTaskDefinition[]>;
package/dist/index.js CHANGED
@@ -4580,6 +4580,9 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
4580
4580
  custom_run_config TEXT,
4581
4581
  trusted_run_context TEXT,
4582
4582
  execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup'),
4583
+ locked_by TEXT,
4584
+ locked_at TEXT,
4585
+ lease_expires_at TEXT,
4583
4586
  created_at TEXT NOT NULL
4584
4587
  );
4585
4588
  CREATE INDEX IF NOT EXISTS idx_lt_tmq_thread ON lt_thread_message_queue(thread_id, status);
@@ -4598,6 +4601,16 @@ var LocalThreadMessageQueueStore = class {
4598
4601
  if (!names.has("execution_mode")) {
4599
4602
  db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN execution_mode TEXT CHECK (execution_mode IS NULL OR execution_mode = 'followup')");
4600
4603
  }
4604
+ if (!names.has("locked_by")) {
4605
+ db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN locked_by TEXT");
4606
+ }
4607
+ if (!names.has("locked_at")) {
4608
+ db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN locked_at TEXT");
4609
+ }
4610
+ if (!names.has("lease_expires_at")) {
4611
+ db.run("ALTER TABLE lt_thread_message_queue ADD COLUMN lease_expires_at TEXT");
4612
+ }
4613
+ db.run("CREATE INDEX IF NOT EXISTS idx_lt_tmq_lease ON lt_thread_message_queue(status, lease_expires_at)");
4601
4614
  }
4602
4615
  async addMessage(params) {
4603
4616
  const trusted = validateQueueTrust(params);
@@ -4722,6 +4735,37 @@ var LocalThreadMessageQueueStore = class {
4722
4735
  ).all(threadId, ...filter.params);
4723
4736
  return rows2.map(rowToMessage);
4724
4737
  }
4738
+ async claimPendingMessages(threadId, scope, options) {
4739
+ const filter = scopeClause(scope);
4740
+ const limitClause = options.limit === void 0 ? "" : ` LIMIT ${Math.max(0, Math.floor(options.limit))}`;
4741
+ const claimedRows = transaction(this.db, () => {
4742
+ const candidates = this.db.prepare(
4743
+ `SELECT * FROM lt_thread_message_queue WHERE thread_id = ? AND status = 'pending'${filter.sql} ORDER BY priority DESC, sequence_order ASC${limitClause}`
4744
+ ).all(threadId, ...filter.params);
4745
+ const startedAt = Date.now();
4746
+ const lockedAt = new Date(startedAt).toISOString();
4747
+ const leaseExpiresAt = new Date(startedAt + options.ttlMs).toISOString();
4748
+ const claimed = [];
4749
+ for (const row of candidates) {
4750
+ const changes = write(
4751
+ this.db,
4752
+ `UPDATE lt_thread_message_queue SET status = 'processing', locked_by = ?, locked_at = ?, lease_expires_at = ? WHERE id = ? AND status = 'pending'`,
4753
+ [options.owner, lockedAt, leaseExpiresAt, row.id]
4754
+ );
4755
+ if (changes > 0) claimed.push(row);
4756
+ }
4757
+ return claimed;
4758
+ });
4759
+ return claimedRows.map(rowToMessage);
4760
+ }
4761
+ async reclaimExpiredProcessing(threadId, scope) {
4762
+ const filter = scopeClause(scope);
4763
+ return transaction(this.db, () => write(
4764
+ this.db,
4765
+ `UPDATE lt_thread_message_queue SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL WHERE thread_id = ? AND status = 'processing' AND lease_expires_at IS NOT NULL AND lease_expires_at <= ?${filter.sql}`,
4766
+ [threadId, nowISO(), ...filter.params]
4767
+ ));
4768
+ }
4725
4769
  async getQueueSize(threadId, scope) {
4726
4770
  const filter = scopeClause(scope);
4727
4771
  const row = this.db.prepare(
@@ -4763,7 +4807,7 @@ var LocalThreadMessageQueueStore = class {
4763
4807
  async resetProcessingToPending(threadId, scope) {
4764
4808
  const filter = scopeClause(scope);
4765
4809
  const result = this.db.prepare(
4766
- `UPDATE lt_thread_message_queue SET status = 'pending' WHERE thread_id = ? AND status = 'processing'${filter.sql}`
4810
+ `UPDATE lt_thread_message_queue SET status = 'pending', locked_by = NULL, locked_at = NULL, lease_expires_at = NULL WHERE thread_id = ? AND status = 'processing'${filter.sql}`
4767
4811
  ).run(threadId, ...filter.params);
4768
4812
  return result.changes;
4769
4813
  }
@@ -5149,6 +5193,27 @@ var LocalScheduleStorage = class {
5149
5193
  `UPDATE lt_scheduled_tasks SET ${setClauses.join(", ")} WHERE task_id = ?`
5150
5194
  ).run(...values);
5151
5195
  }
5196
+ async claimScheduledRun(taskId) {
5197
+ const result = this.db.prepare(
5198
+ `UPDATE lt_scheduled_tasks SET status = 'running', updated_at = ? WHERE task_id = ? AND status = 'pending'`
5199
+ ).run((/* @__PURE__ */ new Date()).toISOString(), taskId);
5200
+ return result.changes > 0;
5201
+ }
5202
+ /**
5203
+ * Requeues `running` tasks whose `updated_at` is older than `olderThanMs` back
5204
+ * to `pending` (recovery after a crash that left a claim unsettled).
5205
+ *
5206
+ * @param olderThanMs Age threshold in milliseconds.
5207
+ * @returns The number of tasks requeued.
5208
+ */
5209
+ async requeueStaleRunning(olderThanMs) {
5210
+ const now = /* @__PURE__ */ new Date();
5211
+ const cutoff = new Date(now.getTime() - olderThanMs).toISOString();
5212
+ const result = this.db.prepare(
5213
+ `UPDATE lt_scheduled_tasks SET status = 'pending', updated_at = ? WHERE status = 'running' AND updated_at < ?`
5214
+ ).run(now.toISOString(), cutoff);
5215
+ return result.changes;
5216
+ }
5152
5217
  async delete(taskId) {
5153
5218
  this.db.prepare(`DELETE FROM lt_scheduled_tasks WHERE task_id = ?`).run(taskId);
5154
5219
  }