@omg-dev/sandbox 0.4.34 → 0.4.37

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.
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as SandboxClient, a as compileStartScript, c as download, d as templateContentVersionRef, f as templateDefinitionHash, g as SandboxApiError, h as waitForSnapshotUpload, i as check, l as files, m as templateVersionRef, n as apt, o as compileStep, p as templateRuntimeContract, r as bakeTemplate, s as defineTemplate, t as applyTemplate, u as run, v as assertTemplateId } from "./templates-BLd13wPT.mjs";
2
- export { SandboxApiError, SandboxClient, applyTemplate, apt, assertTemplateId, bakeTemplate, check, compileStartScript, compileStep, defineTemplate, download, files, run, templateContentVersionRef, templateDefinitionHash, templateRuntimeContract, templateVersionRef, waitForSnapshotUpload };
1
+ import { _ as SandboxApiError, a as check, c as defineTemplate, d as run, f as templateContentVersionRef, g as waitForSnapshotUpload, h as templateVersionRef, i as bakeTemplate, l as download, m as templateRuntimeContract, n as applyTemplate, o as compileStartScript, p as templateDefinitionHash, r as apt, s as compileStep, t as APT_FORCE_IPV4_SNIPPET, u as files, v as SandboxClient, y as assertTemplateId } from "./templates-D-Ap1Yxk.mjs";
2
+ export { APT_FORCE_IPV4_SNIPPET, SandboxApiError, SandboxClient, applyTemplate, apt, assertTemplateId, bakeTemplate, check, compileStartScript, compileStep, defineTemplate, download, files, run, templateContentVersionRef, templateDefinitionHash, templateRuntimeContract, templateVersionRef, waitForSnapshotUpload };
@@ -203,9 +203,10 @@ function shellEnvAssignments(env) {
203
203
  return `${key}=${quote(value)}`;
204
204
  });
205
205
  }
206
+ const APT_FORCE_IPV4_SNIPPET = `mkdir -p /etc/apt/apt.conf.d\nprintf 'Acquire::ForceIPv4 "true";\\n' > /etc/apt/apt.conf.d/99-omg-force-ipv4`;
206
207
  function compileStep(step) {
207
208
  if (step.kind === "files") throw new Error("files steps transfer local content and are applied via applyTemplate(), not compileStep()");
208
- if (step.kind === "apt") return { script: `set -euo pipefail\napt-get update -qq\nDEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends ${step.packages.map(quote).join(" ")}\nrm -rf /var/lib/apt/lists/*` };
209
+ if (step.kind === "apt") return { script: `set -euo pipefail\n${APT_FORCE_IPV4_SNIPPET}\napt-get update -qq\nDEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends ${step.packages.map(quote).join(" ")}\nrm -rf /var/lib/apt/lists/*` };
209
210
  if (step.kind === "archive") {
210
211
  const strip = step.stripComponents ?? 1;
211
212
  if (!Number.isInteger(strip) || strip < 0 || strip > 20) throw new Error("invalid stripComponents");
@@ -420,4 +421,4 @@ async function resolveTemplateVersion(client, versionRef) {
420
421
  }
421
422
  }
422
423
  //#endregion
423
- export { SandboxClient as _, compileStartScript as a, download as c, templateContentVersionRef as d, templateDefinitionHash as f, SandboxApiError as g, waitForSnapshotUpload as h, check as i, files as l, templateVersionRef as m, apt as n, compileStep as o, templateRuntimeContract as p, bakeTemplate as r, defineTemplate as s, applyTemplate as t, run as u, assertTemplateId as v };
424
+ export { SandboxApiError as _, check as a, defineTemplate as c, run as d, templateContentVersionRef as f, waitForSnapshotUpload as g, templateVersionRef as h, bakeTemplate as i, download as l, templateRuntimeContract as m, applyTemplate as n, compileStartScript as o, templateDefinitionHash as p, apt as r, compileStep as s, APT_FORCE_IPV4_SNIPPET as t, files as u, SandboxClient as v, assertTemplateId as y };
@@ -1,2 +1,2 @@
1
- import { a as compileStartScript, c as download, d as templateContentVersionRef, f as templateDefinitionHash, h as waitForSnapshotUpload, i as check, l as files, m as templateVersionRef, n as apt, o as compileStep, p as templateRuntimeContract, r as bakeTemplate, s as defineTemplate, t as applyTemplate, u as run } from "./templates-BLd13wPT.mjs";
2
- export { applyTemplate, apt, bakeTemplate, check, compileStartScript, compileStep, defineTemplate, download, files, run, templateContentVersionRef, templateDefinitionHash, templateRuntimeContract, templateVersionRef, waitForSnapshotUpload };
1
+ import { a as check, c as defineTemplate, d as run, f as templateContentVersionRef, g as waitForSnapshotUpload, h as templateVersionRef, i as bakeTemplate, l as download, m as templateRuntimeContract, n as applyTemplate, o as compileStartScript, p as templateDefinitionHash, r as apt, s as compileStep, t as APT_FORCE_IPV4_SNIPPET, u as files } from "./templates-D-Ap1Yxk.mjs";
2
+ export { APT_FORCE_IPV4_SNIPPET, applyTemplate, apt, bakeTemplate, check, compileStartScript, compileStep, defineTemplate, download, files, run, templateContentVersionRef, templateDefinitionHash, templateRuntimeContract, templateVersionRef, waitForSnapshotUpload };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omg-dev/sandbox",
3
- "version": "0.4.34",
3
+ "version": "0.4.37",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -33,6 +33,16 @@ describe("sandbox templates", () => {
33
33
  expect(Object.isFrozen(template)).toBe(true);
34
34
  });
35
35
 
36
+ test("apt forces IPv4 before the first fetch — the guest has no IPv6 route", () => {
37
+ const { script } = compileStep(apt.packages(["tmux"]));
38
+ const dropIn = script.indexOf("Acquire::ForceIPv4");
39
+ expect(dropIn).toBeGreaterThan(-1);
40
+ expect(script).toContain("/etc/apt/apt.conf.d/99-omg-force-ipv4");
41
+ // Ordering is the whole fix: an AAAA-only attempt before the drop-in lands
42
+ // still burns the step deadline on an unroutable archive.ubuntu.com.
43
+ expect(dropIn).toBeLessThan(script.indexOf("apt-get update"));
44
+ });
45
+
36
46
  test("archive extraction ignores inherited tar policy", () => {
37
47
  const { script } = compileStep(download.archive({ url: "https://example.com/a.tgz", destination: "/home/user/app" }));
38
48
  expect(script).toContain("TAR_OPTIONS= tar");
@@ -148,7 +158,7 @@ describe("sandbox templates", () => {
148
158
 
149
159
  test("LFG owns every runtime dependency and zero-config transport default", () => {
150
160
  const lfg = agentTemplates.lfg;
151
- expect(templateVersionRef(lfg)).toBe("agent-lfg-v43");
161
+ expect(templateVersionRef(lfg)).toBe("agent-lfg-v81");
152
162
  expect(lfg.install).toContainEqual(apt.packages(["tmux"]));
153
163
  expect(lfg.checks).toContainEqual(check.command("tmux"));
154
164
  expect(lfg.checks).toContainEqual(check.file("/home/user/lfg/omg-start.sh"));
@@ -156,7 +166,7 @@ describe("sandbox templates", () => {
156
166
  expect(lfg.start?.env?.LIVE_TRANSPORT).toBe("ws");
157
167
  expect(lfg.start?.readiness?.port).toBe(8766);
158
168
  expect(templateRuntimeContract(lfg, templateVersionRef(lfg))).toEqual({
159
- immutableRef: "agent-lfg-v43",
169
+ immutableRef: "agent-lfg-v81",
160
170
  restoreKind: "full_rootfs",
161
171
  startCommand: "exec /home/user/.omg/template/bootstrap.sh",
162
172
  readinessPort: 8766,
@@ -196,7 +206,26 @@ describe("sandbox templates", () => {
196
206
  .filter((step): step is Extract<typeof step, { kind: "run" }> => step.kind === "run")
197
207
  .map((step) => step.command);
198
208
  expect(commands.some((c) => c.includes("recipe.sh"))).toBe(true);
199
- expect(lfg.metadata?.lfgRelease).toBe("v0.1.209");
209
+ // The LFG release is pinned in three places that a bump has to move
210
+ // together: the bundle download URL, the install.json the guest writes
211
+ // (what self-update compares against), and the metadata the dashboard
212
+ // reads. A hardcoded version here just goes stale on every bump — and did,
213
+ // sitting at v0.1.209 while the catalog had moved on to v0.1.296. Assert
214
+ // the invariant that actually matters instead: all three agree.
215
+ const pinnedRelease = lfg.metadata?.lfgRelease as string | undefined;
216
+ expect(pinnedRelease).toMatch(/^v\d+\.\d+\.\d+$/);
217
+ expect(
218
+ lfg.install.some(
219
+ (step) =>
220
+ step.kind === "archive" &&
221
+ "url" in step &&
222
+ typeof step.url === "string" &&
223
+ step.url.includes(`/releases/download/${pinnedRelease}/lfg-bundle.tar.gz`),
224
+ ),
225
+ ).toBe(true);
226
+ expect(
227
+ commands.some((c) => c.includes(`"release":"${pinnedRelease}"`)),
228
+ ).toBe(true);
200
229
  const lfgInstall = commands.find((c) => c.includes("bun install --production"));
201
230
  expect(lfgInstall).toBeDefined();
202
231
  expect(lfgInstall!).not.toContain("delete manifest.workspaces");
package/src/templates.ts CHANGED
@@ -179,12 +179,26 @@ function shellEnvAssignments(env: Record<string, string>): string[] {
179
179
  });
180
180
  }
181
181
 
182
+ // Guest VMs are IPv4-only: apps/infra/rootfs/init writes a v4-only
183
+ // /etc/resolv.conf and the netns contract hands the guest 169.254.0.2 with no
184
+ // IPv6 route. Ubuntu's mirrors publish AAAA records anyway, and apt walks the
185
+ // resolved addresses serially with no happy-eyeballs fallback — so a bake
186
+ // either dies on `Cannot initiate the connection to archive.ubuntu.com:80
187
+ // (2620:2d:4000:1::101). - connect (101: Network is unreachable)` or burns the
188
+ // whole 10-minute step deadline on an unroutable mirror. Both were silent:
189
+ // agent-lfg just stayed stale at the last good bake.
190
+ //
191
+ // Written as a drop-in rather than an inline `-o` so it lands before the first
192
+ // fetch AND persists into the baked snapshot — later apt calls in the guest
193
+ // (the agent's runtime tmux fallback, user apps) inherit it for free.
194
+ export const APT_FORCE_IPV4_SNIPPET = `mkdir -p /etc/apt/apt.conf.d\nprintf 'Acquire::ForceIPv4 "true";\\n' > /etc/apt/apt.conf.d/99-omg-force-ipv4`;
195
+
182
196
  export function compileStep(step: TemplateStep): { script: string; timeoutMs?: number } {
183
197
  if (step.kind === "files") {
184
198
  throw new Error("files steps transfer local content and are applied via applyTemplate(), not compileStep()");
185
199
  }
186
200
  if (step.kind === "apt") {
187
- return { script: `set -euo pipefail\napt-get update -qq\nDEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends ${step.packages.map(quote).join(" ")}\nrm -rf /var/lib/apt/lists/*` };
201
+ return { script: `set -euo pipefail\n${APT_FORCE_IPV4_SNIPPET}\napt-get update -qq\nDEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends ${step.packages.map(quote).join(" ")}\nrm -rf /var/lib/apt/lists/*` };
188
202
  }
189
203
  if (step.kind === "archive") {
190
204
  const strip = step.stripComponents ?? 1;