Files
stack/agents/darkwing/work/slice1-s3/rehearse.mjs
T
jason.woltjeandClaude Opus 5.5 4ac133dd8a docs(slice1): row 38 S3 build packet, probes and live rehearsal (darkwing)
Candidate for #1520 against 81339889: packages/tasks and the bus task
verbs. Live run rehearsed on a scratch v2.7.0 container; the estate run
waits for T236's base URL.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 18:33:50 -05:00

53 lines
4.0 KiB
JavaScript

// Darkwing, S3: rehearse packages/tasks/live/run.mjs on the scratch container. 127.0.0.1 only.
// Builds the runbook's install (svc owner, bots it owns, a label it owns, scoped tokens), then runs
// the live script with the tokens in its environment. Passwords and tokens stay in memory.
import { randomBytes } from "node:crypto";
import { writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
const [ORIGIN, RUN, OUT] = process.argv.slice(2);
if (!/^http:\/\/127\.0\.0\.1:\d+$/.test(ORIGIN)) throw new Error("scratch origin on 127.0.0.1 only");
const BASE = ORIGIN + "/api/v2";
const secrets = [];
async function api(method, path, body, auth) {
const headers = { "Content-Type": "application/json" };
if (auth) headers.Authorization = `Bearer ${auth}`;
const r = await fetch(BASE + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
return { status: r.status, json };
}
const must = (r, l) => { if (r.status >= 300) throw new Error(`${l}: ${r.status} ${r.json?.code ?? ""}`); return r.json; };
const password = randomBytes(18).toString("base64url"); secrets.push(password);
must(await api("POST", "/register", { username: "svc-demo", email: "[email protected]", password }), "register");
const O = must(await api("POST", "/login", { username: "svc-demo", password }), "login").token; secrets.push(O);
const P = must(await api("POST", "/projects", { title: "demo" }, O), "project").id;
const K = must(await api("GET", `/projects/${P}/views`, undefined, O), "views").items.find((v) => v.view_kind === "kanban").id;
const rename = { "To-Do": "todo", Doing: "in-progress", Done: "done" };
for (const b of must(await api("GET", `/projects/${P}/views/${K}/buckets`, undefined, O), "buckets").items)
must(await api("PUT", `/projects/${P}/views/${K}/buckets/${b.id}`, { title: rename[b.title] }, O), "rename");
for (const title of ["in-review", "blocked"]) must(await api("POST", `/projects/${P}/views/${K}/buckets`, { title }, O), title);
const label = must(await api("POST", "/labels", { title: "slice-1" }, O), "label").id;
const SCOPES = {
sync: { projects: ["read_one", "views_buckets", "views_buckets_tasks_get"], projects_views: ["read_all"], tasks: ["read_all", "read_one"], tasks_comments: ["read_all"] },
pm: { tasks: ["read_one", "create", "update"], tasks_assignees: ["create", "delete"], tasks_relations: ["create", "delete"], tasks_labels: ["create", "delete"], tasks_comments: ["create"], labels: ["read_all"], projects: ["views_buckets_tasks"] },
worker: { tasks: ["read_one", "update"], tasks_comments: ["create"], projects: ["views_buckets_tasks"] },
};
const env = { ...process.env };
const setup = { business: "demo", baseUrl: ORIGIN, project: P, roles: {}, labels: { "slice-1": label } };
for (const r of ["sync", "pm", "coder", "reviewer"]) {
const b = must(await api("POST", "/user/bots", { username: `bot-demo-${r}`, name: `demo ${r}` }, O), `bot ${r}`);
must(await api("POST", `/projects/${P}/users`, { username: b.username, permission: r === "sync" ? 0 : 1 }, O), `share ${r}`);
const t = await api("POST", "/tokens", { title: `s3-live-${r}`, owner_id: b.id, expires_at: "2030-01-01T00:00:00Z", permissions: SCOPES[r] ?? SCOPES.worker }, O);
if (t.status !== 201) throw new Error(`mint ${r} ${t.status}`);
secrets.push(t.json.token);
env["VK_LIVE_" + r.toUpperCase()] = t.json.token;
const entry = { botId: b.id, env: "VK_LIVE_" + r.toUpperCase(), expires: "2029-12-31" };
if (r === "sync") setup.sync = entry; else setup.roles[r] = entry;
}
writeFileSync(`${OUT}/setup.json`, JSON.stringify(setup, null, 1) + "\n", { mode: 0o600 });
const run = spawnSync(process.execPath, [RUN, `${OUT}/setup.json`, OUT, "--base", ORIGIN], { env, encoding: "utf8" });
const all = (run.stdout ?? "") + (run.stderr ?? "");
if (secrets.some((s) => all.includes(s))) { console.error("secret in the run output; not shown"); process.exit(3); }
process.stdout.write(all);
console.log("exit", run.status);
process.exit(run.status ?? 1);