Expose scoped Context source withdrawal over HTTP and MCP
Summary
Exposes scoped source withdrawal as the shared Context operation withdraw_source over HTTP (POST /api/context) and MCP (withdraw_music_context_source). It is the first lifecycle mutation reachable through both transports.
- Input.
request_idplus exactly one ofsource_idorsource_version_id. Version IDs are what callers can already see on main, assourceVersionIdsin brief and saved-brief input manifests. After the existing actor/workspace authorization, the operation calls the newwithdraw_context_request_sourceRPC withp_actor,p_owner,p_request,p_source,p_source_version. The database rechecks membership, resolves a version to its source, requires that source to be a recorded input of the request, and reuses the existing withdrawal primitive. - Receipt (
context-source-withdrawal-v1). Returns the resolvedsource_id,withdrawn_at,already_withdrawn, andaffectedlineage (results, documents, request_ids). The counts are identical on replay. - Effect. Future authorized reads and briefs exclude the withdrawn evidence. Dependent saved briefs read back
unavailable. The withdrawal is sticky for that workspace, source kind and URL: the database migration fences late and in-flight writers, so an older job cannot republish it. Results are relabelledwithdrawn(rows kept), and original files are not deleted. - MCP annotations. There is no restore, so Context operations can now carry
destructive/noticethrough delegated OAuth registration.withdraw_music_context_sourcenow advertisesdestructiveHint: truewith a "Cannot be undone" notice. All other Context operations keepdestructiveHint: false, and the catalog test asserts both.
New SRP files hold the schema, receipt type and operation. Shared files receive single entries: the schema spread, one delegation line, one RPC allowlist entry, one MCP tool entry, one description sentence, and a 3-line policy pass-through in registerFullOAuthTools.ts. The README subsection is appended at the end.
Refs recoupable/app#2124 (child of epic recoupable/app#2116).
Merge sequencing
docs → database → api → app. Sibling PRs on branch claude/ctx-2124-source-withdrawal:
- recoupable/docs#364 (OpenAPI variant);
- recoupable/database#102 (migration
20261010212400_context_source_withdrawal.sql). It now takes five parameters; this PR fails at runtime until that migration is applied.
This PR does not depend on any open PR. Callers find version IDs in brief manifests on main, and api#985 + database#92 would later add a read-only per-request source listing.
Known rebase interactions with open sibling PRs:
- Ticket #2125 PRs (docs#361, api#1005) add "source withdrawal … not exposed yet". Whichever merges second must drop that clause.
- api#1011 (#2120 ownership parity across every action) will need a
withdraw_sourcecase. fullOAuthCatalog.test.tsmoves 84 → 85 tools, so any sibling that adds a tool needs the same one-line rebase.
Verification
pnpm exec vitest run lib/context: 69 files passed, 8 skipped; 336 tests passed, 8 skipped, 0 failed (the skipped tests are live tests).- Targeted:
sourceWithdrawalOperation.test.ts,sourceWithdrawalTransports.test.ts, newlib/supabase/context_requests/__tests__/sourceWithdrawalRpc.test.ts,fullOAuthCatalog.test.ts,contextToolOperations.test.ts: 5 files, 14 tests passed. pnpm exec vitest run lib/supabase/context_requests lib/mcp/oauth: 22 files and 89 tests passed.fullOAuthRouting.test.tsfails to load because the gitignored build artifactlib/mcp/extension/generated-ui.tsis missing (pre-existing; same on main). I added a temporary stub of that file and deleted it afterwards. With the stub,vitest run lib/context lib/mcp lib/supabase/context_requestsgave 533 passed, 8 skipped, 1 failed;fullOAuthRouting.test.tspasses. The one failure isregisterRecoupExtension.test.ts, which asserts real generated UI copy against the stub.pnpm exec tsc --noEmit: 199 errors, none in the 12 changed .ts files. All are pre-existing in untouched files (same count as the review baseline).pnpm exec eslint <12 changed .ts files>: 0 problems.pnpm exec prettier --check <same> lib/context/README.md: clean.- Red → green:
- Reverting the
callContextRpcallowlist entry fails the new adapter test. Before this change, no test covered it. - Reverting the destructive pass-through to
destructive: falsefailsfullOAuthCatalog.test.ts. - The new version-ID and exactly-one cases fail on the previous schema.
- The original 8 tests failed before the operation existed.
- Reverting the
- Not verified: hosted deployment, live providers, production database, a real MCP client against the hosted server,
pnpm build(not run by rule).
Out of scope / known gaps
- No restore/un-withdraw operation (hence the destructive hint).
- Scoped corrections, refresh and recompute (
correct_context_spotify_releasestill has no API caller). - Duplicate-article corroboration.
- Customer deletion/retention of original bytes (database#94/#95 + api#986–#991).
- The shared HTTP 409 message still mentions retrying ingestion with the same idempotency key. The docs now say
withdraw_sourcecan be retried unchanged. - Workflows, paid provider calls, credits, and any chat UI for discovering or withdrawing sources.
🤖 Generated with Claude Code