Design documents for Overslash, migrated from the Overfolder workspace repo.
The live product spec is at SPEC.md. These design docs capture the original planning and alternatives considered.
Compliance assessments are not design docs and live at docs/compliance/ — currently the CASA gap assessment for Google restricted-scope verification.
| Document | Status | Summary |
|---|---|---|
| overslash.md | Not Implemented | Core gateway design — identity hierarchy, secrets, OAuth, permissions, approvals, service registry |
| nango-integration.md | Superseded | Nango evaluation — superseded by Overslash owning OAuth natively |
| audit-log.md | Implemented | Audit log expansion — IP capture, full CRUD coverage, filtered queries |
| large-file-handling.md | Implemented | Large file handling — response size limits + streaming proxy (prefer_stream) |
| mcp-integration.md | Superseded | Original MCP design — dual-key inline-approval flow over stdio. Dual-key + mcp setup portions superseded by mcp-oauth-transport.md; the white-label and CLI-priority discussion still applies. |
| mcp-oauth-transport.md | Approved | MCP over Streamable HTTP at POST /mcp with OAuth 2.1 Authorization Server endpoints. overslash mcp reshaped into a stdio↔HTTP compat shim; mcp setup replaced by mcp login. |
| user-stories.md | Draft | End-to-end user stories: OpenClaw direct enrollment, corporate MCP usage, Overfolder/Telegram platform-mediated flow |
| agent-self-management.md | Draft | Future shape for agent self-management via MCP — metaservice bridge for service/template creation, self-vs-downstream approval split, identity-scoped list_secrets, Claude Code permission-rule composition |
| agent-mcp-bootstrap-story.md | Draft — partially implemented | End-to-end agent story: OpenAPI → template → service → OAuth → first call, all over MCP. Pins down the metaservice-bridge gap and lays out PR 1–6 to close it. |
| mcp-elicitation-approvals.md | Adopted (Flow A, default-on) | Approvals surface as elicitation/create for clients that declare the capability. Safe because an unanswered dialog is not a denial: only decline denies, everything else retires the row and returns the byte-identical pending_approval envelope, so URL-reject is the substrate rather than the alternative. Re-prompting is bounded by a per-agent post-cancel cooldown. Storage records only the explicit opt-out (migration 123), because capabilities are always unknown at consent time and a capability-gated default would never fire. Flow B (tasks) still rejected — not declared by any client. URL mode is client-side available: Codex 0.157.0 offers it on 2025-06-18 already, Claude Code 2.1.282 only on 2026-07-28 — which /mcp now also speaks (D103), approval dialogs there being multi round-trip requests (input_required + signed requestState). URL mode is wired on both eras (D104): auth / credential / connect / opted-out-approval links go through the client and the call finishes when the browser flow does; a link that is not completed returns the original body with a url_elicitation note. Codex works interactively; headless codex exec auto-declines elicitations (openai/codex#45621), and D95 reads a decline as a real denial — a narrow hazard Overslash cannot detect, since the two surfaces are identical at initialize. Probe at test-mcp-elicitation/ — note it cannot reach 2026-07-28, so its URL-mode result is not falsifiable. |
| directory-group-sync.md | Implemented | Automatic group provisioning from an external directory — an OIDC groups claim mirrored into directory_groups at sign-in, or a Google Workspace Directory pull (delegated service account; at sign-in, on a schedule, and on demand), conferring Layer 1 membership only through an admin-drawn group_directory_sources edge. Records why directory groups are their own object rather than a fourth groups class, and why an absent claim must not read as an empty one. |
| multi_org_auth.md | Draft | Multi-org per user — global users table, per-org IDP trust domains, subdomain routing (<slug>.app.overslash.com), /auth/switch-org, org creator is a regular admin (no flag), self-hosted SINGLE_ORG_MODE / ALLOW_ORG_CREATION flags |
| external-mcp-services.md | Shipped | External MCP servers as first-class Overslash services — x-overslash-runtime: mcp, tools/list resync, bearer/none auth, executor envelope, disabled tool gating |
| async-execution.md | Shipped | Async (non-blocking) action calls — execution: "async", 202 accepted, claim-and-lease worker on an extended executions row, and the gated path that queues an approved replay instead of dialling it. Records why the Cloud Run cpu_idle reading is inverted and why scale-in, not CPU, is the binding constraint. |
| platform-runtime.md | Implemented | Runtime::Platform — in-process dispatch for agent self-management; kernel functions, PlatformHandler trait, permission anchor mapping, agent template-authoring loop |
| kill-mode-b.md | Done | Removed connection: <uuid> action calls; implemented SPEC §8 "Service + HTTP verb" as the replacement; closes a host-binding gap (DECISIONS D14). |
| agent-credential-provisioning.md | Draft | Agent/PAI-driven credential provisioning — oauth_client_missing typed error, generalize secret_requests→credential_requests (kind discriminator), request_oauth_client MCP action, white-label delegated-backend BYOC collection. Scoped to per-identity BYOC; org-level OAuth deferred. §6 (2026-07) reconciles with the token-vault model: for import-path partners the trigger and redirect URI are partner-side (Overfolder → structured-secret-requests.md); prioritizes BYOC replace/upsert + partner metadata tagging on byoc_credentials ahead of the MCP track. |
| white-label-token-vault.md | Approved — implemented | White-label OAuth as a token vault — the partner runs the OAuth dance and POST /v1/connections/imports the tokens (BYOC pin required); overslash stores + self-refreshes + executes. Headless orgs (orgs.headless) get URL-less auth-recovery envelopes (no gated link/flow row). Removes the integration_managed flag (migrations 084/085). Reverts the per-request redirect_uri (#388/#392) and per-org oauth_redirect_url (#398); removes /v1/oauth/exchange, include_raw, and oauth_connection_flows.redirect_uri. |
| metabase-mcp.md | Shipped (D42/D43) | Metabase MCP install/exercise notes → became services/metabase.yaml + the SQL content policy. Shipped shape (D43): one x-overslash-sql-field: <dotted-path> annotation (string params placed at the path, object params descended into) + jq x-overslash-sql-database → sql_databases config (dialect + safe_functions; the DB label comes from the resolver's scope:, keyed as {name,id} so either spelling grants); risk: dynamic classified per call via pg_query/libpg_query behind the default-off sql_policy feature (release builds enable it); per-table keys + sentinel, column=/column_star= deny screen; column masking still pushes to Metabase/DB. Live harness: docker/metabase/ + Pagila, make metabase-e2e. |
| hubspot-resty-tools.md | Superseded by subactions.md | Scoped/monotype tools over MCP-wrapped services — one tool per (object-type × operation) layered on the raw tools. Tier 1 (name-alias via mcp_tool + param default) works today; Tier 2 adds x-overslash-transform (a jq params→arguments rewrite at the resolve.rs seam) for true REST shapes. Availability-gated verb sets; raw tools stay as escape hatch. Draft spec: hubspot-resty-draft.yaml. |
| subactions.md | Implemented | x-overslash-subactions: variants of one overloaded action (HubSpot manage_crm_objects), classified per item by jq (each + match). Each variant has its own description, risk, permission key (svc:parent/variant:arg, covered by a parent rule), disclose and redact; descriptions compose per item with {item.*}, {count}, {enumerate(…)}. Opt-in callable variants (input_schema + transform) rewrite into the parent and classify identically. Supersedes the Tier-2 x-overslash-transform proposal. |
| policy-engine-mode.md | Draft — exploratory | External-execution services: a template property (x-overslash-execution: external) that lets callers (computer-use harnesses, external SaaS) reuse Overslash's permissions/approvals/audit without proxying. Status-discriminated decided response on POST /v1/actions/call plus an attestation endpoint — one fork at the executor in the existing pipeline, not a parallel mode. |
| layered-service-templates.md | Implemented | Unifies forks and catalog overlays into one layer primitive: a service_templates row with extends (NULL=standalone full doc, set=derived delta over a base) resolved by a recursive fold (resolve(layer)=apply(delta, resolve(extends))). Masks (allowlist/denylist, clamp-up risk, disclose, relabel) are monotonic/order-independent so containment is structural; extensions (add actions/hosts, no auth, no rebinding) are bounded; extends is a live pointer (tracks upstream). Authority is namespace-based (user_template_policy: none|restrictive|full); classifier + scope constraints deferred but reserved. Supersedes the org-catalog-overlay draft. |
| mcp-enrollment-org-scoping.md | Implemented | Guarantee that <org>.{app,api}.overslash.com/mcp onboards agents only into <org>. Today pending.org_id comes from the session, not the subdomain, and DCR clients are org-global — an authenticated cross-org session can divert enrollment. Fix: derive enrollment org from ctx, re-auth on session/subdomain mismatch, scope the fast-path binding by org, and stamp org_id on oauth_mcp_clients. |
| email-integration.md | Implemented (Inline core) | Email send + on-demand read across the standard-IMAP long tail via overfwd, a separate MIT OSS Mailbox Gateway (overspiral/overfwd) presenting a REST facade over IMAP/SMTP. No native mail transport in core; Overslash uses Inline (credential-free) mode via services/email.yaml. Gmail on REST, Graph a later track; Yahoo rides the gateway (no REST API exists, app-password IMAP works — see D37); own-inbox/real-time-inbound deferred. Core changes shipped: SecretRef base64 encode (x-overslash-encode) + generic multi-injection (x-overslash-secret_source: instance|org) + per-instance url override on HTTP Mode C. See D28. |
| credential-templates.md | Implemented | Credential slots (components.x-overslash-secrets) declared once and composed into injections by a jq template (x-overslash-template) — one secret may feed several headers, one header may join several secrets (email.yaml's user:pass). Removes x-overslash-prefix/-encode. Which slots an expression reads is resolved statically from jaq's AST at compile time, so the send path decrypts exactly those; dynamic key access is rejected. Covers the two evaluator hazards: jq errors quote secrets, and "user" + null silently truncates a credential. See D35. |
| event-stream.md | Implemented | GET /v1/events/stream — SSE with a fixed 30s connection ceiling and Last-Event-ID resume (SPEC §10). Durable events table whose BIGSERIAL id is the cursor; an AFTER INSERT trigger pg_notifys just that id and each replica's PgListener republishes onto a local tokio::broadcast, so no Redis and one delivery path. Visibility is a frozen audience uuid[] mirroring mine/assigned/actionable — deliberately narrower than the un-gated GET /v1/approvals. One services::events::emit seam feeds the stream and webhooks (replacing all 8 dispatch() sites); adds secret_request.*. Dashboard drops to stream-driven updates with polling as fallback. See D45. |
| widget-sdk.md | Approved — in progress | @overslash/sdk — the JS SDK third parties embed to surface approvals, secret requests and connects in their own UI. One package at sdk/, zero runtime deps, subpath exports: headless client + controllers (ports of resolution.svelte.ts, format.ts, oauth-connect.ts) and custom elements themed by --overslash-* custom properties and ::part, no framework bindings. Three auth modes — apiKey (+X-Overslash-As), host-proxy transport (works today), and short-lived aud=widget tokens (backend follow-up, specified here). Realtime is a fetch-based SSE parser over D45's stream (EventSource can't set Authorization; owning the cursor makes reconnect lossless) with polling as fallback. See D46–D50. |
| generic-pagination.md | Implemented | One vocabulary for paging over the eight spellings of page size and six of continuation the shipped corpus carries: x-overslash-pagination names the parameter that bounds a page (seeding its own default: so apply_defaults — the mechanism that already existed — puts it on the wire) and how to reach the next one (cursor / offset / page / link — a whole next URL, from the RFC 8288 header or a dotted body path). A paged result carries a uniform _pagination.next holding the difference between the call just made and the next one, in the action's own parameter names. Read from the raw body and headers, upstream of both the jq filter and the compact render. The gateway never follows a page. Swept onto the whole corpus, with a gate that keeps it swept, and extended to the MCP runtime by unwrapping the result envelope. Builds on D74. |
| service-setup-links.md | Approved — implemented | Service setup links and the credential probe (D83). x-overslash-test nominates one read action per template as its credential probe; POST /v1/services/{id}/test runs it through the ordinary call path — no approval bypass — and returns a verdict carrying status, latency and the upstream error but never the body. create_service auto-mints a signed setup link for every unbound per-instance slot (setup.setup_url), the twin of the OAuth connect.auth_url it already returns; migration 118 adds service_instance_id + credential_key to secret_requests so fulfilling one writes the vault secret and binds the slot. Records why the columns are additive rather than the credential_requests table agent-credential-provisioning.md proposes, and why the public Test button requires a session. Extended by D86: the verdict now gates the instance rather than describing it — a setup-created service is written status: pending_setup, uncallable and invisible to search, until POST /v1/services/{id}/activate gets a green verdict; setup links always require a session, because an anonymous fulfilment can never produce one; and unverified instances are swept after ~24h. |