mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
The OpenShell guest reader opened `/` before walking to the selected workspace, which a sandbox that grants only the workspace refuses, so a worker never started. It now anchors descriptor traversal at the selected root, with no ancestor opens, and keeps the selected-path symlink checks and `O_NOFOLLOW` reads beneath it. When a resource is created but projection fails before a generation exists, `rig environment` can now delete that owned resource by its selected, immutable resource id, with no generation or node binding required. Attached resources are refused before any mutation, the one-dispatch journal and dependent cleanup are kept, and the selection is checked again after the driver's final identity call. Uncertain or lost replies still require observed absence. The example `rig.yaml` gains its required pod label and a pod-local delegation edge, and validates through the normal parser and plan path. The CLI help and the OpenShell README describe cleanup by resource id, `OPENRIG_SESSION_NAME` as attribution rather than authentication, and guest-reachable listener addressing. Runtime +30/-14, tests +165/-2 across five files. No new error code, dependency, migration or policy.
24 lines
1.7 KiB
TypeScript
24 lines
1.7 KiB
TypeScript
import { afterEach, expect, it, vi } from "vitest";
|
|
import { environmentCommand } from "../src/commands/environment.js";
|
|
import { STATE_FILE, type LifecycleDeps } from "../src/daemon-lifecycle.js";
|
|
import type { EnvironmentCommandDeps } from "../src/commands/environment.js";
|
|
|
|
afterEach(() => vi.restoreAllMocks());
|
|
it.each([undefined, "generation-one"])("sends the explicit cleanup selection with generation %s", async generation => {
|
|
const lifecycleDeps: LifecycleDeps = {
|
|
spawn: vi.fn(), fetch: vi.fn(async () => ({ ok: true })), kill: vi.fn(() => true),
|
|
readFile: vi.fn((path: string) => path === STATE_FILE ? JSON.stringify({ pid: 123, port: 7433, db: "test.sqlite", startedAt: "2026-04-20T00:00:00Z" }) : null),
|
|
writeFile: vi.fn(), removeFile: vi.fn(), exists: vi.fn((path: string) => path === STATE_FILE),
|
|
mkdirp: vi.fn(), openForAppend: vi.fn(() => 3), isProcessAlive: vi.fn(() => true),
|
|
};
|
|
const post = vi.fn(async () => ({ status: 200, data: { state: "confirmed" } }));
|
|
const deps: EnvironmentCommandDeps = { lifecycleDeps, clientFactory: () => ({ post }) as never };
|
|
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
await environmentCommand(deps).parseAsync(["node", "rig", "effect", "binding-one", "--effect", "delete-owned", "--operation", "delete-one",
|
|
...(generation ? ["--generation", generation] : ["--resource-id", "compute-one"]), "--engine-socket", "/owned/engine.sock", "--json"]);
|
|
expect(post).toHaveBeenCalledWith("/api/environments/binding-one/effect", {
|
|
effect: "delete-owned", generation: generation ?? null, ...(generation ? {} : { resourceId: "compute-one" }), operationId: "delete-one", engineSocket: "/owned/engine.sock",
|
|
}, { timeoutMs: 120_000 });
|
|
expect(log).toHaveBeenCalled();
|
|
});
|