ditdash U-J5a/b: the converge button (registry, dispatch, decision surface, panel) #10

Merged
jsutter merged 4 commits from ditdash-u-j5 into main 2026-10-12 06:37:40 +00:00
Owner

J5a (sensitive): the operation registry, the dispatch client, the decision endpoint, the D6 identity fail-closed without custody (the WS-0 convention). J5b: the panel, the relayed prompt, the verbatim refusal render. Critic fixes: exact-tenant run binding + event run_id validation (colliding-tenants fixture), the stale-prompt gate, the runs-list token sweep. Critic: PASS-WITH-FINDINGS, fixed.

J5a (sensitive): the operation registry, the dispatch client, the decision endpoint, the D6 identity fail-closed without custody (the WS-0 convention). J5b: the panel, the relayed prompt, the verbatim refusal render. Critic fixes: exact-tenant run binding + event run_id validation (colliding-tenants fixture), the stale-prompt gate, the runs-list token sweep. Critic: PASS-WITH-FINDINGS, fixed.
The registry (src/operations.rs): the typed Operation (converge on a
tenant) with the frozen run-state vocabulary
(dispatched|awaiting-sign-in|planned|gated|applying|succeeded|failed —
byte-identical to converge.yml's contract test), the D1/D6 dispatch
client (workflow_dispatch via the DITDASH_DISPATCH_TOKEN identity; a
failed dispatch leaves no phantom run), the D2 job-log poll (parses the
run's converge_state + device_code JSON lines; a failed poll keeps the
last observed state, never a fabricated advance), the D3 per-run
decision record (bearer decision_token for the run's poll; the write
path is the initiating operator's session alone), the D7 second-dispatch
guard (one active run per tenant; the forge concurrency group and the
dito lock stay the authorities), the terminal-state collector nudge
(U-F3, fired once), and the FR-22 action row. In-memory only: a restart
drops pending records and the run's bounded wait fails closed to
apply-not-confirmed (FR-24).

The surface: POST /api/v1/runs (dispatch; JSON for tools, the panel's
urlencoded reason form with a 303 back), GET /api/v1/runs (collection),
GET /api/v1/runs/{run_id} (state + plan; the relayed prompt scoped to
the initiating operator's session alone — a foreign operator sees the
run, never the prompt), GET|POST /api/v1/runs/{run_id}/decision (the
bare D3 record the workflow polls; Apply/Decline recorded once). All
operator@msp only except the bearer-gated decision GET; below the role
every route answers the same 404 (zero-leak). The tenant resource gains
`data.actions` (operator@msp + lag>0 or drift + no active run + a
configured identity → available; the blocking reason otherwise; ABSENT
below operator@msp) and the J5a-rendered actions row. The run never acks
a gate — no ack path exists, not even behind a flag.

The token discipline: DITDASH_DISPATCH_TOKEN reads via config.rs, rides
only the forge Authorization header, and is asserted absent from every
response body. The manifest ref stays UNDECLARED (a present-but-unseeded
ref aborts deploys — WS-0); the comment carries the re-add recipe. The
decision endpoint notes its forward-auth dependency (U-K3-router) —
until then the run times out fail-closed.

Tests: 14 operations.rs unit tests through a loopback forge stub (typed
inputs, guard, log parsing, prompt scoping, verbatim refusal text,
nudge-once, decision once, poll-failure stance, D6 fix naming), the
HTTP rows (role denials, dispatch→poll→guard flow, prompt isolation,
bearer gate, registry restart, token-never-renders), the policy-matrix
operator@msp row, the canon `decision` exemption (the §26.8 rename +
the workflow's fixed path), and the refreshed tenant--operator snapshot.

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
The panel (src/views.rs): the converge section now carries the run view —
the state badge + cause, the run metadata (actor, reason, the bound
expected rev, stamps, the decision record), the relayed device prompt
(verification link, code, expiry — rendered ONLY for the initiating
operator's session), the plan publication (phases, affected actions,
restarts, would-gate lines, gate ids), the Apply/Decline decision
buttons (initiator only; the write posts to the decision path), the
progress lines, and on terminal states dito's refusal bytes VERBATIM in
a <pre> — the exact CLI command rides inside, "Nothing was changed."
renders as emitted, and no ack path exists (no field names an ack; the
dashboard never acks a gate). A terminal record links its change
history.

The feed (src/surface.rs): the converge seam moves here — the tenant
page's signals (lag from the release block, drift from the What's-live
verdicts) and the one attachment that folds `data.actions` + `data.run`
into the tenant resource for page and twin alike. Below operator@msp
NOTHING attaches: the verb is absent from the actions list AND the run
feed never enters a viewer's twin or page (the zero-leak rule — a J5a
gap this feed wiring closes; the viewer case is pinned).

Tests (tests/pages.rs): the seeded-registry fixtures render each run
state without a forge — the initiating session's prompt + decision
buttons vs a foreign operator's run-only view; the gate refusal
byte-for-byte with its CLI command and the no-ack assertion; the
zero-leak row (viewer@msp and viewer@tenant: no panel, no run feed, in
page or twin); the field-for-field twin→page parity of the new render
(every scalar of a seeded planned run appears on the page); the blocked
action's reason render; and a new `converge--operator.sha256` role
snapshot (the suite's UPDATE_SNAPSHOTS mechanism).

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
Critic findings on the U-J5 converge surface:
- resolve_forge_run takes the tenant from the record (never the run-id
  split) and matches the title boundary exactly (after 'converge <tenant>'
  comes ' (' or the end): prefix-colliding tenants (e2e / e2e-lab) never
  bind each other's runs.
- refresh drops converge_state lines whose run_id is not the record's —
  the log is resolved by title, so a sibling run's lines must not advance.
- the device-prompt panel renders only while the run still awaits sign-in
  with the decision pending: no stale prompt after the run advances.
- the runs list joins the token-never-renders sweep.

Fixtures: the colliding-tenants listing (e2e vs e2e-lab), the
mismatched-event drop, and the stale-prompt absence.

Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
Assisted-by: worker:synthetic/hf:deepseek-ai/DeepSeek-V4.1-Flash
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
deepnet/ditdash!10
No description provided.