@framers/agentos-ext-account-sources 0.3.0 → 0.5.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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @framers/agentos-ext-account-sources
2
2
 
3
- A person's own accounts as sources. This library defines `AccountSourceConnector` and the `SourceRef` every passage carries, with helpers for line ranges, links, bounded body reads, requests under the shared public-sources limiter and the cache lifetime of a read, and the contracts and pure rules of a durable, resumable synchronization.
3
+ A person's own accounts as sources. This library defines `AccountSourceConnector` and the `SourceRef` every passage carries, with helpers for line ranges, links, bounded body reads, requests under the shared public-sources limiter and the cache lifetime of a read, the contracts, pure rules and PostgreSQL store of a durable, resumable synchronization, and weighted request quotas that every worker shares.
4
4
 
5
- Install `@framers/agentos-ext-account-sources` with its peer `@framers/agentos` 0.10.40 or later. Node.js 20.19 or later is required. The runtime dependency is `@framers/agentos-ext-public-sources`.
5
+ Install `@framers/agentos-ext-account-sources` with its peer `@framers/agentos` 0.13.134 or later. Node.js 20.19 or later is required. The runtime dependencies are `@framers/agentos-ext-public-sources` and `@framers/sql-storage-adapter`.
6
6
 
7
7
  ## Connector
8
8
 
@@ -184,6 +184,99 @@ The rules are pure and take no lock: the store that commits a cursor reads the s
184
184
 
185
185
  `new SourceSyncFault(code, retryAt?)` carries a fixed failure code as its message (`source_sync_<code>`), never a provider's text, exposes `code` and `retryAt`, and throws `invalid_source_sync_fault` for an unknown code or a `retryAt` that is not a nonnegative safe integer. `assertSourceSyncPolicy(policy)` throws `invalid_source_sync_policy` unless every policy value is a positive safe integer.
186
186
 
187
+ ### The SQL store
188
+
189
+ ```ts
190
+ import { createPostgresAdapter } from '@framers/sql-storage-adapter';
191
+ import { sourceDerivationSchema } from '@framers/agentos/cognition/library';
192
+ import { SqlAccountSourceSyncStore, sourceSyncSchema } from '@framers/agentos-ext-account-sources';
193
+
194
+ const db = createPostgresAdapter({ connectionString: process.env.DATABASE_URL! });
195
+ await db.open();
196
+ // The host's migration runs the DDL; the store never does.
197
+ for (const sql of [...sourceDerivationSchema(), ...sourceSyncSchema()]) await db.exec(sql);
198
+
199
+ const store = new SqlAccountSourceSyncStore<PreparedPassages>(db, { bindings });
200
+ const scope = { tenantId: 'tenant-1', ownerId: 'owner-1', connectionId: 'connection-1' };
201
+ await store.create(scope, policy);
202
+ await store.control(scope, { type: 'connect_started' });
203
+ await store.control(scope, { type: 'grant_ready', use: { searchAskEnabled: true, liveEnabled: false } });
204
+ const handle = await store.claim(scope, 'scheduled'); // null unless the connection is due and free
205
+ ```
206
+
207
+ `SqlAccountSourceSyncStore` keeps each connection's state, policy, open checkpoint, lease and barrier, the cycle's work rows, the provenance of retained sources and the connection's exclusions in the tables `sourceSyncSchema(prefix)` creates: six statements, with a prefix of a lower-case letter and at most 31 lower-case letters, digits or underscores (`agentos_source_sync` unless given). It needs a PostgreSQL adapter with transactions and runs no DDL. Its `fence` is the core `SqlSourceDerivationStore` on the same adapter, over the tables of `sourceDerivationSchema(derivationPrefix)`: every change to an owner's sources runs under that owner's fence, the one that also commits and delivers results derived from them.
208
+
209
+ The host supplies seven bindings. The store calls each inside its own transaction, on the transaction adapter it passes:
210
+
211
+ | Binding | What it does |
212
+ |---|---|
213
+ | `active(tx, owner)` | Answers false for an account that is deleted or being deleted, whose admission is off or whose purge is pending; the store then claims, commits and reactivates nothing for it and answers no source state |
214
+ | `allowance(tx, scope)` | Answers the connection's document and held-character ceilings after unrelated sources, and the remaining indexing balance |
215
+ | `write(tx, scope, dependency, document, prepared)` | Writes the document's text and the host's prepared passages and vectors |
216
+ | `chargeIndexed(tx, scope, characters)` | Charges committed characters to the indexing allowance |
217
+ | `purge(tx, owner, affected)` | Deletes the host's text, passages and vectors in the affected scope |
218
+ | `purgeResults(tx, owner, ids)` | Deletes the host's rows for these derived results |
219
+ | `rebind(tx, scope, generation)` | Moves the connection's host rows to a new generation without changing their content, version or expiry |
220
+
221
+ Provider reads, embedding and model calls run between store calls, never inside a binding.
222
+
223
+ A sync holds a lease. `claim(scope, trigger)` answers a handle only when the owner is active, the `active` binding agrees, no barrier or live lease is set and `sourceSyncDue` holds; `renew(handle)` extends the lease by `leaseMs`; `release(handle, event?)` gives it up, records a deferral or a fixed failure when one is given, and keeps the checkpoint and work for the next claim. `openCycle` opens a checkpoint or answers the one already open, `page` records a page and queues its ids in one transaction (the same page sent again right after answers the same checkpoint; any other stale revision answers null), and `takeWork` marks the next due item attempted before the read. Every call made with a handle checks the owner fence, the lease token and expiry, the generation and the `active` binding in the same transaction; once any of them has moved on, `release(handle)` answers false and every other call answers null.
224
+
225
+ `applyResult(handle, work, result, prepared)` keeps a `retry` result unresolved. Any other result first removes the source's previous copy and every derived result that depends on it, through the fence. A `document` result then replaces it in the same transaction (host write, indexing charge and provenance) when the source is not excluded and fits: the store ranks this connection's retained sources and the new one by activity time, newest first with ties broken by source id, keeps those inside `maxRetainedDocuments`, `maxHeldCharacters` and the allowance, and removes this connection's other sources, never another connection's. A document that does not fit is recorded as skipped with `document_limit`, `held_limit` or `indexing_limit`, and an excluded one with `excluded`. A failing binding rolls the whole application back. The answer carries a new handle, because each removal advances the owner's fence; later calls use it. `finish` commits the cycle's target cursor once every work item is terminal, counting from the stored work, and `progress` records the counts so far.
226
+
227
+ Controls run with the owner blocked. `control(scope, event, policy?)` applies `connect_started`, `grant_ready`, `pause`, `resume`, `use_changed`, `policy_changed` (with the new policy), `grant_lost` or `disconnect`: it records a content-free barrier and drops the open cycle in one transaction, then completes the barrier and reactivates the owner. Connecting, grant loss and disconnecting remove the connection's sources, their derived results and its exclusions. Pause, resume, a use change and a policy change move retained content to the new generation with its version kept and its expiry never extended, so no result derived before the change can be delivered again; a policy change also removes the sources the new limits leave out and shortens the expiry of the rest to the new window. `exclude(scope, sourceId)` removes one source and keeps a content-free exclusion; `resetExclusions(scope)` clears them. Both raise the generation and clear the cursor. A control other than disconnecting throws `stale_source_sync` while the `active` binding refuses the owner. A barrier left by a failure blocks claims and reactivation until `recoverControl(scope)` completes it, and a lifecycle worker calls it even for a paused connection. `expire(scope, limit)` removes retained sources whose expiry has passed, also while the connection is paused, then the owner's expired derived results; reads refuse an expired source either way.
228
+
229
+ `readCurrent(tx, derivation)` answers the current state of a derivation's sources for the fence's commit and delivery checks, and a connection with a barrier or pending revalidation reads as disconnected. `snapshot(scope)` answers metadata for authenticated controls and `documents(scope)` the retained provenance; neither authorizes delivery. Work rows and barriers hold ids and fixed values only. The documents table holds provenance, message timing and counts, never text; treat it, like the host's own tables, as restricted data for retention and backups.
230
+
231
+ ### Request quotas
232
+
233
+ ```ts
234
+ import { randomUUID } from 'node:crypto';
235
+ import { createPostgresAdapter } from '@framers/sql-storage-adapter';
236
+ import { readCapped, SourceQuota, sourceQuotaSchema, sourceRetryAt } from '@framers/agentos-ext-account-sources';
237
+
238
+ const db = createPostgresAdapter({ connectionString: process.env.DATABASE_URL! });
239
+ await db.open();
240
+ // The host's migration runs the DDL; the quota never does.
241
+ for (const sql of sourceQuotaSchema()) await db.exec(sql);
242
+
243
+ const quota = new SourceQuota(db, {
244
+ policy: {
245
+ projectMinute: 6_000, mailboxMinute: 600, projectDay: 500_000,
246
+ projectConcurrency: 8, mailboxConcurrency: 2, timeoutMs: 15_000, slotGraceMs: 15_000,
247
+ weights: { list: 5, read: 20, changes: 2, profile: 1, labels: 1 },
248
+ },
249
+ });
250
+
251
+ /** One attempt to read an item; a retry calls it again, with a new attempt id. */
252
+ async function readItem(url: string, attempt: number): Promise<Uint8Array | null> {
253
+ const scope = { projectKey: 'project-1', mailboxKey: 'mailbox-1' };
254
+ const result = await quota.run({ ...scope, method: 'read', attemptId: randomUUID() }, async (signal) => {
255
+ const response = await fetch(url, { signal });
256
+ if (response.status === 429) {
257
+ await response.body?.cancel();
258
+ return { body: null, retryAfter: response.headers.get('retry-after') };
259
+ }
260
+ return { body: await readCapped(response, 2_000_000), retryAfter: null };
261
+ });
262
+ if (result.kind !== 'completed') return null; // nothing was sent; a deferral says when to try again
263
+ if (result.value.body === null) {
264
+ await quota.defer(scope, sourceRetryAt(Date.now(), attempt, result.value.retryAfter));
265
+ }
266
+ return result.value.body;
267
+ }
268
+ ```
269
+
270
+ `SourceQuota` admits provider requests against weighted budgets that every worker shares through PostgreSQL: a project's rolling minute (`projectMinute`), one mailbox's rolling minute (`mailboxMinute`) and a project's UTC day (`projectDay`). Each budget is a core `SqlSpendMeter` under a table prefix of its own (`<prefix>_pm`, `<prefix>_mm` and `<prefix>_pd`), and at most `projectConcurrency` requests of a project and `mailboxConcurrency` of one mailbox are in flight at once. The quota needs a PostgreSQL adapter with transactions and runs no DDL: the host's migration runs, each with `exec`, the seven scripts `sourceQuotaSchema(prefix)` answers, under a prefix of a lower-case letter and at most 27 lower-case letters, digits or underscores (`agentos_source_quota` unless given). Keys are opaque strings of 1 to 512 characters; a mailbox key stays the same for its mailbox and is never an address. The tables hold keys, counts and times, never content.
271
+
272
+ `run(request, send, signal?)` decides in one transaction under the project's row lock. An attempt id seen before answers `{ kind: 'duplicate' }`, as does an attempt that cannot start its call within a second of its reservation. A recorded provider wait, a full concurrency limit or a budget without room for the method's weight answers `{ kind: 'deferred', reason, retryAt }`, with the wait's own time, the earliest expiry of the slots in the way, 60,001 milliseconds on for a minute budget, or the next UTC midnight for the day; a refusal by one budget rolls back what the others took. Otherwise the transaction consumes the weight in all three budgets and records the attempt's slot, and `run` calls `send` once, outside any transaction, with a signal that aborts after `timeoutMs` or with the caller's signal, then answers `{ kind: 'completed', value }`. Nothing is sent for a duplicate or a deferral. Units once consumed stay consumed: after a failed request, and after a provider wait recorded between the reservation and the call defers the request. A retry is a new `run` with a new attempt id, and pays again. `send` consumes or cancels the response body before it resolves, honors the signal and never retries. The slot frees when `send` settles or, for a process that dies mid-request, once a second to start, `timeoutMs` and `slotGraceMs` have passed; the attempt id stays refused either way.
273
+
274
+ `run` throws `source_sync_invalid_response` for a key outside those bounds or a method without a weight, `source_sync_cancelled` when the caller's signal aborted before the call or by the time `send` resolves, `source_sync_network` when `send` resolves after its timeout aborted it, and `source_sync_storage_unavailable` when the store cannot answer. Every worker on the same tables runs the same policy: the first one to use a project records its policy there, and a worker with another policy gets `source_sync_storage_unavailable` too. An error that `send` throws reaches the caller unchanged, unless the store then fails to free the slot.
275
+
276
+ `defer(scope, retryAt)` records a provider's wait until `retryAt`, in epoch milliseconds, for one mailbox (`{ projectKey, mailboxKey }`) or the whole project (`{ projectKey }`). A shorter wait never replaces a longer one, and every worker honors it, across restarts. `compact()` deletes attempt records a day after their slots expired, mailbox waits that have passed, the meters' reservations settled more than a day ago and the minute budgets' rows idle for a day, at most 1,000 rows of each a call; the daily totals stay. Attempt ids must never repeat: once its record is compacted, an old id is no longer refused.
277
+
278
+ `sourceRetryAt(now, attempt, retryAfter, random?)` answers when a failed request may be tried again, for attempts 1 to 5: `now` plus one second doubled for each earlier attempt, with up to as much again added as jitter (`random`, from 0 to 1, `Math.random()` unless given), capped at 60 seconds. A `Retry-After` value, in delta seconds or an exact IMF-fixdate, wins when it is later, and is never capped; a value it cannot read is ignored. A `now` that is not a nonnegative safe integer, an attempt outside 1 to 5, a jitter outside 0 to 1 or a time past the latest date throws `source_sync_invalid_response`.
279
+
187
280
  ## License
188
281
 
189
282
  Apache-2.0. This pack carries a copy of the repository's license.
package/dist/index.d.ts CHANGED
@@ -11,4 +11,9 @@ export * from './cachePolicy.js';
11
11
  /** Durable synchronization contracts and pure state/checkpoint rules. */
12
12
  export * from './sync/types.js';
13
13
  export * from './sync/state.js';
14
+ /** The migration-owned schema and the SQL store that persists synchronization under the owner fence. */
15
+ export { sourceSyncSchema } from './sync/schema.js';
16
+ export { SqlAccountSourceSyncStore, type SourceSyncStoredDocument, type SourceSyncAllowance, type SourceSyncStorageBindings, type SourceSyncStoreOptions, type SourceSyncHandle, type SourceSyncSnapshot, type SourceSyncControl, type SourceSyncApplication, type SourceSyncBarrier, } from './sync/SqlAccountSourceSyncStore.js';
17
+ /** Weighted request budgets, request slots and provider retry times shared by every worker through SQL. */
18
+ export { SourceQuota, sourceQuotaSchema, sourceRetryAt, type SourceQuotaMethod, type SourceQuotaPolicy, type SourceQuotaRequest, type SourceQuotaReason, type SourceQuotaResult, type SourceQuotaOptions, } from './sync/SourceQuota.js';
14
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,cAAc,YAAY,CAAC;AAC3B,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAC/B,8DAA8D;AAC9D,cAAc,iBAAiB,CAAC;AAChC,oEAAoE;AACpE,cAAc,cAAc,CAAC;AAC7B,0EAA0E;AAC1E,cAAc,kBAAkB,CAAC;AACjC,yEAAyE;AACzE,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,cAAc,YAAY,CAAC;AAC3B,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAC/B,8DAA8D;AAC9D,cAAc,iBAAiB,CAAC;AAChC,oEAAoE;AACpE,cAAc,cAAc,CAAC;AAC7B,0EAA0E;AAC1E,cAAc,kBAAkB,CAAC;AACjC,yEAAyE;AACzE,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,wGAAwG;AACxG,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,yBAAyB,EACzB,KAAK,wBAAwB,EAAE,KAAK,mBAAmB,EAAE,KAAK,yBAAyB,EACvF,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,KAAK,kBAAkB,EAC3E,KAAK,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,KAAK,iBAAiB,GAC3E,MAAM,qCAAqC,CAAC;AAC7C,2GAA2G;AAC3G,OAAO,EACL,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAC7C,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EACvE,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,GACxE,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -11,4 +11,9 @@ export * from './cachePolicy.js';
11
11
  /** Durable synchronization contracts and pure state/checkpoint rules. */
12
12
  export * from './sync/types.js';
13
13
  export * from './sync/state.js';
14
+ /** The migration-owned schema and the SQL store that persists synchronization under the owner fence. */
15
+ export { sourceSyncSchema } from './sync/schema.js';
16
+ export { SqlAccountSourceSyncStore, } from './sync/SqlAccountSourceSyncStore.js';
17
+ /** Weighted request budgets, request slots and provider retry times shared by every worker through SQL. */
18
+ export { SourceQuota, sourceQuotaSchema, sourceRetryAt, } from './sync/SourceQuota.js';
14
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,cAAc,YAAY,CAAC;AAC3B,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAC/B,8DAA8D;AAC9D,cAAc,iBAAiB,CAAC;AAChC,oEAAoE;AACpE,cAAc,cAAc,CAAC;AAC7B,0EAA0E;AAC1E,cAAc,kBAAkB,CAAC;AACjC,yEAAyE;AACzE,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,cAAc,YAAY,CAAC;AAC3B,yDAAyD;AACzD,cAAc,gBAAgB,CAAC;AAC/B,8DAA8D;AAC9D,cAAc,iBAAiB,CAAC;AAChC,oEAAoE;AACpE,cAAc,cAAc,CAAC;AAC7B,0EAA0E;AAC1E,cAAc,kBAAkB,CAAC;AACjC,yEAAyE;AACzE,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,wGAAwG;AACxG,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,yBAAyB,GAI1B,MAAM,qCAAqC,CAAC;AAC7C,2GAA2G;AAC3G,OAAO,EACL,WAAW,EAAE,iBAAiB,EAAE,aAAa,GAG9C,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @fileoverview Persistent weighted request quotas on PostgreSQL through `@framers/sql-storage-adapter`: three
3
+ * released core spend meters (a project's rolling minute, a mailbox's rolling minute and a project's UTC day),
4
+ * request slots that every worker shares and that expire after a process dies, and durable provider retry times.
5
+ * @module account-sources/sync/SourceQuota
6
+ */
7
+ import type { StorageAdapter } from '@framers/sql-storage-adapter';
8
+ /** The kind of provider call a request makes; the policy gives each kind its weight. */
9
+ export type SourceQuotaMethod = 'list' | 'read' | 'changes' | 'profile' | 'labels';
10
+ /**
11
+ * Budgets and limits that every worker sharing the tables holds identically. Every value is a positive safe integer,
12
+ * `timeoutMs` is at most 60,000, `slotGraceMs` lies between `timeoutMs` and 120,000, and `mailboxConcurrency` is at
13
+ * most `projectConcurrency`; `SourceQuota` throws `invalid_source_quota_policy` otherwise.
14
+ */
15
+ export interface SourceQuotaPolicy {
16
+ /** Weighted units a project may consume in any rolling 60-second window. */
17
+ readonly projectMinute: number;
18
+ /** Weighted units one mailbox may consume in any rolling 60-second window. */
19
+ readonly mailboxMinute: number;
20
+ /** Weighted units a project may consume in one UTC day. */
21
+ readonly projectDay: number;
22
+ /** Requests a project may have in flight across every worker. */
23
+ readonly projectConcurrency: number;
24
+ /** Requests one mailbox may have in flight across every worker. */
25
+ readonly mailboxConcurrency: number;
26
+ /** Milliseconds after dispatch before the request's signal aborts. */
27
+ readonly timeoutMs: number;
28
+ /** Milliseconds a slot outlives its request's deadline, so a process that dies mid-request frees it. */
29
+ readonly slotGraceMs: number;
30
+ /** Units each kind of call consumes. */
31
+ readonly weights: Readonly<Record<SourceQuotaMethod, number>>;
32
+ }
33
+ /** One provider request attempt. Keys are opaque strings of 1 to 512 characters, never an address. */
34
+ export interface SourceQuotaRequest {
35
+ /** The project whose budgets and slots every worker shares. */
36
+ readonly projectKey: string;
37
+ /** A stable key for the mailbox inside the project. */
38
+ readonly mailboxKey: string;
39
+ /** The kind of call, which sets the attempt's weight. */
40
+ readonly method: SourceQuotaMethod;
41
+ /** Unique for one HTTP attempt. A network retry needs a new id. */
42
+ readonly attemptId: string;
43
+ }
44
+ /** Why a request was deferred: a budget without room, a full concurrency limit or a recorded provider wait. */
45
+ export type SourceQuotaReason = 'project_minute' | 'mailbox_minute' | 'project_day' | 'project_concurrency' | 'mailbox_concurrency' | 'provider_wait';
46
+ /**
47
+ * What `SourceQuota.run` answers: the transport's value; a deferral with the earliest time, in epoch milliseconds, to
48
+ * try again, nothing sent; or `duplicate` for an attempt id seen before, which never sends.
49
+ */
50
+ export type SourceQuotaResult<T> = {
51
+ readonly kind: 'completed';
52
+ readonly value: T;
53
+ } | {
54
+ readonly kind: 'deferred';
55
+ readonly reason: SourceQuotaReason;
56
+ readonly retryAt: number;
57
+ } | {
58
+ readonly kind: 'duplicate';
59
+ };
60
+ /** The quota's table prefix, policy and clock. */
61
+ export interface SourceQuotaOptions {
62
+ /** Prefix of the quota tables, the one given to `sourceQuotaSchema`; `agentos_source_quota` unless given. */
63
+ readonly tablePrefix?: string;
64
+ /** The budgets and limits, the same in every worker. */
65
+ readonly policy: SourceQuotaPolicy;
66
+ /** Tests may inject a clock; production uses the database clock across workers. */
67
+ readonly clock?: (tx: StorageAdapter) => Promise<number>;
68
+ }
69
+ /**
70
+ * The quota tables' DDL under a prefix of a lower-case letter and at most 27 lower-case letters, digits or underscores
71
+ * (`agentos_source_quota` unless given), as seven scripts for the host's migration to run with `exec`: the project,
72
+ * mailbox and attempt tables, the attempts' index, then a core spend meter's tables and indexes for each budget under
73
+ * `<prefix>_pm`, `<prefix>_mm` and `<prefix>_pd`. Throws `invalid_source_quota_prefix` for another prefix.
74
+ */
75
+ export declare function sourceQuotaSchema(prefix?: string): readonly string[];
76
+ /** Combines released meters; this class never reimplements their accounting SQL. */
77
+ export declare class SourceQuota {
78
+ private readonly db;
79
+ private readonly options;
80
+ private readonly t;
81
+ private readonly policy;
82
+ private readonly fingerprint;
83
+ /**
84
+ * Takes a PostgreSQL adapter with transactions and runs no DDL. Throws `source_quota_requires_postgres` for another
85
+ * adapter, `invalid_source_quota_prefix` for a bad prefix and `invalid_source_quota_policy` for a bad policy.
86
+ */
87
+ constructor(db: StorageAdapter, options: SourceQuotaOptions);
88
+ private now;
89
+ private project;
90
+ private meter;
91
+ private reserve;
92
+ private begin;
93
+ private release;
94
+ /** Consume weighted capacity and call the transport once. No inline HTTP retries. */
95
+ run<T>(request: SourceQuotaRequest, send: (signal: AbortSignal) => Promise<T>, parent?: AbortSignal): Promise<SourceQuotaResult<T>>;
96
+ /** A longer provider wait survives restarts, generation changes and shorter updates. */
97
+ defer(scope: {
98
+ readonly projectKey: string;
99
+ readonly mailboxKey?: string;
100
+ }, retryAt: number): Promise<void>;
101
+ /** Remove expired attempt evidence after 24 hours and safely old meter records. Daily totals remain. */
102
+ compact(): Promise<void>;
103
+ }
104
+ /** Retry-After is never clamped down to the local exponential-backoff ceiling. */
105
+ export declare function sourceRetryAt(now: number, attempt: number, retryAfter: string | null, random?: number): number;
106
+ //# sourceMappingURL=SourceQuota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SourceQuota.d.ts","sourceRoot":"","sources":["../../src/sync/SourceQuota.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * @fileoverview Persistent weighted request quotas on PostgreSQL through `@framers/sql-storage-adapter`: three
3
+ * released core spend meters (a project's rolling minute, a mailbox's rolling minute and a project's UTC day),
4
+ * request slots that every worker shares and that expire after a process dies, and durable provider retry times.
5
+ * @module account-sources/sync/SourceQuota
6
+ */
7
+ import { randomUUID } from 'node:crypto';
8
+ import { SqlSpendMeter, spendMeterDdl } from '@framers/agentos/safety/runtime';
9
+ import { SourceSyncFault } from './state.js';
10
+ class QuotaDeferred extends Error {
11
+ reason;
12
+ retryAt;
13
+ constructor(reason, retryAt) {
14
+ super('source_quota_deferred');
15
+ this.reason = reason;
16
+ this.retryAt = retryAt;
17
+ }
18
+ }
19
+ class QuotaDuplicate extends Error {
20
+ constructor() { super('source_quota_duplicate'); }
21
+ }
22
+ const integer = (n) => Number.isSafeInteger(n) && n >= 0;
23
+ const key = (s) => typeof s === 'string' && s.length > 0 && s.length <= 512;
24
+ const dayEnd = (now) => (Math.floor(now / 86_400_000) + 1) * 86_400_000;
25
+ function tables(prefix = 'agentos_source_quota') {
26
+ if (!/^[a-z][a-z0-9_]{0,27}$/.test(prefix))
27
+ throw new Error('invalid_source_quota_prefix');
28
+ return { projects: `"${prefix}_projects"`, mailboxes: `"${prefix}_mailboxes"`,
29
+ attempts: `"${prefix}_attempts"`, pm: `${prefix}_pm`, mm: `${prefix}_mm`, pd: `${prefix}_pd` };
30
+ }
31
+ /**
32
+ * The quota tables' DDL under a prefix of a lower-case letter and at most 27 lower-case letters, digits or underscores
33
+ * (`agentos_source_quota` unless given), as seven scripts for the host's migration to run with `exec`: the project,
34
+ * mailbox and attempt tables, the attempts' index, then a core spend meter's tables and indexes for each budget under
35
+ * `<prefix>_pm`, `<prefix>_mm` and `<prefix>_pd`. Throws `invalid_source_quota_prefix` for another prefix.
36
+ */
37
+ export function sourceQuotaSchema(prefix = 'agentos_source_quota') {
38
+ const t = tables(prefix);
39
+ return [
40
+ `CREATE TABLE IF NOT EXISTS ${t.projects} (project_key TEXT PRIMARY KEY,policy TEXT NOT NULL,retry_at BIGINT NOT NULL DEFAULT 0)`,
41
+ `CREATE TABLE IF NOT EXISTS ${t.mailboxes} (project_key TEXT NOT NULL,mailbox_key TEXT NOT NULL,
42
+ retry_at BIGINT NOT NULL DEFAULT 0,PRIMARY KEY(project_key,mailbox_key))`,
43
+ `CREATE TABLE IF NOT EXISTS ${t.attempts} (attempt_id TEXT PRIMARY KEY,project_key TEXT NOT NULL,
44
+ mailbox_key TEXT NOT NULL,token TEXT NOT NULL,send_by BIGINT NOT NULL,expires_at BIGINT NOT NULL,
45
+ started BOOLEAN NOT NULL DEFAULT FALSE,finished BOOLEAN NOT NULL DEFAULT FALSE)`,
46
+ `CREATE INDEX IF NOT EXISTS "${prefix}_active" ON ${t.attempts}(project_key,finished,expires_at)`,
47
+ spendMeterDdl(t.pm), spendMeterDdl(t.mm), spendMeterDdl(t.pd),
48
+ ];
49
+ }
50
+ function checkedPolicy(policy) {
51
+ const weights = { list: policy.weights.list, read: policy.weights.read,
52
+ changes: policy.weights.changes, profile: policy.weights.profile, labels: policy.weights.labels };
53
+ const copy = { projectMinute: policy.projectMinute, mailboxMinute: policy.mailboxMinute,
54
+ projectDay: policy.projectDay, projectConcurrency: policy.projectConcurrency,
55
+ mailboxConcurrency: policy.mailboxConcurrency, timeoutMs: policy.timeoutMs,
56
+ slotGraceMs: policy.slotGraceMs, weights: Object.freeze(weights) };
57
+ const numbers = [copy.projectMinute, copy.mailboxMinute, copy.projectDay, copy.projectConcurrency,
58
+ copy.mailboxConcurrency, copy.timeoutMs, copy.slotGraceMs, ...Object.values(weights)];
59
+ if (!numbers.every((n) => integer(n) && n > 0) || copy.timeoutMs > 60_000
60
+ || copy.slotGraceMs < copy.timeoutMs || copy.slotGraceMs > 120_000
61
+ || copy.mailboxConcurrency > copy.projectConcurrency)
62
+ throw new Error('invalid_source_quota_policy');
63
+ return Object.freeze(copy);
64
+ }
65
+ /** Combines released meters; this class never reimplements their accounting SQL. */
66
+ export class SourceQuota {
67
+ db;
68
+ options;
69
+ t;
70
+ policy;
71
+ fingerprint;
72
+ /**
73
+ * Takes a PostgreSQL adapter with transactions and runs no DDL. Throws `source_quota_requires_postgres` for another
74
+ * adapter, `invalid_source_quota_prefix` for a bad prefix and `invalid_source_quota_policy` for a bad policy.
75
+ */
76
+ constructor(db, options) {
77
+ this.db = db;
78
+ this.options = options;
79
+ if (db.kind !== 'postgres' || !db.capabilities.has('transactions'))
80
+ throw new Error('source_quota_requires_postgres');
81
+ this.t = tables(options.tablePrefix);
82
+ this.policy = checkedPolicy(options.policy);
83
+ this.fingerprint = JSON.stringify(this.policy);
84
+ }
85
+ async now(tx) {
86
+ const n = this.options.clock ? await this.options.clock(tx)
87
+ : Number((await tx.get('SELECT floor(extract(epoch from clock_timestamp()) * 1000)::bigint AS now'))?.now);
88
+ if (!integer(n) || n > 8_639_999_913_600_000)
89
+ throw new Error('invalid_source_quota_clock');
90
+ return n;
91
+ }
92
+ async project(tx, projectKey) {
93
+ if (!key(projectKey))
94
+ throw new Error('invalid_source_quota_key');
95
+ await tx.run(`INSERT INTO ${this.t.projects}(project_key,policy) VALUES($1,$2) ON CONFLICT DO NOTHING`, [projectKey, this.fingerprint]);
96
+ const row = await tx.get(`SELECT policy,retry_at FROM ${this.t.projects} WHERE project_key=$1 FOR UPDATE`, [projectKey]);
97
+ if (!row || row.policy !== this.fingerprint)
98
+ throw new Error('source_quota_policy_mismatch');
99
+ return row;
100
+ }
101
+ meter(tx, prefix, allowance, daily) {
102
+ return new SqlSpendMeter({ db: tx, tablePrefix: prefix, allowanceFor: () => allowance,
103
+ ...(daily ? { periodOf: (now) => String(Math.floor(now / 86_400_000)) } : { windowMs: 60_000 }),
104
+ ensureSchema: false, requireShared: true, leaseMs: 120_000, maxAttemptsPerOperation: 1,
105
+ unknownAfterLease: 'consume', retry: { attempts: 1, baseDelayMs: 0, maxDelayMs: 0, deadlineMs: 1000 } });
106
+ }
107
+ async reserve(request) {
108
+ if (![request.projectKey, request.mailboxKey, request.attemptId].every(key)
109
+ || !Object.hasOwn(this.policy.weights, request.method))
110
+ throw new SourceSyncFault('invalid_response');
111
+ return this.db.transaction(async (tx) => {
112
+ const project = await this.project(tx, request.projectKey);
113
+ const now = await this.now(tx);
114
+ if (await tx.get(`SELECT attempt_id FROM ${this.t.attempts} WHERE attempt_id=$1`, [request.attemptId]))
115
+ throw new QuotaDuplicate();
116
+ const mailbox = await tx.get(`SELECT retry_at FROM ${this.t.mailboxes}
117
+ WHERE project_key=$1 AND mailbox_key=$2`, [request.projectKey, request.mailboxKey]);
118
+ const retryAt = Math.max(Number(project.retry_at), Number(mailbox?.retry_at ?? 0));
119
+ if (retryAt > now)
120
+ throw new QuotaDeferred('provider_wait', retryAt);
121
+ const slots = await tx.all(`SELECT mailbox_key,expires_at FROM ${this.t.attempts} WHERE project_key=$1 AND finished=FALSE AND expires_at>$2`, [request.projectKey, now]);
122
+ const local = slots.filter((s) => s.mailbox_key === request.mailboxKey);
123
+ if (local.length >= this.policy.mailboxConcurrency) {
124
+ throw new QuotaDeferred('mailbox_concurrency', Math.min(...local.map((s) => Number(s.expires_at))));
125
+ }
126
+ if (slots.length >= this.policy.projectConcurrency) {
127
+ throw new QuotaDeferred('project_concurrency', Math.min(...slots.map((s) => Number(s.expires_at))));
128
+ }
129
+ const projectId = JSON.stringify([request.projectKey]);
130
+ const mailboxId = JSON.stringify([request.projectKey, request.mailboxKey]);
131
+ const meters = [
132
+ { meter: this.meter(tx, this.t.pm, this.policy.projectMinute, false), accountId: projectId,
133
+ reason: 'project_minute', retryAt: now + 60_001 },
134
+ { meter: this.meter(tx, this.t.mm, this.policy.mailboxMinute, false), accountId: mailboxId,
135
+ reason: 'mailbox_minute', retryAt: now + 60_001 },
136
+ { meter: this.meter(tx, this.t.pd, this.policy.projectDay, true), accountId: projectId,
137
+ reason: 'project_day', retryAt: dayEnd(now) },
138
+ ];
139
+ for (const entry of meters) {
140
+ const reserved = await entry.meter.reserve({ accountId: entry.accountId,
141
+ operationId: request.attemptId, units: this.policy.weights[request.method], now });
142
+ if (reserved.status !== 'reserved') {
143
+ if (reserved.reason !== 'allowance_exhausted')
144
+ throw new QuotaDuplicate();
145
+ // Propagate past the outer transaction: nested adapter calls have no savepoint.
146
+ throw new QuotaDeferred(entry.reason, entry.retryAt);
147
+ }
148
+ const settled = await entry.meter.settle({ operationId: request.attemptId, outcome: 'consumed', now });
149
+ if (settled.status !== 'settled' || settled.state !== 'consumed')
150
+ throw new Error('source_quota_settle_failed');
151
+ }
152
+ const permit = { token: randomUUID(), sendBy: now + 1000,
153
+ expiresAt: now + 1000 + this.policy.timeoutMs + this.policy.slotGraceMs, timeoutMs: this.policy.timeoutMs };
154
+ await tx.run(`INSERT INTO ${this.t.attempts}(attempt_id,project_key,mailbox_key,token,send_by,expires_at)
155
+ VALUES($1,$2,$3,$4,$5,$6)`, [request.attemptId, request.projectKey, request.mailboxKey,
156
+ permit.token, permit.sendBy, permit.expiresAt]);
157
+ return permit;
158
+ });
159
+ }
160
+ async begin(request, permit) {
161
+ await this.db.transaction(async (tx) => {
162
+ const project = await this.project(tx, request.projectKey);
163
+ const now = await this.now(tx);
164
+ const mailbox = await tx.get(`SELECT retry_at FROM ${this.t.mailboxes}
165
+ WHERE project_key=$1 AND mailbox_key=$2`, [request.projectKey, request.mailboxKey]);
166
+ const wait = Math.max(Number(project.retry_at), Number(mailbox?.retry_at ?? 0));
167
+ if (wait > now)
168
+ throw new QuotaDeferred('provider_wait', wait);
169
+ const updated = await tx.run(`UPDATE ${this.t.attempts} SET started=TRUE
170
+ WHERE attempt_id=$1 AND project_key=$2 AND mailbox_key=$3 AND token=$4
171
+ AND started=FALSE AND finished=FALSE AND send_by>=$5 AND expires_at>$5`, [request.attemptId, request.projectKey, request.mailboxKey, permit.token, now]);
172
+ if (updated.changes !== 1)
173
+ throw new QuotaDuplicate();
174
+ });
175
+ }
176
+ async release(request, permit) {
177
+ await this.db.transaction(async (tx) => {
178
+ await this.project(tx, request.projectKey);
179
+ await tx.run(`UPDATE ${this.t.attempts} SET finished=TRUE WHERE attempt_id=$1 AND token=$2
180
+ AND project_key=$3 AND mailbox_key=$4`, [request.attemptId, permit.token, request.projectKey, request.mailboxKey]);
181
+ });
182
+ }
183
+ /** Consume weighted capacity and call the transport once. No inline HTTP retries. */
184
+ async run(request, send, parent) {
185
+ if (parent?.aborted)
186
+ throw new SourceSyncFault('cancelled');
187
+ let permit;
188
+ try {
189
+ permit = await this.reserve(request);
190
+ }
191
+ catch (error) {
192
+ if (error instanceof QuotaDeferred)
193
+ return { kind: 'deferred', reason: error.reason, retryAt: error.retryAt };
194
+ if (error instanceof QuotaDuplicate)
195
+ return { kind: 'duplicate' };
196
+ if (error instanceof SourceSyncFault)
197
+ throw error;
198
+ throw new SourceSyncFault('storage_unavailable');
199
+ }
200
+ const controller = new AbortController();
201
+ const abort = () => controller.abort();
202
+ parent?.addEventListener('abort', abort, { once: true });
203
+ let timer;
204
+ let began = false;
205
+ try {
206
+ try {
207
+ await this.begin(request, permit);
208
+ began = true;
209
+ }
210
+ catch (error) {
211
+ if (error instanceof QuotaDeferred)
212
+ return { kind: 'deferred', reason: error.reason, retryAt: error.retryAt };
213
+ if (error instanceof QuotaDuplicate)
214
+ return { kind: 'duplicate' };
215
+ throw new SourceSyncFault('storage_unavailable');
216
+ }
217
+ if (parent?.aborted)
218
+ throw new SourceSyncFault('cancelled');
219
+ timer = setTimeout(abort, permit.timeoutMs);
220
+ // The callback must consume or cancel its body before resolving and honor abort.
221
+ const value = await send(controller.signal);
222
+ if (controller.signal.aborted)
223
+ throw new SourceSyncFault(parent?.aborted ? 'cancelled' : 'network');
224
+ return { kind: 'completed', value };
225
+ }
226
+ finally {
227
+ if (timer !== undefined)
228
+ clearTimeout(timer);
229
+ parent?.removeEventListener('abort', abort);
230
+ // Request units remain consumed even if begin, transport, parsing or cleanup fails.
231
+ try {
232
+ await this.release(request, permit);
233
+ }
234
+ catch {
235
+ if (began)
236
+ throw new SourceSyncFault('storage_unavailable');
237
+ }
238
+ }
239
+ }
240
+ /** A longer provider wait survives restarts, generation changes and shorter updates. */
241
+ async defer(scope, retryAt) {
242
+ if (!key(scope.projectKey) || (scope.mailboxKey !== undefined && !key(scope.mailboxKey))
243
+ || !integer(retryAt) || retryAt > 8_639_999_913_600_000)
244
+ throw new SourceSyncFault('invalid_response');
245
+ try {
246
+ await this.db.transaction(async (tx) => {
247
+ await this.project(tx, scope.projectKey);
248
+ if (scope.mailboxKey === undefined) {
249
+ await tx.run(`UPDATE ${this.t.projects} SET retry_at=GREATEST(retry_at,$2) WHERE project_key=$1`, [scope.projectKey, retryAt]);
250
+ }
251
+ else {
252
+ await tx.run(`INSERT INTO ${this.t.mailboxes}(project_key,mailbox_key,retry_at) VALUES($1,$2,$3)
253
+ ON CONFLICT(project_key,mailbox_key) DO UPDATE SET retry_at=GREATEST(${this.t.mailboxes}.retry_at,excluded.retry_at)`, [scope.projectKey, scope.mailboxKey, retryAt]);
254
+ }
255
+ });
256
+ }
257
+ catch {
258
+ throw new SourceSyncFault('storage_unavailable');
259
+ }
260
+ }
261
+ /** Remove expired attempt evidence after 24 hours and safely old meter records. Daily totals remain. */
262
+ async compact() {
263
+ try {
264
+ await this.db.transaction(async (tx) => {
265
+ const now = await this.now(tx);
266
+ await tx.run(`DELETE FROM ${this.t.attempts} WHERE attempt_id IN (SELECT attempt_id FROM ${this.t.attempts} WHERE expires_at < $1 ORDER BY expires_at LIMIT 1000) AND expires_at < $1`, [Math.max(0, now - 86_400_000)]);
267
+ await tx.run(`DELETE FROM ${this.t.mailboxes} WHERE (project_key,mailbox_key) IN (SELECT project_key,mailbox_key FROM ${this.t.mailboxes} WHERE retry_at <= $1 LIMIT 1000) AND retry_at <= $1`, [now]);
268
+ for (const meter of [this.meter(tx, this.t.pm, this.policy.projectMinute, false),
269
+ this.meter(tx, this.t.mm, this.policy.mailboxMinute, false), this.meter(tx, this.t.pd, this.policy.projectDay, true)]) {
270
+ await meter.purge({ before: Math.max(0, now - 86_400_000), limit: 1000 });
271
+ }
272
+ });
273
+ }
274
+ catch {
275
+ throw new SourceSyncFault('storage_unavailable');
276
+ }
277
+ }
278
+ }
279
+ /** Retry-After is never clamped down to the local exponential-backoff ceiling. */
280
+ export function sourceRetryAt(now, attempt, retryAfter, random = Math.random()) {
281
+ if (!integer(now) || !Number.isInteger(attempt) || attempt < 1 || attempt > 5
282
+ || !Number.isFinite(random) || random < 0 || random > 1)
283
+ throw new SourceSyncFault('invalid_response');
284
+ const base = Math.min(60_000, 1000 * 2 ** (attempt - 1));
285
+ let at = now + Math.min(60_000, Math.floor(base * (1 + random)));
286
+ if (retryAfter !== null) {
287
+ const value = retryAfter.trim();
288
+ if (/^\d+$/.test(value)) {
289
+ const seconds = Number(value);
290
+ if (!integer(seconds) || !integer(now + seconds * 1000))
291
+ throw new SourceSyncFault('invalid_response');
292
+ at = Math.max(at, now + seconds * 1000);
293
+ }
294
+ else {
295
+ const parsed = Date.parse(value);
296
+ if (integer(parsed) && new Date(parsed).toUTCString() === value)
297
+ at = Math.max(at, parsed);
298
+ }
299
+ }
300
+ if (!integer(at) || at > 8_639_999_913_600_000)
301
+ throw new SourceSyncFault('invalid_response');
302
+ return at;
303
+ }
304
+ //# sourceMappingURL=SourceQuota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SourceQuota.js","sourceRoot":"","sources":["../../src/sync/SourceQuota.ts"],"names":[],"mappings":"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"}