vikunja: row 62 gate-read probe, cross-project task read works for the sync key (sage)

GETs only, sync key 9. Project list 200, total 30 (31 with archived #15).
#18, #20 and #21 have parent 19, none archived. GET /tasks with
filter project in 19, 18, 20, 21: 200, 42 tasks across all four. The
per-project open reads give 18, 4, 3 and 1. A follow-up row moves a tick
to the single read after row 62 is done.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 14:14:43 -05:00
co-authored by Claude Opus 5.5
parent 9a51b34d68
commit ef69a2c4ac
2 changed files with 109 additions and 0 deletions
@@ -0,0 +1,76 @@
{
"at": "2026-10-11T19:14:20.231Z",
"key": "sync (key 9)",
"reads": [
{
"path": "/projects?per_page=50&page=1",
"status": 200,
"count": 30,
"total": 30,
"code": null
},
{
"path": "/tasks?filter=project%20in%2019%2C%2018%2C%2020%2C%2021&sort_by=id&order_by=asc&per_page=50&page=1",
"status": 200,
"count": 42,
"total": 42,
"code": null,
"projects": [
18,
19,
20,
21
]
},
{
"path": "/projects/19/tasks?filter=done%20%3D%20false&sort_by=id&order_by=asc&per_page=50&page=1",
"status": 200,
"count": 18,
"total": 18,
"code": null
},
{
"path": "/projects/18/tasks?filter=done%20%3D%20false&sort_by=id&order_by=asc&per_page=50&page=1",
"status": 200,
"count": 4,
"total": 4,
"code": null
},
{
"path": "/projects/20/tasks?filter=done%20%3D%20false&sort_by=id&order_by=asc&per_page=50&page=1",
"status": 200,
"count": 3,
"total": 3,
"code": null
},
{
"path": "/projects/21/tasks?filter=done%20%3D%20false&sort_by=id&order_by=asc&per_page=50&page=1",
"status": 200,
"count": 1,
"total": 1,
"code": null
}
],
"tree": [
{
"id": 19,
"parent": 33,
"archived": false
},
{
"id": 18,
"parent": 19,
"archived": false
},
{
"id": 20,
"parent": 19,
"archived": false
},
{
"id": 21,
"parent": 19,
"archived": false
}
]
}
@@ -0,0 +1,33 @@
// Sage, 2026-10-11: read-only, row 62 probes.md "Gate read". With the sync key: the project list
// (status, page count, total, and parent and archived for #19 and its children 18, 20, 21), one
// cross-project task read, and the four per-project open reads for comparison. GETs only; records
// no titles; refuses to overwrite OUT; prints no secret.
// Usage: node probe-62.mjs https://tasks.woltje.com SYNC_TOKEN_FILE OUT_JSON
import { readFileSync, writeFileSync, existsSync } from "node:fs";
const [ORIGIN, KEY, 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 BASE = ORIGIN + "/api/v2", TREE = [19, 18, 20, 21];
const key = readFileSync(KEY, "utf8").trim();
const get = async (path) => {
const r = await fetch(BASE + path, { headers: { Authorization: `Bearer ${key}` }, redirect: "error" });
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
const items = json?.items ?? (Array.isArray(json) ? json : null);
return { path, status: r.status, count: items ? items.length : null, total: json?.total ?? null, code: r.status === 200 ? null : json?.code ?? null, items };
};
const strip = ({ items, ...rest }) => rest;
const rec = { at: new Date().toISOString(), key: "sync (key 9)", reads: [] };
const list = await get("/projects?per_page=50&page=1");
rec.reads.push(strip(list));
rec.tree = TREE.map((id) => {
const p = (list.items ?? []).find((x) => x.id === id);
return p ? { id, parent: p.parent_project_id, archived: p.is_archived } : { id, listed: false };
});
const filter = encodeURIComponent(`project in ${TREE.join(", ")}`);
const cross = await get(`/tasks?filter=${filter}&sort_by=id&order_by=asc&per_page=50&page=1`);
rec.reads.push({ ...strip(cross), projects: cross.items ? [...new Set(cross.items.map((t) => t.project_id))].sort((a, b) => a - b) : null });
for (const id of TREE) rec.reads.push(strip(await get(`/projects/${id}/tasks?filter=${encodeURIComponent("done = false")}&sort_by=id&order_by=asc&per_page=50&page=1`)));
const out = JSON.stringify(rec, null, 1) + "\n";
if (out.includes(key)) throw new Error("secret in output; not written");
writeFileSync(OUT, out, { flag: "wx" });
process.stdout.write(out);