sage: row 188 landing record: P19 411 (N1) and comments 361-363 script and record, 409/410 description restore after a partial done update, SESSIONS line
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// Sage, 2026-10-20: row 188 (#1686) landing. Files Filbert's row 188 N1 (the REPO logical-path gap) as one P19
|
||||
// task, owner Sage, no due date (the mos-0955 form); comments the landing on P19 409, 410 and 385 and marks 409
|
||||
// and 410 done (their tests landed). Per mos-0882. Sage's own key (bot user 13); the sync key reads back. No labels,
|
||||
// assignees, due dates or bucket moves. Refuses if the N1 title exists. Prints no secret.
|
||||
// Correction: the 11:12Z run sent { done: true } alone and Vikunja blanked the descriptions of 409 and 410;
|
||||
// restore-409-410-desc.mjs put them back at 11:13Z. The done step below now sends the whole v1 task, as close-404.mjs does.
|
||||
// Usage: node file-r188-landed.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON LANDING_SHA
|
||||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||
const [ORIGIN, KEY, SYNC, OUT, LANDING] = process.argv.slice(2);
|
||||
if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only");
|
||||
if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing");
|
||||
if (!/^[0-9a-f]{9}$/.test(LANDING ?? "")) throw new Error("LANDING_SHA: 9 hex characters");
|
||||
const PROJECT = 19;
|
||||
const SIG = "Created by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
|
||||
const PSIG = "Posted by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
|
||||
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
const ps = (sig, xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(sig)}</p>`;
|
||||
const N1 = {
|
||||
title: "stack-release.sh and webui-service.sh: the proof file repository check uses a logical REPO path (row 188 N1)",
|
||||
description: ps(SIG, [
|
||||
`Filbert's row 188 r1 approve (Gitea #1686 comment 29433), N1, landing ${LANDING}. scripts/stack-release.sh sets REPO="$(pwd)" after cd "$(dirname "$0")/.." (:22-23), and scripts/webui-service.sh:67 sets REPO with cd ... && pwd. Both are the logical path. The proof file check compares it with realpath -e of the file (stack-release.sh:774-775, webui-service.sh:196), which is physical. So if either script runs through a symlinked path, such as ~/src/mosaic-stack-dev-test, a proof file inside the repository is not refused. The copies side uses realpath -m and is fine. The tests can't see it because the fixture base is realpathSync'd.`,
|
||||
"The fix is pwd -P, or realpath on REPO, in both scripts, plus a test that runs the script through a symlinked checkout.",
|
||||
"Mos's ruling (mos-0959): the fix lands after Part B, since it changes the TOOL blob the Part B window file names. Until then the Part B run brief calls both scripts by /mnt/storage/src/mosaic-stack and PB3 checks pwd -P = pwd. Part B's SLOT is outside the repository by either path.",
|
||||
"Owner Sage. No queue row until scheduled (the mos-0955 form). Not live-affecting.",
|
||||
]),
|
||||
};
|
||||
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
|
||||
async function api(method, path, body, auth) {
|
||||
const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" });
|
||||
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
|
||||
return { status: r.status, json };
|
||||
}
|
||||
const list = (j) => j?.items ?? (Array.isArray(j) ? j : []);
|
||||
async function existing() {
|
||||
const out = [];
|
||||
for (let page = 1; page < 20; page++) {
|
||||
const r = await api("GET", `/api/v2/projects/${PROJECT}/tasks?per_page=50&page=${page}`, undefined, sync);
|
||||
if (r.status !== 200) throw new Error(`list tasks: ${r.status}`);
|
||||
out.push(...list(r.json));
|
||||
if (list(r.json).length < 50) return out;
|
||||
}
|
||||
throw new Error("more than 19 pages");
|
||||
}
|
||||
const rec = { at: new Date().toISOString(), project: PROJECT, landing: LANDING, created: null, comments: [], done: [], problems: [] };
|
||||
const save = () => {
|
||||
const out = JSON.stringify(rec, null, 1) + "\n";
|
||||
if (out.includes(key) || out.includes(sync)) throw new Error("secret in the record; not written");
|
||||
writeFileSync(OUT, out);
|
||||
};
|
||||
const CHECK = { 385: /X-authentik-username from 127\.0\.0\.1/, 409: /M8/, 410: /M9/ };
|
||||
for (const [id, re] of Object.entries(CHECK)) {
|
||||
const t = await api("GET", `/api/v2/tasks/${id}`, undefined, sync);
|
||||
if (t.status !== 200 || t.json?.project_id !== PROJECT || !re.test(t.json?.title ?? "")) throw new Error(`task ${id}: ${t.status}, title check failed; nothing written`);
|
||||
}
|
||||
if ((await existing()).some((t) => t.title === N1.title)) throw new Error("N1 title exists; refusing");
|
||||
writeFileSync(OUT, "", { flag: "wx" });
|
||||
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: N1.title, description: N1.description }, key);
|
||||
rec.created = { status: r.status, id: r.json?.id ?? null };
|
||||
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) rec.problems.push(`create: ${r.status} ${r.json?.message ?? ""}`);
|
||||
else {
|
||||
const back = await api("GET", `/api/v2/tasks/${r.json.id}`, undefined, sync);
|
||||
Object.assign(rec.created, { readBack: back.status, done: back.json?.done, inverse: `mark done: POST /api/v1/tasks/${r.json.id} done true (sage key); delete is Jason's or ops-01's` });
|
||||
if (back.status !== 200 || back.json?.title !== N1.title || back.json?.done) rec.problems.push("create read-back differs");
|
||||
}
|
||||
save();
|
||||
const n1 = rec.created.id ? `#${rec.created.id}` : "(not created; see the record)";
|
||||
const LAND = `Row 188 (Gitea #1686) has landed as ${LANDING} on refactor: stack-release T54, tests only. A carried proof pair whose file was chmod-ed, swapped for a symlink or removed now has a test that refuses it before anything stops, in activate and rollback; a proof file inside the repository, directly or through a directory symlink, has one too. Filbert approved round 1 (comment 29433) and killed M8, M9, M10 and his own M11; Sage's gate passed (cli 453/453 and every scripts/test-*.sh). scripts/stack-release.sh is unchanged, blob 2586a2a37cd2, so the Part B window file (A2 passed, mos-0958) holds.`;
|
||||
const POSTS = [
|
||||
{ id: 409, body: [LAND, "This task's gap (M8) is closed by T54 test 1 and test 2. Marked done."], done: true },
|
||||
{ id: 410, body: [LAND, "This task's gap (M9) is closed by T54 tests 3 and 4. Marked done."], done: true },
|
||||
{ id: 385, body: [LAND, `Precondition 2 of the Part B window is met. Filbert's N1, a logical REPO path in the repository check of both scripts, is P19 ${n1}; per mos-0959 it lands after Part B, and the Part B run brief calls both scripts by the physical path. The window still waits for Jason's J1 and Q1.`], done: false },
|
||||
];
|
||||
for (const P of POSTS) {
|
||||
const c = await api("PUT", `/api/v1/tasks/${P.id}/comments`, { comment: ps(PSIG, P.body) }, key);
|
||||
const e = { task: P.id, status: c.status, id: c.json?.id ?? null };
|
||||
rec.comments.push(e);
|
||||
if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) { rec.problems.push(`comment ${P.id}: ${c.status}`); save(); continue; }
|
||||
const back = await api("GET", `/api/v2/tasks/${P.id}/comments?per_page=50`, undefined, sync);
|
||||
e.found = Boolean(list(back.json).find((x) => x.id === c.json.id));
|
||||
if (!e.found) rec.problems.push(`comment ${P.id} read-back`);
|
||||
if (P.done) {
|
||||
const full = await api("GET", `/api/v1/tasks/${P.id}`, undefined, key);
|
||||
const d = await api("POST", `/api/v1/tasks/${P.id}`, { ...full.json, done: true }, key);
|
||||
const db = await api("GET", `/api/v2/tasks/${P.id}`, undefined, sync);
|
||||
rec.done.push({ task: P.id, status: d.status, readBack: db.status, done: db.json?.done, inverse: `POST /api/v1/tasks/${P.id} done false (sage key)` });
|
||||
if (d.status >= 300 || db.json?.done !== true) rec.problems.push(`done ${P.id}: ${d.status}`);
|
||||
}
|
||||
save();
|
||||
}
|
||||
save();
|
||||
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
||||
if (rec.problems.length) process.exit(2);
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"at": "2026-10-20T11:12:55.633Z",
|
||||
"project": 19,
|
||||
"landing": "8fc0ee216",
|
||||
"created": {
|
||||
"status": 201,
|
||||
"id": 411,
|
||||
"readBack": 200,
|
||||
"done": false,
|
||||
"inverse": "mark done: POST /api/v1/tasks/411 done true (sage key); delete is Jason's or ops-01's"
|
||||
},
|
||||
"comments": [
|
||||
{
|
||||
"task": 409,
|
||||
"status": 201,
|
||||
"id": 361,
|
||||
"found": true
|
||||
},
|
||||
{
|
||||
"task": 410,
|
||||
"status": 201,
|
||||
"id": 362,
|
||||
"found": true
|
||||
},
|
||||
{
|
||||
"task": 385,
|
||||
"status": 201,
|
||||
"id": 363,
|
||||
"found": true
|
||||
}
|
||||
],
|
||||
"done": [
|
||||
{
|
||||
"task": 409,
|
||||
"status": 200,
|
||||
"readBack": 200,
|
||||
"done": true,
|
||||
"inverse": "POST /api/v1/tasks/409 done false (sage key)"
|
||||
},
|
||||
{
|
||||
"task": 410,
|
||||
"status": 200,
|
||||
"readBack": 200,
|
||||
"done": true,
|
||||
"inverse": "POST /api/v1/tasks/410 done false (sage key)"
|
||||
}
|
||||
],
|
||||
"problems": []
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"at": "2026-10-20T11:13:48.293Z",
|
||||
"tasks": [
|
||||
{
|
||||
"task": 409,
|
||||
"before": {
|
||||
"title": "stack-release test gap: no test kills a carried proof pair skipping proof_checks (row 187 M8)",
|
||||
"done": true,
|
||||
"project_id": 19,
|
||||
"index": 242,
|
||||
"priority": 0,
|
||||
"due_date": "0001-01-01T00:00:00Z",
|
||||
"bucket_id": 0,
|
||||
"descLen": 0
|
||||
},
|
||||
"status": 200,
|
||||
"readBack": 200,
|
||||
"after": {
|
||||
"title": "stack-release test gap: no test kills a carried proof pair skipping proof_checks (row 187 M8)",
|
||||
"done": true,
|
||||
"project_id": 19,
|
||||
"index": 242,
|
||||
"priority": 0,
|
||||
"due_date": "0001-01-01T00:00:00Z",
|
||||
"bucket_id": 0,
|
||||
"descLen": 765
|
||||
},
|
||||
"descriptionSame": true
|
||||
},
|
||||
{
|
||||
"task": 410,
|
||||
"before": {
|
||||
"title": "stack-release test gap: no test kills dropping the repository case from the proof file check (row 187 M9)",
|
||||
"done": true,
|
||||
"project_id": 19,
|
||||
"index": 243,
|
||||
"priority": 0,
|
||||
"due_date": "0001-01-01T00:00:00Z",
|
||||
"bucket_id": 0,
|
||||
"descLen": 0
|
||||
},
|
||||
"status": 200,
|
||||
"readBack": 200,
|
||||
"after": {
|
||||
"title": "stack-release test gap: no test kills dropping the repository case from the proof file check (row 187 M9)",
|
||||
"done": true,
|
||||
"project_id": 19,
|
||||
"index": 243,
|
||||
"priority": 0,
|
||||
"due_date": "0001-01-01T00:00:00Z",
|
||||
"bucket_id": 0,
|
||||
"descLen": 622
|
||||
},
|
||||
"descriptionSame": true
|
||||
}
|
||||
],
|
||||
"problems": []
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Sage, 2026-10-20: file-r188-landed.mjs marked P19 409 and 410 done with a POST of { done: true } alone, and
|
||||
// Vikunja blanked both descriptions (close-404.mjs sends the whole v1 task; this script missed that). This puts
|
||||
// back the descriptions file-r187-landed.mjs created (landing 9a71e4e80), sending the v1 task as GET returns it
|
||||
// with only the description changed. Refuses unless the current description is empty. Sage's own key (bot user
|
||||
// 13); the sync key reads back. Prints no secret.
|
||||
// Usage: node restore-409-410-desc.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON
|
||||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
|
||||
if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only");
|
||||
if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing");
|
||||
const SIG = "Created by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
|
||||
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
const ps = (sig, xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(sig)}</p>`;
|
||||
const SRC = "Filbert's row 187 r1 approve (Gitea #1685), landing 9a71e4e80.";
|
||||
const TAIL = "Owner Sage. No queue row until scheduled (the mos-0955 form). Not live-affecting.";
|
||||
const TASKS = [
|
||||
{ id: 409, title: "stack-release test gap: no test kills a carried proof pair skipping proof_checks (row 187 M8)", description: ps(SIG, [
|
||||
`${SRC} Filbert's mutant M8 makes a carried pair (the pointer's pair, no pair flags given) skip proof_checks. It survived T53 12/12. BUILD.md says the checks run on a carried pair, so a file moved or chmod-ed since the pair went in refuses before the stop, but no T53 case changes the file between the activate that set the pair and the run that carries it.`,
|
||||
"The fix is a T53 case that sets a pair, chmods the file to 644 or moves it, then plans and runs an activate or rollback that carries it and expects proof-file-invalid with no stop.",
|
||||
TAIL,
|
||||
]) },
|
||||
{ id: 410, title: "stack-release test gap: no test kills dropping the repository case from the proof file check (row 187 M9)", description: ps(SIG, [
|
||||
`${SRC} Filbert's mutant M9 drops the repository case from the realpath check in proof_file_problem. It survived T53 12/12. T53 refuses a proof file inside the copies but has no case for one inside the repository, which webui-service.sh render refuses after the stop.`,
|
||||
"The fix is a T53 case with a mode-600 proof file under the test's repository root that expects proof-file-invalid with no stop.",
|
||||
TAIL,
|
||||
]) },
|
||||
];
|
||||
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
|
||||
async function api(method, path, body, auth) {
|
||||
const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" });
|
||||
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
|
||||
return { status: r.status, json };
|
||||
}
|
||||
const pick = (j) => ({ title: j?.title, done: j?.done, project_id: j?.project_id, index: j?.index, priority: j?.priority, due_date: j?.due_date, bucket_id: j?.bucket_id, descLen: (j?.description ?? "").length });
|
||||
const rec = { at: new Date().toISOString(), tasks: [], problems: [] };
|
||||
const save = () => { const o = JSON.stringify(rec, null, 1) + "\n"; if (o.includes(key) || o.includes(sync)) throw new Error("secret in record"); writeFileSync(OUT, o); };
|
||||
for (const T of TASKS) {
|
||||
const full = await api("GET", `/api/v1/tasks/${T.id}`, undefined, key);
|
||||
if (full.status !== 200 || full.json?.title !== T.title || full.json?.project_id !== 19 || (full.json?.description ?? "") !== "") throw new Error(`task ${T.id}: ${full.status}, title, project or non-empty description check failed; nothing written`);
|
||||
}
|
||||
writeFileSync(OUT, "", { flag: "wx" });
|
||||
for (const T of TASKS) {
|
||||
const full = await api("GET", `/api/v1/tasks/${T.id}`, undefined, key);
|
||||
const e = { task: T.id, before: pick(full.json) };
|
||||
const u = await api("POST", `/api/v1/tasks/${T.id}`, { ...full.json, description: T.description }, key);
|
||||
const back = await api("GET", `/api/v2/tasks/${T.id}`, undefined, sync);
|
||||
Object.assign(e, { status: u.status, readBack: back.status, after: pick(back.json), descriptionSame: back.json?.description === T.description });
|
||||
if (u.status >= 300 || back.status !== 200 || !e.descriptionSame || back.json?.title !== T.title || back.json?.done !== true) rec.problems.push(`task ${T.id}: update or read-back differs`);
|
||||
rec.tasks.push(e); save();
|
||||
}
|
||||
save();
|
||||
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
||||
if (rec.problems.length) process.exit(2);
|
||||
@@ -1346,3 +1346,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-20T10:29:37Z | Sage (Opus 5.5, 1ef1e4f8) | Part B window file for Mos's A2 check (mos-0928, P19 385) | drafted agents/sage/work/window-plan/repin-partB-proof.md (blob 77843e62747e) on a843ae284: B3 and B-R1 are live-copy activates of 490e2771d with the pair and --no-proof, no re-pin needed (490e2771d..a843ae284 changes only the tool and its test); Q1 raised: J2 first conflicts with the uid checks in stack-release.sh:777, webui-service.sh:201 and serve.mjs:228; plan b05eb85e unchanged; P19 409/410 gate the run (mos-0956); runs nothing
|
||||
2026-10-20T10:36:45Z | Sage (Opus 5.5, 1ef1e4f8) | Part B window file draft 2 (mos-0957 F1, F2) and row 188 brief (#1686, P19 409/410) | draft 2 blob 55da51c0462b (148a9503d); Mos A2 recheck passed (mos-0958, ruling: B6 (1) applies to whichever chief B0's launches list shows, id recorded at B0); brief 240f21650, row 188 added and briefed (revs 2207-2208, 525990ecb), pushed 742e98976..525990ecb; Darkwing launched (sage-1278); Vikunja comments 358 (P19 385), 359 (409), 360 (410); nothing live changed
|
||||
2026-10-20T11:08:34Z | Filbert (Opus 5.5, 048f3c63) | row 188 r1 review (#1686, manifest 86aa549bad75) | approve, comment 29433, rev 2212; stack-release 151/151 on my slot run; M8-M10 and my M11 killed by T54; N1 REPO logical-path gap in stack-release.sh and webui-service.sh (mos-0959, filed after Part B)
|
||||
2026-10-20T11:14:11Z | Sage (Opus 5.5, 1ef1e4f8) | row 188 landing (#1686, P19 409/410), N1 to P19 | landed 8fc0ee216 (patch-id c9a56b167c07, stack-release.sh blob 2586a2a37cd2 unchanged), queue 31e328958 revs 2209-2214, pushed b9c1c9fe2..31e328958 with Filbert's 23a609c5c; #1686 comment 29435, closed; P19 411 (N1, after Part B per mos-0959), comments 361-363, 409/410 done; my done update blanked 409/410 descriptions, restored 11:13Z (restore-409-410-desc.mjs), script fixed
|
||||
|
||||
Reference in New Issue
Block a user