data-chat-mini: MotherDuck guides as the context engine (+ guide manager UI)#79
data-chat-mini: MotherDuck guides as the context engine (+ guide manager UI)#79matsonj wants to merge 5 commits into
Conversation
…e manager UI) Replaces the IndexedDB context-layer placeholder with the MotherDuck guide subsystem as the real context engine, and adds human-driven guide management. Context engine (read + write via MCP guides): - Allowlist guide-write tools (create/update/edit_guide_content) with a personal-only guard (assertGuideWriteAllowed); data stays read-only. - System prompt switches from the local query_context_layer Step-0 protocol to the guide protocol: read guides.md first; persist durable learnings as small personal guides under users/<username>/. - Chat route stops advertising the local CONTEXT_TOOLS; guide tools dispatch like any MCP tool. The legacy round-trip plumbing stays only for the demo harness pending a re-record (docs/context-layer-guide-migration.md). Guide panel + manager: - New /api/guides route: list/get + POST(create), PATCH(update_guide + set_guide_access + update_guide_metadata, per-step errors), DELETE, ?version=N. - Sidebar "Guides" panel renders MCP guides; clicking a card opens a centered markdown popover (no chevron) with view / raw-markdown edit / create / version history / promote-to-org / delete / opt-in references editor. Notes / known limits: - The app's read-scaling token can edit existing guides and promote-to-org, but create + demote-to-personal need a username claim it lacks — fix is a user-scoped/OAuth token (integration-plan open question). - Demo validation kept green on the existing transcript; full guide-based re-record deferred (see docs/context-layer-guide-migration.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| `${name}: this app may only write personal guides — set access:"user" (org-wide guides are admin-only).`, | ||
| ); | ||
| } | ||
| if (name === 'create_guide') { |
There was a problem hiding this comment.
[P1] Enforce the personal-guide boundary for every model-visible guide write. update_guide and edit_guide_content are now allowlisted for the agent, but this guard only checks the path when name === "create_guide"; an update/edit call can still target revenue-billing/foo.md or any other org guide. The PR notes that this app token can edit existing guides, so relying on MCP-side ownership is not a safe backstop here. Please require update/edit targets to resolve to users/<current-user>/... (and reject or resolve id targets before dispatch), or keep these tools out of the model allowlist.
There was a problem hiding this comment.
Fixed in cef565c. assertGuideWriteAllowed now guards every guide-write tool (create/update/edit_guide_content), not just create: it requires a users/… path and rejects id-based targeting (can't be namespace-checked pre-dispatch). An update_guide/edit_guide_content aimed at revenue-billing/foo.md or an opaque id is now rejected before dispatch, so we no longer lean on MCP-side ownership for org guides.
| name: string, | ||
| args: Record<string, unknown>, | ||
| ): Promise<{ ok: true; data: Record<string, unknown> } | { ok: false; error: string }> { | ||
| const { text, isError } = await executeToolWithStatus(client, name, args, true); |
There was a problem hiding this comment.
[P1] Add route-level authorization before using the internal MCP bypass. Every browser request to this route runs with the server MotherDuck token, but PATCH can call set_guide_access / update_guide_metadata and DELETE can call delete_guide for an arbitrary path; the UI also exposes promote/delete for org guides. With no app auth or role check in front of this route, any app visitor can mutate or soft-delete shared guide content if the token has that permission. Please gate mutating verbs to the current user personal namespace, or require an authenticated admin path before calling internal guide-write tools.
There was a problem hiding this comment.
Fixed in cef565c. Added a route-level rejectNonPersonal(...) gate that runs before the internal MCP bypass on every mutating verb — POST, PATCH (incl. set_guide_access / update_guide_metadata and the newPath rename target), and DELETE. Non-users/… paths get a 403 with no MCP call. Verified live: DELETE/promote on revenue-billing/awr-definition.md and a rename into revenue-billing/ all return 403. (The app itself still relies on deployment-level protection for general access; this confines the blast radius to the personal namespace so shared org guides can't be mutated/deleted from the app. Org-guide management needs an admin/OAuth path out of band.)
| // Personal-guide sandbox: block org-wide / non-users writes before dispatch. | ||
| assertGuideWriteAllowed(name, args); | ||
| const result = await client.callTool({ name, arguments: args }, undefined, requestOptions); | ||
| if (result.structuredContent != null) { |
There was a problem hiding this comment.
[P2] Wire guide-write failures into the chat dispatch failure detector. executeToolWithStatus will stringify structured { success: false, error } payloads from create_guide, update_guide, and edit_guide_content, but detectPayloadFailure still only recognizes the old context/dive write tools. In chat, the documented no authenticated username create failure would currently produce a successful tool_end, refresh the guide panel, and let the model tell the user that durable context was saved when nothing landed. Please add the guide write tool names to that detector and cover one false-success case in tests.
There was a problem hiding this comment.
Fixed in cef565c. Added create_guide / update_guide / edit_guide_content to SUCCESS_FIELD_TOOLS, so detectPayloadFailure now catches their { success:false, error } payloads. The no authenticated username create failure now produces an error tool_end (and the panel-refresh is gated on !tc.error, so it no longer fires on failure). Added lib/tool-invocation.test.ts covering the false-success case + the passthrough cases.
…namespace + detect write failures
- assertGuideWriteAllowed now guards EVERY guide-write tool (create/update/
edit_guide_content), not just create: requires a users/… path and rejects
id-based targeting, so the model can't edit org guides. (P1)
- /api/guides gates all mutating verbs (POST/PATCH/DELETE, incl. set_guide_access,
update_guide_metadata, delete_guide, and rename newPath) to the personal
namespace before the internal MCP bypass — 403 otherwise. Closes the hole
where any app visitor could mutate/delete shared org guides. (P1)
- detectPayloadFailure recognizes create_guide/update_guide/edit_guide_content,
so a { success:false } write (e.g. "no authenticated username") surfaces as a
real tool error instead of a false success + spurious panel refresh. Added
lib/tool-invocation.test.ts covering the false-success case. (P2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| } | ||
|
|
||
| if (typeof body.access === 'string') { | ||
| const res = await callWrite(client, 'set_guide_access', { path, access: body.access }); |
There was a problem hiding this comment.
[P1] Block org visibility changes from the unauthenticated guide route. The new guard confines the path to users/..., but PATCH still forwards access: "organization" to set_guide_access for those personal paths, and the sidebar still exposes “Promote to org-wide”. That means any app visitor who can hit this route can publish guide content into the org-wide context layer with the server token, even though the route comment says org guides are read-only from the app. Please reject access: "organization" here (and hide/disable the promote action) until there is an authenticated admin/OAuth path for org-guide management.
There was a problem hiding this comment.
Fixed in f67d603. PATCH /api/guides now rejects access:"organization" with a 403 before any MCP call (demote to "user" — de-escalation — is still allowed). The sidebar "Promote to org-wide" control is disabled (kept visible with a tooltip pointing at the admin/OAuth path) and the setAccess handler is gone. Added app/api/guides/route.test.ts covering the org-promote block, the non-personal-path block, and the rename-into-org block. Verified live: PATCH {path:"users/matson/nba/nba-box-scores-v2.md", access:"organization"} → 403, guide still access:user.
…d route
The personal-namespace path gate still allowed PATCH { access: "organization" }
on a users/… guide, so any visitor could publish personal guide content into the
org-wide context layer.
- /api/guides PATCH now rejects access:"organization" with 403 before any MCP
call (demote to "user" still allowed). Added route tests for the org-promote
block, the non-personal-path block, and the rename-into-org block.
- Sidebar popover disables the "Promote to org-wide" control (kept visible with a
tooltip) and drops the now-unreachable setAccess handler. Org visibility needs
an authenticated admin/OAuth path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
✅ Approved — holding merge until the guides subsystem is live on the prod MotherDuck MCP. This branch targets staging, where
No code changes pending; safe to merge as-is once (1) lands. |
Replaces the IndexedDB context-layer placeholder with MotherDuck guides as the real context engine, adds the guide manager UI, and prepares the app for the production MCP rollout.
Production launch state
get_guide,list_guides, the full guide lifecycle) pluslist_viewsandlist_macros.https://api.motherduck.cominstead of staging.npm run mcp:validatechecks all 16 production tools the app consumes, their required input fields, and liveget_guide("guides.md")/list_guidesreads.Tool create_guide not foundfor it; deployment was rolled back pending the entitlement/token fix.Context engine
get_guide("guides.md")before SQL and uses relevant org/personal guides as durable context.create_guide,update_guide, andedit_guide_content, guarded to personalusers/<username>/...paths.query_rwis not advertised or dispatched.isErrorand{ success: false }guide-write failures.Guide panel and manager
/api/guidessupports list/read/version history and guarded personal create/update/delete operations.Verification
npm test: 91/91npm run typecheck: cleannpm run lint: cleannpm run build: clean on Node 24.12.0npm run audit: 0 vulnerabilitiesmotherduckdb/mono@9ec82b57; a protected production deploy/smoke test confirmed the current Vercel PAT lacks guide tools, and the alias was restored todpl_6wkP2vawYt7cJoWSYcjQsbCfpM4Q.Deferred
docs/context-layer-guide-migration.md.