@namzu/sandbox 14.0.0 → 16.0.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +924 -0
  2. package/README.md +369 -14
  3. package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
  4. package/dist/backends/aci-standby-pool/index.js +13 -1
  5. package/dist/backends/aci-standby-pool/index.js.map +1 -1
  6. package/dist/backends/docker/index.d.ts +169 -6
  7. package/dist/backends/docker/index.d.ts.map +1 -1
  8. package/dist/backends/docker/index.js +499 -85
  9. package/dist/backends/docker/index.js.map +1 -1
  10. package/dist/backends/firecracker/index.d.ts.map +1 -1
  11. package/dist/backends/firecracker/index.js +12 -2
  12. package/dist/backends/firecracker/index.js.map +1 -1
  13. package/dist/backends/firecracker/protocol.d.ts +459 -8
  14. package/dist/backends/firecracker/protocol.d.ts.map +1 -1
  15. package/dist/backends/firecracker/protocol.js +136 -0
  16. package/dist/backends/firecracker/protocol.js.map +1 -1
  17. package/dist/backends/firecracker/transport.d.ts +539 -6
  18. package/dist/backends/firecracker/transport.d.ts.map +1 -1
  19. package/dist/backends/firecracker/transport.js +1171 -24
  20. package/dist/backends/firecracker/transport.js.map +1 -1
  21. package/dist/backends/kubernetes/egress-policy.d.ts +1181 -13
  22. package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -1
  23. package/dist/backends/kubernetes/egress-policy.js +2350 -31
  24. package/dist/backends/kubernetes/egress-policy.js.map +1 -1
  25. package/dist/backends/kubernetes/identity.d.ts +193 -0
  26. package/dist/backends/kubernetes/identity.d.ts.map +1 -0
  27. package/dist/backends/kubernetes/identity.js +147 -0
  28. package/dist/backends/kubernetes/identity.js.map +1 -0
  29. package/dist/backends/kubernetes/index.d.ts +678 -33
  30. package/dist/backends/kubernetes/index.d.ts.map +1 -1
  31. package/dist/backends/kubernetes/index.js +1180 -95
  32. package/dist/backends/kubernetes/index.js.map +1 -1
  33. package/dist/backends/kubernetes/ingress-policy.d.ts +375 -0
  34. package/dist/backends/kubernetes/ingress-policy.d.ts.map +1 -0
  35. package/dist/backends/kubernetes/ingress-policy.js +1050 -0
  36. package/dist/backends/kubernetes/ingress-policy.js.map +1 -0
  37. package/dist/backends/kubernetes/k8s-client.d.ts +213 -4
  38. package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -1
  39. package/dist/backends/kubernetes/k8s-client.js +359 -52
  40. package/dist/backends/kubernetes/k8s-client.js.map +1 -1
  41. package/dist/backends/kubernetes/lease.d.ts +40 -14
  42. package/dist/backends/kubernetes/lease.d.ts.map +1 -1
  43. package/dist/backends/kubernetes/lease.js +68 -18
  44. package/dist/backends/kubernetes/lease.js.map +1 -1
  45. package/dist/backends/kubernetes/objects.d.ts +423 -3
  46. package/dist/backends/kubernetes/objects.d.ts.map +1 -1
  47. package/dist/backends/kubernetes/objects.js +364 -2
  48. package/dist/backends/kubernetes/objects.js.map +1 -1
  49. package/dist/backends/kubernetes/per-sandbox-policy.d.ts +219 -0
  50. package/dist/backends/kubernetes/per-sandbox-policy.d.ts.map +1 -0
  51. package/dist/backends/kubernetes/per-sandbox-policy.js +375 -0
  52. package/dist/backends/kubernetes/per-sandbox-policy.js.map +1 -0
  53. package/dist/backends/kubernetes/rbac.d.ts +153 -0
  54. package/dist/backends/kubernetes/rbac.d.ts.map +1 -0
  55. package/dist/backends/kubernetes/rbac.js +177 -0
  56. package/dist/backends/kubernetes/rbac.js.map +1 -0
  57. package/dist/backends/kubernetes/sandbox.d.ts +81 -14
  58. package/dist/backends/kubernetes/sandbox.d.ts.map +1 -1
  59. package/dist/backends/kubernetes/sandbox.js +149 -15
  60. package/dist/backends/kubernetes/sandbox.js.map +1 -1
  61. package/dist/backends/kubernetes/transport.d.ts +935 -9
  62. package/dist/backends/kubernetes/transport.d.ts.map +1 -1
  63. package/dist/backends/kubernetes/transport.js +1958 -62
  64. package/dist/backends/kubernetes/transport.js.map +1 -1
  65. package/dist/backends/kubernetes/workspace.d.ts +1149 -18
  66. package/dist/backends/kubernetes/workspace.d.ts.map +1 -1
  67. package/dist/backends/kubernetes/workspace.js +2825 -186
  68. package/dist/backends/kubernetes/workspace.js.map +1 -1
  69. package/dist/backends/remote-execution-controller.d.ts +14 -0
  70. package/dist/backends/remote-execution-controller.d.ts.map +1 -1
  71. package/dist/backends/remote-execution-controller.js.map +1 -1
  72. package/dist/index.d.ts +294 -18
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +280 -10
  75. package/dist/index.js.map +1 -1
  76. package/dist/testing/sandbox-conformance.d.ts +39 -5
  77. package/dist/testing/sandbox-conformance.d.ts.map +1 -1
  78. package/dist/testing/sandbox-conformance.js +436 -5
  79. package/dist/testing/sandbox-conformance.js.map +1 -1
  80. package/package.json +3 -3
  81. package/src/backends/aci-standby-pool/index.ts +16 -1
  82. package/src/backends/docker/index.ts +617 -100
  83. package/src/backends/firecracker/index.ts +14 -2
  84. package/src/backends/firecracker/protocol.ts +514 -6
  85. package/src/backends/firecracker/transport.ts +1492 -40
  86. package/src/backends/kubernetes/egress-policy.ts +3334 -55
  87. package/src/backends/kubernetes/identity.ts +261 -0
  88. package/src/backends/kubernetes/index.ts +1785 -127
  89. package/src/backends/kubernetes/ingress-policy.ts +1344 -0
  90. package/src/backends/kubernetes/k8s-client.ts +444 -54
  91. package/src/backends/kubernetes/lease.ts +75 -19
  92. package/src/backends/kubernetes/objects.ts +626 -6
  93. package/src/backends/kubernetes/per-sandbox-policy.ts +497 -0
  94. package/src/backends/kubernetes/rbac.ts +192 -0
  95. package/src/backends/kubernetes/sandbox.ts +218 -20
  96. package/src/backends/kubernetes/transport.ts +2733 -124
  97. package/src/backends/kubernetes/workspace.ts +4476 -222
  98. package/src/backends/remote-execution-controller.ts +14 -0
  99. package/src/index.ts +668 -19
  100. package/src/testing/sandbox-conformance.ts +540 -5
@@ -16,36 +16,48 @@
16
16
  *
17
17
  * One `setTimeout` chained per tick, never `setInterval`: a renewal PATCH
18
18
  * that takes longer than the interval must not queue a second one behind
19
- * it. The interval is HALF the TTL, so a single failed tick still leaves a
20
- * whole half-TTL of headroom for the next one to succeed, and it is
21
- * jittered ±10% so a hundred handles acquired in the same second do not
22
- * PATCH the API server in the same millisecond forever after.
19
+ * it. On SUCCESS the interval is HALF the TTL, jittered ±10% so a hundred
20
+ * handles acquired in the same second do not PATCH the API server in the
21
+ * same millisecond forever after.
23
22
  *
24
23
  * The timer is `unref`'d: a host process that has finished its work should
25
24
  * exit, not linger because a sandbox handle is still counting. A handle
26
25
  * nobody destroyed then expires on the cluster's clock exactly as an
27
26
  * abandoned one does, which is the behaviour the TTL exists for.
28
27
  *
28
+ * ## A failed tick does not wait for the next half-TTL
29
+ *
30
+ * Waiting a full half-TTL before retrying a FAILED renewal means one blip at
31
+ * exactly the wrong moment is a coin flip against the object's own
32
+ * `shutdownTime`: the retry and the expiry are both roughly a TTL after the
33
+ * last success, so a single failure can lose that race. A failed tick
34
+ * instead retries on capped exponential backoff — starting at one second,
35
+ * doubling, capped at whichever is smaller of thirty seconds or a
36
+ * twentieth of the TTL — so an outage around a scheduled renewal gets many
37
+ * attempts inside the window that actually matters, not one. Every success
38
+ * resets the backoff and returns the loop to the normal half-TTL cadence.
39
+ *
29
40
  * ## Every tick is bounded
30
41
  *
31
- * A renewal that FAILS is survivable — it is reported and retried with half
32
- * a TTL of headroom. A renewal that HANGS is not: the next tick is scheduled
42
+ * A renewal that FAILS is survivable — it is reported and retried on a
43
+ * short backoff. A renewal that HANGS is not: the next tick is scheduled
33
44
  * only after the current one settles, so a PATCH that never answers parks
34
45
  * the loop forever, reports nothing, and lets the lease expire in silence —
35
46
  * precisely the defect this file exists to close, moved onto the failure
36
47
  * path. An API server that accepts a connection and then never responds is
37
48
  * an ordinary cluster event, so each PATCH runs under its own deadline: it
38
49
  * aborts the request through the signal the client already takes, and an
39
- * expiry is then just another reported failure that retries on the next
40
- * tick.
50
+ * expiry is then just another reported failure that retries on backoff.
41
51
  *
42
52
  * ## What each outcome means
43
53
  *
44
- * - Success → the object's expiry moves a full TTL into the future.
54
+ * - Success → the object's expiry moves a full TTL into the future, the
55
+ * backoff resets, and the next tick is a half-TTL away again.
45
56
  * - Any error, a tick that ran out of time included → reported to
46
- * `onRenewalError` and RETRIED on the next tick. A transient API blip
47
- * must not tear down a working sandbox, and there is still half a TTL of
48
- * headroom.
57
+ * `onRenewalError` and RETRIED on a backoff far shorter than the
58
+ * half-TTL interval. A transient API blip must not tear down a working
59
+ * sandbox, and the loop keeps trying rather than spend the object's
60
+ * remaining headroom waiting.
49
61
  * - Already gone (404/410) → the object this handle owns no longer exists.
50
62
  * Nothing will bring it back, so the loop stops and the handle is marked
51
63
  * gone; every later call fails with a named error instead of dialing an
@@ -86,7 +98,7 @@ export interface LeaseRenewalOptions {
86
98
  * Defaults to a quarter of the interval, capped at
87
99
  * {@link MAX_RENEWAL_TIMEOUT_MS} — a fraction rather than the whole
88
100
  * interval so that a stalled API server still leaves the loop several
89
- * attempts inside the half-TTL of headroom.
101
+ * backoff-paced attempts before the next regular half-TTL tick.
90
102
  */
91
103
  readonly patchTimeoutMs?: number
92
104
  /** Deterministic jitter for tests. Defaults to `Math.random`. */
@@ -100,6 +112,23 @@ export interface LeaseRenewalOptions {
100
112
  */
101
113
  const MAX_RENEWAL_TIMEOUT_MS = 30_000
102
114
 
115
+ /**
116
+ * The floor of the retry backoff after a failed renewal: one second. Far
117
+ * short of the half-TTL interval, on purpose — a failure needs another
118
+ * chance long before the object's `shutdownTime` is at risk, not after
119
+ * waiting as long as a successful tick would have.
120
+ */
121
+ const RETRY_BACKOFF_FLOOR_MS = 1_000
122
+
123
+ /**
124
+ * The ceiling of the retry backoff, whichever is smaller: thirty seconds, or
125
+ * a twentieth of the TTL. The TTL fraction keeps a short-TTL sandbox (tests,
126
+ * mainly) from retrying so slowly that the backoff alone could still lose
127
+ * the race against expiry; thirty seconds keeps an hour-plus TTL from
128
+ * retrying needlessly often once the ceiling is reached.
129
+ */
130
+ const MAX_RETRY_BACKOFF_MS = 30_000
131
+
103
132
  /** ±10%: enough to spread a synchronised fleet, far too little to matter
104
133
  * against a half-TTL of headroom. */
105
134
  const JITTER_FRACTION = 0.1
@@ -120,13 +149,23 @@ export class KubernetesLeaseRenewal {
120
149
  private started = false
121
150
  private readonly baseIntervalMs: number
122
151
  private readonly patchTimeoutMs: number
152
+ private readonly retryBackoffCapMs: number
123
153
  private readonly random: () => number
154
+ /**
155
+ * Non-gone failures since the last success (or since the loop started).
156
+ * Reset to 0 by every success; drives how far the next retry backs off.
157
+ */
158
+ private consecutiveFailures = 0
124
159
 
125
160
  constructor(private readonly options: LeaseRenewalOptions) {
126
161
  this.baseIntervalMs = options.intervalMs ?? Math.max(1, (options.ttlSeconds * 1_000) / 2)
127
162
  this.patchTimeoutMs =
128
163
  options.patchTimeoutMs ??
129
164
  Math.max(1, Math.min(MAX_RENEWAL_TIMEOUT_MS, Math.round(this.baseIntervalMs / 4)))
165
+ this.retryBackoffCapMs = Math.max(
166
+ 1,
167
+ Math.min(MAX_RETRY_BACKOFF_MS, (options.ttlSeconds * 1_000) / 20),
168
+ )
130
169
  this.random = options.random ?? Math.random
131
170
  }
132
171
 
@@ -144,7 +183,7 @@ export class KubernetesLeaseRenewal {
144
183
  start(): void {
145
184
  if (this.stopped || this.timer !== undefined) return
146
185
  this.started = true
147
- this.schedule()
186
+ this.scheduleNext(this.baseIntervalMs)
148
187
  }
149
188
 
150
189
  stop(): void {
@@ -155,19 +194,31 @@ export class KubernetesLeaseRenewal {
155
194
  }
156
195
  }
157
196
 
158
- private schedule(): void {
197
+ private scheduleNext(baseMs: number): void {
159
198
  if (this.stopped) return
160
199
  const timer = setTimeout(
161
200
  () => {
162
201
  void this.tick()
163
202
  },
164
- jitteredInterval(this.baseIntervalMs, this.random),
203
+ jitteredInterval(baseMs, this.random),
165
204
  )
166
205
  // A pending renewal must never be the reason a host process stays up.
167
206
  timer.unref?.()
168
207
  this.timer = timer
169
208
  }
170
209
 
210
+ /**
211
+ * The delay before the NEXT retry after a non-gone failure: capped
212
+ * exponential backoff from {@link RETRY_BACKOFF_FLOOR_MS}, doubling on
213
+ * every consecutive failure, ceilinged at {@link retryBackoffCapMs}.
214
+ * Called only once `consecutiveFailures` has already been incremented for
215
+ * the failure that just happened, so the first retry uses the floor.
216
+ */
217
+ private retryDelayMs(): number {
218
+ const doubled = RETRY_BACKOFF_FLOOR_MS * 2 ** (this.consecutiveFailures - 1)
219
+ return Math.min(this.retryBackoffCapMs, doubled)
220
+ }
221
+
171
222
  /** Exposed for tests: one renewal attempt plus its scheduling decision. */
172
223
  async tick(): Promise<void> {
173
224
  this.timer = undefined
@@ -189,10 +240,15 @@ export class KubernetesLeaseRenewal {
189
240
  return
190
241
  }
191
242
  // Everything else is transient until proven otherwise: report it
192
- // and try again on the next tick, which is still half a TTL
193
- // before anything expires.
243
+ // and retry on a backoff far shorter than the half-TTL interval —
244
+ // a coin-flip race against the object's own expiry is exactly what
245
+ // this file exists to avoid.
246
+ this.consecutiveFailures += 1
194
247
  this.options.onRenewalError?.(error)
248
+ this.scheduleNext(this.retryDelayMs())
249
+ return
195
250
  }
196
- this.schedule()
251
+ this.consecutiveFailures = 0
252
+ this.scheduleNext(this.baseIntervalMs)
197
253
  }
198
254
  }