@namzu/sandbox 13.0.0 → 15.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 (104) hide show
  1. package/CHANGELOG.md +1147 -0
  2. package/README.md +447 -0
  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.map +1 -1
  7. package/dist/backends/docker/index.js +19 -1
  8. package/dist/backends/docker/index.js.map +1 -1
  9. package/dist/backends/firecracker/index.d.ts.map +1 -1
  10. package/dist/backends/firecracker/index.js +12 -2
  11. package/dist/backends/firecracker/index.js.map +1 -1
  12. package/dist/backends/firecracker/protocol.d.ts +481 -8
  13. package/dist/backends/firecracker/protocol.d.ts.map +1 -1
  14. package/dist/backends/firecracker/protocol.js +136 -0
  15. package/dist/backends/firecracker/protocol.js.map +1 -1
  16. package/dist/backends/firecracker/transport.d.ts +642 -14
  17. package/dist/backends/firecracker/transport.d.ts.map +1 -1
  18. package/dist/backends/firecracker/transport.js +1307 -34
  19. package/dist/backends/firecracker/transport.js.map +1 -1
  20. package/dist/backends/kubernetes/egress-policy.d.ts +1296 -0
  21. package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -0
  22. package/dist/backends/kubernetes/egress-policy.js +2458 -0
  23. package/dist/backends/kubernetes/egress-policy.js.map +1 -0
  24. package/dist/backends/kubernetes/identity.d.ts +193 -0
  25. package/dist/backends/kubernetes/identity.d.ts.map +1 -0
  26. package/dist/backends/kubernetes/identity.js +147 -0
  27. package/dist/backends/kubernetes/identity.js.map +1 -0
  28. package/dist/backends/kubernetes/index.d.ts +1019 -0
  29. package/dist/backends/kubernetes/index.d.ts.map +1 -0
  30. package/dist/backends/kubernetes/index.js +1756 -0
  31. package/dist/backends/kubernetes/index.js.map +1 -0
  32. package/dist/backends/kubernetes/ingress-policy.d.ts +375 -0
  33. package/dist/backends/kubernetes/ingress-policy.d.ts.map +1 -0
  34. package/dist/backends/kubernetes/ingress-policy.js +1050 -0
  35. package/dist/backends/kubernetes/ingress-policy.js.map +1 -0
  36. package/dist/backends/kubernetes/k8s-client.d.ts +334 -0
  37. package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -0
  38. package/dist/backends/kubernetes/k8s-client.js +553 -0
  39. package/dist/backends/kubernetes/k8s-client.js.map +1 -0
  40. package/dist/backends/kubernetes/lease.d.ts +145 -0
  41. package/dist/backends/kubernetes/lease.d.ts.map +1 -0
  42. package/dist/backends/kubernetes/lease.js +201 -0
  43. package/dist/backends/kubernetes/lease.js.map +1 -0
  44. package/dist/backends/kubernetes/objects.d.ts +702 -0
  45. package/dist/backends/kubernetes/objects.d.ts.map +1 -0
  46. package/dist/backends/kubernetes/objects.js +518 -0
  47. package/dist/backends/kubernetes/objects.js.map +1 -0
  48. package/dist/backends/kubernetes/per-sandbox-policy.d.ts +219 -0
  49. package/dist/backends/kubernetes/per-sandbox-policy.d.ts.map +1 -0
  50. package/dist/backends/kubernetes/per-sandbox-policy.js +407 -0
  51. package/dist/backends/kubernetes/per-sandbox-policy.js.map +1 -0
  52. package/dist/backends/kubernetes/privilege-probe.d.ts +136 -0
  53. package/dist/backends/kubernetes/privilege-probe.d.ts.map +1 -0
  54. package/dist/backends/kubernetes/privilege-probe.js +185 -0
  55. package/dist/backends/kubernetes/privilege-probe.js.map +1 -0
  56. package/dist/backends/kubernetes/rbac.d.ts +153 -0
  57. package/dist/backends/kubernetes/rbac.d.ts.map +1 -0
  58. package/dist/backends/kubernetes/rbac.js +177 -0
  59. package/dist/backends/kubernetes/rbac.js.map +1 -0
  60. package/dist/backends/kubernetes/sandbox.d.ts +190 -0
  61. package/dist/backends/kubernetes/sandbox.d.ts.map +1 -0
  62. package/dist/backends/kubernetes/sandbox.js +433 -0
  63. package/dist/backends/kubernetes/sandbox.js.map +1 -0
  64. package/dist/backends/kubernetes/transport.d.ts +1048 -0
  65. package/dist/backends/kubernetes/transport.d.ts.map +1 -0
  66. package/dist/backends/kubernetes/transport.js +2093 -0
  67. package/dist/backends/kubernetes/transport.js.map +1 -0
  68. package/dist/backends/kubernetes/workspace.d.ts +1512 -0
  69. package/dist/backends/kubernetes/workspace.d.ts.map +1 -0
  70. package/dist/backends/kubernetes/workspace.js +3703 -0
  71. package/dist/backends/kubernetes/workspace.js.map +1 -0
  72. package/dist/backends/remote-execution-controller.d.ts +14 -0
  73. package/dist/backends/remote-execution-controller.d.ts.map +1 -1
  74. package/dist/backends/remote-execution-controller.js.map +1 -1
  75. package/dist/index.d.ts +350 -2
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +344 -34
  78. package/dist/index.js.map +1 -1
  79. package/dist/testing/sandbox-conformance.d.ts +227 -0
  80. package/dist/testing/sandbox-conformance.d.ts.map +1 -0
  81. package/dist/testing/sandbox-conformance.js +896 -0
  82. package/dist/testing/sandbox-conformance.js.map +1 -0
  83. package/package.json +5 -4
  84. package/src/backends/aci-standby-pool/index.ts +16 -1
  85. package/src/backends/docker/index.ts +22 -1
  86. package/src/backends/firecracker/index.ts +14 -2
  87. package/src/backends/firecracker/protocol.ts +541 -6
  88. package/src/backends/firecracker/transport.ts +1687 -64
  89. package/src/backends/kubernetes/egress-policy.ts +3448 -0
  90. package/src/backends/kubernetes/identity.ts +261 -0
  91. package/src/backends/kubernetes/index.ts +2670 -0
  92. package/src/backends/kubernetes/ingress-policy.ts +1344 -0
  93. package/src/backends/kubernetes/k8s-client.ts +742 -0
  94. package/src/backends/kubernetes/lease.ts +254 -0
  95. package/src/backends/kubernetes/objects.ts +983 -0
  96. package/src/backends/kubernetes/per-sandbox-policy.ts +542 -0
  97. package/src/backends/kubernetes/privilege-probe.ts +261 -0
  98. package/src/backends/kubernetes/rbac.ts +192 -0
  99. package/src/backends/kubernetes/sandbox.ts +593 -0
  100. package/src/backends/kubernetes/transport.ts +2895 -0
  101. package/src/backends/kubernetes/workspace.ts +5640 -0
  102. package/src/backends/remote-execution-controller.ts +14 -0
  103. package/src/index.ts +838 -35
  104. package/src/testing/sandbox-conformance.ts +1202 -0
@@ -0,0 +1,227 @@
1
+ /**
2
+ * The {@link Sandbox} contract, as a suite a backend author runs against
3
+ * their own implementation.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * `@namzu/sdk`'s `Sandbox` interface is the one thing every backend in this
8
+ * package promises to implement the same way — `exec`'s exit codes, the
9
+ * `AbortSignal` contract, a `writeFile`/`readFile` round trip, terminal
10
+ * ownership on `destroy()` — and until now nothing PROVED that two backends
11
+ * agreed on any of it. Each backend carried its own bespoke test file
12
+ * (`sandbox-surface.test.ts`, `backend.test.ts`, …), written by whoever
13
+ * built that backend, checking whatever that author thought to check. A
14
+ * shared contract can be silently narrower than either file: this suite is
15
+ * the thing that would have caught it.
16
+ *
17
+ * ## Why it takes its runner as an argument
18
+ *
19
+ * The same shape as `@namzu/sdk/testing`'s checkpoint-store and provider
20
+ * driver suites, and for the same two reasons: `@namzu/sandbox` gains no
21
+ * test dependency from publishing it, and a caller can pass a RECORDING
22
+ * `describe`/`it` and run the whole contract as ordinary code — which is
23
+ * how `testing/__tests__/conformance-fails-a-broken-sandbox.test.ts`
24
+ * proves a deliberately wrong `Sandbox` fails it.
25
+ *
26
+ * ## What is asserted, and what deliberately is not
27
+ *
28
+ * Contract behaviour only — never a backend-specific object, field or
29
+ * error string. A case never inspects `sandbox.constructor.name`, never
30
+ * matches an error message, and never assumes a particular
31
+ * {@link SandboxEnvironment}. `openTerminal`, `openTcpConnection` and
32
+ * `walkFiles` are OPTIONAL on {@link Sandbox} by the SDK's own contract — a
33
+ * backend that cannot honour one must omit it rather than accept and ignore
34
+ * it — so a factory whose sandbox omits any of them skips that section rather
35
+ * than failing it. Every other section runs against every sandbox.
36
+ *
37
+ * ## Where it runs today
38
+ *
39
+ * Both `backends/kubernetes/__tests__/conformance.test.ts` and
40
+ * `backends/firecracker/__tests__/conformance.test.ts` call this against a
41
+ * real `agent/agent.cjs` on a loopback socket — proving the suite is
42
+ * backend-agnostic rather than one backend's tests wearing a new name.
43
+ * `packages/sandbox/k8s/scripts/contract-suite.mjs` runs it a third time,
44
+ * against a live cluster.
45
+ *
46
+ * ## Not published from `@namzu/sandbox`'s entry point
47
+ *
48
+ * The package has no `testing` subpath today (unlike `@namzu/sdk`), and
49
+ * this batch does not add one — promoting this to a public import path is
50
+ * a deliberate, separate decision. Within the monorepo a caller imports it
51
+ * by relative path, exactly as the two files above do:
52
+ *
53
+ * ```ts sketch
54
+ * import { defineSandboxConformance } from '../../../testing/sandbox-conformance.js'
55
+ *
56
+ * defineSandboxConformance({
57
+ * describe, it, expect,
58
+ * label: 'my-backend',
59
+ * makeSandbox: async () => ({ sandbox: await myBackend.create(), dispose: async () => {} }),
60
+ * })
61
+ * ```
62
+ */
63
+ import type { OpenTerminalOptions, Sandbox, TerminalSession } from '@namzu/sdk';
64
+ import type { ConformanceAssertion, ConformanceDescribe, ConformanceExpect, ConformanceIt } from '@namzu/sdk/testing';
65
+ /**
66
+ * The contract revision these assertions express. Carried on the describe
67
+ * label so a failure is legible on sight as "the sandbox contract", the
68
+ * same convention `PROVIDER_DRIVER_CONTRACT_VERSION` uses — raised only
69
+ * when a case is ADDED or TIGHTENED, never on a rewording.
70
+ *
71
+ * 3 is the `walkFiles`, concurrent-`exec` and `exec`-timeout sections, none
72
+ * of which needs a guest feature that did not already exist.
73
+ *
74
+ * **The ranged and streamed read cases deliberately did NOT raise it
75
+ * further.** Raising it for them would assert that every backend this suite
76
+ * runs against implements them, and one does not: the Firecracker tier's
77
+ * guest lives in a golden rootfs image that is NOT built from this
78
+ * repository — nothing here builds one, `packages/sandbox/package.json#files`
79
+ * does not even ship `agent/`, and `README.md` documents the image as
80
+ * something the operator builds and canaries on their own schedule. A
81
+ * deployment therefore runs whatever agent its last image build baked in,
82
+ * and a contract version that claimed otherwise would be a claim about
83
+ * images this repository cannot see. Those two cases are gated on
84
+ * {@link SandboxConformanceOptions.supportsRangedAndStreamedReads}
85
+ * instead, and skip by name when a backend does not declare them.
86
+ */
87
+ export declare const SANDBOX_CONTRACT_VERSION = 3;
88
+ /** A sandbox to test, plus whatever teardown building it required. */
89
+ export interface SandboxConformanceHandle {
90
+ readonly sandbox: Sandbox;
91
+ /**
92
+ * Called after each case, pass or fail — closes fixture servers, restores
93
+ * environment variables, removes temp directories. Distinct from
94
+ * `sandbox.destroy()`, which the suite calls itself (idempotently) as
95
+ * part of every case's teardown; `dispose` is for what `makeSandbox`
96
+ * itself stood up, not for the sandbox's own lifecycle.
97
+ */
98
+ dispose?(): void | Promise<void>;
99
+ }
100
+ /**
101
+ * Build one fresh {@link Sandbox}. Called once per case, so no case can be
102
+ * affected by another's writes, aborts or destroys — the suite never
103
+ * assumes a shared instance and never reuses one across cases.
104
+ */
105
+ export type MakeSandbox = () => SandboxConformanceHandle | Promise<SandboxConformanceHandle>;
106
+ export interface SandboxConformanceOptions {
107
+ readonly describe: ConformanceDescribe;
108
+ readonly it: ConformanceIt;
109
+ readonly expect: ConformanceExpect;
110
+ readonly makeSandbox: MakeSandbox;
111
+ /** Names the backend in test output. Defaults to `sandbox`. */
112
+ readonly label?: string;
113
+ /**
114
+ * Whether this backend's guest can run the `openTcpConnection` positive
115
+ * case's listener at all — by default {@link nodeGuestListener}, `node
116
+ * -e`. Defaults to `true`: every backend this suite ships against runs
117
+ * `agent/agent.cjs` in the guest, and that agent IS node, so node on the
118
+ * guest's own `PATH` is a precondition of the agent existing rather than
119
+ * an extra capability this suite demands.
120
+ *
121
+ * Set `false` for a guest that cannot run a listener this way at all
122
+ * (no `openTerminal`, or an image with neither node nor a substitute) —
123
+ * the case then SKIPS, its own title stating why, rather than failing a
124
+ * backend for a capability its contract never promised. A backend that
125
+ * can run *some* listener, just not node, keeps this `true` (or omits
126
+ * it) and supplies {@link SandboxConformanceOptions.guestListenerCommand}
127
+ * instead.
128
+ */
129
+ readonly guestCanRunNode?: boolean;
130
+ /**
131
+ * Overrides the program the `openTcpConnection` positive case starts
132
+ * inside the guest. Defaults to {@link nodeGuestListener}. A guest
133
+ * without node but with, say, busybox `nc` can supply its own command as
134
+ * long as it reports the bound port the way
135
+ * {@link GuestListenerCommand.parsePort} expects.
136
+ */
137
+ readonly guestListenerCommand?: () => GuestListenerCommand;
138
+ /**
139
+ * Whether this backend's guest honours `readFile`'s `offset`/`length`
140
+ * and implements {@link Sandbox.readFileStream}.
141
+ *
142
+ * **Defaults to `false`, and the default is the honest one.** These
143
+ * cases are not in {@link SANDBOX_CONTRACT_VERSION} — see the comment
144
+ * on that constant for why — so the suite cannot assume a backend has
145
+ * them. A backend whose guest is built from this repository sets it
146
+ * `true`; every other backend gets the cases as named skips, counted in
147
+ * the runner's own totals and titled with the reason, rather than as
148
+ * failures for a contract it never agreed to.
149
+ *
150
+ * A backend that sets this `true` while its guest ignores
151
+ * `offset`/`length` FAILS, and that is the point: returning the whole
152
+ * file where a slice was asked for is a wrong answer, not a missing
153
+ * capability.
154
+ */
155
+ readonly supportsRangedAndStreamedReads?: boolean;
156
+ }
157
+ /**
158
+ * A program the `openTcpConnection` positive case can start INSIDE a guest
159
+ * through {@link Sandbox.openTerminal}, and dial back into over
160
+ * `openTcpConnection` itself.
161
+ *
162
+ * Starting the listener in the guest — rather than in the orchestrator/test
163
+ * process, which is what this case used to do — is the whole point: a
164
+ * listener on the HOST'S loopback only ever proves anything for a backend
165
+ * whose "guest" happens to share that loopback (a Firecracker fixture over a
166
+ * local socket, a fake-agent-in-this-process kubernetes test). It never
167
+ * proves anything for a real remote guest, which cannot dial the
168
+ * orchestrator's loopback at all — that gap is exactly what let the case
169
+ * pass in every colocated fixture and fail the one time it ran against a
170
+ * live cluster.
171
+ */
172
+ export interface GuestListenerCommand {
173
+ /** The program `openTerminal` runs as the session's top-level process. */
174
+ readonly command: string;
175
+ readonly args: readonly string[];
176
+ /**
177
+ * Reads the port the listener bound out of everything it has printed to
178
+ * its terminal so far. Returns `undefined` until the listener has
179
+ * reported one — the suite polls this as output arrives rather than
180
+ * parsing a single chunk, because a pty may deliver the report split
181
+ * across reads.
182
+ */
183
+ parsePort(output: string): number | undefined;
184
+ }
185
+ /**
186
+ * The default {@link GuestListenerCommand}: `node -e` binding an ephemeral
187
+ * port on the GUEST's own loopback, echoing `conformance-reply:<payload>`
188
+ * back for the first chunk of the one connection it accepts, then reporting
189
+ * the bound port on its own stdout — the only way the host, which cannot
190
+ * inspect a real remote guest's open ports any other way, learns which port
191
+ * to dial.
192
+ *
193
+ * Every backend this suite ships against runs `agent/agent.cjs` in the
194
+ * guest, which is itself node — so node on the guest's `PATH` is not an
195
+ * extra requirement this suite invents, it is a precondition of the agent
196
+ * existing at all. A backend whose guest genuinely cannot run node (or
197
+ * cannot run `openTerminal`) declares that through
198
+ * {@link SandboxConformanceOptions.guestCanRunNode} or supplies its own
199
+ * command via {@link SandboxConformanceOptions.guestListenerCommand}.
200
+ */
201
+ export declare function nodeGuestListener(): GuestListenerCommand;
202
+ /**
203
+ * Start `listener` through `openTerminal`, and resolve once it has reported
204
+ * the port it bound.
205
+ *
206
+ * The returned `stop()` kills the terminal's owned process tree — the exact
207
+ * ownership guarantee the `openTerminal` section above already proves
208
+ * `destroy()` gets for free, used here to tear the listener down without
209
+ * waiting for the whole sandbox to go away.
210
+ *
211
+ * Takes `openTerminal` as a plain function rather than a `Sandbox`, so a
212
+ * unit test can exercise the port-parsing and exit-races above without a
213
+ * `Sandbox` fixture — see `__tests__/guest-listener.test.ts`.
214
+ */
215
+ export declare function startGuestListener(openTerminal: (options: OpenTerminalOptions) => Promise<TerminalSession>, listener: GuestListenerCommand): Promise<{
216
+ readonly port: number;
217
+ stop(): Promise<void>;
218
+ }>;
219
+ /**
220
+ * Register the {@link Sandbox} contract against one backend.
221
+ *
222
+ * Call it once per backend. It registers cases through the supplied
223
+ * `describe`/`it`; it does not run them.
224
+ */
225
+ export declare function defineSandboxConformance(options: SandboxConformanceOptions): void;
226
+ export type { ConformanceAssertion, ConformanceDescribe, ConformanceExpect, ConformanceIt };
227
+ //# sourceMappingURL=sandbox-conformance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-conformance.d.ts","sourceRoot":"","sources":["../../src/testing/sandbox-conformance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAGH,OAAO,KAAK,EACX,mBAAmB,EACnB,OAAO,EAIP,eAAe,EACf,MAAM,YAAY,CAAA;AAEnB,OAAO,KAAK,EACX,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAEzC,sEAAsE;AACtE,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAA;AAE5F,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAA;IACtC,QAAQ,CAAC,EAAE,EAAE,aAAa,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,oBAAoB,CAAA;IAC1D;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,OAAO,CAAA;CACjD;AAgED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAC7C;AAKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,IAAI,oBAAoB,CAsBxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACvC,YAAY,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,eAAe,CAAC,EACxE,QAAQ,EAAE,oBAAoB,GAC5B,OAAO,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CA0C3D;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAozBjF;AAKD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,aAAa,EAAE,CAAA"}