From 68ce7f3548beb390649cb4ad66f03e23f1bd8449 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Wed, 14 Oct 2026 12:49:41 -0500 Subject: [PATCH] docs: row 116 brief round 2, land-work reads the bus database read-only (#1610) Darkwing asked how land-work reads the decision. The Console's path is human-only (human-cli refuses agent environments, #225), so the conductor can't use it. land-work now opens bus.sqlite readOnly, runs SELECTs only and refuses bus-schema on a digest mismatch. The brief records what f71c73c9 holds: task_ref is null, the raise's target and approvalChoice give the branch and the merge option, and the work ref comes from the question. agents/sage/work/row116/live-rows.json holds the live rows for the replay fixtures. Co-Authored-By: Claude Opus 5.5 --- agents/sage/work/row116/live-rows.json | 414 ++++++++++++++++++ ...2026-10-14_merge-path-for-approved-work.md | 54 ++- 2 files changed, 464 insertions(+), 4 deletions(-) create mode 100644 agents/sage/work/row116/live-rows.json diff --git a/agents/sage/work/row116/live-rows.json b/agents/sage/work/row116/live-rows.json new file mode 100644 index 000000000..d5fd85842 --- /dev/null +++ b/agents/sage/work/row116/live-rows.json @@ -0,0 +1,414 @@ +{ + "schema": { + "decisions": [ + "seq", + "id", + "at", + "business", + "project", + "raised_by_role", + "raised_by_run", + "class", + "action", + "route_to", + "question", + "options", + "recommendation", + "task_ref", + "requirement_ref", + "blocking", + "supersedes" + ], + "decision_events": [ + "seq", + "decision", + "at", + "op", + "by", + "choice", + "note", + "via" + ], + "events": [ + "seq", + "id", + "at", + "business", + "kind", + "actor_role", + "actor_run", + "subject", + "corrects", + "body" + ], + "messages": [ + "seq", + "id", + "at", + "business", + "from_role", + "from_run", + "to_role", + "class", + "in_reply_to", + "decision", + "corrects", + "body" + ] + }, + "decision": [ + { + "seq": 2, + "id": "f71c73c9-c759-4aca-98d9-3e22e8c42b36", + "at": "2026-10-14T16:33:48.544Z", + "business": "mosaic-stack", + "project": null, + "raised_by_role": "pm", + "raised_by_run": "ree689d5b17bf", + "class": "gated", + "action": "git.merge.protected", + "route_to": "human", + "question": "Can I squash-merge work/vikunja-19-265 into refactor? It's PRD v1.1, which you approved in decision 03130d08. Request 6870b3ca. The reviewer approved commit 8ae5a149 in verdict d3f97f9d, and I've closed task vikunja:19/265. The branch changes only four files: docs/prd/mosaic-stack.md, packages/tasks/requirements.json and two requirements tests. Its history also carries 010d30cbe, task 19/229's parseRequirementId commit, which never landed on refactor, plus a commit that reverts it. The coder couldn't drop that commit with a rebase, because push_working refuses non-fast-forward pushes. A squash merge keeps refactor's history clean and avoids a merge-order trap: unsquashed, the revert would delete src/parse-requirement-id.mjs if 19/229's branch had landed first. The six new ids (REQ-ORG-1, REQ-ORG-2, REQ-USER-1, REQ-THREAD-1, REQ-TPL-1, REQ-FED-1) don't reach list_requirements until this merges, so the Organization tasks wait on it.", + "options": "[{\"key\":\"yes\",\"text\":\"Squash-merge work/vikunja-19-265 into refactor\"},{\"key\":\"no\",\"text\":\"Don't merge yet\"}]", + "recommendation": "yes", + "task_ref": null, + "requirement_ref": null, + "blocking": 0, + "supersedes": null + } + ], + "decision_events": [ + { + "seq": 2, + "decision": "f71c73c9-c759-4aca-98d9-3e22e8c42b36", + "at": "2026-10-14T16:35:32.002Z", + "op": "resolved", + "by": "jason", + "choice": "yes", + "note": null, + "via": "cli" + } + ], + "raise_allowed": [ + { + "seq": 386, + "id": "6500ce49-ee8d-4cda-bda0-c8de4dd9397f", + "kind": "action.allowed", + "subject": "f71c73c9-c759-4aca-98d9-3e22e8c42b36", + "actor_role": "pm", + "at": "2026-10-14T16:33:48.545Z", + "body": "{\"decision\":\"f71c73c9-c759-4aca-98d9-3e22e8c42b36\",\"action\":\"git.merge.protected\",\"operation\":\"decision.raise\",\"task_ref\":null,\"target\":\"refactor\",\"approvalChoice\":\"yes\",\"holderRun\":null}" + }, + { + "seq": 387, + "id": "130e1d4d-e9db-4fa2-9a27-d318fff2aae2", + "kind": "human.input", + "subject": "f71c73c9-c759-4aca-98d9-3e22e8c42b36", + "actor_role": null, + "at": "2026-10-14T16:35:32.003Z", + "body": "{\"kind\":\"answer\",\"decision\":\"f71c73c9-c759-4aca-98d9-3e22e8c42b36\",\"op\":\"resolved\",\"choice\":\"yes\"}" + } + ], + "verdict_events": [ + { + "seq": 353, + "id": "99b58b9e-2d2e-4a3a-9229-138ed4305758", + "kind": "task.created", + "subject": "vikunja:19/265", + "actor_role": "pm", + "at": "2026-10-14T16:28:58.238Z", + "body": "{\"request\":\"6870b3ca-83aa-4b55-857f-31a2a2910316\",\"requirement\":\"REQ-ROLE-3\"}" + }, + { + "seq": 354, + "id": "10297f1e-f6ca-4cee-b423-10ea1335df20", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "pm", + "at": "2026-10-14T16:29:02.823Z", + "body": "{\"action\":\"task.assign\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 355, + "id": "8f4a9c74-dc54-4ff2-aceb-3d7a66d72346", + "kind": "task.assigned", + "subject": "vikunja:19/265", + "actor_role": "pm", + "at": "2026-10-14T16:29:03.047Z", + "body": "{\"role\":\"coder\",\"previous\":[]}" + }, + { + "seq": 360, + "id": "43e6ff27-0182-41a1-a668-cf5f7adbe104", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "coder", + "at": "2026-10-14T16:30:26.392Z", + "body": "{\"project\":\"stack\",\"branch\":\"work/prd-v1-1\",\"ref\":\"work/vikunja-19-265\",\"commit\":\"1b3018313bfddc5535f9fe4d994a7ed97d9e5597\",\"previous\":null,\"action\":\"git.push.working\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 361, + "id": "e4de55f5-3d98-4a4b-9a1f-b2758f2b8ec8", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "coder", + "at": "2026-10-14T16:30:29.471Z", + "body": "{\"action\":\"review.request\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 362, + "id": "9b57eb2d-a6ca-4803-943b-71122fad6f6d", + "kind": "review.requested", + "subject": "vikunja:19/265", + "actor_role": "coder", + "at": "2026-10-14T16:30:29.472Z", + "body": "{\"task_ref\":\"vikunja:19/265\",\"project\":\"stack\",\"ref\":\"work/vikunja-19-265\",\"commit\":\"1b3018313bfddc5535f9fe4d994a7ed97d9e5597\",\"to\":\"reviewer\",\"message\":\"f0488786-0a8e-4566-8430-793d428f221f\",\"held\":null}" + }, + { + "seq": 367, + "id": "c8874c5d-2ce0-47ee-9a7b-05fcd9a9bbdf", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "reviewer", + "at": "2026-10-14T16:32:21.989Z", + "body": "{\"action\":\"review.verdict\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 368, + "id": "0a169903-cd6e-42d8-83aa-dccbeb7558c1", + "kind": "review.verdict", + "subject": "vikunja:19/265", + "actor_role": "reviewer", + "at": "2026-10-14T16:32:21.990Z", + "body": "{\"task_ref\":\"vikunja:19/265\",\"request\":\"9b57eb2d-a6ca-4803-943b-71122fad6f6d\",\"commit\":\"1b3018313bfddc5535f9fe4d994a7ed97d9e5597\",\"verdict\":\"changes\",\"message\":\"864dd14a-1508-46b6-813c-59c559f50e3e\"}" + }, + { + "seq": 370, + "id": "e6c95fa9-3b77-4cc3-a58f-8cacdd9fcbdc", + "kind": "action.refused", + "subject": "vikunja:19/265", + "actor_role": "coder", + "at": "2026-10-14T16:32:30.194Z", + "body": "{\"code\":\"non-fast-forward\"}" + }, + { + "seq": 371, + "id": "34ab6b62-1a75-4994-b999-ee1f6f8dd6f7", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "coder", + "at": "2026-10-14T16:32:34.903Z", + "body": "{\"project\":\"stack\",\"branch\":\"work/prd-v1-1\",\"ref\":\"work/vikunja-19-265\",\"commit\":\"8ae5a149152b35a6d8370924775725e5b36c7b92\",\"previous\":\"1b3018313bfddc5535f9fe4d994a7ed97d9e5597\",\"action\":\"git.push.working\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 372, + "id": "fdb418cb-8a9a-4567-a748-808cdc67f099", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "coder", + "at": "2026-10-14T16:32:37.405Z", + "body": "{\"action\":\"review.request\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 373, + "id": "a3a3c863-a4dd-4c5f-be15-e521a77c79ee", + "kind": "review.requested", + "subject": "vikunja:19/265", + "actor_role": "coder", + "at": "2026-10-14T16:32:37.406Z", + "body": "{\"task_ref\":\"vikunja:19/265\",\"project\":\"stack\",\"ref\":\"work/vikunja-19-265\",\"commit\":\"8ae5a149152b35a6d8370924775725e5b36c7b92\",\"to\":\"reviewer\",\"message\":\"02ed7d6a-874c-4621-a639-cb7ed23999df\",\"held\":\"r5f11f56cb1a2\"}" + }, + { + "seq": 378, + "id": "73387835-52fe-474c-8817-6d632845cab3", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "reviewer", + "at": "2026-10-14T16:33:11.504Z", + "body": "{\"action\":\"review.verdict\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 379, + "id": "d3f97f9d-f5c6-4f81-924d-d3c0698692f4", + "kind": "review.verdict", + "subject": "vikunja:19/265", + "actor_role": "reviewer", + "at": "2026-10-14T16:33:11.505Z", + "body": "{\"task_ref\":\"vikunja:19/265\",\"request\":\"a3a3c863-a4dd-4c5f-be15-e521a77c79ee\",\"commit\":\"8ae5a149152b35a6d8370924775725e5b36c7b92\",\"verdict\":\"approve\",\"message\":\"3d9ab722-955d-4121-980f-801c3fe91bbd\"}" + }, + { + "seq": 383, + "id": "2cb95a6a-1782-4b20-847d-6dc4b807e295", + "kind": "action.allowed", + "subject": "vikunja:19/265", + "actor_role": "pm", + "at": "2026-10-14T16:33:30.904Z", + "body": "{\"action\":\"task.close\",\"class\":\"within-role\",\"target\":\"vikunja:19/265\"}" + }, + { + "seq": 384, + "id": "b308369d-7146-4960-9ed8-f9a11bc1c4ba", + "kind": "task.closed", + "subject": "vikunja:19/265", + "actor_role": "pm", + "at": "2026-10-14T16:33:31.028Z", + "body": "{\"verdict\":\"d3f97f9d-f5c6-4f81-924d-d3c0698692f4\"}" + } + ], + "task229": [ + { + "seq": 161, + "id": "564b5b7b-a397-4dcd-be05-3e076ea9f5da", + "kind": "task.created", + "subject": "vikunja:19/229", + "actor_role": "pm", + "at": "2026-10-12T19:31:35.565Z", + "body": "{\"request\":\"a6f7b637-c3d0-4472-a0f3-1203c6bdb36f\",\"requirement\":\"REQ-QA-1\"}" + }, + { + "seq": 164, + "id": "74c0323a-9c8b-4149-9931-53ba9d9634a2", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "pm", + "at": "2026-10-12T19:31:43.669Z", + "body": "{\"action\":\"task.assign\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 165, + "id": "66411a05-882c-4064-a9de-ee80f0e8b4b5", + "kind": "task.assigned", + "subject": "vikunja:19/229", + "actor_role": "pm", + "at": "2026-10-12T19:31:43.926Z", + "body": "{\"role\":\"coder\",\"previous\":[]}" + }, + { + "seq": 179, + "id": "fff11a92-0af8-4e46-91bd-ab0714d07c3b", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-12T19:32:48.743Z", + "body": "{\"action\":\"task.update.assigned\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 180, + "id": "1e9737d4-c302-4ad6-b97c-c6298bdbd166", + "kind": "task.state", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-12T19:32:48.958Z", + "body": "{\"role\":\"coder\",\"state\":\"in-progress\",\"previous\":\"todo\",\"percent_done\":0,\"comment\":false}" + }, + { + "seq": 185, + "id": "24531218-4e71-4ba5-9c62-2f7c62fa6a0f", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-12T19:33:07.963Z", + "body": "{\"action\":\"task.update.assigned\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 186, + "id": "778c6223-c62a-40ab-9772-edc0f2b22a86", + "kind": "task.state", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-12T19:33:08.161Z", + "body": "{\"role\":\"coder\",\"state\":\"in-review\",\"previous\":\"in-progress\",\"percent_done\":0,\"comment\":true}" + }, + { + "seq": 228, + "id": "a7340eb3-6813-4b96-84f9-c75a9e370f5d", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "pm", + "at": "2026-10-12T19:51:19.826Z", + "body": "{\"action\":\"task.schedule\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 289, + "id": "3b1f6ae9-17e5-4386-92ad-79eb1485bc56", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-13T19:53:20.783Z", + "body": "{\"project\":\"stack\",\"branch\":\"work/req-qa-1-parse-requirement-id\",\"ref\":\"work/vikunja-19-229\",\"commit\":\"010d30cbe65ae54f140fc524adab5e92264d2964\",\"previous\":null,\"action\":\"git.push.working\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 290, + "id": "e8ced63c-bea9-48cd-ae09-6271588e69cd", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-13T19:53:23.613Z", + "body": "{\"action\":\"review.request\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 291, + "id": "dd2c9b0f-0888-4718-9bbf-5ceca8452308", + "kind": "review.requested", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-13T19:53:23.614Z", + "body": "{\"task_ref\":\"vikunja:19/229\",\"project\":\"stack\",\"ref\":\"work/vikunja-19-229\",\"commit\":\"010d30cbe65ae54f140fc524adab5e92264d2964\",\"to\":\"reviewer\",\"message\":\"2b06c06f-9661-4031-97ef-e823067a1197\"}" + }, + { + "seq": 297, + "id": "a620bde7-90c8-4ba3-b26e-b044fa5ceb8c", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "reviewer", + "at": "2026-10-13T19:53:56.456Z", + "body": "{\"action\":\"review.verdict\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 298, + "id": "1fbd1ed1-2226-45c7-9cca-b959662932f3", + "kind": "review.verdict", + "subject": "vikunja:19/229", + "actor_role": "reviewer", + "at": "2026-10-13T19:53:56.457Z", + "body": "{\"task_ref\":\"vikunja:19/229\",\"request\":\"dd2c9b0f-0888-4718-9bbf-5ceca8452308\",\"commit\":\"010d30cbe65ae54f140fc524adab5e92264d2964\",\"verdict\":\"approve\",\"message\":\"bc93c602-c6ee-4214-825c-a027aa7e8f16\"}" + }, + { + "seq": 300, + "id": "64e310e2-5940-4909-ab74-570b8a0d8318", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-13T19:54:01.535Z", + "body": "{\"action\":\"task.update.assigned\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 301, + "id": "2ab1440f-b48f-4886-ab5b-2bfa5132ea9a", + "kind": "task.state", + "subject": "vikunja:19/229", + "actor_role": "coder", + "at": "2026-10-13T19:54:01.929Z", + "body": "{\"role\":\"coder\",\"state\":\"in-review\",\"previous\":\"in-review\",\"percent_done\":1,\"comment\":true}" + }, + { + "seq": 304, + "id": "46ca2baa-923e-4ab2-ad77-8e6ad07d2751", + "kind": "action.allowed", + "subject": "vikunja:19/229", + "actor_role": "pm", + "at": "2026-10-13T19:54:21.713Z", + "body": "{\"action\":\"task.close\",\"class\":\"within-role\",\"target\":\"vikunja:19/229\"}" + }, + { + "seq": 305, + "id": "6c122422-7857-4a7d-9727-5cb910d9cd53", + "kind": "task.closed", + "subject": "vikunja:19/229", + "actor_role": "pm", + "at": "2026-10-13T19:54:21.796Z", + "body": "{\"verdict\":\"1fbd1ed1-2226-45c7-9cca-b959662932f3\"}" + } + ], + "source": "read-only from ~/.mosaic-dev/bus/bus.sqlite at 2026-10-14T17:48Z by sage; schema columns listed" +} diff --git a/docs/plans/2026-10-14_merge-path-for-approved-work.md b/docs/plans/2026-10-14_merge-path-for-approved-work.md index ffe22d000..7a610f379 100644 --- a/docs/plans/2026-10-14_merge-path-for-approved-work.md +++ b/docs/plans/2026-10-14_merge-path-for-approved-work.md @@ -60,10 +60,13 @@ and tell Sage. Don't add a verb. ### What ships 1. `land-work [--base refactor] [--dry-run]`. It reads, - and never writes, the decision and the verdict through the existing - read-only path the Console and `mosaic` use. It never opens - bus.sqlite for writing. It refuses with a named reason and exit 2 - unless all of these hold: + and never writes, the decision and the verdict. It opens + `/bus/bus.sqlite` with `node:sqlite` `readOnly: true`, runs + SELECTs only, and refuses `bus-schema` unless `schemaDigest(db)` + equals `SCHEMA_SHA256` from `packages/bus/src/store.mjs` (imported, + not copied). See "Read path" below for why it isn't the Console's + path. It refuses with a named reason and exit 2 unless all of these + hold: - the decision is resolved, by a human, with the option that means merge. The decision names one work ref (`work/vikunja-

-`) and one target branch (exactly `refactor`; `next` and `main` always @@ -100,6 +103,49 @@ and tell Sage. Don't add a verb. 8. The worktree, its `node_modules` link and any Docker network named for it are removed on every path, including refusals. +### Read path (Sage, 2026-10-14, after Darkwing's question) + +Round 1 of this brief said to read through "the existing read-only path +the Console and `mosaic` use". That path is human-only. `human-cli` +refuses inside an agent environment and the broker checks the caller's +ancestry (#225), so the conductor, which in practice is Sage's seat, +can't use it. Asking a human to run `mosaic trail` before each landing, +or to save the trail to files the tool trusts, would bring back the +hand-routing this row removes. + +So `land-work` reads the bus database directly, read-only, on the host. +That's the same trust as the conductor's other host reads, and no +session can reach the tool. No broker verb and no change under +`packages/bus/src`. The schema digest check fails closed when the store +changes, and the queries go in one module, so a schema change is one +place to fix. + +What f71c73c9 actually holds, read from the live database: + +- `decisions`: `action` is `git.merge.protected`, `class` is `gated`, + `route_to` is `human`, `task_ref` is null, `options` is + `[{"key":"yes",...},{"key":"no",...}]`. +- `decision_events`: one `resolved` by `jason`, `choice` `yes`, `via` + `cli`. +- The raise's `action.allowed` event (`operation` `decision.raise`) + carries `target: "refactor"` and `approvalChoice: "yes"`, with + `task_ref` null. + +So the merge option is the raise's `approvalChoice`. The target branch +is the raise's `target`. The work ref comes from the question text, +which must name exactly one `work/vikunja-

-` and no other work +ref. The task is `vikunja:

/` from that ref. If a later decision +sets `task_ref`, it must agree with the ref from the question, or the +tool refuses. + +Tests build their fixture database through the real `Store` (and the +broker where that's simpler), so the schema digest matches. The 19/265 +replay fixture copies the live rows: the decision, its event, the +raise's event, and the task's `review.verdict` events (seq 368 +`changes` on 1b301831, seq 379 `approve` on 8ae5a149, id d3f97f9d). +19/229 had no merge decision. Its replay uses a fixture decision of the +same shape over verdict 1fbd1ed1 (`approve`, 010d30cbe). + ### Not in this row - Push, merge to `next` or `main`, re-pin or restart. Those stay