feat(queue): Piece D, reviews as issue comments, raw per-seat token helper (row 12, #1508)

queue move ID in-review posts the review request as a Gitea comment and
review record reads verdicts back, so reviews stop being files in
docs/plans/reviews/. On a comment round, in-review to waiting-on-jason
now needs every listed reviewer's approval for the current round, the
same as in-review to done (Filbert r1 C1). scripts/gitea-api.sh reads
the raw per-seat token files (lead decisions 37 to 39): config built and
checked before curl starts, export attribute cleared, fixed base URL.
test-queue.sh skips its live checks outside the canonical root.

Darkwing authored. Filbert approved D r2 (cf1d3fd0) after r1 (a2dc2302)
and corrected the plan (293747cd). Rocko reviewed the helper (e896192f,
2096b0a3), and Sage's lead check passed under decision 38. Manifest
b402fb38, 19 files.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-27 10:07:29 -05:00
co-authored by Claude Opus 5.5
parent cdcedb2741
commit f539466fcb
19 changed files with 2541 additions and 78 deletions
+52
View File
@@ -0,0 +1,52 @@
// A stand-in for scripts/gitea-api.sh in the review tests: same argv
// (METHOD PATH [BODY]), same output (body on stdout, "HTTP <code>" on
// stderr, exit 1 on a non-2xx code). It reads no credential file; the login
// comes from the credential path's agents/<login>/ segment. Every call is
// appended to $FAKE_GITEA_LOG, and posted comments are served back from it.
// $FAKE_GITEA_SCENARIO names a JSON file of rules that override the
// defaults: {rules: [{method, path (regex), http, body, sleepMs, fail}]}.
import { appendFileSync, existsSync, readFileSync } from "node:fs";
const [method, path, body] = process.argv.slice(2);
const env = process.env;
const read = (f) => (f && existsSync(f) ? readFileSync(f, "utf8") : null);
const scenario = JSON.parse(read(env.FAKE_GITEA_SCENARIO) ?? "{}");
const prior = (read(env.FAKE_GITEA_LOG) ?? "").split("\n").filter(Boolean).map((l) => JSON.parse(l));
const cred = env.MOSAIC_GITEA_CREDENTIAL_FILE ?? "";
const loginOf = (p) => /\/agents\/([^/]+)\/secrets\//.exec(p)?.[1] ?? null;
const login = loginOf(cred);
appendFileSync(env.FAKE_GITEA_LOG, `${JSON.stringify({ method, path, body: body === undefined ? null : JSON.parse(body), cred })}\n`);
function answer(http, obj) {
if (obj !== undefined) process.stdout.write(typeof obj === "string" ? obj : JSON.stringify(obj));
process.stderr.write(`HTTP ${http}\n`);
if (http < 200 || http > 299) {
process.stderr.write(`gitea-api: ${method} ${path} failed (HTTP ${http})\n`);
process.exit(1);
}
process.exit(0);
}
const issueUrl = (n) => `https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack/issues/${n}`;
const rule = (scenario.rules ?? []).find((r) => r.method === method && new RegExp(r.path).test(path));
if (rule) {
if (rule.sleepMs) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, rule.sleepMs);
if (rule.fail) {
process.stderr.write("gitea-api: request failed\n");
process.exit(1);
}
if (rule.http) answer(rule.http, rule.body);
}
let m;
if (method === "GET" && path === "user") answer(200, { login });
if (method === "POST" && (m = /^repos\/mosaicstack\/stack\/issues\/(\d+)\/comments$/.exec(path))) {
const id = 1000 + prior.filter((p) => p.method === "POST").length;
answer(201, { id, issue_url: issueUrl(m[1]), body: JSON.parse(body).body, user: { login } });
}
if (method === "GET" && (m = /^repos\/mosaicstack\/stack\/issues\/comments\/(\d+)$/.exec(path))) {
const id = Number(m[1]);
const post = prior.filter((p) => p.method === "POST")[id - 1000];
if (post) answer(200, { id, issue_url: issueUrl(/issues\/(\d+)\/comments$/.exec(post.path)[1]), body: post.body.body, user: { login: loginOf(post.cred) } });
answer(404, { message: "not found" });
}
answer(404, { message: "no route" });
+6 -3
View File
@@ -1,10 +1,13 @@
// Child process for the SIGKILL tests. argv: SRC_DIR STEP REQUEST_JSON.
// Runs one mutation from the working directory and kills itself with
// SIGKILL when the write path reaches STEP (8.5).
// SIGKILL when the write path reaches STEP (8.5). STEP#N kills at the Nth
// time the step is reached: a request's outcome write is its second lock.
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const [src, step, json] = process.argv.slice(2);
const [src, spec, json] = process.argv.slice(2);
const [step, nth = "1"] = spec.split("#");
let seen = 0;
const store = await import(pathToFileURL(join(src, "store.mjs")).href);
store.mutate({ hook: (name) => { if (name === step) process.kill(process.pid, "SIGKILL"); } }, JSON.parse(json));
store.mutate({ hook: (name) => { if (name === step && ++seen === Number(nth)) process.kill(process.pid, "SIGKILL"); } }, JSON.parse(json));
process.stdout.write("finished without reaching the step\n");