@namzu/sandbox 18.1.1 → 20.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 (43) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +35 -5
  3. package/dist/backends/docker/index.d.ts +46 -1
  4. package/dist/backends/docker/index.d.ts.map +1 -1
  5. package/dist/backends/docker/index.js +187 -51
  6. package/dist/backends/docker/index.js.map +1 -1
  7. package/dist/egress/index.d.ts +1 -0
  8. package/dist/egress/index.d.ts.map +1 -1
  9. package/dist/egress/index.js +1 -0
  10. package/dist/egress/index.js.map +1 -1
  11. package/dist/egress/profile-wiring.d.ts +40 -0
  12. package/dist/egress/profile-wiring.d.ts.map +1 -0
  13. package/dist/egress/profile-wiring.js +75 -0
  14. package/dist/egress/profile-wiring.js.map +1 -0
  15. package/dist/egress/profile.d.ts +153 -0
  16. package/dist/egress/profile.d.ts.map +1 -0
  17. package/dist/egress/profile.js +244 -0
  18. package/dist/egress/profile.js.map +1 -0
  19. package/dist/egress/proxy.d.ts +16 -0
  20. package/dist/egress/proxy.d.ts.map +1 -1
  21. package/dist/egress/proxy.js +37 -3
  22. package/dist/egress/proxy.js.map +1 -1
  23. package/dist/index.d.ts +19 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +12 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/seed/index.d.ts +165 -0
  28. package/dist/seed/index.d.ts.map +1 -0
  29. package/dist/seed/index.js +505 -0
  30. package/dist/seed/index.js.map +1 -0
  31. package/dist/testing/sandbox-conformance.d.ts +30 -1
  32. package/dist/testing/sandbox-conformance.d.ts.map +1 -1
  33. package/dist/testing/sandbox-conformance.js +18 -0
  34. package/dist/testing/sandbox-conformance.js.map +1 -1
  35. package/package.json +3 -3
  36. package/src/backends/docker/index.ts +246 -51
  37. package/src/egress/index.ts +1 -0
  38. package/src/egress/profile-wiring.ts +125 -0
  39. package/src/egress/profile.ts +380 -0
  40. package/src/egress/proxy.ts +53 -3
  41. package/src/index.ts +54 -5
  42. package/src/seed/index.ts +710 -0
  43. package/src/testing/sandbox-conformance.ts +52 -6
@@ -0,0 +1,505 @@
1
+ /**
2
+ * Sandbox seeds: git repositories a host wants present inside a sandbox,
3
+ * prepared by an idempotent step the caller runs, and prepared once on storage
4
+ * that outlives the sandbox (a kubernetes workspace's disk, a docker scratch
5
+ * bind the host reuses).
6
+ *
7
+ * The idea is ax's workspace repositories, prepared once with a marker on a
8
+ * durable disk; the code is this package's own. It is level-triggered in the
9
+ * sense that matters: every call looks at what is there and does only what is
10
+ * missing, so running it after every create or resume is the intended use.
11
+ *
12
+ * Everything runs through `Sandbox.exec`, so a file-root jail on the backend
13
+ * does not matter, and the guest needs `sh`, `git`, `find`, `mkdir`, `mktemp`,
14
+ * `rm` and a `mv` that takes `-T` (GNU coreutils; checked by a probe, not by
15
+ * name).
16
+ *
17
+ * WHAT IS TRUSTED, AND WHAT IS NOT. The seed marker lives in guest-writable
18
+ * storage, so an agent can forge it. It is never the reason a repository is
19
+ * skipped: every call checks each repository directly (it exists, its origin is
20
+ * the seed's URL, and the pinned or recorded commit is an ancestor of its
21
+ * HEAD), and the marker only remembers which commit a `ref` resolved to, and
22
+ * under which `ref`. A marker value that is not a commit id is ignored rather
23
+ * than passed to git.
24
+ *
25
+ * A CHANGED `ref` IS NOT THE SAME SEED. The recorded commit is used only when
26
+ * it was recorded under what the seed names now: the same `ref`, the default
27
+ * branch for none, or a pin (a pin's record never stands in for a `ref` or the
28
+ * default branch once the pin is dropped). Otherwise the check looks in the
29
+ * repository itself: the ref must exist there (as a remote-tracking branch or
30
+ * a tag; `refs/remotes/origin/HEAD` for the default branch) and name exactly
31
+ * HEAD. Sharing a line of history with HEAD is not enough, because the ref is
32
+ * often there without having been checked out: a clone deeper than 1 carries
33
+ * the tags in its history, and a pinned commit's full clone carries every
34
+ * remote branch. So a checkout of another branch, tag or commit is drift like
35
+ * any other, and a seed whose `ref` moved never reports the old checkout as
36
+ * `present`. The cost is on the safe side: with no record under the ref, local
37
+ * commits on top of it, or a `--branch` clone whose `ref` is then dropped
38
+ * (such a clone has no `origin/HEAD`), are drift too. A digest match is never
39
+ * a reason to skip a check either.
40
+ *
41
+ * NO CREDENTIAL ENTERS THE GUEST. URLs with a user name or password, `ssh://`
42
+ * and `git@host:path` are refused, since each would put a secret or a private
43
+ * key inside the sandbox. `https://` is for public repositories. `http://` is
44
+ * for one documented path: on the docker backend the egress proxy upgrades a
45
+ * plain request to HTTPS and stamps `brokeredCredentials` for the host, so a
46
+ * private repository can be cloned with no token in the guest. On every other
47
+ * backend private repositories are out of scope.
48
+ *
49
+ * NOTHING IS EVER DELETED OR RE-CLONED BUT THIS CALL'S OWN WORK. A repository
50
+ * whose origin or history no longer matches is drift: refused by default, or
51
+ * reported. A clone lands in `<dir>.namzu-partial-<nonce>` and is moved into
52
+ * place with `mv -T`, so two hosts preparing the same disk never see each
53
+ * other's half-written clone; the loser of that race removes only its own
54
+ * partial. Partials older than an hour, which only a crashed call leaves, are
55
+ * removed at the end, after this call's own clones have finished, so a peer's
56
+ * clone that is still running is left alone.
57
+ */
58
+ import { createHash, randomBytes } from 'node:crypto';
59
+ /** A seed refused, or a step that failed. `repository` names the repository when there is one. */
60
+ export class SandboxSeedError extends Error {
61
+ code;
62
+ repository;
63
+ name = 'SandboxSeedError';
64
+ constructor(code, message, repository) {
65
+ super(`sandbox seed: ${message}`);
66
+ this.code = code;
67
+ this.repository = repository;
68
+ }
69
+ }
70
+ const DNS_1123_LABEL = /^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$/;
71
+ const COMMIT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
72
+ const REF = /^[A-Za-z0-9._][A-Za-z0-9._/-]*$/;
73
+ const PATH_SEGMENT = /^[A-Za-z0-9._-]+$/;
74
+ /** The check's drift states, and what each says about the repository. */
75
+ const DRIFT_STATES = new Map([
76
+ ['origin', 'has a different origin URL'],
77
+ ['history', 'no longer contains its pinned or recorded commit'],
78
+ [
79
+ 'ref',
80
+ "does not hold the seed's ref (the default branch when it names none): with no record under that ref, HEAD must be exactly the commit the ref names in the repository, and it is another branch, tag or commit",
81
+ ],
82
+ [
83
+ 'pin',
84
+ 'is not checked out at its pinned commit: with no record of that pin, HEAD must be exactly the pinned commit, and it is another commit',
85
+ ],
86
+ ['occupied', 'is a directory that is not a git repository'],
87
+ ]);
88
+ /**
89
+ * What CHECK looks up for a seed that names no `ref` and has no record: the
90
+ * remote's default branch, as `refs/remotes/origin/HEAD` (which a clone with
91
+ * no `--branch` leaves). It starts with ':', which no `ref` the seed accepts
92
+ * can, so it never collides with a branch or tag name.
93
+ */
94
+ const DEFAULT_BRANCH = ':default';
95
+ /**
96
+ * The marker's `refs` entry for a repository: what its recorded commit was
97
+ * resolved from. A pinned repository records `:commit`, so dropping the pin
98
+ * never lets the pinned commit stand in for the default branch or a `ref`.
99
+ */
100
+ function recordedFrom(repo) {
101
+ return repo.commit !== undefined ? ':commit' : (repo.ref ?? '');
102
+ }
103
+ /** How old a partial clone must be before a later call removes it. */
104
+ const STALE_PARTIAL_MINUTES = 60;
105
+ function refuseUrl(url, where) {
106
+ if (typeof url !== 'string' || url.length === 0) {
107
+ throw new SandboxSeedError('invalid', `${where} is not a URL`);
108
+ }
109
+ if (/^[^/]*@[^/]*:/.test(url) && !url.includes('://')) {
110
+ throw new SandboxSeedError('invalid', `${where} is an scp-style SSH address; SSH would need a private key inside the sandbox, so only https:// (and http:// for a proxy-brokered host) is accepted`);
111
+ }
112
+ let parsed;
113
+ try {
114
+ parsed = new URL(url);
115
+ }
116
+ catch {
117
+ throw new SandboxSeedError('invalid', `${where} is not a URL`);
118
+ }
119
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
120
+ throw new SandboxSeedError('invalid', `${where} uses ${parsed.protocol.replace(/:$/, '')}; only https:// (and http:// for a proxy-brokered host) is accepted, because other transports would need a credential or a key inside the sandbox`);
121
+ }
122
+ if (parsed.username !== '' || parsed.password !== '') {
123
+ throw new SandboxSeedError('invalid', `${where} carries a user name or password, which would put a credential inside the sandbox; on docker, broker it with brokeredCredentials and an http:// URL instead`);
124
+ }
125
+ if (/\s/.test(url))
126
+ throw new SandboxSeedError('invalid', `${where} contains whitespace`);
127
+ return url;
128
+ }
129
+ function refuseDir(dir, where) {
130
+ const segments = dir.split('/');
131
+ if (dir.startsWith('/') ||
132
+ segments.some((segment) => segment === '..' || segment === '.' || !PATH_SEGMENT.test(segment))) {
133
+ throw new SandboxSeedError('invalid', `${where} ${JSON.stringify(dir)} must be a relative path of plain segments (letters, digits, '.', '_', '-'), with no '..'`);
134
+ }
135
+ if (segments[0] === '.namzu' || segments.some((segment) => segment.includes('.namzu-partial-'))) {
136
+ throw new SandboxSeedError('invalid', `${where} ${JSON.stringify(dir)} is reserved: .namzu/ holds the seed marker, and '.namzu-partial-' names a clone in progress`);
137
+ }
138
+ return dir;
139
+ }
140
+ /**
141
+ * Validate a seed and return it frozen. Refused with `SandboxSeedError`
142
+ * (`code: 'invalid'`): a name that is not a DNS-1123 label, no repositories,
143
+ * a repository name repeated, a URL outside the rules in the module doc, a
144
+ * `ref` that is not a plain branch or tag name, a `commit` that is not a full
145
+ * commit id, a `dir` that is absolute, climbs or is under `.namzu/`, two
146
+ * repositories in one directory or one inside the other, and `depth` that is not a positive integer or is set beside
147
+ * `commit`.
148
+ */
149
+ export function defineSandboxSeed(input) {
150
+ if (typeof input?.name !== 'string' || !DNS_1123_LABEL.test(input.name)) {
151
+ throw new SandboxSeedError('invalid', `name ${JSON.stringify(input?.name)} is not a DNS-1123 label`);
152
+ }
153
+ if (!Array.isArray(input.repositories) || input.repositories.length === 0) {
154
+ throw new SandboxSeedError('invalid', 'repositories must list at least one repository');
155
+ }
156
+ const names = new Set();
157
+ const dirs = [];
158
+ const repositories = input.repositories.map((repo, index) => {
159
+ const where = `repositories[${index}]`;
160
+ if (typeof repo?.name !== 'string' || !DNS_1123_LABEL.test(repo.name)) {
161
+ throw new SandboxSeedError('invalid', `${where}.name ${JSON.stringify(repo?.name)} is not a DNS-1123 label`);
162
+ }
163
+ if (names.has(repo.name)) {
164
+ throw new SandboxSeedError('invalid', `${where}.name ${JSON.stringify(repo.name)} is listed twice`);
165
+ }
166
+ names.add(repo.name);
167
+ const url = refuseUrl(repo.url, `${where}.url`);
168
+ if (repo.ref !== undefined &&
169
+ (typeof repo.ref !== 'string' || !REF.test(repo.ref) || repo.ref.includes('..'))) {
170
+ throw new SandboxSeedError('invalid', `${where}.ref ${JSON.stringify(repo.ref)} is not a branch or tag name`);
171
+ }
172
+ if (repo.commit !== undefined &&
173
+ (typeof repo.commit !== 'string' || !COMMIT_ID.test(repo.commit))) {
174
+ throw new SandboxSeedError('invalid', `${where}.commit ${JSON.stringify(repo.commit)} is not a full lowercase commit id`);
175
+ }
176
+ if (repo.depth !== undefined) {
177
+ if (!Number.isInteger(repo.depth) || repo.depth < 1) {
178
+ throw new SandboxSeedError('invalid', `${where}.depth ${JSON.stringify(repo.depth)} is not a positive integer`);
179
+ }
180
+ if (repo.commit !== undefined) {
181
+ throw new SandboxSeedError('invalid', `${where}.depth is set beside commit; a pinned commit is fetched with its history`);
182
+ }
183
+ }
184
+ const dir = refuseDir(repo.dir ?? repo.name, `${where}.dir`);
185
+ if (dirs.includes(dir)) {
186
+ throw new SandboxSeedError('invalid', `${where}.dir ${JSON.stringify(dir)} is used by another repository`);
187
+ }
188
+ // A clone creates its parent directories, so a repository inside
189
+ // another's directory would occupy it before that one is cloned.
190
+ const nested = dirs.find((other) => other.startsWith(`${dir}/`) || dir.startsWith(`${other}/`));
191
+ if (nested !== undefined) {
192
+ throw new SandboxSeedError('invalid', `${where}.dir ${JSON.stringify(dir)} and ${JSON.stringify(nested)} are nested; each repository needs a directory of its own, neither inside the other`);
193
+ }
194
+ dirs.push(dir);
195
+ return Object.freeze({
196
+ name: repo.name,
197
+ url,
198
+ ...(repo.ref !== undefined ? { ref: repo.ref } : {}),
199
+ ...(repo.commit !== undefined ? { commit: repo.commit } : {}),
200
+ ...(repo.dir !== undefined ? { dir: repo.dir } : {}),
201
+ ...(repo.depth !== undefined ? { depth: repo.depth } : {}),
202
+ });
203
+ });
204
+ return Object.freeze({ name: input.name, repositories: Object.freeze(repositories) });
205
+ }
206
+ /**
207
+ * A stable digest of the seed's content: SHA-256 over its normalised form,
208
+ * hex. Two seeds with the same repositories in the same order have the same
209
+ * digest whatever key order they were written in.
210
+ */
211
+ export function sandboxSeedDigest(seed) {
212
+ const defined = defineSandboxSeed(seed);
213
+ const canonical = {
214
+ name: defined.name,
215
+ repositories: defined.repositories.map((repo) => [
216
+ repo.name,
217
+ repo.url,
218
+ repo.ref ?? null,
219
+ repo.commit ?? null,
220
+ repo.dir ?? repo.name,
221
+ repo.depth ?? null,
222
+ ]),
223
+ };
224
+ return createHash('sha256').update(JSON.stringify(canonical)).digest('hex');
225
+ }
226
+ function tail(text) {
227
+ const trimmed = text.trim();
228
+ return trimmed.length > 800 ? `…${trimmed.slice(-800)}` : trimmed;
229
+ }
230
+ const PREFLIGHT = `
231
+ for tool in git find mkdir mktemp rm; do
232
+ command -v "$tool" >/dev/null 2>&1 || { printf 'missing %s\\n' "$tool"; exit 3; }
233
+ done
234
+ mkdir -p "$1/.namzu/seed" || { printf 'unwritable %s\\n' "$1"; exit 4; }
235
+ probe=$(mktemp -d "$1/.namzu/seed/probe.XXXXXX") || { printf 'unwritable %s\\n' "$1"; exit 4; }
236
+ mkdir "$probe/a"
237
+ if mv -T "$probe/a" "$probe/b" 2>/dev/null; then ok=1; else ok=0; fi
238
+ rm -rf "$probe"
239
+ [ "$ok" = 1 ] || { printf 'missing mv -T\\n'; exit 3; }
240
+ `;
241
+ // Arguments, per repository: dir, url, expected commit ('' for none), ref to
242
+ // find in the repository when there is no expected commit ('' for none,
243
+ // DEFAULT_BRANCH for the remote's default branch), and '1' when the expected
244
+ // commit is a pin the marker has no record of, which HEAD must then equal
245
+ // exactly (the ancestor rule holds only for a pin this call recorded). A ref found that way must
246
+ // be exactly HEAD: sharing a line of history with HEAD is not holding it, since
247
+ // a clone deeper than 1 carries the tags in its history and a pinned commit's
248
+ // full clone carries every remote branch. Prints '<state> <head>', and for
249
+ // 'present' the commit it held HEAD to ('-' for none).
250
+ const CHECK = `
251
+ while [ "$#" -ge 5 ]; do
252
+ dir=$1; url=$2; want=$3; ref=$4; exact=$5; shift 5
253
+ if [ ! -e "$dir" ]; then printf 'missing -\\n'; continue; fi
254
+ if [ ! -e "$dir/.git" ]; then printf 'occupied -\\n'; continue; fi
255
+ origin=$(git -C "$dir" config --get remote.origin.url 2>/dev/null || true)
256
+ head=$(git -C "$dir" rev-parse --verify --quiet HEAD 2>/dev/null || printf -- '-')
257
+ if [ "$origin" != "$url" ]; then printf 'origin %s\\n' "$head"; continue; fi
258
+ if [ -z "$want" ] && [ -n "$ref" ]; then
259
+ if [ "$ref" = "${DEFAULT_BRANCH}" ]; then
260
+ tip=$(git -C "$dir" rev-parse --verify --quiet "refs/remotes/origin/HEAD^{commit}" 2>/dev/null || true)
261
+ else
262
+ tip=$(git -C "$dir" rev-parse --verify --quiet "refs/remotes/origin/$ref^{commit}" 2>/dev/null ||
263
+ git -C "$dir" rev-parse --verify --quiet "refs/tags/$ref^{commit}" 2>/dev/null || true)
264
+ fi
265
+ if [ -z "$tip" ] || [ "$tip" != "$head" ]; then printf 'ref %s\\n' "$head"; continue; fi
266
+ want=$tip
267
+ elif [ -n "$want" ] && [ "$exact" = 1 ]; then
268
+ if [ "$want" != "$head" ]; then printf 'pin %s\\n' "$head"; continue; fi
269
+ elif [ -n "$want" ] && ! git -C "$dir" merge-base --is-ancestor "$want" HEAD 2>/dev/null; then
270
+ printf 'history %s\\n' "$head"; continue
271
+ fi
272
+ printf 'present %s %s\\n' "$head" "\${want:--}"
273
+ done
274
+ `;
275
+ // Arguments: dir, url, ref ('' for default), commit ('' for none), depth ('' for full), nonce.
276
+ const CLONE = `
277
+ set -e
278
+ dir=$1; url=$2; ref=$3; commit=$4; depth=$5; nonce=$6
279
+ mkdir -p "$(dirname "$dir")"
280
+ partial="$dir.namzu-partial-$nonce"
281
+ set -- clone --quiet
282
+ [ -n "$depth" ] && set -- "$@" --depth "$depth"
283
+ [ -n "$ref" ] && set -- "$@" --branch "$ref"
284
+ git -c credential.helper= "$@" -- "$url" "$partial"
285
+ if [ -n "$commit" ]; then git -C "$partial" -c advice.detachedHead=false checkout --quiet --detach "$commit"; fi
286
+ head=$(git -C "$partial" rev-parse HEAD)
287
+ if mv -T "$partial" "$dir" 2>/dev/null; then printf 'cloned %s\\n' "$head"; else rm -rf -- "$partial"; printf 'raced %s\\n' "$head"; fi
288
+ `;
289
+ // Arguments: marker path, content, this call's nonce, then the parent
290
+ // directories to sweep. The temporary name carries the nonce, not the shell's
291
+ // PID: two sandboxes sharing one disk often run this as the same PID, each in
292
+ // its own PID namespace.
293
+ const FINISH = `
294
+ set -e
295
+ marker=$1; content=$2; nonce=$3; shift 3
296
+ tmp="$marker.tmp.$nonce"
297
+ if ! { printf '%s' "$content" > "$tmp" && mv -f -- "$tmp" "$marker"; }; then
298
+ rm -f -- "$tmp"
299
+ exit 1
300
+ fi
301
+ for parent in "$@"; do
302
+ [ -d "$parent" ] || continue
303
+ find "$parent" -mindepth 1 -maxdepth 1 -type d -name '*.namzu-partial-*' -mmin +${STALE_PARTIAL_MINUTES} -exec rm -rf -- {} + 2>/dev/null || true
304
+ done
305
+ `;
306
+ /**
307
+ * The recorded commit of each repository whose record was made under the ref
308
+ * the seed names now. A record made under another ref says nothing about this
309
+ * one, so it is dropped and the repository is checked against the ref itself.
310
+ */
311
+ function readMarker(raw, seed) {
312
+ const commits = new Map();
313
+ let parsed;
314
+ try {
315
+ parsed = JSON.parse(raw);
316
+ }
317
+ catch {
318
+ return commits;
319
+ }
320
+ const recorded = parsed?.commits;
321
+ const refs = parsed?.refs;
322
+ if (recorded === null || typeof recorded !== 'object')
323
+ return commits;
324
+ if (refs === null || typeof refs !== 'object')
325
+ return commits;
326
+ for (const repo of seed.repositories) {
327
+ const value = recorded[repo.name];
328
+ const ref = refs[repo.name];
329
+ if (ref !== recordedFrom(repo))
330
+ continue;
331
+ // Guest-writable: a value that is not a commit id is ignored rather
332
+ // than handed to git, where it could be read as an option.
333
+ if (typeof value === 'string' && COMMIT_ID.test(value))
334
+ commits.set(repo.name, value);
335
+ }
336
+ return commits;
337
+ }
338
+ /**
339
+ * The ref CHECK looks for in the repository: none when a commit is pinned or
340
+ * recorded, otherwise the seed's `ref` or, with none, the default branch.
341
+ */
342
+ function refToFind(repo, want) {
343
+ return want === '' ? (repo.ref ?? DEFAULT_BRANCH) : '';
344
+ }
345
+ function joinPath(root, dir) {
346
+ return `${root.replace(/\/+$/, '')}/${dir}`;
347
+ }
348
+ function parentOf(path) {
349
+ return path.slice(0, path.lastIndexOf('/')) || '/';
350
+ }
351
+ /**
352
+ * Make the seed's repositories present under `options.root`, doing only what
353
+ * is missing, and report what was found. See the module doc for what is
354
+ * trusted, what is refused and what is never deleted.
355
+ */
356
+ export async function ensureSandboxSeed(sandbox, seed, options) {
357
+ const defined = defineSandboxSeed(seed);
358
+ const root = options?.root;
359
+ if (typeof root !== 'string' ||
360
+ !root.startsWith('/') ||
361
+ root.split('/').includes('..') ||
362
+ /\s/.test(root)) {
363
+ throw new SandboxSeedError('invalid', `root ${JSON.stringify(root)} must be an absolute path inside the sandbox with no '..' and no whitespace; it is required because the right place depends on the backend (a kubernetes workspace's disk mount, layout.scratch on docker)`);
364
+ }
365
+ const onDrift = options.onDrift ?? 'refuse';
366
+ const digest = sandboxSeedDigest(defined);
367
+ const markerPath = joinPath(root, `.namzu/seed/${defined.name}.json`);
368
+ const exec = async (script, args) => {
369
+ options.signal?.throwIfAborted();
370
+ return await sandbox.exec('sh', ['-c', script, 'namzu-seed', ...args], {
371
+ env: { GIT_TERMINAL_PROMPT: '0' },
372
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
373
+ ...(options.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
374
+ });
375
+ };
376
+ // 1. The tools, by probe. A guest with no `sh` at all fails the exec itself.
377
+ let preflight;
378
+ try {
379
+ preflight = await exec(PREFLIGHT, [root]);
380
+ }
381
+ catch (error) {
382
+ options.signal?.throwIfAborted();
383
+ throw new SandboxSeedError('tool-missing', `could not run sh in the sandbox (${error instanceof Error ? error.message : String(error)}); the image needs sh, git, find, mkdir, mktemp, rm and GNU mv`);
384
+ }
385
+ if (preflight.exitCode !== 0) {
386
+ const line = preflight.stdout.trim();
387
+ const missing = line.startsWith('missing ') ? line.slice('missing '.length) : undefined;
388
+ if (missing !== undefined || preflight.exitCode === 127) {
389
+ throw new SandboxSeedError('tool-missing', `the sandbox image has no ${missing ?? 'sh'}; it needs sh, git, find, mkdir, mktemp, rm and a mv that takes -T (GNU coreutils)`);
390
+ }
391
+ throw new SandboxSeedError('invalid', `root ${JSON.stringify(root)} is not writable in the sandbox: ${tail(preflight.stdout + preflight.stderr)}`);
392
+ }
393
+ // 2. What is there. The marker is read for the commits refs resolved to, and
394
+ // only for those; every repository is then checked directly.
395
+ const markerRead = await exec('cat -- "$1" 2>/dev/null || true', [markerPath]);
396
+ const recorded = readMarker(markerRead.stdout, defined);
397
+ const paths = defined.repositories.map((repo) => joinPath(root, repo.dir ?? repo.name));
398
+ const expected = defined.repositories.map((repo) => repo.commit ?? recorded.get(repo.name) ?? '');
399
+ const checkArgs = [];
400
+ defined.repositories.forEach((repo, index) => {
401
+ const want = expected[index];
402
+ const unrecordedPin = repo.commit !== undefined && recorded.get(repo.name) !== repo.commit;
403
+ checkArgs.push(paths[index], repo.url, want, refToFind(repo, want), unrecordedPin ? '1' : '');
404
+ });
405
+ const checked = await exec(CHECK, checkArgs);
406
+ if (checked.exitCode !== 0) {
407
+ throw new SandboxSeedError('invalid', `could not inspect the seed root: ${tail(checked.stderr)}`);
408
+ }
409
+ const lines = checked.stdout.trim().split('\n');
410
+ const states = defined.repositories.map((repo, index) => {
411
+ const [state, head, held] = (lines[index] ?? '').split(' ');
412
+ if (!DRIFT_STATES.has(state ?? '') && state !== 'missing' && state !== 'present') {
413
+ throw new SandboxSeedError('invalid', `unreadable check result for ${repo.name}`, repo.name);
414
+ }
415
+ return { state, head: head ?? '-', held: held !== undefined && held !== '-' ? held : undefined };
416
+ });
417
+ // 3. Drift is refused before anything is cloned, so a refusal leaves the
418
+ // root exactly as it was found.
419
+ const drifted = defined.repositories.filter((_, index) => DRIFT_STATES.has(states[index]?.state ?? ''));
420
+ if (drifted.length > 0 && onDrift === 'refuse') {
421
+ throw new SandboxSeedError('drift', `${drifted
422
+ .map((repo) => {
423
+ const state = states[defined.repositories.indexOf(repo)]?.state ?? '';
424
+ return `${repo.name} ${DRIFT_STATES.get(state)}`;
425
+ })
426
+ .join('; ')}. Nothing was changed; resolve it by hand, or pass onDrift: 'report'`, drifted[0]?.name);
427
+ }
428
+ // 4. Clone what is missing, each into its own partial directory.
429
+ const reports = [];
430
+ const commits = {};
431
+ const refs = {};
432
+ const record = (repo, commit) => {
433
+ commits[repo.name] = commit;
434
+ refs[repo.name] = recordedFrom(repo);
435
+ };
436
+ for (const [index, repo] of defined.repositories.entries()) {
437
+ const state = states[index];
438
+ const path = paths[index];
439
+ if (state.state === 'present') {
440
+ reports.push({ name: repo.name, status: 'present', commit: state.head });
441
+ record(repo, state.held ?? state.head);
442
+ continue;
443
+ }
444
+ if (DRIFT_STATES.has(state.state)) {
445
+ reports.push({ name: repo.name, status: 'drifted', commit: state.head });
446
+ const known = recorded.get(repo.name);
447
+ if (known !== undefined)
448
+ record(repo, known);
449
+ continue;
450
+ }
451
+ const nonce = randomBytes(6).toString('hex');
452
+ const depth = repo.commit !== undefined ? '' : String(repo.depth ?? 1);
453
+ const cloned = await exec(CLONE, [
454
+ path,
455
+ repo.url,
456
+ repo.ref ?? '',
457
+ repo.commit ?? '',
458
+ depth,
459
+ nonce,
460
+ ]);
461
+ const [outcome, head] = cloned.stdout.trim().split('\n').at(-1)?.split(' ') ?? [];
462
+ if (cloned.exitCode !== 0 ||
463
+ (outcome !== 'cloned' && outcome !== 'raced') ||
464
+ head === undefined) {
465
+ throw new SandboxSeedError('clone-failed', `cloning ${repo.name} from ${repo.url} failed (exit ${cloned.exitCode}${cloned.timedOut ? ', timed out' : ''}): ${tail(cloned.stderr)}`, repo.name);
466
+ }
467
+ if (outcome === 'cloned') {
468
+ reports.push({ name: repo.name, status: 'cloned', commit: head });
469
+ record(repo, repo.commit ?? head);
470
+ continue;
471
+ }
472
+ // Something took the directory while this call cloned: a peer moved
473
+ // its clone into place first. Ours is gone; check what is there the
474
+ // same way any present repository is checked, and say what it is.
475
+ const want = repo.commit ?? '';
476
+ const recheck = await exec(CHECK, [
477
+ path,
478
+ repo.url,
479
+ want,
480
+ refToFind(repo, want),
481
+ repo.commit !== undefined ? '1' : '',
482
+ ]);
483
+ const [again, peerHead, held] = recheck.stdout.trim().split(' ');
484
+ if (again !== 'present') {
485
+ const what = DRIFT_STATES.get(again ?? '') ?? 'could not be checked';
486
+ if (onDrift === 'refuse') {
487
+ throw new SandboxSeedError('drift', `${repo.name} appeared while this call was cloning it, and ${what}. This call's own clone was removed; nothing else was changed`, repo.name);
488
+ }
489
+ reports.push({ name: repo.name, status: 'drifted', commit: peerHead ?? '-' });
490
+ continue;
491
+ }
492
+ reports.push({ name: repo.name, status: 'present', commit: peerHead ?? '-' });
493
+ record(repo, repo.commit ?? (held !== undefined && held !== '-' ? held : (peerHead ?? head)));
494
+ }
495
+ // 5. The marker, written whole and moved into place; then stale partials.
496
+ const content = JSON.stringify({ seed: defined.name, digest, commits, refs });
497
+ const parents = [...new Set(paths.map(parentOf))];
498
+ const finishNonce = randomBytes(6).toString('hex');
499
+ const finished = await exec(FINISH, [markerPath, content, finishNonce, ...parents]);
500
+ if (finished.exitCode !== 0) {
501
+ throw new SandboxSeedError('invalid', `could not write the seed marker: ${tail(finished.stderr)}`);
502
+ }
503
+ return { digest, repositories: reports };
504
+ }
505
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/seed/index.ts"],"names":[],"mappings":"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"}
@@ -61,7 +61,25 @@
61
61
  * ```
62
62
  */
63
63
  import type { OpenTerminalOptions, Sandbox, TerminalSession } from '@namzu/sdk';
64
- import type { ConformanceAssertion, ConformanceDescribe, ConformanceExpect, ConformanceIt } from '@namzu/sdk/testing';
64
+ /**
65
+ * The runner shapes the suite takes, declared here rather than imported.
66
+ * `@namzu/sdk/testing` exported them beside the checkpoint-store suite, and
67
+ * that suite and these exports went with it; they are structurally the
68
+ * same types, so a runner written against either still fits.
69
+ */
70
+ /** The assertions the suite uses, and nothing more. */
71
+ interface ConformanceAssertion {
72
+ toBe(expected: unknown): void;
73
+ toEqual(expected: unknown): void;
74
+ toBeGreaterThan(expected: number): void;
75
+ toMatch(expected: RegExp): void;
76
+ }
77
+ /** Shape of the `expect` a runner supplies. */
78
+ type ConformanceExpect = (actual: unknown) => ConformanceAssertion;
79
+ /** Shape of the `it` a runner supplies. */
80
+ type ConformanceIt = (name: string, body: () => Promise<void>) => unknown;
81
+ /** Shape of the `describe` a runner supplies. */
82
+ type ConformanceDescribe = (name: string, body: () => void) => unknown;
65
83
  /**
66
84
  * The contract revision these assertions express. Carried on the describe
67
85
  * label so a failure is legible on sight as "the sandbox contract", the
@@ -153,6 +171,17 @@ export interface SandboxConformanceOptions {
153
171
  * capability.
154
172
  */
155
173
  readonly supportsRangedAndStreamedReads?: boolean;
174
+ /**
175
+ * A git repository the guest can clone, and an absolute directory to
176
+ * clone it under, for the `ensureSandboxSeed` case. Absent (the default),
177
+ * the case is a named skip: this suite cannot assume a guest has git or a
178
+ * route to any repository. The URL follows the seed's own rules (`https://`,
179
+ * or `http://` for a proxy-brokered host, no credentials).
180
+ */
181
+ readonly seed?: {
182
+ readonly url: string;
183
+ readonly root: string;
184
+ };
156
185
  }
157
186
  /**
158
187
  * A program the `openTcpConnection` positive case can start INSIDE a guest
@@ -1 +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"}
1
+ {"version":3,"file":"sandbox-conformance.d.ts","sourceRoot":"","sources":["../../src/testing/sandbox-conformance.ts"],"names":[],"mappings":"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"}
@@ -61,6 +61,7 @@
61
61
  * ```
62
62
  */
63
63
  import { createHash } from 'node:crypto';
64
+ import { ensureSandboxSeed } from '../seed/index.js';
64
65
  /**
65
66
  * The contract revision these assertions express. Carried on the describe
66
67
  * label so a failure is legible on sight as "the sandbox contract", the
@@ -892,5 +893,22 @@ export function defineSandboxConformance(options) {
892
893
  }));
893
894
  });
894
895
  });
896
+ describe(`${label} seed`, () => {
897
+ const seedOption = options.seed;
898
+ const title = 'prepares a seed once, and a second call finds it present at the same commit';
899
+ it(seedOption === undefined ? `${title} (skipped: no seed repository was given)` : title, seedOption === undefined
900
+ ? async () => { }
901
+ : withSandbox(async (sandbox) => {
902
+ const seed = {
903
+ name: 'conformance',
904
+ repositories: [{ name: 'repo', url: seedOption.url }],
905
+ };
906
+ const first = await ensureSandboxSeed(sandbox, seed, { root: seedOption.root });
907
+ expect(first.repositories[0]?.status).toBe('cloned');
908
+ const second = await ensureSandboxSeed(sandbox, seed, { root: seedOption.root });
909
+ expect(second.repositories[0]?.status).toBe('present');
910
+ expect(second.repositories[0]?.commit).toBe(first.repositories[0]?.commit);
911
+ }));
912
+ });
895
913
  }
896
914
  //# sourceMappingURL=sandbox-conformance.js.map