The section surface is live (app#568) and the backend now assembles a
document_selection ContextBundle profile (functions#650). This
ticket builds the vehicle that carries counsel's ask to an
agent with that context attached.
The CCB is a vehicle, not a toolbar. The chassis is
constant — the same bar, in the same place, with the same two affordances
(Command and Talk, D29) — and everything
between them changes with what counsel is looking at. The verbs are
cargo; the bar is the truck.
| Concern | File |
|---|---|
| The ruling: which verbs a focus resolves to (origin × section kind × signals) | src/components/documents/ccb/ccb-presentation.ts (+ 16 unit tests) |
| What each verb looks like and which lane it fires down | src/components/documents/ccb/ccb-verb-catalog.tsx |
| The chassis (pure, storyable — every state reachable without a socket) | src/components/documents/ccb/document-ccb-chassis.tsx |
| The ask pipeline: context gate → agent | src/components/documents/ccb/use-ccb-ask.ts |
| The in-place answer (first frame → stream → refusal) | src/components/documents/ccb/ccb-answer-card.tsx |
| The focus seam (shell provides, view publishes) | src/components/documents/ccb/document-focus-context.tsx |
The wire: proxy → gateway (HR-21 assertContract) |
src/app/api/internal/matters/[matterId]/context/bundles/document-selection/route.ts,
src/lib/documents/gateway.ts
|
The bar is mounted by document-permalink-shell.tsx — the shared
chrome above Original, Insights and the machine copy. That is what makes
"constant" structural rather than merely visual: there is no per-view copy of
the bar to drift.
But what counsel is pointing at is known one level down, inside whichever view is mounted. So the shell provides a focus context and the view publishes into it — the section selected in the outline, the span dragged on the page, or a gutter mark clicked. A view that has no notion of focus publishes nothing, and the bar honestly falls back to "Whole document": with nothing selected, the document is the scope.
resolveCcbVerbs(focus) reads three axes and returns at most three
verbs (D20's hard ceiling). The chassis maps over the result and dispatches by
lane — it contains no knowledge of any individual verb, so adding one is a
registry entry, never an edit to the bar.
| Axis | Rule |
|---|---|
| Content origin |
Uploaded document text → Explain · Impact · Note.
Agent-produced content (the machine's own mark) → Why · Cite.
"Why" never appears on uploaded text: there is no
reasoning trail behind opposing counsel's words, and offering one would be
a lie about where the text came from.
|
| Section kind | An opt-in override table. Kind labels are counsel's own words (D26 — the catalog is firm-owned and editable), so no taxonomy is shipped; every kind falls through to the origin default until a firm says otherwise. |
| Present signals | A signal verb exists only while its signal does. A contradiction chip appears when the focused section holds a live contradiction mark, and not otherwise — it is never a permanently-greyed button waiting for one. Signals sort first and survive the ≤3 cap, because a signal the engine actually holds outranks a verb counsel can reach anywhere. |
The order is the whole point.
document_selection profile. A privileged document is REFUSED and
a strategy document EXCLUDED — typed 403s decided at the query layer before a
single section is read. We ask for the context before we ask the
agent, so a refusal is something counsel is TOLD, not something they infer
from a thin answer. A refusal stops the pipeline: no agent turn is fired.
That second choice is what buys D29's handoff continuity for free:
the ask and its answer are already turns on the panel's thread, so
Talk / Continue in the panel opens a conversation that
CONTINUES this moment instead of restarting it. No new runtime, no broker change,
no second socket.
The instant counsel asks, the answer card paints the focused section's
stored summary (D15) — already in hand from the section layer,
so it costs nothing and arrives before the model has produced a token. It is
labelled as what it is ("Stored read"), never passed off as the answer, and the
shared AgentWorkingLine keeps the wait alive beneath it (motion +
ticking seconds + phase). The live stream then replaces it.
An empty box that waits three seconds teaches counsel the surface is slow; a true sentence teaches them it is working. Where there is no stored summary (an unsegmented document), the card goes straight to the wait rather than filling the gap with a guess.
A privileged or strategy-excluded context renders the typed refusal, never a silent empty state. "We would not assemble this" and "we could not assemble this" are different sentences, and counsel is owed the right one — so a 403 with a known code renders as a refusal, and anything else renders as an error with a retry.
AgentWorkingLine — extracted from the Agent Chat Panel's
ThinkingView so the CCB waits exactly the way the panel does. One §19
waiting state, not two that drift.CcbVerbChip — the shared verb chip; the floating selection bar
(DocumentCounselBar, Story-2168) was migrated onto it, so there is one CCB
with two presentations rather than two bars.useMiraConversationRail / useAgentDockStore — the existing ACP rail and
dock state. Talk opens the ACP; it was not rebuilt.Button / PrimaryButton, Input, Popover, MICRO_TRANSITION,
useTickingSeconds, the Story-2281 section layer.The context gate is real. A real ask on a staging document
(matter 13, HARSHA_VENKATESH_PLAIN.pdf) issued:
POST /api/internal/matters/13/context/bundles/document-selection → 200
request: {"budget":"M","focus":{"document_id":"be39d48c-…","schema_version":"document_focus.v1","section_ids":[]}}
response: {"schema_version":"context_bundle.v1","profile":"document_selection",
"focus_axis":{…echoed…},"engine_state":"live","token_estimate":68,
"version_hash":"5f30c943c72f5858…","sections":[{"kind":"doc_identity",…},
{"kind":"section_summaries","items":[]}]}
A real context_bundle.v1, through the proxy, past
assertContract. section_summaries is empty because
that document is not segmented yet — an honest state, not an error.
The refusal is real. With a document marked privileged, the
same ask returned 403 pramaan.context.privileged_refused and the CCB
rendered "Context withheld — privileged. This document is marked privileged, so
it is never assembled into an agent's context. Nothing was sent, and nothing was
read." No agent turn was fired. (The privilege flag was reverted afterwards.)
Known gap — the agent leg is blocked on staging, and NOT by this
change. The launch leaves the app correctly (matter_id =
the matter UUID per Bug-2210) and the broker answers
run.accepted, then run.failed with
kind=validation_error:
"I could not start this run because required runtime information was
invalid" (retryable=false).
The same failure reproduces from the Agent Dock's own composer — code this ticket does not touch — so Mira's converse run is currently broken on staging independently of the CCB. Filed separately; broker changes are explicitly out of scope here.
The CCB's behaviour under that failure is itself correct and was verified live: it surfaces the honest error card ("That ask didn't get through. Nothing was sent to the record.") rather than spinning forever — Story-2181 / §19's rule that a stalled run must surface, never masquerade as thinking.
119 file(s), +12036 / -668 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +46 | -1 |
| messages/hi.json | Config | +46 | -1 |
| openapi/app-api.json | Config | +1825 | -529 |
| openapi/permission-map.json | Config | +9 | -0 |
| src/app/api/internal/matters/[matterId]/context/bundles/document-selection/route.ts | App route | +90 | -0 |
| src/components/agents/agent-chat-panel.tsx | Component | +13 | -56 |
| src/components/agents/agent-working-line.stories.tsx | Component | +49 | -0 |
| src/components/agents/agent-working-line.tsx | Component | +100 | -0 |
| src/components/documents/ccb/ccb-answer-card.stories.tsx | Component | +140 | -0 |
| src/components/documents/ccb/ccb-answer-card.tsx | Component | +236 | -0 |
| src/components/documents/ccb/ccb-fixtures.ts | Component | +67 | -0 |
| src/components/documents/ccb/ccb-focus-chip.stories.tsx | Component | +33 | -0 |
| src/components/documents/ccb/ccb-focus-chip.tsx | Component | +67 | -0 |
| src/components/documents/ccb/ccb-presentation.test.ts | Component | +173 | -0 |
| src/components/documents/ccb/ccb-presentation.ts | Component | +168 | -0 |
| src/components/documents/ccb/ccb-verb-catalog.tsx | Component | +81 | -0 |
| src/components/documents/ccb/ccb-verb-chip.stories.tsx | Component | +61 | -0 |
| src/components/documents/ccb/ccb-verb-chip.tsx | Component | +82 | -0 |
| src/components/documents/ccb/document-ccb-chassis.stories.tsx | Component | +191 | -0 |
| src/components/documents/ccb/document-ccb-chassis.tsx | Component | +160 | -0 |
| src/components/documents/ccb/document-ccb.tsx | Component | +149 | -0 |
| src/components/documents/ccb/document-focus-context.tsx | Component | +84 | -0 |
| src/components/documents/ccb/use-ccb-ask.ts | Component | +177 | -0 |
| src/components/documents/viewer/document-counsel-bar.tsx | Component | +37 | -73 |
| src/components/documents/viewer/document-original-pane.tsx | Component | +79 | -0 |
| src/components/documents/viewer/document-permalink-shell.tsx | Component | +11 | -1 |
| src/components/documents/viewer/document-reading-surface.tsx | Component | +20 | -1 |
| src/components/documents/viewer/section-mark-popover.tsx | Component | +24 | -1 |
| src/gen/client/contextDocumentSelectionBundle.ts | Generated contract | +79 | -0 |
| src/gen/client/index.ts | Generated contract | +1 | -0 |
| src/gen/hooks/index.ts | Generated contract | +4 | -0 |
| src/gen/hooks/useContextDocumentSelectionBundle.ts | Generated contract | +165 | -0 |
| src/gen/index.ts | Generated contract | +104 | -2 |
| src/gen/schemas/assertion.json | Generated contract | +146 | -0 |
| src/gen/schemas/assertionEdge.json | Generated contract | +80 | -0 |
| src/gen/schemas/assertionKind.json | Generated contract | +15 | -0 |
| src/gen/schemas/buildWatermark.json | Generated contract | +29 | -0 |
| src/gen/schemas/bundleSection.json | Generated contract | +1471 | -0 |
| src/gen/schemas/bundleSectionKind.json | Generated contract | +15 | -0 |
| src/gen/schemas/cardAmount.json | Generated contract | +105 | -0 |
| src/gen/schemas/cardAssertion.json | Generated contract | +95 | -0 |
| src/gen/schemas/cardDate.json | Generated contract | +103 | -0 |
| src/gen/schemas/cardParty.json | Generated contract | +100 | -0 |
| src/gen/schemas/chronologyEvent.json | Generated contract | +107 | -0 |
| src/gen/schemas/citedValueDate.json | Generated contract | +90 | -0 |
| src/gen/schemas/citedValueStr.json | Generated contract | +90 | -0 |
| src/gen/schemas/confirmationStatus.json | Generated contract | +7 | -0 |
| src/gen/schemas/contextBundle.json | Generated contract | +1662 | -0 |
| src/gen/schemas/contradictionItem.json | Generated contract | +163 | -0 |
| src/gen/schemas/datePrecision.json | Generated contract | +7 | -0 |
| src/gen/schemas/documentCard.json | Generated contract | +683 | -0 |
| src/gen/schemas/documentFocus.json | Generated contract | +48 | -0 |
| src/gen/schemas/documentIdentityCard.json | Generated contract | +54 | -0 |
| src/gen/schemas/documentInsightBrief.json | Generated contract | +1 | -1 |
| src/gen/schemas/documentSelectionBundleRequest.json | Generated contract | +64 | -0 |
| src/gen/schemas/edgeType.json | Generated contract | +14 | -0 |
| src/gen/schemas/evidenceHit.json | Generated contract | +95 | -0 |
| src/gen/schemas/matterBriefFrameView.json | Generated contract | +9 | -0 |
| src/gen/schemas/matterBriefView.json | Generated contract | +9 | -0 |
| src/gen/schemas/matterFrameEditRequest.json | Generated contract | +14 | -0 |
| src/gen/schemas/sectionSummaryItem.json | Generated contract | +56 | -0 |
| src/gen/types/Assertion.ts | Generated contract | +67 | -0 |
| src/gen/types/AssertionEdge.ts | Generated contract | +54 | -0 |
| src/gen/types/AssertionKind.ts | Generated contract | +20 | -0 |
| src/gen/types/BuildWatermark.ts | Generated contract | +17 | -0 |
| src/gen/types/BundleSection.ts | Generated contract | +49 | -0 |
| src/gen/types/BundleSectionKind.ts | Generated contract | +20 | -0 |
| src/gen/types/CardAmount.ts | Generated contract | +39 | -0 |
| src/gen/types/CardAssertion.ts | Generated contract | +29 | -0 |
| src/gen/types/CardDate.ts | Generated contract | +39 | -0 |
| src/gen/types/CardParty.ts | Generated contract | +33 | -0 |
| src/gen/types/ChronologyEvent.ts | Generated contract | +44 | -0 |
| src/gen/types/CitedValueDate.ts | Generated contract | +27 | -0 |
| src/gen/types/CitedValueStr.ts | Generated contract | +27 | -0 |
| src/gen/types/ConfirmationStatus.ts | Generated contract | +13 | -0 |
| src/gen/types/ContextBundle.ts | Generated contract | +72 | -0 |
| src/gen/types/ContextDocumentSelectionBundle.ts | Generated contract | +78 | -0 |
| src/gen/types/ContradictionItem.ts | Generated contract | +24 | -0 |
| src/gen/types/DatePrecision.ts | Generated contract | +13 | -0 |
| src/gen/types/DocumentCard.ts | Generated contract | +71 | -0 |
| src/gen/types/DocumentFocus.ts | Generated contract | +34 | -0 |
| src/gen/types/DocumentIdentityCard.ts | Generated contract | +49 | -0 |
| src/gen/types/DocumentSelectionBundleRequest.ts | Generated contract | +26 | -0 |
| src/gen/types/EdgeType.ts | Generated contract | +19 | -0 |
| src/gen/types/EvidenceHit.ts | Generated contract | +35 | -0 |
| src/gen/types/MatterBriefFrameView.ts | Generated contract | +9 | -0 |
| src/gen/types/MatterFrameEditRequest.ts | Generated contract | +8 | -0 |
| src/gen/types/SectionSummaryItem.ts | Generated contract | +76 | -0 |
| src/gen/types/index.ts | Generated contract | +54 | -2 |
| src/gen/zod/assertionEdgeSchema.ts | Generated contract | +54 | -0 |
| src/gen/zod/assertionKindSchema.ts | Generated contract | +25 | -0 |
| src/gen/zod/assertionSchema.ts | Generated contract | +48 | -0 |
| src/gen/zod/buildWatermarkSchema.ts | Generated contract | +23 | -0 |
| src/gen/zod/bundleSectionKindSchema.ts | Generated contract | +25 | -0 |
| src/gen/zod/bundleSectionSchema.ts | Generated contract | +56 | -0 |
| src/gen/zod/cardAmountSchema.ts | Generated contract | +36 | -0 |
| src/gen/zod/cardAssertionSchema.ts | Generated contract | +30 | -0 |
| src/gen/zod/cardDateSchema.ts | Generated contract | +39 | -0 |
| src/gen/zod/cardPartySchema.ts | Generated contract | +36 | -0 |
| src/gen/zod/chronologyEventSchema.ts | Generated contract | +34 | -0 |
| src/gen/zod/citedValueDateSchema.ts | Generated contract | +23 | -0 |
| src/gen/zod/citedValueStrSchema.ts | Generated contract | +23 | -0 |
| src/gen/zod/confirmationStatusSchema.ts | Generated contract | +17 | -0 |
| src/gen/zod/contextBundleSchema.ts | Generated contract | +63 | -0 |
| src/gen/zod/contextDocumentSelectionBundleSchema.ts | Generated contract | +94 | -0 |
| src/gen/zod/contradictionItemSchema.ts | Generated contract | +34 | -0 |
| src/gen/zod/datePrecisionSchema.ts | Generated contract | +17 | -0 |
| src/gen/zod/documentCardSchema.ts | Generated contract | +85 | -0 |
| src/gen/zod/documentFocusSchema.ts | Generated contract | +40 | -0 |
| src/gen/zod/documentIdentityCardSchema.ts | Generated contract | +45 | -0 |
| src/gen/zod/documentSelectionBundleRequestSchema.ts | Generated contract | +25 | -0 |
| src/gen/zod/edgeTypeSchema.ts | Generated contract | +24 | -0 |
| src/gen/zod/evidenceHitSchema.ts | Generated contract | +35 | -0 |
| src/gen/zod/index.ts | Generated contract | +36 | -0 |
| src/gen/zod/matterBriefFrameViewSchema.ts | Generated contract | +8 | -0 |
| src/gen/zod/matterFrameEditRequestSchema.ts | Generated contract | +7 | -0 |
| src/gen/zod/sectionSummaryItemSchema.ts | Generated contract | +52 | -0 |
| src/lib/documents/gateway.ts | Library | +41 | -0 |
| src/lib/kubb-client.ts | Library | +12 | -0 |