Atlas reshapes the register and proposes catalog writes — applied in the browser, confirm-first, no fabrication
The Dock-chat brain now emits two terminal intents on its turn:
view_intent (a set_view reshape) and
propose_operation (a confirm-first create/edit/archive). The
brain holds no router or mutation credentials (RFC-APB-002 §C cl.3): it
proposes; the app applies. UAT Run-2
found both intents only described their action in the browser —
they never took effect. This Story is the pramaan-app consumer that
closes the loop.
Repo: pramaan-app only (ADR-031 — the brain half landed in
pramaan-agents #137, Closes Bug-1753 + Bug-1754).
What a counsel sees
- set_view On the Documents Register, the counsel types “group these by status and show only evidence.” Atlas replies, and the already-loaded list regroups and slices in place — no spinner, no refetch, no fabricated rows.
- propose_operation The counsel says “create a forum called NCLT-Mumbai.” Atlas renders a confirm card (“Create forum NCLT-Mumbai?”). Nothing is written until the counsel taps Confirm; then the forum is created and the list cache refreshes. Cancel writes nothing.
- delete → archive The counsel asks to delete a matter type. The brain refuses and offers archive instead; the app renders the archive proposal. There is no delete control, ever (D-10).
- graceful refusal A reshape by a field that does not exist on the surface is refused with a toast — the view is left untouched, nothing crashes (UAT-020).
The wire contract (consumed, not redefined)
On the dock-chat turn (atlas-dock-turn-v1), two new terminal fields, null on a plain turn:
view_intent = {"kind":"set_view","edits":[{"op":"set_group_by","field":"status","value":null,"mode":null}]}
propose_operation = {"kind":"propose_operation","operation":"create","entity":"forum",
"params":{"name":"NCLT-Mumbai"},"confirm_prompt":"Create forum NCLT-Mumbai?"}
op ∈ set_group_by · set_slice_by · set_primary_sort · set_field_sum ·
toggle_hierarchy · toggle_field — the exact verbs of the shipped
view-edits.ts. operation ∈ create | edit | archive;
delete is unrepresentable by design.
How set_view applies (Transport C — client-side)
| Step | Where | What |
|---|---|---|
| 1. Lift off the wire | src/lib/agents/atlas-rail.ts | reduceFrame maps turn.completed.view_intent → RailState.setView (reset on run.accepted), exactly as it already does for navigate_to. |
| 2. Dispatch on the surface | agent-dock-mount.tsx | On turn settle, dispatches {name:"set_view", toolParams:{edits}} through the existing client-tool dispatcher. Off a register surface the tool is absent → the dispatch drops (no parallel path). |
| 3. Fold onto the loaded view | src/lib/documents/register/atlas-view-intent.ts | Pure applyAtlasViewEdits(view, edits) maps each op to its shipped view-edits.ts transform. The register’s buildRegisterModel(rows, view) re-runs over the rows already in memory. |
| 4. Refuse what doesn’t exist | same | An edit naming a field not on the surface — or one that can’t take the op — is collected in rejected and skipped; valid edits still apply. The register bridge toasts the refusals (UAT-020). |
view_fields — grounding the no-invented-field guarantee
The Documents Register publishes its reshapeable column/axis set as the
surface’s view_fields ({key,label}), which rides
into the Dock’s current_context each turn
(cpc-store → buildCurrentPageContext). Without it
the brain offers no reshape and refuses; with it, it can only name fields
that exist.
current_context is an opaque digest the
broker forwards verbatim, so adding view_fields is
forward-compatible. The exact key the merged brain reads should be
confirmed against pramaan-agents #137 before the end-to-end
demo (the field is emitted as view_fields per the issue contract).
How propose_operation applies (confirm-first write)
| Step | Where | What |
|---|---|---|
| 1. Lift off the wire | atlas-rail.ts | reduceFrame maps propose_operation → RailState.proposeOperation. A delete (or any non-create/edit/archive op) is dropped at the boundary — it can never reach a card. |
| 2. Render the card | use-client-tool-dispatcher.ts | A new proposeOperation() pushes a ProposalItem (the existing confirm card). The body is the brain’s confirm_prompt; the chrome (buttons, notes, title) is i18n. |
| 3. Confirm runs the existing mutation | src/lib/agents/propose-operation.ts | resolveProposeOperation(op, entity) returns the generated client fn (appCreateForum, appArchiveMatterType, …) + the list url. On confirm it runs under the caller’s own session (RLS), then invalidates the list cache. Nothing runs before confirm. |
| 4. Honest outcome | same | The generated fn Zod-parses its request at the trust boundary (HR-21/HR-22), so malformed params throw there and the app shows an honest failure — never a faked success. |
delete is unrepresentable (D-10)
The operation union admits only create | edit | archive, and
the executor registry has no delete path. Forums archive resolves to
null too (the backend exposes only forum delete, which is
unrepresentable) — so the app refuses gracefully rather than rendering a
destructive control. The brain offers archive; the app applies archive.
Acceptance criteria → evidence
| AC | Status | Evidence |
|---|---|---|
| UAT-010/011/012 — group/sort/slice reshape the loaded list, no round-trip | met | atlas-view-intent.test.ts (group/sort/slice + “show only X”); applied over in-memory rows via buildRegisterModel. |
| UAT-014/015/016/017 — create/archive propose → confirm → write; nothing before confirm | met | propose-operation.test.ts (resolver) + dispatcher renders a card whose mutation runs only in onConfirm. |
| UAT-018/041 + D-10 — delete refused, archive offered, no delete control | met | Op union excludes delete; reduceFrame drops it (atlas-rail.test.ts); registry has no delete path. |
| UAT-020 — reshape by an absent field refuses gracefully | met | applyAtlasViewEdits collects rejected + the register bridge toasts; view untouched. |
| No fabrication, no silent write; mutations under caller session (RLS) | met | set_view never fetches; writes go through generated client fns (browser→proxy→gateway), confirm-gated. |
| Verify gate green; i18n in both locales | met | typecheck · lint (0 errors) · 824 tests · build · route-registry fresh; new keys added to en.json + hi.json. |
What is not in this Story
edithas no UAT path this Story — it resolves tonull(graceful refusal) and is left to a follow-up.- End-to-end demo needs the staging brain deploy emitting the two terminal fields; the app side is wired and unit-proven now.
- No Dock
.featurescenario for forums create/archive exists in this repo to wire (the referenced @pending-until-Atlas scenario is not present here).
Files changed
16 file(s), +999 / -7 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +8 | -2 |
| messages/hi.json | Config | +8 | -2 |
| src/components/agents/agent-dock-mount.tsx | Component | +19 | -0 |
| src/components/documents/register/register-view.tsx | Component | +5 | -0 |
| src/components/documents/register/use-register-agent-bridge.ts | Component | +96 | -0 |
| src/lib/agents/atlas-rail.test.ts | Library | +101 | -0 |
| src/lib/agents/atlas-rail.ts | Library | +95 | -0 |
| src/lib/agents/propose-operation.test.ts | Library | +36 | -0 |
| src/lib/agents/propose-operation.ts | Library | +143 | -0 |
| src/lib/agents/use-client-tool-dispatcher.ts | Library | +83 | -1 |
| src/lib/agents/use-dock-chat-rail.ts | Library | +5 | -1 |
| src/lib/documents/register/atlas-view-intent.test.ts | Library | +116 | -0 |
| src/lib/documents/register/atlas-view-intent.ts | Library | +239 | -0 |
| src/lib/surface-registry/current-context.ts | Library | +20 | -0 |
| src/lib/surface-registry/use-surface.ts | Library | +14 | -1 |
| src/stores/cpc-store.ts | Source | +11 | -0 |