deepline 0.3.443 → 0.3.445

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 (99) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +27 -6
  3. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +153 -31
  4. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +32 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +9 -2
  6. package/dist/cli/{auth-CP2CAI7J.mjs → auth-QNWYRLML.mjs} +4 -4
  7. package/dist/cli/auth-ZA4FRBSX.js +40 -0
  8. package/dist/cli/{billing-JYSP3C76.js → billing-3HFPC32I.js} +73 -73
  9. package/dist/cli/{billing-N36FL3GI.mjs → billing-ULHQPQ2L.mjs} +4 -4
  10. package/dist/cli/{chunk-Y45RAHEA.mjs → chunk-2L2D2SXH.mjs} +2 -2
  11. package/dist/cli/{chunk-6NUGMJQA.mjs → chunk-3OBHZADJ.mjs} +1 -1
  12. package/dist/cli/{chunk-WOUFCLTM.mjs → chunk-3SELRVWB.mjs} +5 -5
  13. package/dist/cli/{chunk-EWXXWSQM.js → chunk-5JJ6JYTL.js} +6 -6
  14. package/dist/cli/{chunk-XIJL6UMV.js → chunk-6DQTJ3DY.js} +35 -35
  15. package/dist/cli/{chunk-DOKTNYMQ.mjs → chunk-6LVOC2HD.mjs} +1 -1
  16. package/dist/cli/{chunk-CGZ2KKPM.js → chunk-732QWWPG.js} +10 -10
  17. package/dist/cli/{chunk-MRW6XPDH.mjs → chunk-BEI6ZL6E.mjs} +1 -1
  18. package/dist/cli/{chunk-MZLZNISS.mjs → chunk-C54S4QPU.mjs} +2 -2
  19. package/dist/cli/{chunk-OSYT3QQD.js → chunk-COCZROBN.js} +26 -26
  20. package/dist/cli/{chunk-KZ2VAXB6.js → chunk-CQ3ZZ3RL.js} +16 -16
  21. package/dist/cli/{chunk-DPEPHLAF.mjs → chunk-E5JZH6CI.mjs} +1 -1
  22. package/dist/cli/{chunk-MWUTMJCU.js → chunk-EI2NN6HP.js} +1 -1
  23. package/dist/cli/{chunk-GVPHPLAX.mjs → chunk-FB7VBDA3.mjs} +3 -3
  24. package/dist/cli/{chunk-ZXJQ26QG.js → chunk-FC7LS3Y5.js} +50 -50
  25. package/dist/cli/{chunk-KJH4C2IJ.js → chunk-GGMJ2VTZ.js} +81 -81
  26. package/dist/cli/{chunk-YV6N4BF2.js → chunk-H6HZMADG.js} +52 -52
  27. package/dist/cli/{chunk-75VUSWF3.js → chunk-HFXG7GGH.js} +6 -6
  28. package/dist/cli/{chunk-FU7ELMKS.mjs → chunk-IXJN2NLA.mjs} +1 -1
  29. package/dist/cli/{chunk-HHOJXBQQ.mjs → chunk-LBZUIZKQ.mjs} +2 -2
  30. package/dist/cli/{chunk-LVM7CSAH.js → chunk-MMMA4OLB.js} +8 -8
  31. package/dist/cli/{chunk-L3CW2G7L.mjs → chunk-NHWNFLFK.mjs} +2 -2
  32. package/dist/cli/{chunk-757V3FDW.js → chunk-OAVHRVPA.js} +2 -2
  33. package/dist/cli/{chunk-MQYBXB2J.mjs → chunk-SAN2HUYZ.mjs} +8 -8
  34. package/dist/cli/{chunk-B6AWQQK6.mjs → chunk-SSRKG7I5.mjs} +3 -3
  35. package/dist/cli/{chunk-VXGLDGOF.mjs → chunk-T7SLOS2J.mjs} +4 -4
  36. package/dist/cli/{chunk-PXXIBSBG.js → chunk-TABMEHIP.js} +4 -4
  37. package/dist/cli/{chunk-K4ELTQAT.js → chunk-THZL4XHV.js} +8 -8
  38. package/dist/cli/{chunk-6RKZVPFA.js → chunk-TX6F2BB6.js} +2 -2
  39. package/dist/cli/{chunk-BE2GX674.js → chunk-V247SCRQ.js} +20 -20
  40. package/dist/cli/{chunk-FRZLWUM6.mjs → chunk-YXY6FCRU.mjs} +3 -3
  41. package/dist/cli/{chunk-4A45A2MH.mjs → chunk-ZRWX23RQ.mjs} +1 -1
  42. package/dist/cli/{csv-ZQFVZ4R3.js → csv-NAATVV3O.js} +12 -12
  43. package/dist/cli/{csv-Z53PJEPZ.mjs → csv-QXMJNWPE.mjs} +3 -3
  44. package/dist/cli/{db-4CNIBR5P.mjs → db-2ADNWVDN.mjs} +4 -4
  45. package/dist/cli/{db-OHBRQL2N.js → db-IB426AKH.js} +25 -25
  46. package/dist/cli/{describe-AEM7SYXF.js → describe-6T2HEQ2V.js} +16 -16
  47. package/dist/cli/{describe-GAR4MEGX.mjs → describe-UM4PAI2I.mjs} +12 -12
  48. package/dist/cli/{executions-CST4IDL3.js → executions-3WVCUBHH.js} +6 -6
  49. package/dist/cli/{executions-YBZPFAD6.mjs → executions-4NCMXHDI.mjs} +4 -4
  50. package/dist/cli/{feedback-MEJZGB4C.js → feedback-C4IBRZKH.js} +7 -7
  51. package/dist/cli/{feedback-FHJE6YDU.mjs → feedback-OKLN3VLD.mjs} +3 -3
  52. package/dist/cli/{git-FIGXIUM5.mjs → git-A6XP6LIW.mjs} +4 -4
  53. package/dist/cli/git-RRZOWDCN.js +24 -0
  54. package/dist/cli/index.js +131 -131
  55. package/dist/cli/index.mjs +36 -36
  56. package/dist/cli/{legacy-noops-BE3VVOYG.js → legacy-noops-ALY2PBIT.js} +7 -7
  57. package/dist/cli/{legacy-noops-AXHAIAMH.mjs → legacy-noops-LCQXCYQY.mjs} +4 -4
  58. package/dist/cli/{monitors-5OTJUY43.js → monitors-5C6PSVAT.js} +84 -84
  59. package/dist/cli/{monitors-YTWELXP7.mjs → monitors-T3YN2RYD.mjs} +4 -4
  60. package/dist/cli/{org-Y5DMTECB.js → org-4V76A3LD.js} +66 -66
  61. package/dist/cli/{org-WCKS4ZDK.mjs → org-7WLA42NC.mjs} +4 -4
  62. package/dist/cli/{play-B6Y6ZCPV.js → play-UT3TLBM6.js} +283 -283
  63. package/dist/cli/{play-UYAZYXHV.mjs → play-WRHGX3WB.mjs} +10 -10
  64. package/dist/cli/{providers-IZVVGFAO.mjs → providers-UA2DJLBP.mjs} +4 -4
  65. package/dist/cli/{providers-U7BLFNT4.js → providers-ZR4SRGJF.js} +7 -7
  66. package/dist/cli/{quickstart-BY7N6GXZ.mjs → quickstart-IT46STSN.mjs} +3 -3
  67. package/dist/cli/{quickstart-MTNOTGIH.js → quickstart-QN5FXZWS.js} +5 -5
  68. package/dist/cli/search-INZD56I2.js +30 -0
  69. package/dist/cli/{search-SJXJKATQ.mjs → search-QKK5SGMV.mjs} +5 -5
  70. package/dist/cli/{secrets-C2FN2YMO.js → secrets-FX3IVBUP.js} +13 -13
  71. package/dist/cli/{secrets-IA6JBZEF.mjs → secrets-GWXM2FH4.mjs} +4 -4
  72. package/dist/cli/{sessions-MNK7ZR2Y.mjs → sessions-FDZ24XY4.mjs} +4 -4
  73. package/dist/cli/{sessions-HGJQTLZP.js → sessions-G6LCRYTC.js} +5 -5
  74. package/dist/cli/{settings-MRMSITAL.js → settings-6W42BCZE.js} +75 -75
  75. package/dist/cli/{settings-7UBMCVFK.mjs → settings-77ODFZUE.mjs} +8 -8
  76. package/dist/cli/{setup-MD4F55X4.mjs → setup-5OBB5UGB.mjs} +7 -7
  77. package/dist/cli/{setup-UX7KNUOW.js → setup-T3FF5IRN.js} +42 -42
  78. package/dist/cli/{skills-W7SAJDG6.mjs → skills-ADG7CBUP.mjs} +5 -5
  79. package/dist/cli/skills-ALCERVAB.js +29 -0
  80. package/dist/cli/tools-FNWIMNNK.js +44 -0
  81. package/dist/cli/{tools-VAWJLJAK.mjs → tools-OBZRYZ7K.mjs} +12 -12
  82. package/dist/cli/update-N7I4MXRK.js +31 -0
  83. package/dist/cli/{update-B2UOERFE.mjs → update-ODKOYFCA.mjs} +7 -7
  84. package/dist/cli/{workflow-3BYE3YFQ.mjs → workflow-RGKEVZSP.mjs} +5 -5
  85. package/dist/cli/{workflow-K2YJNF64.js → workflow-TBLNAOXJ.js} +15 -15
  86. package/dist/index.js +6 -6
  87. package/dist/index.mjs +3 -3
  88. package/dist/install-integrity.json +78 -78
  89. package/dist/release.d.mts +1 -1
  90. package/dist/release.d.ts +1 -1
  91. package/dist/release.js +2 -2
  92. package/dist/release.mjs +1 -1
  93. package/package.json +1 -1
  94. package/dist/cli/auth-AZVWLMZN.js +0 -40
  95. package/dist/cli/git-PANOVLJC.js +0 -24
  96. package/dist/cli/search-W7VXB4YP.js +0 -30
  97. package/dist/cli/skills-ZY4KFM33.js +0 -29
  98. package/dist/cli/tools-6G4PXAZ3.js +0 -44
  99. package/dist/cli/update-3D4R5FW6.js +0 -31
@@ -205,7 +205,7 @@ export const SDK_RELEASE = {
205
205
  // getters keep their established compatibility behavior.
206
206
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
207
207
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
208
- version: '0.3.443',
208
+ version: '0.3.445',
209
209
  updateSummary: {
210
210
  introducedIn: '0.3.90',
211
211
  summary:
@@ -1102,6 +1102,27 @@ function appRuntimeRetryDelayMs(
1102
1102
  return appRuntimeRetryDelaysForAction(action)[attempt - 1] ?? 1_000;
1103
1103
  }
1104
1104
 
1105
+ const APP_RUNTIME_RETRY_AFTER_MIN_SPREAD_MS = 1_000;
1106
+
1107
+ /**
1108
+ * Every caller shed at once gets the same retry-after. Spreading above it,
1109
+ * instead of taking max() with a smaller jittered step, keeps them from
1110
+ * retrying in lockstep.
1111
+ */
1112
+ function appRuntimeRetrySleepMs(
1113
+ ladderDelayMs: number,
1114
+ retryAfterMs: number | null | undefined,
1115
+ ): number {
1116
+ const jitteredMs = applyRetryJitter(ladderDelayMs);
1117
+ const floorMs = retryAfterMs ?? 0;
1118
+ if (floorMs <= jitteredMs) return jitteredMs;
1119
+ const spreadMs = Math.min(
1120
+ floorMs,
1121
+ Math.max(ladderDelayMs, APP_RUNTIME_RETRY_AFTER_MIN_SPREAD_MS),
1122
+ );
1123
+ return floorMs + Math.round(Math.random() * spreadMs);
1124
+ }
1125
+
1105
1126
  function appRuntimeRetryAfterMs(
1106
1127
  response: Response,
1107
1128
  body: string,
@@ -1292,9 +1313,9 @@ async function retryAppRuntimeBodyTimeoutOrThrow(input: {
1292
1313
  error: input.error,
1293
1314
  })
1294
1315
  ) {
1295
- const retryDelayMs = Math.max(
1296
- applyRetryJitter(appRuntimeRetryDelayMs(input.action, input.attempt)),
1297
- input.retryAfterMs ?? 0,
1316
+ const retryDelayMs = appRuntimeRetrySleepMs(
1317
+ appRuntimeRetryDelayMs(input.action, input.attempt),
1318
+ input.retryAfterMs,
1298
1319
  );
1299
1320
  recordAppRuntimeRetryFailure({
1300
1321
  telemetry: input.telemetry,
@@ -1860,9 +1881,9 @@ async function postAppRuntimeApi<TResponse>(
1860
1881
  contentType: response.headers.get('content-type'),
1861
1882
  })
1862
1883
  ) {
1863
- const retryDelayMs = Math.max(
1864
- applyRetryJitter(appRuntimeRetryDelayMs(body.action, attempt)),
1865
- appRuntimeRetryAfterMs(response, responseText) ?? 0,
1884
+ const retryDelayMs = appRuntimeRetrySleepMs(
1885
+ appRuntimeRetryDelayMs(body.action, attempt),
1886
+ appRuntimeRetryAfterMs(response, responseText),
1866
1887
  );
1867
1888
  recordAppRuntimeRetryFailure({
1868
1889
  telemetry: retryTelemetry,
@@ -1,5 +1,9 @@
1
1
  export type GatewayPostgresLane = 'ordinary' | 'commit';
2
2
 
3
+ export type GatewayPostgresAdmissionTenant = string | null;
4
+ export const GATEWAY_POSTGRES_MACHINE_TENANT: GatewayPostgresAdmissionTenant =
5
+ null;
6
+
3
7
  export const GATEWAY_POSTGRES_MAX_ORDINARY_ACTIVE = 8;
4
8
  export const GATEWAY_POSTGRES_MAX_TOTAL_ACTIVE = 10;
5
9
  export const GATEWAY_POSTGRES_MAX_CONSECUTIVE_COMMIT_PRIORITY_GRANTS = 8;
@@ -16,8 +20,10 @@ export const GATEWAY_POSTGRES_MAX_WAITING_PER_LANE = 256;
16
20
  export type GatewayPostgresAdmissionSnapshot = {
17
21
  active: Record<GatewayPostgresLane, number> & { total: number };
18
22
  waiting: Record<GatewayPostgresLane, number>;
23
+ waitingTenants: Record<GatewayPostgresLane, number>;
19
24
  cancelled: number;
20
25
  overloaded: number;
26
+ fairShareEvictions: number;
21
27
  commitPriorityGrants: number;
22
28
  };
23
29
 
@@ -30,7 +36,7 @@ export class GatewayPostgresAdmissionCancelledError extends Error {
30
36
 
31
37
  /**
32
38
  * Load shed at the admission boundary. It is only ever thrown before a waiter
33
- * is enqueued, so no pooled client, statement, or provider call exists for the
39
+ * is granted, so no pooled client, statement, or provider call exists for the
34
40
  * rejected request. Callers must map it to a retryable back-pressure response.
35
41
  */
36
42
  export class GatewayPostgresAdmissionOverloadedError extends Error {
@@ -38,24 +44,31 @@ export class GatewayPostgresAdmissionOverloadedError extends Error {
38
44
  readonly lane: GatewayPostgresLane;
39
45
  readonly waiting: number;
40
46
  readonly limit: number;
47
+ readonly reason: 'queue_full' | 'fair_share_evicted';
41
48
 
42
49
  constructor(input: {
43
50
  lane: GatewayPostgresLane;
44
51
  waiting: number;
45
52
  limit: number;
53
+ reason?: 'queue_full' | 'fair_share_evicted';
46
54
  }) {
55
+ const reason = input.reason ?? 'queue_full';
47
56
  super(
48
- `Gateway Postgres ${input.lane} admission queue is full (${input.waiting} waiting, limit ${input.limit}).`,
57
+ reason === 'queue_full'
58
+ ? `Gateway Postgres ${input.lane} admission queue is full (${input.waiting} waiting, limit ${input.limit}).`
59
+ : `Gateway Postgres ${input.lane} admission queue is full (${input.waiting} waiting, limit ${input.limit}); this waiter yielded its place to an org with a shorter queue.`,
49
60
  );
50
61
  this.name = 'GatewayPostgresAdmissionOverloadedError';
51
62
  this.lane = input.lane;
52
63
  this.waiting = input.waiting;
53
64
  this.limit = input.limit;
65
+ this.reason = reason;
54
66
  }
55
67
  }
56
68
 
57
69
  type Waiter = {
58
70
  lane: GatewayPostgresLane;
71
+ tenant: GatewayPostgresAdmissionTenant;
59
72
  signal: AbortSignal | null;
60
73
  onAbort: (() => void) | null;
61
74
  resolve: (release: () => void) => void;
@@ -63,15 +76,32 @@ type Waiter = {
63
76
  settled: boolean;
64
77
  };
65
78
 
79
+ type LaneQueue = {
80
+ ring: Map<GatewayPostgresAdmissionTenant, Waiter[]>;
81
+ size: number;
82
+ };
83
+
84
+ /**
85
+ * Fair across orgs within each lane, so one org's burst cannot shed or starve
86
+ * every other org on this Machine.
87
+ */
66
88
  export class GatewayPostgresAdmission {
67
- readonly #waiting: Record<GatewayPostgresLane, Waiter[]> = {
68
- ordinary: [],
69
- commit: [],
89
+ readonly #waiting: Record<GatewayPostgresLane, LaneQueue> = {
90
+ ordinary: { ring: new Map(), size: 0 },
91
+ commit: { ring: new Map(), size: 0 },
92
+ };
93
+ readonly #activeByTenant: Record<
94
+ GatewayPostgresLane,
95
+ Map<GatewayPostgresAdmissionTenant, number>
96
+ > = {
97
+ ordinary: new Map(),
98
+ commit: new Map(),
70
99
  };
71
100
  #ordinaryActive = 0;
72
101
  #commitActive = 0;
73
102
  #cancelled = 0;
74
103
  #overloaded = 0;
104
+ #fairShareEvictions = 0;
75
105
  #commitPriorityGrants = 0;
76
106
  #consecutiveCommitPriorityGrants = 0;
77
107
 
@@ -95,29 +125,27 @@ export class GatewayPostgresAdmission {
95
125
 
96
126
  async acquire(
97
127
  lane: GatewayPostgresLane,
98
- options: { signal?: AbortSignal } = {},
128
+ options: {
129
+ signal?: AbortSignal;
130
+ tenant?: GatewayPostgresAdmissionTenant;
131
+ } = {},
99
132
  ): Promise<() => void> {
133
+ const tenant = options.tenant ?? GATEWAY_POSTGRES_MACHINE_TENANT;
100
134
  if (options.signal?.aborted) {
101
135
  this.#cancelled += 1;
102
136
  throw new GatewayPostgresAdmissionCancelledError();
103
137
  }
104
- if (this.#canGrant(lane) && this.#waiting.commit.length === 0) {
105
- return this.#grant(lane);
138
+ if (this.#canGrant(lane) && this.#waiting.commit.size === 0) {
139
+ return this.#grant(lane, tenant);
106
140
  }
107
- const waiting = this.#waiting[lane].length;
108
- const limit = this.#maxWaitingPerLane();
109
- if (waiting >= limit) {
110
- this.#overloaded += 1;
111
- throw new GatewayPostgresAdmissionOverloadedError({
112
- lane,
113
- waiting,
114
- limit,
115
- });
141
+ if (this.#waiting[lane].size >= this.#maxWaitingPerLane()) {
142
+ this.#shedForFullLane(lane, tenant);
116
143
  }
117
144
 
118
145
  return await new Promise<() => void>((resolve, reject) => {
119
146
  const waiter: Waiter = {
120
147
  lane,
148
+ tenant,
121
149
  signal: options.signal ?? null,
122
150
  onAbort: null,
123
151
  resolve,
@@ -130,7 +158,7 @@ export class GatewayPostgresAdmission {
130
158
  reject(new GatewayPostgresAdmissionCancelledError());
131
159
  };
132
160
  waiter.signal?.addEventListener('abort', waiter.onAbort, { once: true });
133
- this.#waiting[lane].push(waiter);
161
+ this.#enqueue(waiter);
134
162
  if (waiter.signal?.aborted) {
135
163
  waiter.onAbort();
136
164
  return;
@@ -147,11 +175,16 @@ export class GatewayPostgresAdmission {
147
175
  total: this.#activeTotal(),
148
176
  },
149
177
  waiting: {
150
- ordinary: this.#waiting.ordinary.length,
151
- commit: this.#waiting.commit.length,
178
+ ordinary: this.#waiting.ordinary.size,
179
+ commit: this.#waiting.commit.size,
180
+ },
181
+ waitingTenants: {
182
+ ordinary: this.#waiting.ordinary.ring.size,
183
+ commit: this.#waiting.commit.ring.size,
152
184
  },
153
185
  cancelled: this.#cancelled,
154
186
  overloaded: this.#overloaded,
187
+ fairShareEvictions: this.#fairShareEvictions,
155
188
  commitPriorityGrants: this.#commitPriorityGrants,
156
189
  };
157
190
  }
@@ -183,15 +216,23 @@ export class GatewayPostgresAdmission {
183
216
  );
184
217
  }
185
218
 
186
- #grant(lane: GatewayPostgresLane): () => void {
219
+ #grant(
220
+ lane: GatewayPostgresLane,
221
+ tenant: GatewayPostgresAdmissionTenant,
222
+ ): () => void {
187
223
  if (lane === 'ordinary') this.#ordinaryActive += 1;
188
224
  else this.#commitActive += 1;
225
+ const activeByTenant = this.#activeByTenant[lane];
226
+ activeByTenant.set(tenant, (activeByTenant.get(tenant) ?? 0) + 1);
189
227
  let released = false;
190
228
  return () => {
191
229
  if (released) return;
192
230
  released = true;
193
231
  if (lane === 'ordinary') this.#ordinaryActive -= 1;
194
232
  else this.#commitActive -= 1;
233
+ const remaining = (activeByTenant.get(tenant) ?? 1) - 1;
234
+ if (remaining > 0) activeByTenant.set(tenant, remaining);
235
+ else activeByTenant.delete(tenant);
195
236
  this.#drain();
196
237
  };
197
238
  }
@@ -199,19 +240,19 @@ export class GatewayPostgresAdmission {
199
240
  #drain(): void {
200
241
  while (this.#activeTotal() < this.#maxTotalActive()) {
201
242
  const ordinaryCanRun =
202
- this.#waiting.ordinary.length > 0 &&
243
+ this.#waiting.ordinary.size > 0 &&
203
244
  this.#ordinaryActive < this.#maxOrdinaryActive();
204
245
  if (
205
246
  ordinaryCanRun &&
206
- this.#waiting.commit.length > 0 &&
247
+ this.#waiting.commit.size > 0 &&
207
248
  this.#consecutiveCommitPriorityGrants >=
208
249
  GATEWAY_POSTGRES_MAX_CONSECUTIVE_COMMIT_PRIORITY_GRANTS
209
250
  ) {
210
251
  this.#consecutiveCommitPriorityGrants = 0;
211
- this.#resolve(this.#waiting.ordinary.shift()!);
252
+ this.#resolve(this.#dequeueLeastActiveTenant('ordinary')!);
212
253
  continue;
213
254
  }
214
- const commit = this.#waiting.commit.shift();
255
+ const commit = this.#dequeueLeastActiveTenant('commit');
215
256
  if (commit) {
216
257
  if (ordinaryCanRun) {
217
258
  this.#commitPriorityGrants += 1;
@@ -224,29 +265,110 @@ export class GatewayPostgresAdmission {
224
265
  }
225
266
  if (ordinaryCanRun) {
226
267
  this.#consecutiveCommitPriorityGrants = 0;
227
- this.#resolve(this.#waiting.ordinary.shift()!);
268
+ this.#resolve(this.#dequeueLeastActiveTenant('ordinary')!);
228
269
  continue;
229
270
  }
230
- if (this.#waiting.ordinary.length === 0) {
271
+ if (this.#waiting.ordinary.size === 0) {
231
272
  this.#consecutiveCommitPriorityGrants = 0;
232
273
  }
233
274
  return;
234
275
  }
235
276
  }
236
277
 
278
+ #enqueue(waiter: Waiter): void {
279
+ const queue = this.#waiting[waiter.lane];
280
+ const tenantQueue = queue.ring.get(waiter.tenant);
281
+ if (tenantQueue) tenantQueue.push(waiter);
282
+ else queue.ring.set(waiter.tenant, [waiter]);
283
+ queue.size += 1;
284
+ }
285
+
286
+ #shedForFullLane(
287
+ lane: GatewayPostgresLane,
288
+ tenant: GatewayPostgresAdmissionTenant,
289
+ ): void {
290
+ const queue = this.#waiting[lane];
291
+ const waiting = queue.size;
292
+ const limit = this.#maxWaitingPerLane();
293
+ const longest = this.#longestTenantQueue(queue);
294
+ const own = queue.ring.get(tenant)?.length ?? 0;
295
+ this.#overloaded += 1;
296
+ if (!longest || own >= longest.length) {
297
+ throw new GatewayPostgresAdmissionOverloadedError({
298
+ lane,
299
+ waiting,
300
+ limit,
301
+ });
302
+ }
303
+ const evicted = longest[longest.length - 1]!;
304
+ this.#remove(evicted);
305
+ this.#fairShareEvictions += 1;
306
+ evicted.reject(
307
+ new GatewayPostgresAdmissionOverloadedError({
308
+ lane,
309
+ waiting,
310
+ limit,
311
+ reason: 'fair_share_evicted',
312
+ }),
313
+ );
314
+ }
315
+
316
+ #dequeueLeastActiveTenant(lane: GatewayPostgresLane): Waiter | undefined {
317
+ const queue = this.#waiting[lane];
318
+ const activeByTenant = this.#activeByTenant[lane];
319
+ let chosen: GatewayPostgresAdmissionTenant | undefined;
320
+ let chosenActive = Infinity;
321
+ for (const tenant of queue.ring.keys()) {
322
+ const active = activeByTenant.get(tenant) ?? 0;
323
+ if (active < chosenActive) {
324
+ chosen = tenant;
325
+ chosenActive = active;
326
+ if (active === 0) break;
327
+ }
328
+ }
329
+ if (chosen === undefined) return undefined;
330
+ const tenantQueue = queue.ring.get(chosen)!;
331
+ const waiter = tenantQueue.shift()!;
332
+ queue.size -= 1;
333
+ this.#rotateToBack(queue, chosen, tenantQueue);
334
+ return waiter;
335
+ }
336
+
337
+ #rotateToBack(
338
+ queue: LaneQueue,
339
+ tenant: GatewayPostgresAdmissionTenant,
340
+ tenantQueue: Waiter[],
341
+ ): void {
342
+ queue.ring.delete(tenant);
343
+ if (tenantQueue.length > 0) queue.ring.set(tenant, tenantQueue);
344
+ }
345
+
346
+ #longestTenantQueue(queue: LaneQueue): Waiter[] | null {
347
+ let longest: Waiter[] | null = null;
348
+ for (const tenantQueue of queue.ring.values()) {
349
+ if (!longest || tenantQueue.length > longest.length) {
350
+ longest = tenantQueue;
351
+ }
352
+ }
353
+ return longest;
354
+ }
355
+
237
356
  #resolve(waiter: Waiter): void {
238
357
  waiter.settled = true;
239
358
  this.#cleanup(waiter);
240
- waiter.resolve(this.#grant(waiter.lane));
359
+ waiter.resolve(this.#grant(waiter.lane, waiter.tenant));
241
360
  }
242
361
 
243
362
  #remove(waiter: Waiter): boolean {
244
363
  if (waiter.settled) return false;
245
364
  const queue = this.#waiting[waiter.lane];
246
- const index = queue.indexOf(waiter);
247
- if (index < 0) return false;
365
+ const tenantQueue = queue.ring.get(waiter.tenant);
366
+ const index = tenantQueue ? tenantQueue.indexOf(waiter) : -1;
367
+ if (!tenantQueue || index < 0) return false;
248
368
  waiter.settled = true;
249
- queue.splice(index, 1);
369
+ tenantQueue.splice(index, 1);
370
+ if (tenantQueue.length === 0) queue.ring.delete(waiter.tenant);
371
+ queue.size -= 1;
250
372
  this.#cleanup(waiter);
251
373
  return true;
252
374
  }
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  acquireRateStateViaAppRuntime,
3
+ AppRuntimeApiResponseError,
4
+ isAppRuntimeApiCapacityError,
3
5
  penalizeRateStateViaAppRuntime,
4
6
  releaseRateStateViaAppRuntime,
5
7
  RateStateValidationUnavailableError,
@@ -141,6 +143,20 @@ function sleepWithAbort(
141
143
  */
142
144
  const RATE_BLOCK_HORIZON_MS = 1_000;
143
145
  const STORE_FAILURE_RETRY_DELAYS_MS = [100, 250] as const;
146
+ const CAPACITY_SHED_DEFAULT_RETRY_AFTER_MS = 1_000;
147
+
148
+ function capacityShedRetryAfterMs(error: unknown): number | null {
149
+ const shed =
150
+ isAppRuntimeApiCapacityError(error) ||
151
+ (error instanceof AppRuntimeApiResponseError &&
152
+ error.retryable &&
153
+ error.status === 429);
154
+ if (!shed) return null;
155
+ const retryAfterMs = (error as AppRuntimeApiResponseError).retryAfterMs;
156
+ return retryAfterMs && retryAfterMs > 0
157
+ ? retryAfterMs
158
+ : CAPACITY_SHED_DEFAULT_RETRY_AFTER_MS;
159
+ }
144
160
  /**
145
161
  * Jitter fraction applied to a server-supplied `waitMs` so a synchronized wave
146
162
  * of buckets that all hit granted=0 at the same instant re-probe at slightly
@@ -1140,6 +1156,22 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
1140
1156
  // A provider hold is not a store failure. Preserve its deadline for
1141
1157
  // the caller's durable suspension or non-durable error path.
1142
1158
  if (error instanceof ProviderExhaustedError) throw error;
1159
+ // A capacity shed is back-pressure from a working store. Wait as for
1160
+ // a zero grant, then return so the acquirer re-checks its wait budget.
1161
+ const shedRetryAfterMs = capacityShedRetryAfterMs(error);
1162
+ if (shedRetryAfterMs !== null) {
1163
+ this.onStoreFailure({
1164
+ bucketId,
1165
+ error: normalizeError(error).message,
1166
+ });
1167
+ await this.sleep(
1168
+ Math.min(
1169
+ shedRetryAfterMs + Math.round(this.jitter() * shedRetryAfterMs),
1170
+ MAX_ACQUIRE_SLEEP_MS,
1171
+ ),
1172
+ );
1173
+ return;
1174
+ }
1143
1175
  // Fail closed. A degraded rate-state store must not grant unlimited
1144
1176
  // provider calls; after this bounded retry ladder the run fails loudly.
1145
1177
  consecutiveFailures += 1;
@@ -14,8 +14,15 @@ import {
14
14
  RuntimeSandboxProvider,
15
15
  } from './runtime-sandbox-operation';
16
16
 
17
- /** The safe baseline when an organization has no current override. */
18
- export const DEFAULT_PLAY_SANDBOX_CONCURRENCY = 4;
17
+ /**
18
+ * The baseline when an organization has no current override. It sits above
19
+ * every ordinary organization's observed peak (at most 46 live sandboxes in the
20
+ * week before 2026-10-29) and bounds a burst: on 2026-10-29 one organization
21
+ * held 2,838 live sandboxes polling the receipt gateway. Keep in sync with the
22
+ * Convex control plane's DEFAULT_PLAY_SANDBOX_CONCURRENCY in
23
+ * packages/convex/auth.ts.
24
+ */
25
+ export const DEFAULT_PLAY_SANDBOX_CONCURRENCY = 50;
19
26
  export const MAX_PLAY_SANDBOX_CONCURRENCY = 250;
20
27
  /** A crashed worker cannot retain capacity forever. */
21
28
  export const RUNTIME_SANDBOX_CAPACITY_LEASE_MS = 50 * 60 * 1_000;
@@ -13,13 +13,13 @@ import {
13
13
  reportSetupCompleted,
14
14
  resolvePendingAuthClaimForResume,
15
15
  statusBannerLine
16
- } from "./chunk-MZLZNISS.mjs";
17
- import "./chunk-6NUGMJQA.mjs";
18
- import "./chunk-4A45A2MH.mjs";
16
+ } from "./chunk-C54S4QPU.mjs";
17
+ import "./chunk-3OBHZADJ.mjs";
18
+ import "./chunk-ZRWX23RQ.mjs";
19
19
  import "./chunk-4QF2K7TM.mjs";
20
20
  import "./chunk-DLIRSHV6.mjs";
21
21
  import "./chunk-HBL4PM5Q.mjs";
22
- import "./chunk-DOKTNYMQ.mjs";
22
+ import "./chunk-6LVOC2HD.mjs";
23
23
  import "./chunk-WZYKRD7T.mjs";
24
24
  import "./chunk-KRD6H754.mjs";
25
25
  export {
@@ -0,0 +1,40 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ var _chunkFC7LS3Y5js = require('./chunk-FC7LS3Y5.js');
17
+ require('./chunk-5JJ6JYTL.js');
18
+ require('./chunk-TABMEHIP.js');
19
+ require('./chunk-ALJJRZNJ.js');
20
+ require('./chunk-XNYIZMNQ.js');
21
+ require('./chunk-DIAWYWT5.js');
22
+ require('./chunk-EI2NN6HP.js');
23
+ require('./chunk-OG64RBAC.js');
24
+ require('./chunk-COGTRVWW.js');
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+ exports.EXIT_WORKSPACE_MISMATCH = _chunkFC7LS3Y5js.EXIT_WORKSPACE_MISMATCH; exports.authStatusLine = _chunkFC7LS3Y5js.authStatusLine; exports.clearPendingClaim = _chunkFC7LS3Y5js.clearPendingClaim; exports.clearSatisfiedPendingClaim = _chunkFC7LS3Y5js.clearSatisfiedPendingClaim; exports.handleRegister = _chunkFC7LS3Y5js.handleRegister; exports.handleStatus = _chunkFC7LS3Y5js.handleStatus; exports.handleWait = _chunkFC7LS3Y5js.handleWait; exports.httpJson = _chunkFC7LS3Y5js.httpJson; exports.readPendingAuthClaim = _chunkFC7LS3Y5js.readPendingAuthClaim; exports.readStatusBanner = _chunkFC7LS3Y5js.readStatusBanner; exports.registerAuthCommands = _chunkFC7LS3Y5js.registerAuthCommands; exports.reportSetupCompleted = _chunkFC7LS3Y5js.reportSetupCompleted; exports.resolvePendingAuthClaimForResume = _chunkFC7LS3Y5js.resolvePendingAuthClaimForResume; exports.statusBannerLine = _chunkFC7LS3Y5js.statusBannerLine;