decisions, vikunja-setup, S7: 114 (#162 round 1, Q113-Q119), 115 (order around I1, S7 restart); P19 #219-#222; S7 run log

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-12 13:31:07 -05:00
co-authored by Claude Opus 5.5
parent 4f8ac1e7eb
commit 681de77811
4 changed files with 249 additions and 0 deletions
@@ -0,0 +1,44 @@
# Slice 1 S7 run log (row 42, #1524)
Append-only. Brief: `docs/plans/2026-10-04_slice-1.md`, "Slice 1 S7:
acceptance, five pieces in a row". Gate: Jason, from the five trails.
## 2026-10-12T18:30Z start (lead decisions 94, 114, 115)
Row 42 moved briefed to in-progress at queue rev 673 (op
sage-42-start-q118), on Mos's sage-0092 and Jason's Q118 A.
What counts as a piece: one of the first five Mosaic Stack requests Jason
sends to the PM through the Console or `mosaic talk pm` after the Q119
confirm at 2026-10-12T18:27Z. A request sent to T3 Mos doesn't count. Mos
gives Jason the command and logs the slip on Vikunja #205 (decision 114).
What each piece needs to pass (the brief): carried to a closed, reviewed
task; its trail in `mosaic trail` and the Console; only gated decisions
reached Jason; the bus event counts, which are the first measures
(REQ-EVT-1).
How I track it. Agent seats can't read the trail or the bus (REQ-DEC-3;
`scripts/mosaic trail` from this seat refused before 2026-10-12T18:30Z, "this runs only
from a human shell"). So I track each piece through what I can read: the
P19 task the PM creates (sync key), and any queue row and Gitea issue the
piece makes. For each piece this log records the trail subject (the task
ref `vikunja:19/<id>`, or the message id if Jason gives it), so Jason can
read the trail and count the events in the Console `#/trail` or with
`mosaic trail <subject>` at his gate. Nobody builds an agent read path
around that (decision 115).
Known gaps on day one:
- The Console doesn't show the reply to a web send yet. That's row 81
(Dewey, in progress); `mosaic talk pm` shows replies until it lands.
- Mos files the friction they see on #205 with the dogfood label.
## Pieces
| # | Sent (UTC) | Channel | Request | P19 task | Trail subject | Decisions to Jason | Closed and reviewed | Event counts (Jason) |
|---|---|---|---|---|---|---|---|---|
| 1 | | | | | | | | |
| 2 | | | | | | | | |
| 3 | | | | | | | | |
| 4 | | | | | | | | |
| 5 | | | | | | | | |
@@ -0,0 +1,39 @@
{
"at": "2026-10-12T18:30:17.774Z",
"project": 19,
"created": [
{
"title": "Q115: system branch floor enforced by the gate (decision 114)",
"id": 219,
"index": 70,
"inverse": "mark done: POST /api/v1/tasks/219 done true; delete is Jason's or ops-01's",
"readBack": 200,
"createdBy": "bot-mosaic-stack-sage"
},
{
"title": "Q114: mosaic settings set for TARGET settings, gated and trailed (decision 114)",
"id": 220,
"index": 71,
"inverse": "mark done: POST /api/v1/tasks/220 done true; delete is Jason's or ops-01's",
"readBack": 200,
"createdBy": "bot-mosaic-stack-sage"
},
{
"title": "Q116 ops: deploy CLIProxyAPI read-only behind Authentik on mosaic.woltje.com (ops-01 via Mos)",
"id": 221,
"index": 72,
"inverse": "mark done: POST /api/v1/tasks/221 done true; delete is Jason's or ops-01's",
"readBack": 200,
"createdBy": "bot-mosaic-stack-sage"
},
{
"title": "Q116 stack: Console shows CLIProxyAPI usage per account, read-only (decision 114)",
"id": 222,
"index": 73,
"inverse": "mark done: POST /api/v1/tasks/222 done true; delete is Jason's or ops-01's",
"readBack": 200,
"createdBy": "bot-mosaic-stack-sage"
}
],
"problems": []
}
@@ -0,0 +1,93 @@
// Sage, 2026-10-12: Mos sage-0092 and lead decisions 114-115. Files one task in project 19 for each piece of
// work from #162 round 1 (Q114, Q115, Q116's ops and stack parts), in next order around I1. Sage's own key
// (bot user 13). Refuses if any title already exists. The sync key reads each task back. No labels, no
// assignees, no bucket moves. Rollback is marking a task done; deletion is Jason's or ops-01's. Prints no secret.
// Usage: node file-162-round1.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 PROJECT = 19;
const SIG = "Created by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const p = (...xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(SIG)}</p>`;
const ORDER = "Order (lead decision 115): Sage writes the I1 brief (#153) first, then Q115's brief, then Q114's. Q115 is built before the I1 images; Q114 follows Q115 and can run beside D1-D2 and I1. Q116's ops part starts now with ops-01; its stack part follows the ops part and row 77.";
const TASKS = [
{
title: "Q115: system branch floor enforced by the gate (decision 114)",
description: p(
"Jason's Q115 A, confirmed by Q119. A system branch floor that the gate enforces in code. Agents never push to a protected branch. A protected branch changes only by a PR with green CI and review, which is how R28 merges continue. A repo can tighten the floor, never loosen it. A repo that names no protected branch has its default branch protected.",
"Today git.push.protected and git.merge.protected are always gated and reach Jason as decisions (packages/business/src/vocabulary.mjs:42-43). git.workingBranch and git.protectedBranches exist only at the project layer and nothing outside vars.mjs reads them. The brief settles how the floor turns those into refusals and checks, and adds the system-layer key.",
"Next step: Sage writes the brief and queue row, after the I1 brief (#153). Second in this set.",
ORDER,
),
},
{
title: "Q114: mosaic settings set for TARGET settings, gated and trailed (decision 114)",
description: p(
"Jason's Q114 A, confirmed by Q119. A CLI write path, mosaic settings set, for repo locations, task locations, repo settings and project settings (the gap table in #206). Every write goes through the gate and into the trail. Browser editing waits for I4.",
"If the design lets an agent loosen a setting Jason set, it goes back to Jason through Mos before it ships. Branch settings follow Q115's floor, so this comes after Q115.",
"Next step: Sage writes the brief and queue row after Q115's. Third in this set.",
ORDER,
),
},
{
title: "Q116 ops: deploy CLIProxyAPI read-only behind Authentik on mosaic.woltje.com (ops-01 via Mos)",
description: p(
"Jason's Q116 A, confirmed by Q119. ops-01's part, routed through Mos: run CLIProxyAPI with its WebUI at a path on the existing mosaic.woltje.com host, behind Authentik, admitting jason.woltje only (as the Console, Q108). Jason's A approves this exposure. A new DNS name or subdomain goes back to Jason first.",
"The management key goes in a slot that ops-01 wires by name. No agent reads it and nothing prints it. Q105's file-secret token is unchanged. No session traffic goes through CLIProxyAPI until Jason rules on I1's egress routing design.",
"Can start now. First in this set.",
ORDER,
),
},
{
title: "Q116 stack: Console shows CLIProxyAPI usage per account, read-only (decision 114)",
description: p(
"Jason's Q116 A, confirmed by Q119. The stack part: the Console shows usage per account from CLIProxyAPI, read-only. No session traffic through it until Jason rules on I1's egress routing design. No agent reads the management key; the stack names it by slot only.",
"Follows the ops task (deployment behind Authentik) and queue row 77, since it is a Console landing (decision 109). Sage writes the brief and queue row then. Fourth in this set.",
ORDER,
),
},
];
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, created: [], 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 clash = (await existing()).filter((t) => TASKS.some((x) => x.title === t.title));
if (clash.length) throw new Error(`exists: ${clash.map((t) => `#${t.id} ${t.title}`).join("; ")}; refusing`);
writeFileSync(OUT, "", { flag: "wx" });
for (const t of TASKS) {
rec.created.push({ title: t.title });
save();
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: t.title, description: t.description }, key);
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) {
rec.problems.push(`create ${t.title}: ${r.status} ${r.json?.code ?? ""} ${r.json?.message ?? ""}`);
break;
}
Object.assign(rec.created.at(-1), { id: r.json.id, index: r.json.index, inverse: `mark done: POST /api/v1/tasks/${r.json.id} done true; delete is Jason's or ops-01's` });
const back = await api("GET", `/api/v2/tasks/${r.json.id}`, undefined, sync);
Object.assign(rec.created.at(-1), { readBack: back.status, createdBy: back.json?.created_by?.username });
if (back.status !== 200 || back.json?.title !== t.title || back.json?.project_id !== PROJECT || back.json?.description !== t.description) rec.problems.push(`${t.title} read-back differs`);
}
save();
process.stdout.write(JSON.stringify(rec.created.map(({ id, title, readBack, createdBy }) => ({ id, title, readBack, createdBy })), null, 1) + "\n");
if (rec.problems.length) { process.stdout.write(JSON.stringify(rec.problems) + "\n"); process.exit(2); }
+73
View File
@@ -2547,3 +2547,76 @@ which stay with him. Each item names who decided it and what happened.
verb-level writer `packages/tasks` enforces (pm by definition, the
assigned role, or any role), and whether a decision is needed,
which the test reads from `roles/*.json` instead of copying.
114. **#162 baseline round 1: Q113-Q118, confirmed by Q119
(2026-10-12).** Jason, relayed by Mos (sage-0092), verbatim: "Q113:
C" "Q114: A" "Q115: A" "Q116: A" "Q117: A" "Q118: A", then "Q119:
yes", confirming Mos's readings. The full text is in
jason-master-restart DECISIONS.md, entries 18:23Z and 18:27Z. This
lifts sage-0091's hold on the six #162 items.
- Q113, where instructions live: stack roles take instructions only
from role files in this repository, changed only by reviewed
commits. The M1 file (row 84) is the first. `~/.mosaic` prose stays
frozen, and a seat's prose retires when that seat moves into a
stack role. After I4 the Console changes a role file only by
opening a PR, never by writing the file.
- Q114, settings: build `mosaic settings set` now for repo
locations, task locations, repo settings and project settings.
Every write goes through the gate and into the trail. Browser
editing waits for I4. A design that lets an agent loosen a
setting Jason set goes back to Jason through Mos before it ships.
- Q115, the branch floor: a system floor that the gate enforces in
code. Agents never push to a protected branch. A protected branch
changes only by a PR with green CI and review, which is how R28
merges continue. A repo can tighten the floor, never loosen it.
A repo that names no protected branch has its default branch
protected. Today `git.push.protected` and `git.merge.protected`
are always gated, so they reach Jason as decisions
(`packages/business/src/vocabulary.mjs:42-43`), and
`git.workingBranch` and `git.protectedBranches` exist only at the
project layer, read by nothing outside `vars.mjs`. The brief
settles how the floor turns those into refusals and checks.
- Q116, CLIProxyAPI: it comes into the stack read-only. Its WebUI
serves from a path on mosaic.woltje.com behind Authentik, admitting
jason.woltje only, and Jason's A approves that exposure. A new DNS
name or subdomain goes back to him. The Console shows usage per
account. No session traffic goes through it until Jason rules on
I1's egress routing design. No agent reads the management key;
ops-01 wires it by slot name. Q105's file-secret token is
unchanged.
- Q117, T3 seats: no T3 bridge. Seats move into stack roles one at a
time, Mos first through M1-M3. About 10-17 is a latest-by marker
only (decision 111).
- Q118, Jason's channel: Jason sends Mosaic Stack requests through
the Console or `mosaic talk pm`. Agent seats can't run `mosaic
talk` (REQ-DEC-3; Mos measured the refusal at 18:28Z), so when
Jason sends one to T3 Mos, Mos gives him the command and logs the
slip on Vikunja #205, and doesn't relay it. The day-one gap is row
81, replies to a web send (Dewey, in progress).
115. **Where the #162 work goes around I1, and S7 restarts (2026-10-12).**
Mos asked (sage-0092) for P19 tasks in next order around I1. Q97's
order stands (S7, M1-M2, D1-D2 alongside I1, I2-I6), and the new
work slots in by dependency.
- My writing order: the I1 brief (#153) first, since I1 gates I2-I6
and the Q116 routing ruling. Then the Q115 brief, then Q114's.
- Q115, the branch floor, is built before the I1 images. It is a
gate rule that contained agents rely on, and Q114 needs it to know
what "loosen" means for branch settings.
- Q114, `mosaic settings set`, follows Q115. It touches the CLI and
the business layers, not the Console or the images, so it can run
beside D1-D2 and I1 with a different owner.
- Q116 has two parts. The ops part (deploy CLIProxyAPI, the path on
mosaic.woltje.com behind Authentik, the key in a slot) is
ops-01's through Mos and can start now. The stack part, a
read-only usage view in the Console, follows the ops part and row
77, since it is a Console landing (decision 109). Session routing
through it waits for Jason's I1 ruling.
- S7 (row 42) restarts now on the Q118 channel. Its five pieces are
the first five Mosaic Stack requests Jason sends to the PM through
the Console or `mosaic talk pm` after the Q119 confirm at 18:27Z.
A request sent to T3 doesn't count; Mos logs it on #205. Agent
seats can't read the trail or the bus (REQ-DEC-3), so the run log
names each piece's trail subject and Vikunja task, and Jason reads
the trails and the event counts in the Console or `mosaic trail`
at his gate. Nobody builds an agent read path around that.
- Filed in P19 by Sage's key: #219 Q115, #220 Q114, #221 Q116 ops
(ops-01 through Mos), #222 Q116 stack.