Rocko's round 2 candidate, packet agents/rocko/work/s4-follow-up/
(build.patch c8cec070, candidate manifest 5b067a9d, 8/8 OK).
- Definite DM refusals wait the full 30-minute cap, counted from the
journal's last refusal, so five refusals span about two hours before
gave-up (lead decision 73). Unknown outcomes keep doubling.
- Broker close sends at host.mjs:144/150/184/188 pass a callback, which
closes the EPIPE window both reviewers found in round 1.
- README documents manual recovery for an open decision.
- trackers-boot test, X9, X14, and Darkwing's round 1 notes 1-4.
- The append type check stays out; Rocko's reason holds (both reviewers
agree).
Reviews: Darkwing approve (comment 26884), Filbert approve (26886).
Landing gate on b13fef4c plus the patch: every node suite and every
scripts/test-*.sh green, test-task 98/0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
67 lines
3.8 KiB
JavaScript
67 lines
3.8 KiB
JavaScript
// Sage's row 39 gate check, not part of the candidate: the S4 host boots the
|
|
// S3 adapter through the real process.mjs when bootConfig emits trackers.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { Client } from "../../bus/src/client.mjs";
|
|
import { bootConfig, loadSystem } from "../src/config.mjs";
|
|
import { startHost, startTimeOf } from "../src/host.mjs";
|
|
import { writeJson } from "../../business/tests/helpers.mjs";
|
|
import { FakeVikunja } from "../../tasks/src/fake.mjs";
|
|
import { SCOPES } from "../../tasks/tests/world.mjs";
|
|
import { fixture, tmp } from "./helpers.mjs";
|
|
|
|
test("bootConfig trackers reach the S3 adapter in the real broker child, which goes ready against a fake Vikunja", async (t) => {
|
|
const root = tmp(t);
|
|
const fake = new FakeVikunja();
|
|
const srv = await fake.listen();
|
|
t.after(() => srv.close());
|
|
const owner = fake.user("svc-acme");
|
|
const bots = {};
|
|
for (const r of ["sync", "pm", "cto", "coder", "reviewer"]) bots[r] = fake.user(`bot-acme-${r}`, { owner });
|
|
const project = fake.project("Acme", owner);
|
|
for (const r of ["pm", "cto", "coder", "reviewer"]) fake.share(project, bots[r], 1);
|
|
fake.share(project, bots.sync, 0);
|
|
fake.install(project);
|
|
|
|
const f = fixture(root, "acme", (doc) => {
|
|
doc.vars["tracker.baseUrl"] = srv.url;
|
|
doc.tracker.sync.botId = bots.sync;
|
|
for (const r of Object.keys(doc.roles)) doc.roles[r].tracker.botId = bots[r];
|
|
return doc;
|
|
});
|
|
const put = (file, token) => {
|
|
writeFileSync(file, token + "\n");
|
|
chmodSync(file, 0o600);
|
|
};
|
|
put(f.doc.tracker.sync.credentials.vikunja.file, fake.token(bots.sync, SCOPES.sync));
|
|
for (const r of Object.keys(f.doc.roles)) put(f.doc.roles[r].credentials.vikunja.file, fake.token(bots[r], r === "pm" ? SCOPES.pm : SCOPES.worker));
|
|
writeJson(join(root, "project", ".mosaic", "project.json"), { projectVersion: 1, id: "stack", vars: { "tracker.project": project } });
|
|
|
|
mkdirSync(f.dataRoot, { recursive: true, mode: 0o700 });
|
|
const boot = bootConfig({ system: loadSystem({ env: f.env }), businessId: "acme", env: f.env });
|
|
assert.deepEqual(boot.trackers, { acme: { baseUrl: srv.url, project, pollSeconds: 60, reconcileMinutes: 60 } });
|
|
const host = await startHost({ boot, business: "acme", log: () => {} });
|
|
t.after(() => host.close(0));
|
|
|
|
const launch = await host.bindLaunch({ business: "acme", role: "pm", run: "pm-run", harness: "pi", pid: process.pid, startTime: startTimeOf(process.pid) });
|
|
const pm = new Client({ path: host.path, cap: launch.cap });
|
|
await pm.call("role.claim");
|
|
let code;
|
|
const end = Date.now() + 15000;
|
|
do {
|
|
code = await pm.call("task.close", {}).then(() => "ok", (e) => e.code);
|
|
if (code !== "tracker-starting") break;
|
|
await new Promise((r) => setTimeout(r, 100));
|
|
} while (Date.now() < end);
|
|
console.log(`task.close {} answered: ${code}; fake saw ${fake.requests.length} requests, first ${fake.requests.slice(0, 3).map((r) => `${r.method} ${r.path} ${r.status}`).join(", ")}`);
|
|
assert.ok(fake.requests.some((r) => r.path === "/info"), "the adapter called the fake");
|
|
assert.doesNotMatch(code, /^(tracker-|credential-|scope-too-broad)/, "the adapter is ready, so the verb fails on its own arguments");
|
|
const before = fake.requests.length;
|
|
const missing = await pm.call("task.close", { task_ref: `vikunja:${project}/999`, verdict: "gate check" }).then(() => "ok", (e) => e.code);
|
|
console.log(`task.close on a missing task answered: ${missing}; it made ${fake.requests.slice(before).map((r) => `${r.method} ${r.path} ${r.status}`).join(", ")}`);
|
|
assert.ok(fake.requests.length > before, "a well-formed verb reached Vikunja through the adapter");
|
|
assert.equal(await host.close(0), 0);
|
|
});
|