deepline 0.3.90 → 0.3.91

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 (87) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +101 -6
  2. package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +2 -556
  3. package/dist/bundling-sources/sdk/src/monitor-fleets.ts +2 -2
  4. package/dist/bundling-sources/sdk/src/release.ts +7 -12
  5. package/dist/bundling-sources/sdk/src/types.ts +6 -1
  6. package/dist/bundling-sources/shared_libs/integration-contracts/monitor-fleet-contract.ts +549 -0
  7. package/dist/bundling-sources/shared_libs/monitors/fleets/errors.ts +96 -0
  8. package/dist/bundling-sources/shared_libs/monitors/fleets/limits.ts +8 -0
  9. package/dist/bundling-sources/shared_libs/monitors/fleets/plan.ts +320 -0
  10. package/dist/bundling-sources/shared_libs/monitors/fleets/verdict.ts +106 -0
  11. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +5 -1
  12. package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +2 -12
  13. package/dist/bundling-sources/shared_libs/monitors/package.json +107 -5
  14. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +12 -7
  15. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +36 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/db-session-lease-store.ts +578 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +521 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-receipt-gateway.ts +1378 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/runtime-topology.ts +1024 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-gateway-route-probe.ts +193 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machine-lifecycle.ts +104 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +1844 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +21 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/deployment/process-tree.ts +40 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +262 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-surface.ts +98 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +303 -0
  29. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-publication-guardian.ts +70 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-recovery.ts +519 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-fly-rollback-alignment.ts +280 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-production-gate.ts +159 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/memory-store.ts +114 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +44 -5
  35. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/child-play-resolver.ts +26 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +3662 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +47 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +84 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-gateway-transport.ts +91 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-heartbeat.ts +424 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +321 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-sheet-persistence-retry.ts +110 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +625 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +46 -3
  45. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +1 -1
  46. package/dist/bundling-sources/shared_libs/play-runtime/webhook-receipt-retry.ts +50 -0
  47. package/dist/bundling-sources/shared_libs/plays/bundling/repository-adapter.ts +24 -0
  48. package/dist/bundling-sources/shared_libs/plays/child-run-projection.ts +174 -0
  49. package/dist/bundling-sources/shared_libs/plays/deploy/api-client.ts +267 -0
  50. package/dist/bundling-sources/shared_libs/plays/deploy/control-plane-types.ts +73 -0
  51. package/dist/bundling-sources/shared_libs/plays/deploy/manifest.ts +162 -0
  52. package/dist/bundling-sources/shared_libs/plays/deploy/project-serializer.ts +87 -0
  53. package/dist/bundling-sources/shared_libs/plays/deploy/source-file-path.ts +26 -0
  54. package/dist/bundling-sources/shared_libs/plays/deploy/target-paths.ts +36 -0
  55. package/dist/bundling-sources/shared_libs/plays/launch/input-contract.ts +400 -0
  56. package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +96 -0
  57. package/dist/bundling-sources/shared_libs/plays/local-file-discovery.ts +385 -0
  58. package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +79 -6
  59. package/dist/bundling-sources/shared_libs/plays/play-reference.ts +117 -0
  60. package/dist/bundling-sources/shared_libs/plays/run-input-summary.ts +260 -0
  61. package/dist/bundling-sources/shared_libs/plays/run-summary-types.ts +49 -0
  62. package/dist/bundling-sources/shared_libs/plays/secrets/errors.ts +19 -0
  63. package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +347 -0
  64. package/dist/bundling-sources/shared_libs/plays/standard-webhooks.ts +117 -0
  65. package/dist/bundling-sources/shared_libs/plays/status-result.ts +388 -0
  66. package/dist/bundling-sources/shared_libs/plays/transform-factor-structure.ts +377 -0
  67. package/dist/bundling-sources/shared_libs/plays/webhook-payload.ts +113 -0
  68. package/dist/cli/index.js +369 -162
  69. package/dist/cli/index.mjs +369 -162
  70. package/dist/{compiler-manifest-BaJfE-5H.d.mts → compiler-manifest-x14KL83E.d.mts} +1 -1
  71. package/dist/{compiler-manifest-BaJfE-5H.d.ts → compiler-manifest-x14KL83E.d.ts} +1 -1
  72. package/dist/index.d.mts +45 -8
  73. package/dist/index.d.ts +45 -8
  74. package/dist/index.js +61 -14
  75. package/dist/index.mjs +61 -14
  76. package/dist/install-integrity.json +54 -2
  77. package/dist/monitor-fleet-contract.d.mts +3 -10
  78. package/dist/monitor-fleet-contract.d.ts +3 -10
  79. package/dist/monitor-fleet-contract.js +2 -0
  80. package/dist/monitor-fleet-contract.mjs +1 -1
  81. package/dist/plays/bundle-play-file.d.mts +2 -2
  82. package/dist/plays/bundle-play-file.d.ts +2 -2
  83. package/dist/release.d.mts +3 -3
  84. package/dist/release.d.ts +3 -3
  85. package/dist/release.js +7 -12
  86. package/dist/release.mjs +7 -12
  87. package/package.json +2 -2
@@ -650,5 +650,41 @@
650
650
  "owner": "platform",
651
651
  "criticality": "maintenance",
652
652
  "failureAlert": "convex_action_wrapper"
653
+ },
654
+ {
655
+ "id": "github.modal-ci-runner-weekly-proof",
656
+ "name": "Modal CI runner",
657
+ "scheduler": "github_actions",
658
+ "cadence": "23 6 * * 1",
659
+ "timezone": "UTC",
660
+ "source": ".github/workflows/modal-ci-runner.yml",
661
+ "locator": "schedule:",
662
+ "owner": "platform",
663
+ "criticality": "maintenance",
664
+ "failureAlert": "github_workflow_run"
665
+ },
666
+ {
667
+ "id": "convex.async-usage-recovery-continuation",
668
+ "name": "Async usage recovery continuation",
669
+ "scheduler": "convex_scheduled_action",
670
+ "cadence": "runAfter per attached async provider job, re-armed from provider truth",
671
+ "timezone": "UTC",
672
+ "source": "packages/convex/scheduledJobsNode.ts",
673
+ "locator": "dispatchAsyncUsageRecoveryContinuation",
674
+ "owner": "billing",
675
+ "criticality": "critical",
676
+ "failureAlert": "convex_action_wrapper"
677
+ },
678
+ {
679
+ "id": "convex.charge-reconciliation-backlog-continuation",
680
+ "name": "Charge reconciliation backlog continuation",
681
+ "scheduler": "convex_scheduled_action",
682
+ "cadence": "runAfter per bounded due-reservation page while a Convex cursor drain is active",
683
+ "timezone": "UTC",
684
+ "source": "packages/convex/scheduledJobsNode.ts",
685
+ "locator": "dispatchChargeReconciliationBacklogContinuation",
686
+ "owner": "billing",
687
+ "criticality": "critical",
688
+ "failureAlert": "convex_action_wrapper"
653
689
  }
654
690
  ]
@@ -242,7 +242,7 @@ type RuntimeApiRequest =
242
242
  sheetContract?: PlaySheetContract | null;
243
243
  contractSnapshot?: unknown;
244
244
  runId: string;
245
- userEmail: string;
245
+ userEmail?: string | null;
246
246
  updates: Array<Omit<PlayRowUpdate, 'rowId'> & { runId?: string }>;
247
247
  }
248
248
  | {
@@ -0,0 +1,578 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type { Redis } from '@upstash/redis';
3
+ import { getRedisClient } from '@deepline/redis/client';
4
+ import {
5
+ createDbSessionResponseSchema,
6
+ type CreateDbSessionResponse,
7
+ type DbSessionLimits,
8
+ type DbSessionOperation,
9
+ } from './/db-session';
10
+
11
+ const KEY_PREFIX = 'plays:db-session-lease:v1';
12
+ const MIN_REUSABLE_LEASE_MS = 60_000;
13
+ const TARGET_RESERVATION_TTL_SECONDS = 60;
14
+
15
+ // Redis stores active scoped Postgres URLs, so every key must include org and
16
+ // run identity. Never key this only by play/table: play names are not globally
17
+ // unique, and table namespaces are caller-authored SDK strings.
18
+ export type DbSessionLeaseLookup = {
19
+ orgId: string;
20
+ workflowId: string;
21
+ runId: string;
22
+ playName: string;
23
+ tableNamespace: string;
24
+ logicalTable: string;
25
+ operations: readonly DbSessionOperation[];
26
+ limits?: DbSessionLimits | undefined;
27
+ userEmail: string;
28
+ };
29
+
30
+ export type DbSessionLeaseStore = {
31
+ getActive(
32
+ input: DbSessionLeaseLookup,
33
+ ): Promise<CreateDbSessionResponse | null>;
34
+ reserveTarget(
35
+ input: DbSessionLeaseLookup,
36
+ ): Promise<
37
+ | { status: 'reserved'; reservationId: string }
38
+ | { status: 'active'; response: CreateDbSessionResponse }
39
+ | { status: 'conflict' }
40
+ >;
41
+ put(
42
+ input: DbSessionLeaseLookup,
43
+ response: CreateDbSessionResponse,
44
+ reservationId: string,
45
+ ): Promise<void>;
46
+ releaseTargetReservation(
47
+ input: DbSessionLeaseLookup,
48
+ reservationId: string,
49
+ ): Promise<void>;
50
+ };
51
+
52
+ const memoryLeaseEntries = new Map<string, LeaseEntry>();
53
+ const memoryTargetEntries = new Map<string, TargetEntry>();
54
+
55
+ type LeaseMetadata = {
56
+ orgId: string;
57
+ workflowId: string;
58
+ runId: string;
59
+ playName: string;
60
+ tableNamespace: string;
61
+ logicalTable: string;
62
+ operations: string[];
63
+ limits: DbSessionLimits;
64
+ userEmail: string;
65
+ };
66
+ type LeaseEntry = {
67
+ kind: 'lease';
68
+ metadata: LeaseMetadata;
69
+ response: CreateDbSessionResponse;
70
+ };
71
+ type TargetReservation = {
72
+ kind: 'reservation';
73
+ reservationId: string;
74
+ metadata: LeaseMetadata;
75
+ };
76
+ type TargetEntry = LeaseEntry | TargetReservation;
77
+
78
+ function stableJson(value: unknown): string {
79
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
80
+ return JSON.stringify(value);
81
+ }
82
+ const record = value as Record<string, unknown>;
83
+ return JSON.stringify(
84
+ Object.keys(record)
85
+ .sort()
86
+ .reduce<Record<string, unknown>>((acc, key) => {
87
+ acc[key] = record[key];
88
+ return acc;
89
+ }, {}),
90
+ );
91
+ }
92
+
93
+ function leaseKey(input: DbSessionLeaseLookup): string {
94
+ // Exact-shape lease key. This is the idempotency path: the same org/run/table
95
+ // request with the same operations/limits/user gets the same active URL back
96
+ // instead of minting another short-lived Postgres role.
97
+ const fingerprint = createHash('sha256')
98
+ .update(
99
+ stableJson({
100
+ orgId: input.orgId,
101
+ workflowId: input.workflowId,
102
+ runId: input.runId,
103
+ playName: input.playName,
104
+ tableNamespace: input.tableNamespace,
105
+ logicalTable: input.logicalTable,
106
+ operations: [...input.operations].sort(),
107
+ limits: input.limits ?? {},
108
+ userEmail: input.userEmail,
109
+ }),
110
+ )
111
+ .digest('hex')
112
+ .slice(0, 32);
113
+ return `${KEY_PREFIX}:org:${input.orgId}:${fingerprint}`;
114
+ }
115
+
116
+ function targetLeaseKey(input: DbSessionLeaseLookup): string {
117
+ // Target key intentionally excludes operations/limits/user. It is a guardrail
118
+ // for "one active credential shape per org/run/play/table target"; callers can
119
+ // reuse the exact lease, but cannot widen privileges while the first URL lives.
120
+ const fingerprint = createHash('sha256')
121
+ .update(
122
+ stableJson({
123
+ orgId: input.orgId,
124
+ workflowId: input.workflowId,
125
+ runId: input.runId,
126
+ playName: input.playName,
127
+ tableNamespace: input.tableNamespace,
128
+ logicalTable: input.logicalTable,
129
+ }),
130
+ )
131
+ .digest('hex')
132
+ .slice(0, 32);
133
+ return `${KEY_PREFIX}:target:org:${input.orgId}:${fingerprint}`;
134
+ }
135
+
136
+ function reservationId(input: DbSessionLeaseLookup): string {
137
+ return createHash('sha256')
138
+ .update(`${leaseKey(input)}:${Date.now()}:${Math.random()}`)
139
+ .digest('hex')
140
+ .slice(0, 32);
141
+ }
142
+
143
+ function metadataFor(input: DbSessionLeaseLookup): LeaseMetadata {
144
+ return {
145
+ orgId: input.orgId,
146
+ workflowId: input.workflowId,
147
+ runId: input.runId,
148
+ playName: input.playName,
149
+ tableNamespace: input.tableNamespace,
150
+ logicalTable: input.logicalTable,
151
+ operations: [...input.operations].sort(),
152
+ limits: input.limits ?? {},
153
+ userEmail: input.userEmail,
154
+ };
155
+ }
156
+
157
+ function remainingLeaseMs(response: CreateDbSessionResponse): number {
158
+ const expiresAtMs = Date.parse(response.expiresAt);
159
+ if (!Number.isFinite(expiresAtMs)) {
160
+ return 0;
161
+ }
162
+ return expiresAtMs - Date.now();
163
+ }
164
+
165
+ function sameLimits(
166
+ left: DbSessionLimits | undefined,
167
+ right: DbSessionLimits | undefined,
168
+ ): boolean {
169
+ return stableJson(left ?? {}) === stableJson(right ?? {});
170
+ }
171
+
172
+ function sameOperations(
173
+ left: readonly DbSessionOperation[],
174
+ right: readonly DbSessionOperation[],
175
+ ): boolean {
176
+ return stableJson([...left].sort()) === stableJson([...right].sort());
177
+ }
178
+
179
+ function operationsSatisfyRequest(
180
+ existing: readonly DbSessionOperation[],
181
+ requested: readonly DbSessionOperation[],
182
+ ): boolean {
183
+ const existingSet = new Set(existing);
184
+ return requested.every((operation) => existingSet.has(operation));
185
+ }
186
+
187
+ function limitsSatisfyRequest(
188
+ existing: DbSessionLimits | undefined,
189
+ requested: DbSessionLimits | undefined,
190
+ ): boolean {
191
+ const existingLimits = existing ?? {};
192
+ const requestedLimits = requested ?? {};
193
+ for (const [key, requestedValue] of Object.entries(requestedLimits)) {
194
+ if (
195
+ typeof requestedValue !== 'number' ||
196
+ !Number.isFinite(requestedValue)
197
+ ) {
198
+ if (
199
+ stableJson((existingLimits as Record<string, unknown>)[key]) !==
200
+ stableJson(requestedValue)
201
+ ) {
202
+ return false;
203
+ }
204
+ continue;
205
+ }
206
+ const existingValue = (existingLimits as Record<string, unknown>)[key];
207
+ if (typeof existingValue !== 'number' || !Number.isFinite(existingValue)) {
208
+ return false;
209
+ }
210
+ if (existingValue < requestedValue) {
211
+ return false;
212
+ }
213
+ }
214
+ return true;
215
+ }
216
+
217
+ function metadataMatchesLookup(
218
+ input: DbSessionLeaseLookup,
219
+ metadata: LeaseMetadata,
220
+ options: { includeExactShape: boolean },
221
+ ): boolean {
222
+ const targetMatches =
223
+ metadata.orgId === input.orgId &&
224
+ metadata.workflowId === input.workflowId &&
225
+ metadata.runId === input.runId &&
226
+ metadata.playName === input.playName &&
227
+ metadata.tableNamespace === input.tableNamespace &&
228
+ metadata.logicalTable === input.logicalTable;
229
+ if (!targetMatches) {
230
+ return false;
231
+ }
232
+ if (!options.includeExactShape) {
233
+ return true;
234
+ }
235
+ return (
236
+ sameOperations(
237
+ metadata.operations as DbSessionOperation[],
238
+ input.operations,
239
+ ) &&
240
+ sameLimits(metadata.limits, input.limits) &&
241
+ metadata.userEmail === input.userEmail
242
+ );
243
+ }
244
+
245
+ function leaseMatchesLookup(
246
+ input: DbSessionLeaseLookup,
247
+ entry: LeaseEntry,
248
+ ): boolean {
249
+ // Redis is not the authority. If a key is poisoned, stale, or computed by a
250
+ // future bug, the response payload still has to match the requested org/table
251
+ // scope before we hand a database URL back to the worker.
252
+ const response = entry.response;
253
+ return (
254
+ metadataMatchesLookup(input, entry.metadata, { includeExactShape: true }) &&
255
+ response.target.orgId === input.orgId &&
256
+ response.playName === input.playName &&
257
+ response.target.tableNamespace === input.tableNamespace &&
258
+ response.target.logicalTable === input.logicalTable &&
259
+ sameOperations(response.operations, input.operations) &&
260
+ sameLimits(response.limits, input.limits)
261
+ );
262
+ }
263
+
264
+ function activeTargetLeaseSatisfiesLookup(
265
+ input: DbSessionLeaseLookup,
266
+ entry: LeaseEntry,
267
+ ): boolean {
268
+ const response = entry.response;
269
+ return (
270
+ metadataMatchesLookup(input, entry.metadata, {
271
+ includeExactShape: false,
272
+ }) &&
273
+ entry.metadata.userEmail === input.userEmail &&
274
+ response.target.orgId === input.orgId &&
275
+ response.playName === input.playName &&
276
+ response.target.tableNamespace === input.tableNamespace &&
277
+ response.target.logicalTable === input.logicalTable &&
278
+ operationsSatisfyRequest(response.operations, input.operations) &&
279
+ limitsSatisfyRequest(response.limits, input.limits)
280
+ );
281
+ }
282
+
283
+ function parseLeaseEntry(raw: unknown): LeaseEntry | null {
284
+ if (!raw || typeof raw !== 'object') {
285
+ return null;
286
+ }
287
+ const record = raw as Record<string, unknown>;
288
+ if (record.kind !== 'lease' || !isLeaseMetadata(record.metadata)) {
289
+ return null;
290
+ }
291
+ const response = createDbSessionResponseSchema.safeParse(record.response);
292
+ if (!response.success) {
293
+ return null;
294
+ }
295
+ return {
296
+ kind: 'lease',
297
+ metadata: record.metadata,
298
+ response: response.data,
299
+ };
300
+ }
301
+
302
+ function parseTargetEntry(raw: unknown): TargetEntry | null {
303
+ const lease = parseLeaseEntry(raw);
304
+ if (lease) {
305
+ return lease;
306
+ }
307
+ if (!raw || typeof raw !== 'object') {
308
+ return null;
309
+ }
310
+ const record = raw as Record<string, unknown>;
311
+ if (
312
+ record.kind !== 'reservation' ||
313
+ typeof record.reservationId !== 'string' ||
314
+ record.reservationId.length === 0 ||
315
+ !isLeaseMetadata(record.metadata)
316
+ ) {
317
+ return null;
318
+ }
319
+ return {
320
+ kind: 'reservation',
321
+ reservationId: record.reservationId,
322
+ metadata: record.metadata,
323
+ };
324
+ }
325
+
326
+ function isLeaseMetadata(value: unknown): value is LeaseMetadata {
327
+ if (!value || typeof value !== 'object') {
328
+ return false;
329
+ }
330
+ const record = value as Record<string, unknown>;
331
+ return (
332
+ typeof record.orgId === 'string' &&
333
+ record.orgId.length > 0 &&
334
+ typeof record.workflowId === 'string' &&
335
+ record.workflowId.length > 0 &&
336
+ typeof record.runId === 'string' &&
337
+ record.runId.length > 0 &&
338
+ typeof record.playName === 'string' &&
339
+ record.playName.length > 0 &&
340
+ typeof record.tableNamespace === 'string' &&
341
+ record.tableNamespace.length > 0 &&
342
+ typeof record.logicalTable === 'string' &&
343
+ record.logicalTable.length > 0 &&
344
+ Array.isArray(record.operations) &&
345
+ record.operations.every((operation) => typeof operation === 'string') &&
346
+ (!record.limits ||
347
+ (typeof record.limits === 'object' && !Array.isArray(record.limits))) &&
348
+ typeof record.userEmail === 'string'
349
+ );
350
+ }
351
+
352
+ export function createRedisDbSessionLeaseStore(
353
+ redis: Redis | null = getRedisClient(),
354
+ ): DbSessionLeaseStore {
355
+ if (!redis && process.env.NODE_ENV !== 'production') {
356
+ return createInMemoryDbSessionLeaseStore();
357
+ }
358
+
359
+ async function readActiveEntry(key: string): Promise<LeaseEntry | null> {
360
+ if (!redis) {
361
+ return null;
362
+ }
363
+ const raw = await redis.get<unknown>(key);
364
+ if (!raw) {
365
+ return null;
366
+ }
367
+ const parsed = parseLeaseEntry(raw);
368
+ if (!parsed || remainingLeaseMs(parsed.response) <= MIN_REUSABLE_LEASE_MS) {
369
+ await redis.del(key);
370
+ return null;
371
+ }
372
+ return parsed;
373
+ }
374
+
375
+ return {
376
+ async getActive(input) {
377
+ if (!redis) {
378
+ return null;
379
+ }
380
+ const key = leaseKey(input);
381
+ const active = await readActiveEntry(key);
382
+ if (!active) {
383
+ return null;
384
+ }
385
+ if (!leaseMatchesLookup(input, active)) {
386
+ // Delete mismatched cache entries instead of falling back silently; a
387
+ // mismatch here means the security invariant around org/run scope broke.
388
+ await redis.del(key);
389
+ return null;
390
+ }
391
+ return active.response;
392
+ },
393
+ async reserveTarget(input) {
394
+ if (!redis) {
395
+ throw new Error(
396
+ 'Redis is required to reserve scoped DB session leases.',
397
+ );
398
+ }
399
+ const key = targetLeaseKey(input);
400
+ const existing = await redis.get<unknown>(key);
401
+ if (existing) {
402
+ const parsed = parseTargetEntry(existing);
403
+ if (!parsed) {
404
+ await redis.del(key);
405
+ } else if (parsed.kind === 'lease') {
406
+ if (remainingLeaseMs(parsed.response) <= MIN_REUSABLE_LEASE_MS) {
407
+ await redis.del(key);
408
+ } else if (
409
+ !metadataMatchesLookup(input, parsed.metadata, {
410
+ includeExactShape: false,
411
+ }) ||
412
+ parsed.response.target.orgId !== input.orgId ||
413
+ parsed.response.playName !== input.playName ||
414
+ parsed.response.target.tableNamespace !== input.tableNamespace ||
415
+ parsed.response.target.logicalTable !== input.logicalTable
416
+ ) {
417
+ await redis.del(key);
418
+ } else {
419
+ return activeTargetLeaseSatisfiesLookup(input, parsed)
420
+ ? { status: 'active', response: parsed.response }
421
+ : { status: 'conflict' };
422
+ }
423
+ } else if (
424
+ metadataMatchesLookup(input, parsed.metadata, {
425
+ includeExactShape: false,
426
+ })
427
+ ) {
428
+ return { status: 'conflict' };
429
+ } else {
430
+ await redis.del(key);
431
+ }
432
+ }
433
+
434
+ const id = reservationId(input);
435
+ const reserved = await redis.set(
436
+ key,
437
+ {
438
+ kind: 'reservation',
439
+ reservationId: id,
440
+ metadata: metadataFor(input),
441
+ } satisfies TargetReservation,
442
+ { ex: TARGET_RESERVATION_TTL_SECONDS, nx: true } as never,
443
+ );
444
+ if (reserved !== 'OK') {
445
+ return { status: 'conflict' };
446
+ }
447
+ return { status: 'reserved', reservationId: id };
448
+ },
449
+ async put(input, response, reservationId) {
450
+ if (!redis) {
451
+ throw new Error('Redis is required to store scoped DB session leases.');
452
+ }
453
+ const key = targetLeaseKey(input);
454
+ const targetEntry = parseTargetEntry(await redis.get<unknown>(key));
455
+ if (
456
+ !targetEntry ||
457
+ targetEntry.kind !== 'reservation' ||
458
+ targetEntry.reservationId !== reservationId ||
459
+ !metadataMatchesLookup(input, targetEntry.metadata, {
460
+ includeExactShape: true,
461
+ })
462
+ ) {
463
+ throw new Error(
464
+ 'Scoped DB session target reservation was not held by this request.',
465
+ );
466
+ }
467
+ const ttlSeconds = Math.floor(remainingLeaseMs(response) / 1000);
468
+ if (ttlSeconds <= 0) {
469
+ await redis.del(key);
470
+ return;
471
+ }
472
+ const entry = {
473
+ kind: 'lease',
474
+ metadata: metadataFor(input),
475
+ response,
476
+ } satisfies LeaseEntry;
477
+ await Promise.all([
478
+ redis.set(leaseKey(input), entry, { ex: ttlSeconds }),
479
+ redis.set(key, entry, { ex: ttlSeconds }),
480
+ ]);
481
+ },
482
+ async releaseTargetReservation(input, reservationId) {
483
+ if (!redis) {
484
+ return;
485
+ }
486
+ const key = targetLeaseKey(input);
487
+ const targetEntry = parseTargetEntry(await redis.get<unknown>(key));
488
+ if (
489
+ targetEntry?.kind === 'reservation' &&
490
+ targetEntry.reservationId === reservationId
491
+ ) {
492
+ // Same target-index rule: target entries are only hints for active lease
493
+ // exclusivity. Validate the payload before using it for any decision.
494
+ await redis.del(key);
495
+ }
496
+ },
497
+ };
498
+ }
499
+
500
+ function createInMemoryDbSessionLeaseStore(): DbSessionLeaseStore {
501
+ async function readActiveEntry(key: string): Promise<LeaseEntry | null> {
502
+ const entry = memoryLeaseEntries.get(key) ?? null;
503
+ if (!entry || remainingLeaseMs(entry.response) <= MIN_REUSABLE_LEASE_MS) {
504
+ memoryLeaseEntries.delete(key);
505
+ return null;
506
+ }
507
+ return entry;
508
+ }
509
+
510
+ return {
511
+ async getActive(input) {
512
+ const active = await readActiveEntry(leaseKey(input));
513
+ if (!active) {
514
+ return null;
515
+ }
516
+ if (!leaseMatchesLookup(input, active)) {
517
+ memoryLeaseEntries.delete(leaseKey(input));
518
+ return null;
519
+ }
520
+ return active.response;
521
+ },
522
+ async reserveTarget(input) {
523
+ const key = targetLeaseKey(input);
524
+ const existing = memoryTargetEntries.get(key);
525
+ if (existing) {
526
+ if (existing.kind === 'lease') {
527
+ if (remainingLeaseMs(existing.response) <= MIN_REUSABLE_LEASE_MS) {
528
+ memoryTargetEntries.delete(key);
529
+ } else if (activeTargetLeaseSatisfiesLookup(input, existing)) {
530
+ return { status: 'active', response: existing.response };
531
+ } else {
532
+ return { status: 'conflict' };
533
+ }
534
+ } else if (
535
+ metadataMatchesLookup(input, existing.metadata, {
536
+ includeExactShape: false,
537
+ })
538
+ ) {
539
+ return { status: 'conflict' };
540
+ }
541
+ }
542
+
543
+ const id = reservationId(input);
544
+ memoryTargetEntries.set(key, {
545
+ kind: 'reservation',
546
+ reservationId: id,
547
+ metadata: metadataFor(input),
548
+ });
549
+ return { status: 'reserved', reservationId: id };
550
+ },
551
+ async put(input, response, reservationId) {
552
+ const leaseEntry: LeaseEntry = {
553
+ kind: 'lease',
554
+ metadata: metadataFor(input),
555
+ response,
556
+ };
557
+ memoryLeaseEntries.set(leaseKey(input), leaseEntry);
558
+ const targetKey = targetLeaseKey(input);
559
+ const target = memoryTargetEntries.get(targetKey);
560
+ if (
561
+ target?.kind === 'reservation' &&
562
+ target.reservationId === reservationId
563
+ ) {
564
+ memoryTargetEntries.set(targetKey, leaseEntry);
565
+ }
566
+ },
567
+ async releaseTargetReservation(input, reservationId) {
568
+ const key = targetLeaseKey(input);
569
+ const existing = memoryTargetEntries.get(key);
570
+ if (
571
+ existing?.kind === 'reservation' &&
572
+ existing.reservationId === reservationId
573
+ ) {
574
+ memoryTargetEntries.delete(key);
575
+ }
576
+ },
577
+ };
578
+ }