Dewey's round 3 candidate, manifest
agents/dewey/work/queue-40/candidate-manifest-r3.sha256 (d0aa0ded,
27 files, checked OK in the canonical tree).
- WebUI inbox, tasks, agents and trail views, read-only over /api/bus.
The README says the bus proof ends at the Console process.
- CHAT-03 seal: the engine command is fixed, the engine environment is
explicit, SEAL_FLAGS has --no-approve, escalating is cleared on throw.
- Terminal input typed after Ctrl-T or Ctrl-O is held. Only the run whose
own parse set held drains it (T1), and #run catches errors per action.
- DEFERRED keeps N2 and moves F2 to done, citing T1.
Reviews: Filbert approve (comment 27011, rev 260), Darkwing approve
(27013, rev 264). Landing gate on 8cad7722 plus the candidate: webui 22,
conversation 161, control-board 124, every scripts/test-*.sh green,
test-task 98/0. Mutant Mr survives; its flows test is the first
follow-up row.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
526 lines
27 KiB
Markdown
526 lines
27 KiB
Markdown
# Slice 1 S5: WebUI views for inbox, tasks, agents and trails (design note)
|
|
|
|
Dewey, 2026-10-04. Row 40, #1522. Design only: no code, no commit. S5
|
|
follows S4, so nothing here starts the build.
|
|
|
|
Revision 2, same day: lead decision 56 (c9c1699a) answered Q1 to Q5 and
|
|
accepted all five recommendations. Sections 1, 3.2, 3.5, 5 and 7 now say
|
|
what was decided. Section 8 is a new finding for the Q1 module. The
|
|
mockup is in `mockups/slice1/`, checked by `checks/slice1-verify.mjs`.
|
|
Its fixtures follow schema v3a, and the verify script loads every row
|
|
into that schema.
|
|
|
|
Revision 3, same day. Three things changed:
|
|
- Schema v3b (179ffe35, lead decision 60) landed. It adds the
|
|
`task_current` view and builds `tasks_open` on it (lead decision 59).
|
|
The fixture check now loads v3b and reads current state from those two
|
|
views. It agrees with the page on every task, #42 included. Section 8
|
|
records the ruling.
|
|
- The mockup is reshaped to the Q1 module (Sage's Q1 shapes). `data.js`
|
|
holds raw v3b rows written the way the candidate broker writes them.
|
|
`mockups/slice1/q1.js` ports the four reader verbs, and `reads(S)` in
|
|
`app.js` builds the views only from what those verbs return. A new
|
|
check runs Rocko's own candidate reader over the same rows and
|
|
compares. Section 1 and section 7 describe this.
|
|
- Inbox rows now carry `authorization {action, target, approvalChoice}`
|
|
from the `decision.raise` event, and the views show it (section 3.1).
|
|
What the page needs and no Q1 verb returns is labelled on the page and
|
|
listed for Rocko in section 9.
|
|
|
|
Sources, pinned by sha256 prefix:
|
|
- brief `docs/plans/2026-10-04_slice-1.md` (655e1290, commit 43c48d7a), S5
|
|
section and the shared rules;
|
|
- data model note (948b94ce), addendum A (0b36acb0), addendum B (14e747f5).
|
|
B wins over A, and A wins over the note;
|
|
- schema v3 `agents/darkwing/work/slice1-proto/schema-v3.sql` (084d0a07,
|
|
commit 7ed83178, lead decision 55), replaced by schema v3a
|
|
`schema-v3a.sql` (d55e41fd), which S2 builds on, then by schema v3b
|
|
`schema-v3b.sql` (179ffe35, lead decision 60). The mockup follows v3b;
|
|
- `packages/bus` as committed in d27042fa (S2c, row 44; S2 was
|
|
38828a2c, S2b 4afab552): broker.mjs 51e03b33, store.mjs e69cb395,
|
|
views.mjs 446b6da4;
|
|
- PRD draft 0.4 `docs/prd/mosaic-stack.md` (d907705a): REQ-WEB-1,
|
|
REQ-DEC-1 to 4, REQ-CLI-1, REQ-EVT-1; round 3, 7B;
|
|
- DEFERRED.md, "CHAT-03 I1 round 2 follow-ups, for the I3 brief".
|
|
|
|
Not read on purpose: today's queue. The views follow the data model.
|
|
|
|
## 1. What the WebUI reads, and through what
|
|
|
|
REQ-WEB-1 says the WebUI shows the same data as the CLI. The S4 brief says
|
|
every CLI command reads through the broker and never opens `bus.sqlite`.
|
|
The WebUI follows the same rule:
|
|
|
|
- The browser never talks to the broker. The WebUI server (`packages/webui`)
|
|
calls the broker's read verbs over the local socket and serves JSON to
|
|
the page, the way it proxies the board today.
|
|
- The WebUI never opens `bus.sqlite`, the business file or a token file.
|
|
Bucket titles, bot names and launch limits come from the broker, which
|
|
already loaded them.
|
|
- The WebUI adds no write route for slice 1 data (section 6).
|
|
|
|
One set of read functions serves both renderers, so the two can't drift.
|
|
Lead decision 56, Q1: the module lives in `packages/bus`, Rocko owns it,
|
|
and both the CLI and the WebUI import it. The CLI formats text and the
|
|
WebUI server serves JSON. The module, `packages/bus/src/views.mjs`, is a
|
|
thin client: `inbox`, `tasks`, `agents` and `trail(subject)`, each
|
|
one broker call. The mockup's `mockups/slice1/q1.js` stands in for those
|
|
four calls over fixture rows, and the verify script checks it returns
|
|
what the broker at commit d27042fa returns. `reads(S)` in `mockups/slice1/app.js`
|
|
builds the view objects from the stand-in alone. A second object,
|
|
`missing(S)`, holds what no verb returns. Each entry names its source,
|
|
and every renderer that shows one labels it "not in the Q1 module".
|
|
When Rocko pins an example fixture, it replaces `data.js`.
|
|
|
|
## 2. Routes
|
|
|
|
Console keeps its shell, its palettes and its keyboard rules. The left
|
|
column gains a section list above the project tree. Hash routes work with
|
|
the back button, as the mockups did.
|
|
|
|
| Route | View | Main source |
|
|
|---|---|---|
|
|
| `#/` | Control board (unchanged) | the board scan |
|
|
| `#/inbox` | Decisions routed to the human, open first | `decisions`, `decision_events` |
|
|
| `#/inbox/<id>` | One decision, its options and its history | same, plus `supersedes` chain |
|
|
| `#/tasks` | Tasks by state | latest `task_snapshots` per task, `task.*` events |
|
|
| `#/tasks/<ref>` | One task and its trail | same, plus the trail (section 3.4) |
|
|
| `#/agents` | Role instances, holders and live sessions | business file via broker, `role_claims`, `session.*` events |
|
|
| `#/trail/task/<ref>`, `#/trail/decision/<id>` | The ordered trail | events, decisions, messages, deliveries |
|
|
| `#/conversation/...` | CHAT-02 history and CHAT-03 drive (existing) | the board and `packages/conversation` |
|
|
|
|
A task ref in a URL is `vikunja:<project>/<task>`, encoded. The `#12` a
|
|
person sees in Vikunja is display only (addendum A section 1).
|
|
|
|
## 3. The views
|
|
|
|
Every view keeps the Console states: loading, empty, error with the last
|
|
good data kept and a warning, and refusal with the reason. All
|
|
agent-authored text (questions, option text, recommendations, message
|
|
bodies, task titles and descriptions) is shown as text under the CHAT-02
|
|
rules: Markdown stays as source, and controls and bidi marks show as
|
|
visible symbols.
|
|
|
|
### 3.1 Inbox
|
|
|
|
- Rows: decisions with `route_to = 'human'` and no closing row (`resolved`,
|
|
`withdrawn`, `expired`). Blocking first (the `urgent_inbox` view), then
|
|
oldest first. The note's rule is "gated first, then oldest", and every
|
|
human-routed decision is gated, so `blocking` is the useful split.
|
|
- Each row: question, class and action, raised by role and run, age,
|
|
`task_ref` and `requirement_ref` as links, a "blocking" badge, and
|
|
whether a DM went out (a `deliveries` row with transport `discord-dm`).
|
|
- Detail: the options as a numbered list of key and text, the
|
|
recommendation marked, the `seen` history, and earlier versions through
|
|
`supersedes`, each shown `withdrawn`.
|
|
- Approval context, from the row's `authorization`: the card and the
|
|
detail say "Choosing “<key>” approves <action> on <target>.", and the
|
|
approving option carries an "approves <action>" badge. The approving
|
|
choice need not be the recommendation: in the fixture, the conflict on
|
|
#48 recommends `queue`, and `vikunja` approves `task.close`. A decision
|
|
without a recorded raise says no option is marked as approving.
|
|
- Resolution stays in the CLI (REQ-DEC-3). The detail shows the exact
|
|
command, `mosaic decide <id> <key>`, one per option, with a Copy button.
|
|
Copy puts text on the clipboard and does nothing else. There is no
|
|
Resolve button, disabled or otherwise.
|
|
- A second tab, "Routed to roles", lists open cross-role decisions by
|
|
arbiter (`pm`, `cto`). It's read-only too. Closed decisions get a
|
|
"Recent" list: choice, by, via and time open.
|
|
|
|
### 3.2 Tasks
|
|
|
|
- Rows: the latest snapshot of each task in the business, grouped by the
|
|
five bucket titles (`todo`, `in-progress`, `in-review`, `blocked`,
|
|
`done`). `done` is collapsed and shows the last 20.
|
|
- Columns: `#index` and title, assignee (bot name), requirement id (from
|
|
`task.created`, not the description line), due date, priority, percent
|
|
done, queue row if the task has one, and the time of the last change.
|
|
- Markers, each linked to its event:
|
|
- "changed in Vikunja" for a task in `task_external_changes`. A task the
|
|
broker never wrote is always in that view (addendum B section 5), so
|
|
the marker reads "not created by the stack" when no `self` snapshot
|
|
exists;
|
|
- "conflict" for an unresolved `task.conflict`, linked to the decision
|
|
raised to the PM;
|
|
- "missing" with the reason (`moved`, `not-found`, `no-access`) for a
|
|
tombstone, and the new project id for `moved`.
|
|
- Freshness: a line under the heading, "Vikunja last read <time>", from
|
|
the broker status read S3 adds (Q2).
|
|
- Which snapshot to show: not always the latest one. Section 8.
|
|
- "Open in Vikunja" links to the task on `tracker.baseUrl`. It leaves
|
|
Console and changes nothing.
|
|
|
|
### 3.3 Agents
|
|
|
|
- One row per role instance in the business file (`pm`, `cto`, `coder`,
|
|
`reviewer` in slice 1): definition, harness, model.
|
|
- Holder: the latest `role_claims` row for the instance. A `claim` shows
|
|
the holder run, harness, address and time held. `release` and `revoke`
|
|
show "not held" and who released or revoked it; a revoke links its
|
|
decision.
|
|
- A claim from a T3 seat carries "advisory" (decision 46, note 6.3).
|
|
- Live sessions: `session.launched` rows with no matching `session.ended`,
|
|
with model family and `launched_by`. Above the table, live counts against
|
|
the business file's limits, for example "Opus 2 of 4, Sonnet 1 of 4", and
|
|
the launch state from the `launch_state` view ("launching allowed" or
|
|
"launching revoked at <time>").
|
|
- Credentials: per instance and service, the latest of
|
|
`credential.expiring`, `credential.expired` and `credential.changed`.
|
|
Service names and dates only. There is never a value or a path.
|
|
- Today's board seats stay on `#/`. The Agents view is about roles. A seat
|
|
that holds a role links to its board row.
|
|
|
|
### 3.4 Trail
|
|
|
|
The S4 brief lists the order: request, decisions, launches, task changes,
|
|
review, close. The trail view is a single time-ordered list with one row
|
|
per event and a filter by kind. Each row says what happened, which role
|
|
and run did it, and links to the object.
|
|
|
|
For a task, the rows I can join from schema v3:
|
|
- events with `subject` = the task ref (`task.created`, `.assigned`,
|
|
`.state`, `.closed`, `.changed.external`, `.conflict`, `.missing`);
|
|
- decisions with `task_ref` = the task ref, and their `decision_events`;
|
|
- messages that cite one of those decisions, with their deliveries;
|
|
- `review.requested` and `review.verdict`, if their subject is the task ref
|
|
or its queue row.
|
|
|
|
What I couldn't join from the schema alone: which launch worked on the
|
|
task, and which `human.input` was the original request. Q3 was decided:
|
|
`task.created` names the request in `body.request`, and the broker's
|
|
trail follows it to the `human.input` and its message. Launches come in
|
|
through the runs seen on the trail's rows. For a decision, the trail is the decision, its events, the
|
|
messages that cite it and the events whose body cites it.
|
|
|
|
### 3.5 Conversations and CHAT-03
|
|
|
|
The brief puts two I3 follow-ups into S5, because the live PM session
|
|
needs them:
|
|
- the seal covers the engine command: the I3 entry point takes no engine
|
|
command or `preArgs` from config, so only the pinned Pi starts;
|
|
- an explicit engine environment: the engine starts with an allow-listed
|
|
environment, not `process.env`. That matches addendum A section 4, which
|
|
starts every agent with an allow-listed environment.
|
|
|
|
Lead decision 56, Q5, adds two more of the DEFERRED items to S5: clear
|
|
`escalating` after a throw, and handle takeover plus Enter arriving in one
|
|
chunk. Both sit in files S5 already changes. The test that members are
|
|
listed only under freeze stays deferred.
|
|
|
|
Which session Console drives depends on S6. The PM moves off T3 there, and
|
|
the stack owns its session. Console shows the PM's conversation from
|
|
whatever S6 records, through `packages/conversation`.
|
|
|
|
## 4. Gate E
|
|
|
|
The script for the demonstration is now `S5-GATE-E.md` (2026-10-09).
|
|
This section stays as the design draft it replaced.
|
|
|
|
Round 3, 7B: Gate E is shown during this step, not separately. The brief:
|
|
an interactive demonstration with all seats, then Jason's workday ruling.
|
|
Live cutover still needs its own approval.
|
|
|
|
A draft script, to be fixed once S4 and S6 exist:
|
|
1. Jason opens Console. The board shows the seats, and Agents shows the
|
|
four role instances with their holders.
|
|
2. He sends the PM a one-sentence request from the CLI, then watches the
|
|
task appear in Tasks with its requirement id.
|
|
3. A gated decision reaches the inbox and his DM. He reads it in Console,
|
|
copies the `mosaic decide` command and runs it in a terminal. The
|
|
inbox row closes on the next refresh.
|
|
4. He follows the task's trail to review and close.
|
|
5. He drives a conversation through CHAT-03 and takes control, then hands
|
|
it back.
|
|
6. He uses Console for the rest of his workday and rules on it.
|
|
|
|
Evidence: browser runs against a fixture broker with recorded data, and
|
|
the live run's screen captures. Model calls during the demonstration are
|
|
Jason's to start. I don't run them for discovery.
|
|
|
|
## 5. Questions, now decided
|
|
|
|
Lead decision 56 (c9c1699a) accepted all five recommendations below. Each
|
|
answer is quoted after the question; the original text stays for the
|
|
record.
|
|
|
|
Q1. **One read layer for CLI and WebUI.** Recommendation: S4 exposes the
|
|
inbox, tasks, agents and trail reads as functions over the broker client,
|
|
in a module `packages/webui` can import. The CLI formats text and the
|
|
WebUI serves JSON. For Rocko, through Sage.
|
|
*Decided:* one read module in `packages/bus`, owned by Rocko, imported by
|
|
the CLI and the WebUI.
|
|
|
|
Q2. **Freshness of task data.** A quiet poll writes no snapshot (addendum
|
|
B section 5), so the database can't say when Vikunja was last read, or
|
|
whether the poller is failing. Recommendation: the broker answers a status
|
|
read with the last successful board read and cursor read per project, and
|
|
the last poll error. S3 owns the poller and S2 the read verb. For Darkwing
|
|
and Rocko.
|
|
*Decided:* the broker status read goes in S3, Darkwing's.
|
|
|
|
Q3. **Trail join keys.** `session.launched` and `human.input` carry no
|
|
task ref in the schema. Recommendation: put the task ref in `subject` for
|
|
any event about one task, including `session.launched` when the launch is
|
|
for a task, and `human.input` when it creates one. That is a body and
|
|
subject rule, not a new kind, so the closed kind list holds. For Rocko and
|
|
Darkwing.
|
|
*Decided:* the task ref goes in `subject` for any event about one task.
|
|
`task.create` names the `human.input` that asked for it. Darkwing picks
|
|
whether a trigger or the broker enforces it. Schema v3a does it with
|
|
triggers: `events_task_subject` for every `task.*` event, and
|
|
`events_task_created_body`, which requires `request` (the `human.input`
|
|
id) and `requirement` in the `task.created` body. For `session.launched`
|
|
and the other kinds, the broker sets `subject`.
|
|
|
|
My original Q3 text was wrong on one point. A `human.input` can't carry
|
|
the task ref, because the task doesn't exist when the request is
|
|
recorded. The join runs the other way: the task trail takes the
|
|
`human.input` named in `task.created.request`. The mockup does that, and
|
|
a check confirms that #41's trail shows its own request and not another
|
|
task's.
|
|
|
|
Q4. **`seen` from the browser.** `decision_events` allows `op = 'seen'`,
|
|
and the note lists `webui` under `via`. Recommendation: the WebUI writes
|
|
nothing in slice 1, not even `seen`. Every broker write is stamped from a
|
|
launch record or comes from the CLI outside a run, and an unauthenticated
|
|
loopback page is neither. `seen` from the WebUI comes with Pocket ID. For
|
|
Sage.
|
|
*Decided:* the WebUI writes nothing in slice 1. It shows the `mosaic
|
|
decide` command to copy.
|
|
|
|
Q5. **I3 follow-ups beyond the brief's two.** Recommendation: fix
|
|
`escalating` and the takeover-plus-Enter case in S5, since both sit in
|
|
files S5 already owns. Keep the freeze test as a follow-up. For Sage.
|
|
*Decided:* as recommended (section 3.5).
|
|
|
|
## 6. Boundaries
|
|
|
|
- No write route for decisions, tasks, claims, messages or events. Copy is
|
|
clipboard only.
|
|
- The page and server stay loopback-only with the existing checks (Host,
|
|
Origin, body limits, no CORS). The new routes are GET only.
|
|
- No token value, token path or business-file path reaches the page.
|
|
Credential rows show the service and dates.
|
|
- Agent-authored text is inert.
|
|
- No model calls. Tests use a fixture broker.
|
|
|
|
## 7. Before S4 lands
|
|
|
|
- Done: a Console mockup of the four views, `mockups/slice1/`.
|
|
`checks/slice1-verify.mjs` checks it at eight widths, ten palettes in
|
|
three modes, and covers inert text, read-only behaviour, the inbox,
|
|
tasks, agents and trail rules, the loading/empty/failed/refused states,
|
|
the keyboard and long text.
|
|
- Done: fixtures as raw schema v3b rows (`data.js`), written in the
|
|
broker's order and with its millisecond timestamps. The Store
|
|
refuses any `at` or `read_at` that isn't the 24-character
|
|
`toISOString()` form; the schema doesn't check that, so the fixture had
|
|
to change.
|
|
- Check 15 inserts every fixture row (104) into schema v3b (179ffe35) in
|
|
time order, so its CHECKs and triggers judge the fixture. It compares
|
|
`urgent_inbox`, `task_external_changes`, `task_current` and
|
|
`tasks_open` with the page, and requires the "stale read" marks to be
|
|
exactly the snapshot rows `task_current` skips (#42 only).
|
|
- Check 16 extracts `packages/bus` from commit d27042fa (S2c, row 44;
|
|
S2 was 38828a2c, S2b 4afab552) with `git archive`, so working-tree
|
|
edits don't count. It opens that Store and Broker on a temporary data
|
|
root, inserts the same rows, binds a reader and requests `inbox`,
|
|
`tasks`, `agents` and 21 trails (every task, decision and message). The
|
|
results must equal `q1.js` on the same rows: 203 rows, equal, with
|
|
broker.mjs 51e03b33, store.mjs e69cb395 and schema.sql 179ffe35. It
|
|
checks the reader only: rows go in by SQL, not through the broker's
|
|
writers, so it can't tell whether the broker would write them
|
|
(section 10). It
|
|
writes nothing outside its temporary directory, which it removes. Run
|
|
from a copy outside the repository, it needs `DEWEY_S1_REPO`, or it
|
|
records "not verified".
|
|
- A mutation run in a scratch copy breaks one rule at a time, and each
|
|
check has to fail on the mutant it guards against. Round 3, 21
|
|
mutants, 20 killed on the first pass. The survivor reversed the
|
|
decision trail's order; check 8 only checked the task trail's order,
|
|
and now checks both. M5 (stale mark on the current row) failed check
|
|
15, not check 6 as I had expected; check 15 now owns that rule. After
|
|
the fix, 21 of 21 (`~/dewey-scratch/s1mut2/mutants.txt`, scratch only).
|
|
Report: `evidence/slice1/report.json`.
|
|
- Next: switch `data.js` to Rocko's pinned example fixture when it
|
|
arrives. Section 9's gaps decide what S5-B1 needs beyond the four verbs.
|
|
|
|
## 8. Finding: a stale poll read becomes the latest snapshot
|
|
|
|
*Ruled:* lead decision 59 (e153c3a3) takes the read-side fix and puts it
|
|
in the schema. The poller keeps writing the stale row as evidence. A v3b
|
|
amendment (Darkwing) adds a view of each task's current snapshot that
|
|
skips the stale `poll` row, and rebuilds `tasks_open` on that view. The
|
|
Q1 module, the CLI and the WebUI read that view and don't reimplement the
|
|
rule. When v3b lands, the fixture check moves to it and expects
|
|
`tasks_open` to match the page on every row, #42 included. The Tasks view
|
|
keeps the "stale read, not shown" marker in the snapshot list.
|
|
|
|
*Landed:* v3b (179ffe35, lead decision 60). Check 15 loads it and agrees
|
|
with the page on every task, #42 included. The page reads the current row
|
|
from the stand-in `tasks` verb, which reads `task_current`, and marks as
|
|
stale the snapshot rows after it. The original finding follows.
|
|
|
|
For Rocko (the Q1 module) and Darkwing (S3 poller), through Sage.
|
|
|
|
Addendum B section 5 writes a poll snapshot whenever its digest differs
|
|
from the task's latest snapshot, and ignores a stale read when it detects
|
|
external changes: `task_external_changes` requires
|
|
`p.read_at > ls.at`, so a board read that started before the broker's
|
|
own write doesn't count as a change in Vikunja. The next tick settles it.
|
|
|
|
The stale read is still stored, though, and it is now the latest
|
|
snapshot. Anything that shows "the latest snapshot per task", including
|
|
`tasks_open` and the Tasks view, shows the old bucket until the next tick.
|
|
|
|
Example in the fixtures, task #42:
|
|
- 14:59:40, `self` snapshot: the broker moved #42 to `in-progress`.
|
|
`updated` stays 09:50, because a move between open columns doesn't change
|
|
it (probe P3).
|
|
- 14:59:41, `poll` snapshot, `read_at` 14:59:39: the board read started
|
|
before the move and still sees `todo`. Its digest differs, so it is
|
|
written.
|
|
- Latest snapshot: `todo`. Not external, correctly. Shown as `todo`,
|
|
wrongly.
|
|
|
|
The verify script confirms this against the real schema. After the
|
|
fixture is loaded into schema v3a, `tasks_open` gives `todo` for #42,
|
|
while the page shows `in-progress`. That is the only row where they
|
|
differ.
|
|
|
|
What the mockup does: when choosing the row to display, skip a poll
|
|
snapshot whose `read_at` is at or before the latest `self` snapshot's
|
|
`at`. The task's snapshot list still shows that row, marked "stale read,
|
|
not shown". The verify script checks #42 sits under `in-progress` and the
|
|
marker.
|
|
|
|
Recommendation: the Q1 module applies the same rule, and `tasks_open` gets
|
|
it too, so the CLI and the WebUI agree. The other fix is in the poller:
|
|
don't write a poll snapshot whose `read_at` is at or before the latest
|
|
`self` snapshot. That loses no information the external-change rule uses,
|
|
but it drops a row from the evidence, so I'd leave the choice to Darkwing.
|
|
|
|
## 9. What the views need that no Q1 verb returns
|
|
|
|
*Ruled:* lead decision 63 (Sage, 2026-10-05) sends this list to Rocko
|
|
for S4. Session events go to S6 and credential events to S3. Whatever S4
|
|
doesn't add keeps its "not in the Q1 module" label in S5.
|
|
|
|
For Rocko, through Sage. The reader (d27042fa) has four verbs: `inbox`,
|
|
`tasks`, `agents` and `trail(subject)`. The mockup builds everything it
|
|
can from those. The rest comes from `missing(S)` in `q1.js`, and the page
|
|
labels each one "not in the Q1 module". Suggestions are the smallest
|
|
change I can see; the choice is Rocko's.
|
|
|
|
Inbox and decisions:
|
|
- Decisions routed to roles, and closed decisions (the "Routed to roles"
|
|
tab and "Recent"). `inbox` returns only open, human-routed decisions.
|
|
Suggestion: an argument to `inbox`, or a `decisions` verb.
|
|
- Seen and DM state per inbox row. The page gets them with one trail read
|
|
per decision. Suggestion: `seen_at` and `dm_at` on the inbox row.
|
|
- The forward link from a withdrawn decision to the one that replaced it.
|
|
The trail's decision query matches `id` and `task_ref` only.
|
|
Suggestion: add `OR supersedes = ?`.
|
|
- Approval context for a decision that isn't in the inbox. The page reads
|
|
it from the `decision.raise` event in the trail, which works, but it is
|
|
the same derivation `#decisionView` already does.
|
|
|
|
Tasks:
|
|
- Requirement, conflict, external-change fields, review and the missing
|
|
reason all need one trail read per task. Suggestion: an `external` flag
|
|
from `task_external_changes` and the requirement on each `tasks` row.
|
|
- Bucket titles, bot names and the tracker `baseUrl`. They are in the
|
|
business file and the kanban view, which the broker has loaded.
|
|
- Freshness ("Vikunja last read"): the S3 status read, not built yet.
|
|
|
|
Agents:
|
|
- Role instances, limits and model families, from the business file.
|
|
- Launch state: the `launch_state` view exists in the schema; no verb
|
|
returns it.
|
|
- Live and ended sessions. Nothing writes `session.ended` yet, and
|
|
`session.launched` carries no model and no `launched_by`.
|
|
- Credential notices. Nothing writes credential events yet.
|
|
- `agents` returns only roles whose latest row is a claim, so the view
|
|
can't say who released or revoked a role. Suggestion: return the latest
|
|
row per role and let the reader filter.
|
|
|
|
## 10. Finding: S2b stopped an agent message citing its decision; S2c restores it
|
|
|
|
*Ruled:* lead decision 65 (dd35a0ab) took none of the options below as
|
|
written. For a within-role `message.send`, `decision` is a citation: the
|
|
broker checks the decision exists and stores it in `messages.decision`,
|
|
but doesn't class-match it, doesn't consume it, and leaves it out of the
|
|
`action.allowed` event. For a send that isn't within-role, it stays the
|
|
authority. `messages.decision` keeps its "about this decision" meaning,
|
|
and messages 101 and 102 stay valid. S4 still sends and records the
|
|
Discord DM. The fix is row 44, S2c (#1526, Rocko, Darkwing reviewing).
|
|
|
|
*Landed:* S2c is commit d27042fa (row 44 done at queue rev 157), and it
|
|
restores messages 101 and 102: the broker at d27042fa writes both. The
|
|
probe below, rerun on d27042fa, shows a within-role citing send is
|
|
written, the decision is stored and not consumed (a second citing
|
|
message is written too), an unknown decision refuses with
|
|
`decision-not-found`, and the send's `action.allowed` event is {action
|
|
`message.send`, class `within-role`, target `human`} with no decision.
|
|
No `action.allowed` row outside `decision.raise` names the decision. The
|
|
fixture now carries that event 1 ms before each of 101 and 102, and
|
|
check 16 is pinned to d27042fa. Check 16 still inserts by SQL, so a
|
|
broker-written fixture with a citing `DECISION` message remains the
|
|
regression test it can't give. The original finding follows.
|
|
|
|
For Rocko and Sage. Found 2026-10-05 while repinning check 16 to S2b.
|
|
|
|
`message.send` takes `decision?`, and the broker stores it in
|
|
`messages.decision`. Up to S2 (38828a2c), a role with `message.send` in
|
|
its within-role authority returned before the decision was looked at, so
|
|
`decision` worked as a citation: the PM could send the human a `DECISION`
|
|
message about an open gated decision. S2b (4afab552) passes every agent
|
|
send through `#consumeAuthority`, which now checks a supplied decision
|
|
even for a within-role action. The decision's class and action must
|
|
match `message.send`, so a citation of a `role.launch` decision refuses
|
|
with `decision-mismatch`.
|
|
|
|
Probe (`~/dewey-scratch/s2b-probe.*/probe.mjs`, both commits extracted
|
|
with `git archive`, PM with `message.send` within role, an open blocking
|
|
`role.launch` decision routed to the human):
|
|
|
|
| | 38828a2c | 4afab552 | d27042fa (S2c) |
|
|
| --- | --- | --- | --- |
|
|
| PM `DECISION` message with `decision: <id>` | written | refused, `decision-mismatch` | written; decision stored, not consumed |
|
|
| the same message without `decision` | written | written, plus `action.allowed` {action `message.send`, class `within-role`, target `human`} with subject `human` | written, plus the same `action.allowed` |
|
|
| citing an unknown decision | refused, `decision-not-found` | refused, `decision-not-found` | refused, `decision-not-found` |
|
|
|
|
What depends on the citation:
|
|
- the trail's decision query collects `messages WHERE decision = ?`, and
|
|
deliveries through those messages;
|
|
- the inbox's "DM sent" state, which comes from a `discord-dm` delivery
|
|
of a message citing the decision (section 3.1). The fixture's messages
|
|
101 and 102 are those DMs, and the broker would now refuse both.
|
|
|
|
After S2b, a non-null `messages.decision` can only name a decision that
|
|
approved `message.send` itself, and that decision is used up by the
|
|
send. The column now means "the decision that authorized this message".
|
|
The data model and the views read it as "the decision this message is
|
|
about".
|
|
|
|
Options, for Rocko and Sage:
|
|
1. A separate citation argument (for example `about`), stored apart from
|
|
the authorizing decision. That needs a schema column or an event.
|
|
2. The Discord connector sends the DM from the decision itself, not from
|
|
an agent message, and records its own delivery. The views then read
|
|
"DM sent" from that record.
|
|
3. Keep S2b's rule, and drop "DM sent" and decision messages from the
|
|
views.
|
|
|
|
My recommendation is 2. REQ-DEC-4 and the slice 1 brief's S4 section
|
|
already give the outbound DM for a blocking gated decision to S4,
|
|
"through the existing connector (#1509)", so the PM doesn't need to send
|
|
one. It also leaves `message.send`'s `decision` meaning one thing. S4
|
|
then has to record the DM somewhere the views can read it; Darkwing's
|
|
prototype has a `digest.sent` event with a `transport` for the digest
|
|
side. Until a ruling, the fixture keeps messages
|
|
101 and 102 as they are, and this section marks them as rows the broker
|
|
at 4afab552 would refuse. (Ruled above: they stay, and S2c restores
|
|
them.)
|
|
|