Dibbla Docs Get started Guides Workflows Changelog
Open Console
Reference

Platform capability contract

The OAuth scopes, capability states and error codes the Dibbla platform connector is built on — rendered from the contract itself, so this page cannot say something the contract does not.

The state column is the availability answer

Every table on this page is rendered from a vendored copy of the canonical platform capability contract; the copy’s version and digest are recorded in its contract.lock. Read the state column literally: a capability marked not-yet-available is not reachable remotely. An active /platform grant exposes only the remote capabilities covered by its scopes — see MCP server for the endpoint and authentication model.

The contract is the one answer to what can Dibbla do remotely, and under what authority. It is the same answer at four surfaces — the API, the CLI, MCP and this site — because each of them renders its copy of the same data and each fails its own build when that copy drifts. This page is the docs surface. It has no hand-written scope names anywhere; a build gate refuses them.

Scopes

An OAuth client asks for scopes; a user consents to them; a token carries them. Three rules the tables below encode: a scope never implies another (there is no tree, only the flat list), a static API token can never hold a write scope, and initial consent defaults to the read scopes only.

Scope Class What it grants First public toolset Consent default Static token
platform:identity:read read Read the granted user and organization. Returns only the granted organization; it can never be used to reach another. yes yes yes
platform:apps:read read List and inspect applications in the granted organization. yes yes yes
platform:apps:write write Create and update applications. Routine idempotent writes only. yes — —
platform:apps:restart narrow-write Restart an application. Deliberately not implied by platform:apps:write. yes — —
platform:apps:delete destructive Delete an application. Requires a bound confirmation in addition to the scope. yes — —
platform:deployments:read read Read deployments and their status. yes yes yes
platform:deployment-proposals:write write Create and inspect agent-authored deployment proposals. Approve/deny/apply is absent from the toolset: P-0034 four-eyes review stays in console and CLI. yes — —
platform:deployments:execute narrow-write Start a deployment: upload a source archive through a principal-bound intent and deploy it. Never substitutes for platform:apps:write, and never carries the authority to approve a deployment that this organization's governance held for review. yes — —
platform:deployments:preflight read Validate a dibbla.yaml manifest and preview what a deployment would resolve to, without building or applying anything. Pure inspection: it changes no state and reads none. yes yes yes
platform:logs:read read Read bounded application log windows. yes yes yes
platform:checks:read read Read an application's checks: definitions, runtime enablement and run history. yes yes yes
platform:checks:write write Enable or disable an application's scheduled checks. yes — —
platform:checks:execute narrow-write Run an application's defined checks now. Deliberately not implied by platform:checks:write, and it can only run checks the application already defines. yes — —
platform:secrets:metadata:read read Read secret names and metadata. There is no scope that reads a secret value, by construction. yes yes yes
platform:secrets:write write Set and rotate secrets. Always produces an audit record. yes — —
platform:databases:read read Read organization-owned database inventory and bounded status metadata. This scope is explicitly metadata-only and never grants row access. yes yes yes
platform:databases:rows:read read Read bounded rows through the server-owned structured SELECT surface. Never accepts SQL; catalog allowlists, 100 rows, two seconds, 256 KiB, a SELECT-only role and shape-only query audit are mandatory. yes — —
platform:databases:write write Provision managed databases as durable idempotent operations. yes — —
platform:databases:restore destructive Restore a database from an organization-bound ready immutable file object. Destructive: an existing database of that name is overwritten. Requires a bound confirmation in addition to the scope, and is deliberately not implied by platform:databases:write. yes — —
platform:databases:delete destructive Delete a managed database. Requires a bound confirmation. yes — —
platform:storage:read read Read object-storage bucket inventory and metadata. yes yes yes
platform:storage:write write Create and update buckets and bucket-scoped credentials. yes — —
platform:storage:rotate narrow-write Rotate bucket credentials. Deliberately not implied by platform:storage:write. yes — —
platform:storage:delete destructive Delete a bucket or its contents. Requires a bound confirmation. yes — —
platform:workflows:read read Read workflows, revisions and run history. yes yes yes
platform:workflows:write write Create and update workflow definitions. yes — —
platform:workflows:execute narrow-write Execute a workflow at a pinned immutable revision. Deliberately not implied by platform:workflows:write. yes — —
platform:files:read read Read file metadata and issue principal-bound download tickets. Never returns an internal presign. yes yes yes
platform:files:write write Issue principal-bound upload tickets and register files. yes — —
platform:files:delete destructive Delete a file. Requires a bound confirmation. yes — —
platform:operations:read read Poll a durable operation by its principal-bound operation ID. The ID is data, never authentication. yes yes yes
platform:operations:cancel narrow-write Cancel a durable operation. Deliberately not implied by any write scope. yes — —
platform:workflows:delete destructive Delete a workflow, or permanently purge its run history. Separate from platform:workflows:write because a broad write must never reach a deletion, and both are reached only through a plan a human approves. yes — —
platform:feedback:write write Send product feedback about Dibbla, read the organization's feedback, and withdraw a message you sent. yes — —
platform:tools:execute narrow-write Reserved since DIB-827; the mount it gated was retired by DIB-853. Calling an exposed function (platform_tools action=invoke) follows platform:workflows:read: an organization's exposure policy (registered AND exposed AND role >= min_role, re-checked by workflow-server on every invoke and recorded per call) is the authorization, and a second OAuth gate on top of it only kept scope-less clients out. Kept in the registry so the narrow-write rule list and the history still resolve. Reserved: DIB-827: calling an exposed function is gated by platform:workflows:read, the scope that lists it; exposure policy in workflow-server is the authorization. DIB-853 retired the /platform/tools mount and left this as it was. Not granted, not advertised, never on the consent page. — — —
platform:tools:manage write Expose or unexpose the organization's registered functions as tools on the connector, and set the minimum role that may call each. Consent is one gate; the organization role is the other — workflow-server refuses anyone below admin whatever the grant holds. yes — —
platform:servers:use narrow-write Use one tool server's tools through its own MCP address, /platform/servers/<name> (DIB-1242). The only scope a per-server resource grants: the token's audience is the address, so it lists and calls the functions of that one server — the ones the address's policy opens to the caller — and nothing else on the platform. Calling a function is an action the organization records, which is why it is narrow-write and never a consent default. Reserved: DIB-1242: granted only for a per-server resource, never by /platform. /platform's resource metadata, scope list, WWW-Authenticate challenge and consent page never name it; auth-service grants it only when the RFC 8707 resource is a server's address, and a token holding it verifies on that address alone. — — —

Grant profiles

A scope answers what may this token do. A grant profile answers which of those scopes may this person’s connector hold at all: it is a named scope set, assigned to a principal — an organization and a user — as configuration rather than as code. A connector that asks for more than the assigned profile holds is granted what the profile holds; asking for more is never an error the person has to understand, and never a way to get more.

Two properties matter more than the mechanism:

  • A profile is not keyed to an OAuth client. A hosted client re-registers through dynamic client registration and comes back with a new client_id; the person behind it is the same person, and the assignment still holds.
  • A profile only ever narrows. It cannot widen the contract: a scope has to exist in the registry and be granted by the first public toolset before a profile may name it, and a write still has to be requested by the client and turned on by the person on the approval page. Removing an assignment applies on the next call — there is no cache window.

The read profile is the contract’s consent defaults, which is what every connector had before profiles existed. A write profile adds the reversible writes: the rows marked live below. Delete, restore and deployment execution are in no profile.

Capabilities by state

Each relevant platform operation has exactly one state. Operations that shipped before the connector — the community and support toolsets — are listed under their real state with their real auth; they are not part of the OAuth connector’s directory.

remote-read · 40

Exposed remotely, no side effects. Cannot acknowledge, refresh, rotate or mark-read as a hidden convenience.

CapabilityWhat it doesAuthIn productionScopesNotes
mcp.community_whoami
/community · community_whoami
Identify the calling Dibbla user in the communitystatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.community_search
/community · community_search
Search community postsstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.community_topics
/community · community_topics
List community topicsstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.community_read_topic
/community · community_read_topic
Read one community topicstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.community_notifications
/community · community_notifications
Read the caller's community notificationsstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.support_tickets
/support · support_tickets
List support tickets for an appstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.support_read_ticket
/support · support_read_ticket
Read one support ticketstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.notifications_list
/notifications · notifications_list
List platform notificationsstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
platform.identity.whoami
/platform · platform_whoami
Return the granted user and organizationoauthliveplatform:identity:readShipped by DIB-530 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset; listed in tools/list only for a grant holding the scope, and refused server-side otherwise. `dibbla upgrade` is platform_whoami upgrade=true (DIB-1046/DIB-1047): the same Stripe Checkout link for an owner or admin, the same refusals for anyone else.
platform.apps.list
/platform · platform_apps
List applications in the granted organizationoauthliveplatform:apps:readShipped by DIB-534 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset; listed in tools/list only for a grant holding the scope, and refused server-side otherwise. DIB-704 merged listing and reading one application into platform_apps: omit alias for the list, name one for the application.
platform.apps.get
/platform · platform_apps
Get one application by aliasoauthliveplatform:apps:readShipped by DIB-534 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset; listed in tools/list only for a grant holding the scope, and refused server-side otherwise. DIB-704 merged this with the list into platform_apps; naming an alias is what asks for one application.
platform.apps.card
/platform · platform_app_card
Show one application as a live card inside a chat hostoauth—platform:apps:readShipped by DIB-753 (epic DIB-748) as the connector's first MCP Apps surface (SEP-1865, ratified 2026-01-26): platform_app_card answers with the application's name, URL, status and the steps of the deploy that is producing it, and points at a ui:// resource whose HTML a rendering host draws inline. THE RENDER TOOL IS SEPARATE FROM THE DATA TOOLS on purpose (OpenAI's own recommendation): platform_apps and platform_deployments keep answering the question 'what is true', and this row answers 'show it to the customer'. The card is self-contained — every byte of it is in the resource and every value comes from a tool call, because Claude blocks all external origins from an app iframe until Anthropic's security review lands (claude-ai-mcp #40); the nested live preview is DIB-754's, not this row's. The ui:// URI carries a digest of the card, because a host caches the resource per connector and otherwise serves an old card across a redeploy (measured in DIB-751, not read in documentation). While the deploy runs, the card refreshes itself through platform_app_card_state, an app-only tool (_meta.ui.visibility: ["app"]) the model never sees and can never call; it is declared in mcp_app_only_tools below and is therefore outside the tool budget, and it is registered only for a client that declared the io.modelcontextprotocol/ui extension, so an agent on a host without MCP Apps is not offered a tool it could not use. Both tools need platform:apps:read and NOTHING NEW: the build steps come from the same deployment operation platform_operation already reads, and are included only for a grant that also holds platform:operations:read — a narrower grant gets the card without them rather than an error. A host without the extension gets the whole answer as text from the same call, which is what makes Codex CLI and Claude Code degrade instead of fail.
platform.deployments.list
/platform · platform_deployments
List deploymentsoauthliveplatform:deployments:readShipped by DIB-537 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset as the deployments family: platform_deployments_list (history from the app's Dibbla-managed main branch, one commit per deploy, plus live status), platform_deployment_get (one revision) and platform_deployment_logs (the current deployment's bounded log window; needs platform:logs:read as well). Read only: no start, and rolling back is platform.deployments.rollback on platform_deployment_start. DIB-809 adds view=releases: the images the registry still holds for the app under its immutable dep_… tags, with digest, deploy time, which one runs and whether retention has swept it — the list a rollback chooses from. Listed in tools/list only for a grant holding the scope, and refused server-side otherwise. DIB-704 merged the list, one revision and the current deployment's logs into platform_deployments.
platform.deployments.releases
/platform · platform_deployments
List an application's releases (images it can roll back to)oauth—platform:deployments:readShipped by DIB-809 as platform_deployments(alias, view=releases): every image the registry still holds for the application under its immutable dep_… tag, newest first, with the manifest digest, when it was deployed, the author, which one the live workload runs, whether retention has swept it (RELEASE_GONE if rolled back to) and whether deploy-api holds the release's saved runtime configuration — port, inline env, resources, login gate — which is what recreating a missing Deployment needs. The registry keeps the newest keep_tags per repository (10 in production) plus whatever a live workload references; the platform never garbage-collects images itself. Every successful single-container deploy writes one release row after the rollout, and an application deployed before the table existed gets a row for its running release the first time the list or a rollback is asked for while it is alive. An application whose Deployment is gone still lists as long as it has release rows in the caller's organization; an alias with neither is the one absence answer. Multi-service and stateful applications list nothing they can roll back to — they are re-rendered from dibbla.yaml on every deploy.
platform.deployment_proposals.list
/platform · platform_deployment_proposals
Read deployment proposals and their decision trailoauth—platform:deployments:readShipped by DIB-537/DIB-669 (P-0035 Part B) as platform_deployment_proposals_list, platform_deployment_proposal_get and platform_deployment_proposal_events. Reads only: the proposal, the governance model it was raised under, its decision state and its event trail. Deciding a proposal is a separate capability and is not reachable here — a four-eyes approval is a second human principal, never the proposer’s own grant. DIB-704 merged the whole proposal queue into platform_deployment_proposals; actions list, get and events are this row.
platform.manifests.validate
/platform · platform_deployment_preflight
Validate a dibbla.yaml manifest remotelyoauth—platform:deployments:preflightShipped by DIB-675 (P-0035 Del F) as platform_manifest_validate(alias, manifest, env, profiles, no_public). The manifest travels as text — the same file the caller is about to deploy, never a build archive — and deploy-api answers with the same structured findings the platform's own deploy path produces: the schema rules `dibbla manifest validate` checks locally plus the server-only ones (env-aware resolution, quota limits, multi-public detection), each as {code, path, detail}. Validation is a pure function of caller input and org quotas: nothing is built, applied or stored, and no application state is read, so there is no app to be absent — a bad alias is the only lookup-shaped refusal. DIB-704 merged validation and preview into platform_deployment_preflight; depth=validate is this row.
platform.deployments.preview
/platform · platform_deployment_preflight
Preview what a deployment would changeoauth—platform:deployments:preflightShipped by DIB-675 (P-0035 Del F) as platform_deployment_preview(alias, manifest, env, profiles, no_public), answering the same document the CLI's `dibbla preview` renders: the resolved services (image or build, port, replicas, cpu, memory, environment), skipped services with their reason, the public service, warnings and errors. Nothing is built or applied; nothing is read from the application except the org's quota ceilings, and the tool is annotated read-only. A preview that reports errors is an answer, not a failed call — the document carries valid: false with located findings. DIB-704 merged validation and preview into platform_deployment_preflight; depth=preview is this row.
platform.logs.read
/platform · platform_app_logs
Read a bounded application log windowoauthliveplatform:logs:readShipped by DIB-534 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset; listed in tools/list only for a grant holding the scope, and refused server-side otherwise.
platform.checks.list
/platform · platform_app_checks
List an application's checksoauth—platform:checks:readShipped by DIB-675 (P-0035 Del F) as platform_app_checks_list(alias): the check definitions (id, kind, schedule, classification) and the runtime's enabled state — the same document `dibbla apps checks list` renders. An app with checks enabled org-wide but no dibbla-checks.yaml answers configured: false with zero definitions, and that is an answer, not an error. An app in another organization is absent, in both directions. DIB-704 merged the definitions and the run history into platform_app_checks; view=definitions is this row.
platform.checks.history
/platform · platform_app_checks
Read an application's check run historyoauth—platform:checks:readShipped by DIB-675 (P-0035 Del F) as platform_app_checks_history(alias, check_id, since, limit): past runs newest first, one check's page or every configured check merged, with each run's outcome, code and bounded summary. The CLI's product exit codes have no meaning over MCP; the outcomes are translated in place (pass, fail, error, indeterminate, canceled, skipped_concurrent — each with its meaning in the result) rather than handed over as numbers to scrape. DIB-704 merged this with the definitions into platform_app_checks; view=history is this row.
platform.secrets.list
/platform · platform_secrets
List secret names and metadataoauthliveplatform:secrets:metadata:readShipped by DIB-538 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset; listed in tools/list only for a grant holding the scope, and refused server-side otherwise. A secret value is never returned by any tool.
platform.databases.list
/platform · platform_databases
List managed databasesoauthliveplatform:databases:readShipped by DIB-540 as platform_databases_list with cursor pagination. Returns only organization-owned name, PostgreSQL version, linked app, creation time, size, table count and phase; never DSN, host, user, password or secret material. DIB-704 merged listing and reading one database into platform_databases: omit name for the list.
platform.databases.get
/platform · platform_databases
Inspect bounded database metadataoauthliveplatform:databases:readShipped by DIB-540 as platform_database_get. Returns the same explicit metadata allowlist as list after organization ownership validation; no schema, row data or credentials. DIB-704 merged the list, the metadata and the phase into platform_databases; naming a database is this row.
platform.databases.status
/platform · platform_databases
Read database lifecycle statusoauthliveplatform:databases:readShipped by DIB-540 as platform_database_status. Returns provisioning, ready, restoring or failed with only the bounded database metadata allowlist. DIB-704 merged the phase into platform_databases, which always reports it; a separate tool said the same thing twice.
platform.databases.rows.query
/platform · platform_database_rows_query
Run a bounded structured read-only SELECToauthliveplatform:databases:rows:readShipped by DIB-540 as platform_database_rows_query and intentionally absent from tools/list under metadata-only platform:databases:read. The caller supplies one database, one public base table, explicit catalog-validated columns, typed eq|in|lt|lte|gt|gte|is_null filters, at most one order_by, cursor and limit — never SQL, schema, join, view, CTE, function, expression, offset or wildcard. The server builds one parametrized SELECT with a SELECT-only role and read-only transaction, and enforces 100 rows, two seconds and 256 KiB. Audit stores actor and shape but never filter or cell values.
platform.storage.list
/platform · platform_storage_buckets
List object-storage bucketsoauthliveplatform:storage:readShipped by DIB-541 (P-0035 Part B / P-0026) as platform_storage_buckets_list and platform_storage_bucket_get. Both return only tenant-owned bucket name, quota, used bytes, object count, optional deployment, four secret names, creation time and status; credentials, endpoints, MinIO details, policies and object contents cannot enter the response. Platform and connector buckets are absent from this surface. DIB-704 merged listing and reading one bucket into platform_storage_buckets: omit name for the list.
platform.workflows.list
/platform · platform_workflows
List workflows and revisionsoauthliveplatform:workflows:readShipped by DIB-539 (P-0035 Part B2) on mcp-server's /platform toolset as platform_workflows_list, platform_workflow_get, platform_workflow_revisions, platform_workflow_tools and platform_workflow_validate (validate saves nothing). Read through core's slim API under the verified principal; a workflow in another organization is NOT_FOUND. DIB-704 merged the list, one workflow, its revisions and its HTTP surface into platform_workflows; the function registry moved to platform_catalog and validation stayed its own tool because it takes a whole definition rather than a name.
platform.files.get
/platform · platform_files
Prepare a principal-bound download of a file or an app's own sourceoauthliveplatform:files:readShipped by DIB-543 (P-0035 Part B4) as platform_file_download_prepare plus platform_file_transfer_status; DIB-672 (Part F) adds an agent-redeemed second path over the same machinery and a second origin. Prepare accepts EITHER an opaque source_file_id OR an application alias with an optional revision — never both, never a byte or base64 field — and returns a principal-bound intent with safe metadata, the authenticated fallback link and, for a grant that can read, a principal-bound download URL on this resource's own origin. The application origin is the parity with `dibbla clone`: deploy-api cuts a gzipped tar of that app's Dibbla-managed repository at the named or latest deployed revision, bounded by the same archive limit a deploy obeys, ingests it into the DIB-542 broker under the caller's own binding and mints an ordinary download over the opaque file identity that comes back, so the intent declares the exact content_length and content_sha256 before a byte moves. An app in another organization, an app the caller cannot reach, an installation with version control off and a revision that does not exist are one answer: absent. That URL is never a presigned storage URL and the term is not a synonym for one: it is principal-bound, the caller authenticates, and no storage host is named. Who redeems the one-time ticket is the only difference between the two paths. Browser-mediated: the same authenticated human opens console.<domain>/connector/transfers/<transfer_id>, the server redeems the ticket outside model context and gives only that browser the Dibbla-Transfer session. Agent-redeemed: the client process GETs <resource-origin>/platform/v1/file-transfers/<transfer_id>/bytes with its own platform access token for the same grant; the first range redeems the ticket server-side and the server serves a bounded window, answering 206 with the window it served. A resume names the byte the CLIENT actually reached: only the receiver knows how much of an interrupted range it kept, so any range inside the file is served for as long as the intent lives, and the recorded offset is a high-water mark rather than a cursor a caller must match. Both paths share one transfer_id, one intent ledger and the uncached live-grant check that stops the next range after a revoke. Ticket/session plaintext, broker credentials, presigned storage URLs and file bytes are absent from the durable intent ledger, MCP results, model context, logs and audit; audit keeps correlation id, principal and operation. DIB-704 merged the four transfer steps into one tool; DIB-724 renamed it platform_files, because a tool that also finds, searches and reads a file is not a transfer. action=prepare_download is the archive half of this row. DIB-724 adds the half that moves nothing: action=glob returns the paths of one revision that match a pattern, action=grep returns file, line number and the matching line, and action=read returns a numbered window of one file. All three name an application alias and an optional revision, resolve it through the same lookup the archive does, and answer the same single absence for an app in another organization, an app the caller cannot reach, an installation with version control off, a revision that does not exist and a path that is not in it. They are bounded before they are built — a match count, a line count, a line length and a ceiling on what is read out of the repository — because the caller pays for the answer in context, and every answer says whether it was cut short. Binary content is refused as binary rather than returned as text; the bytes of a file are still the transfer plane's, and no path here can return a whole file, an object key or a storage host. This is the read an agent without a filesystem needs: ChatGPT has no shell to unpack an archive into, and an agent that wants one file should not pay for the whole app.
platform.operations.get
/platform · platform_operation
Poll a durable operation by its principal-bound IDoauthliveplatform:operations:readShipped by DIB-539 (P-0035 Part B3) as platform_operation_status, platform_operation_events, platform_operation_logs and platform_operation_output; extended by DIB-540 to database-operation IDs. Typed phase and cursor-paged content come from the resource's durable engine. The operation id is data, never authentication; an operation in another organization is NOT_FOUND. DIB-704 merged status, events, logs and output into platform_operation; `view` chooses between them. DIB-903: a `git push` to main answers with a deployment operation id; `dibbla deploy status <id> --follow` reads the same ledger through GET /deployment-operations/{id}[/events|/logs] under the human session, so the pusher and an agent on the connector watch one deploy.
platform.apps.maintenance.read
/platform · platform_apps
Read an app’s maintenance settings and runsoauth—platform:apps:read`dibbla apps maintenance status|runs` is platform_apps with view=maintenance: whether scheduled maintenance is on for the application, whether that was decided for the app or inherited from the organization, and its most recent runs. Maintenance is a property of an application, not an authority beside it, so it is a view of the apps read tool rather than a family of its own — which is why DIB-704 could add three capabilities here without adding a tool.
platform.domains.list
/platform · platform_apps
List and verify an application's custom domainsoauth—platform:apps:readDIB-843, on deploy-api's /deployments/{alias}/domains (DIB-842). `dibbla domains list|verify` is platform_apps with view=domains: every custom hostname of the application with its hostname status and certificate status, refreshed from the edge (Cloudflare for SaaS) on every read, a one-line verdict — waiting for DNS, issuing certificate, active, or an error with the provider's reason — and the CNAME record to create. Name a hostname to verify just that one; verify is the listing narrowed, because a read already refreshes. A custom hostname is a property of an application, decided by the same authority as its replicas, so — as with scheduled maintenance (DIB-704) — it is a view of the apps read tool rather than a family of its own; the surface grew by zero tools. Provider ids and the owning organization never enter the answer.
platform.notifications.list
/platform · platform_apps
Read where an application's alerts go, and what happened to themoauth—platform:apps:readDIB-1102, on notify-service's /api/me/subscriptions surface. `dibbla notifications list|events|history` is platform_apps with view=notifications: the notification subscriptions that reach the caller (personal) and, for owners and admins, the organization's; the recent events with each delivery's status (sent, pending, failed or dropped, and why in a sentence); and the catalog's event types, which are what a subscription may name. Alias is optional here and only here: an organization's alerts are not all about one app (pipelines, the trial). Where an app's alerts go is a property of the app, decided by the people who build it, so - as with custom domains (DIB-843) - it is a view of the apps read tool; the surface grew by zero tools. A member who is not an owner or admin sees their own deliveries only.
platform.workflows.publish
/platform · platform_workflows
Read a workflow’s HTTP surface and console URLoauth—platform:workflows:read`dibbla wf api-docs|url` is platform_workflows with view=api: the HTTP endpoint an api node is reachable at, its inputs, and the console URL. Two commands, one question — how is this workflow reached from outside Dibbla — so one view rather than two tools.
platform.workflows.functions
/platform · platform_catalog
Browse the workflow function and provider registryoauth—platform:workflows:read`dibbla functions list|get|providers` are kinds of platform_catalog: workflow_functions, workflow_function and workflow_function_providers. DIB-676 recorded a suspicion that this was a different registry from the one platform_workflow_tools read; it was not — both are the slim API's /functions — so DIB-704 gave the registry one honest name and folded the old tool into it.
platform.identity.organizations
/platform · platform_whoami
List the organizations the caller belongs tooauth—platform:identity:read`dibbla org list` is the organizations field of platform_whoami. It is identity rather than organization data: the grant still acts in one organization and nowhere else, and nothing in the list can be acted on. It exists because it is the only way an agent can tell 'this application does not exist' apart from 'this application is in another organization you can reach, which needs its own authorization' — and the second answer is a different next step for the person.
platform.templates.list
/platform · platform_catalog
List the available project templatesoauth—platform:identity:read`dibbla template list` is platform_catalog with kind=templates. DIB-676 left the authority open because the catalogue is not organization data; DIB-704 settled it on the identity read scope every grant already holds, since naming a wider one for a public list would be asking for something this does not need. Installing from the catalogue stays local-only (cli.scaffold): the files land on the caller's filesystem. Reading it is a plain remote read, and the agent needs it because naming a template that does not exist is the failure it prevents.
platform.tools.list
/platform · platform_tools
List the functions the organization exposes as toolsoauth—platform:workflows:readDIB-853 folded the /platform/tools mount (DIB-764, DIB-827) into /platform as ONE tool, platform_tools, with an action enum; the rows of this family are the actions. action=list answers GET /api/slim/functions?exposed=1 filtered by the caller's role — every entry carries the function's input schema, so an agent reads it and calls action=invoke without a second tool. The list is per-organization data governed by the exposure allowlist and minimum role, not by this contract; it can change between calls, and a function that is unexposed after it was listed answers NOT_FOUND on invoke. A caller holding platform:tools:manage with the owner or admin role additionally sees each exposure's policy (min_role, enabled, registered, exposable) in the same answer, disabled, unregistered and no-longer-exposable rows included, and the candidates: registered functions the engine reports exposable that have no exposure yet (DIB-862), so choosing what to expose never needs platform_catalog. A caller-error refusal on invoke or expose carries field_violations built from the engine's located details, never the engine's message.
platform.tools.invocations
/platform · platform_tools
Read the ledger of calls made to exposed functionsoauth—platform:workflows:readDIB-853 folded the /platform/tools mount (DIB-764, DIB-827) into /platform as ONE tool, platform_tools, with an action enum; the rows of this family are the actions. action=invocations answers GET /api/slim/tool-invocations with the same filters the CLI takes (server, function, source, user, since, limit), newest first; with invocation_id it answers the one row. Who called what, when, from which source and how it ended. The lines a function logged during a call stay on the CLI (`functions invocation <id> --logs`, an NDJSON stream) until an operation-shaped read exists for them.
platform.data_sources.list
/platform · platform_data_sources
List and describe the data sources the organization exposes to agentsoauth—platform:workflows:readDIB-1439, epic DIB-1432: ONE /platform tool, platform_data_sources, with an action enum (list, describe, query); the rows of this family are the reads. A data source is queryable when it is a managed database the organization configured, an owner or admin has exposed it (tool_exposures subject_kind=data_source, DIB-1434) and the caller's role is at least the exposure's min_role. min_role WIDENS access to a source's curated answers to the roles at or above it; it is not a restriction on the database itself, which is reachable to anyone who already holds it through the databases tools or dbproxy, where there is no role check. An unknown, unexposed or switched-off source answers NOT_FOUND, all alike, so exposure is not enumerable; a role below min_role answers FORBIDDEN. Viewers reach the tool (DIB-1184); each source's min_role decides. Reading follows platform:workflows:read, the scope that lists and calls exposed functions (DIB-827), so there is no new scope, and every call is priced against the organization's quota as a read. The tool is readOnlyHint true, destructiveHint false, openWorldHint false: the engine is Dibbla's own (go-toolserver's data source adapter, embedding the question with the source's model), nothing in the source is changed, and no caller-named URL or code is reached. action=list answers GET /api/slim/data-sources: the sources this caller may query, each with its id, source_id, display name, description, capabilities and min_role. action=describe answers the same read for ONE source by id or source_id, with its filterable columns and their types (filter_schema), its search targets (label, table, content columns) and its example questions — everything a query needs and nothing else of the database's schema.
platform.data_sources.query
/platform · platform_data_sources
Ask an exposed data source a question by semantic search with filtersoauth—platform:workflows:readDIB-1439, epic DIB-1432: ONE /platform tool, platform_data_sources, with an action enum (list, describe, query); the rows of this family are the reads. A data source is queryable when it is a managed database the organization configured, an owner or admin has exposed it (tool_exposures subject_kind=data_source, DIB-1434) and the caller's role is at least the exposure's min_role. min_role WIDENS access to a source's curated answers to the roles at or above it; it is not a restriction on the database itself, which is reachable to anyone who already holds it through the databases tools or dbproxy, where there is no role check. An unknown, unexposed or switched-off source answers NOT_FOUND, all alike, so exposure is not enumerable; a role below min_role answers FORBIDDEN. Viewers reach the tool (DIB-1184); each source's min_role decides. Reading follows platform:workflows:read, the scope that lists and calls exposed functions (DIB-827), so there is no new scope, and every call is priced against the organization's quota as a read. The tool is readOnlyHint true, destructiveHint false, openWorldHint false: the engine is Dibbla's own (go-toolserver's data source adapter, embedding the question with the source's model), nothing in the source is changed, and no caller-named URL or code is reached. action=query POSTs /api/slim/data-sources/<id>/query with X-Dibbla-Source: mcp and the grant id: semantic_search with the question, optional filters on the source's declared filter_schema columns only (an undeclared column is VALIDATION_FAILED with field_violations, before anything is minted or embedded), an optional target and top_k at most 20 — the adapter's own cap, shared with workflow agents. workflow-server mints a lease on the source's database per call (DIB-170, redeemable only by go-toolserver, DIB-1436) and records a tool_invocations row with subject_kind data_source and source=mcp, without a result preview. The answer is capped at 16 KiB by the engine: long values are shortened and trailing hits dropped, and the answer says so with truncated true and a truncation_note, which the tool carries unchanged in both contents. Hits are rows the organization's own data holds: the tool says they are data, not instructions, and that row content is untrusted text. There is no free SQL: ro_<db> sees every table, so what can come back is decided by the search configuration and filter schema an administrator curated.

remote-write · 34

Exposed remotely, mutates, uses an idempotency key.

CapabilityWhat it doesAuthIn productionScopesNotes
mcp.community_create_topic
/community · community_create_topic
Create a community topicstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.community_reply
/community · community_reply
Reply to a community topicstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.community_edit_post
/community · community_edit_post
Edit one of the caller's own community postsstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.support_create_ticket
/support · support_create_ticket
Create a support ticketstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.support_reply
/support · support_reply
Reply to a support ticketstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.support_assign
/support · support_assign
Assign a support ticketstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.support_set_status
/support · support_set_status
Set a support ticket's statusstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
mcp.notifications_ack
/notifications · notifications_ack
Acknowledge a platform notificationstatic-bearer——Shipped before P-0035. Authenticates with a static Dibbla bearer validated by auth-service, not an OAuth grant, and is therefore excluded from the connector's directory manifest.
platform.apps.update
/platform · platform_app_config_update
Update an application's configurationoauthliveplatform:apps:writeShipped by DIB-535 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset: the safe configuration update — non-secret environment variables by name (merge, never replace), replicas, cpu and memory within plan; no port, login policy, OAuth scopes or free JSON, and a secret-shaped name or value is refused. Under the same idempotency ledger as restart.
platform.apps.restart
/platform · platform_app_restart
Restart an applicationoauthliveplatform:apps:restartShipped by DIB-535 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset: rolling restart of an application or one named service, under an idempotency key that deploy-api's durable ledger honours (same key + same request → same answer, no second side effect; changed request → conflict; keys never alias across actor, organization or client). Requires platform:apps:restart; platform:apps:write never substitutes.
platform.deployment_proposals.create
/platform · platform_deployment_proposals
Create an agent-authored deployment proposaloauth—platform:deployment-proposals:writeShipped by DIB-537 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset as the deployment_proposals family: platform_deployment_proposal_create (a branch in the caller's push namespace, pinned at its head; title and description; an Idempotency-Key that replays to the same proposal; no environment variables, no secret values, no build options) and, under the same scope — the registry defines it as 'create and inspect' — platform_deployment_proposals_list, _get and _events. There is no approve, deny, apply, close or retry tool; the decision stays in the console (P-0034), and a test pins tools/list against one ever appearing. DIB-704 merged the whole proposal queue into platform_deployment_proposals; action=create is this row.
platform.deployments.rollback
/platform · platform_deployment_start
Roll an application back to an earlier release without a buildoauth—platform:deployments:executeShipped by DIB-809 as platform_deployment_start(alias, release, idempotency_key): release is the FIFTH source of a deployment — a dep_… id from view=releases, or the word previous for the newest release that is not running — and the only one under which nothing is built. deploy-api verifies the image in the registry with a manifest HEAD BEFORE the live Deployment is touched, then rolls the Deployment to it as an ordinary rolling update: env, resources, port and the login gate are inherited from the live workload, the dashboard-managed secrets are untouched, and the application answers with the earlier version within about a minute. Because no build happens the call needs neither BuildKit nor registry writes, which is the point: it is the customer's own way back on the day the registry is full, the incident of 2026-09-12 (callefix-ab, cfixapp) that DIB-807 exists for. When the Deployment itself is gone — a force deploy removed it and the build died — the call recreates the Deployment and its route from the release's image with the saved runtime configuration (the release's own row, else the newest saved row for the alias) and re-materialises the secrets from the secrets store; with no saved configuration at all the answer is RELEASE_CONFIG_UNKNOWN and a source deploy is the way back. A release retention has swept is RELEASE_GONE and names the releases still available; the running application is not touched. A release that already runs is answered unchanged. Multi-service and stateful applications are ROLLBACK_UNSUPPORTED. Unlike the other four sources this call is synchronous and answers the rolled-back release directly, not an operation id: there is no build to wait for and the rollout is the same Kubernetes rolling update a restart is. It keeps platform:deployments:execute — a rollback is a deployment, decided by whoever may deploy — and platform:apps:write and platform:apps:restart never substitute. Organization deployment governance is not consulted: the image was approved when it was first deployed, and a rollback that had to wait for four eyes would not be a way back. Honours the same idempotency ledger as every other write.
platform.checks.settings
/platform · platform_app_checks_set_enabled
Enable or disable an application's scheduled checksoauth—platform:checks:writeShipped by DIB-675 (P-0035 Del F) as platform_app_checks_set_enabled(alias, enabled, idempotency_key): the CLI's `apps checks enable|disable` — it starts or stops the nightly schedule while preserving definitions and history, requires owner or admin (deploy-api enforces the role on the actor's own identity), and carries an idempotency key like every other /platform write.
platform.secrets.delete
/platform · platform_secret_write
Delete a secretoauthliveplatform:secrets:writeShipped by DIB-538 (P-0035 Part B) on mcp-server's OAuth-protected /platform toolset. Routine write with an idempotency key under the same write scope P-0035 assigns to set/rotate/delete; not remote-destructive, because there is no secrets destructive scope and the proposal's plan/confirm flow is for app, database and storage deletion. DIB-704 merged setting and deleting a secret into platform_secret_write; action=delete is this row. Since DIB-928 the tool’s actions are request, status and delete; DIB-1340 added promote.
platform.secrets.request
/platform · platform_secret_write
Ask the person to enter a secret's value on a secure Dibbla pageoauth—platform:secrets:writeShipped by DIB-927 as platform_secret_write action=request(name, title, explanation, alias, service, idempotency_key), and since DIB-928 THE way a secret value reaches the platform from a connector: the value never passes through the conversation because no tool takes one (platform.secrets.set is local-only). title and explanation (DIB-928, required, 80 and 600 bytes, plain text) are the agent’s words for the person — what the secret is, why the app needs it and where to find it — shown as the page’s heading and paragraph so a non-technical person understands what they are asked for; the technical name is shown beneath them. It answers a time-limited https link on the console (/connector/secrets/<request_id>, 15 minutes) and a request_id, and never a value; the tool refuses a value argument on this action. The link is not a bearer capability: deploy-api answers the page only to a signed-in console session of the user this grant acts for, in this organization, holding a role that may write secrets at that moment, and it stores the value only while THIS grant is still live (auth-service's grant validation, live, fail-closed). Another user, another organization or a stranger with the link sees an absence. The page shows the fixed target — organization, application, service, secret name, whether an existing value is replaced — plus the title and explanation as text (never markup), and nothing on it can retarget the write; the value travels once, browser to deploy-api, into the same secrets store the console and the CLI write to, and is not logged, echoed or kept anywhere else. Saving is single-use: pending → saving → completed is atomic, a second save is refused without a rotation, and a store failure returns the request to pending rather than reporting it done. No service is restarted by the save. Routine write under the idempotency ledger. Since DIB-1343 a person makes the same request with their own token: dibbla secrets request (POST /secret-entry-requests, bound to the person, client dibbla-cli, no grant — so there is no grant whose revocation could stop the save). The page, its sign-in and role checks and the single-use save are the ones described above. It is what an agent with a shell runs instead of secrets set, which would put the value in its transcript.
platform.secrets.request_status
/platform · platform_secret_write
Check whether a requested secret has been enteredoauth—platform:secrets:writeShipped by DIB-927 as platform_secret_write action=status(request_id). Answers pending, completed, expired or cancelled with the target's metadata and timestamps, and nothing about the value; a pending answer repeats the link, a finished one does not. The request is answered only to the identical delegated binding that created it — organization, user, client and grant — so another grant's status read is an absence. A completed answer means the value is stored under the requested name and scope and the agent continues as if it had set it. A pre-existing secret of the same name never makes a pending, expired or cancelled request read as completed; an expired one is replaced through a new action=request. Classified remote-write rather than remote-read because it sits under the write scope on the write tool: the request it reads about is the write's own receipt, and a read-only grant is meant to see neither. dibbla secrets request --status <id> is the same read for a request a person made with their own token (GET /secret-entry-requests/{id}/status, DIB-1343): it is answered to that person only, and anyone else’s request is an absence.
platform.secrets.promote
/platform · platform_secret_write
Promote a plain env var to a secret, with a new value entered on a secure Dibbla pageoauth—platform:secrets:writeShipped by DIB-1340 (epic DIB-1336) as platform_secret_write action=promote(name, title, explanation, alias, idempotency_key) under the same write scope as action=request — a parameter on a tool that is already listed, because the tool budget admits no new one. It creates a promotion request: the entry request of platform.secrets.request applied to a plain env var of one application that should have been a secret. alias is required and service is refused: an env var set by a deploy or the console is on every service of the app, so a promotion is app-wide and the secret is the app’s. It answers the same time-limited link and request_id, never a value, and takes none; action=status (platform.secrets.request_status) reads it like any other request. The page, its sign-in, role and live-grant checks and the single-use save are the ones described under platform.secrets.request. The value the person enters there is a NEW one: the variable’s current value has been readable — logs, backups, release records, the history of dibbla.yaml, the conversation — and counts as exposed, so it must be rotated where it was issued, and the save refuses the unchanged value and leaves the request pending. The comparison is against per-request salted digests recorded when the request is created and against the variable’s value at save time; the old value itself is never kept or answered. The save takes the app’s deploy lock: while a deploy runs it is refused (DEPLOY_IN_PROGRESS) and the request stays pending. Under the lock the secret is stored and pushed into the app’s Secret objects, and only once every workload’s Secret is confirmed to hold the new value is the env var removed from every workload of the app, orphaned ones included, and the app restarted onto the secret; the old value is also scrubbed from the app’s release history. Otherwise nothing is removed. Either way the request completes and the new secret is never rolled back; when the env var was not removed, status says env_var_remains and the page tells the person what to do — keep the env var and promote again when the secret had not reached the app yet, or remove it by hand when the removal itself failed. A name that is not an env var of the app is NOT_FOUND. A variable that comes from dibbla.yaml is CONFLICT, because the next deploy would put it back (the answer says to remove the line, deploy, and then use action=request), and so is a service-level secret of the same name, which would override the app’s. Against a platform API that predates promotion, which would make an ordinary request instead, the tool answers UNSUPPORTED and hands out no link. dibbla env promote makes the same request with the person’s own token. It is a row of its own rather than a parameter of platform.secrets.request because its effect is not the request’s: it removes a variable and restarts the app. Until the action shipped this row was not-yet-available (eventual_state remote-write, owner P-0035 Part F, tracked_by DIB-1340).
platform.storage.create
/platform · platform_storage_bucket_write
Create a bucket with scoped credentialsoauthliveplatform:storage:writeShipped by DIB-541 (P-0035 Part B / P-0026) as synchronous platform_storage_bucket_provision. A required idempotency key binds the actor, organization, client, target and payload; exact retry replays, changed payload conflicts. The control plane creates the hard quota, lifecycle rule, bucket-scoped service account and four STORAGE_<NAME>_* secrets atomically from the caller's perspective, and never returns credential material. DIB-704 merged provisioning and rotating into platform_storage_bucket_write; action=provision is this row, and the action enum shows only what the grant's scopes carry.
platform.storage.rotate
/platform · platform_storage_bucket_write
Rotate bucket credentialsoauthliveplatform:storage:rotateShipped by DIB-541 (P-0035 Part B / P-0026) as synchronous, idempotent platform_storage_bucket_rotate under the narrow rotate scope. The old credential is invalidated, the two credential secrets are replaced and every service in a bound deployment must accept a restart before success; there is no no_restart input. Organization-global buckets report that restart is not applicable. Credential material is never returned. DIB-704 merged provisioning and rotating into platform_storage_bucket_write; action=rotate is this row, and a rotate-only grant is offered that action alone.
platform.workflows.update
/platform · platform_workflow_apply
Create or replace a workflow definition in one calloauth—platform:workflows:writeShipped by DIB-674 (P-0035 Part F) as exactly ONE tool, platform_workflow_apply(name, definition, idempotency_key). The whole editing surface is a desired state, not graph mutations: the definition is the COMPLETE workflow in the same slim YAML `dibbla wf` reads, and the stored workflow becomes equal to it — what the definition omits is removed. A family of node, edge, input and wiring tools was deliberately NOT built; the desired state is what makes the change atomic and idempotent by construction, validates the whole graph as one document instead of one mutation at a time, and grows the connector by one tool instead of six. The name in the path is the only name that decides which workflow is written; the one in the document is ignored. The whole graph is validated before anything is saved, so a cycle, a dangling edge, a missing input or an unknown node type refuses the apply with findings that name the location in the definition and leaves no partly saved workflow. A required idempotency key binds actor, organization, client, target and definition through deploy-api's durable ledger: the exact retry replays the first answer and creates no second revision, a changed definition under the same key conflicts. Every apply leaves a revision holding the state it replaced (on a create, the workflow as created), which is what platform.workflows.restore returns to. The answer says what the change did — added, changed, removed — by the author's own step labels and connections, never by internal ids, and never by quoting a field's value back. A definition references credentials and data sources by name; one carrying the engine's reserved per-run _credentials map is refused before any request leaves the connector, so no credential value can reach a stored graph through MCP or model context. A workflow in another organization is absent in both directions, and the result is runnable through the existing platform_workflow_execute path.
platform.workflows.restore
/platform · platform_workflow_apply
Return a workflow to an earlier revisionoauth—platform:workflows:writeShipped by DIB-674 (P-0035 Part F) as platform_workflow_restore(name, revision, idempotency_key), the second and last tool this capability adds. Revisions are the history of applies rather than a separate editing model: platform_workflow_revisions lists them and this puts one back as HEAD. A restore snapshots what it replaces before it runs, so it is itself reversible, and it carries the same required idempotency key as an apply. HEAD is refused as a revision id — restoring HEAD onto HEAD is not a thing a caller means. A workflow or revision in another organization answers the same absence a name that never existed answers. DIB-704 merged restoring into platform_workflow_apply: naming a revision instead of a definition is applying one the workflow already had.
platform.files.put
/platform · platform_files
Prepare a principal-bound resumable browser uploadoauth—platform:files:writeShipped by DIB-543 (P-0035 Part B4) as platform_file_upload_prepare plus platform_file_transfer_abort; DIB-670 (Part F) adds an agent-redeemed second path over the same machinery. Direction-bound platform_file_transfer_status requires platform:files:write for the caller's own upload and platform:files:read for a download; download prepare always requires platform:files:read. Prepare accepts only name, content_type, exact content_length, content_sha256 and idempotency_key—never a byte/base64 field—and returns a principal-bound intent with safe metadata, the authenticated fallback link and, for a grant that can write, a principal-bound upload URL on this resource's own origin. That URL is never a presigned storage URL and the term is not a synonym for one: it is principal-bound, and the caller authenticates. Who redeems the one-time ticket is the only difference between the two paths. Browser-mediated: the authenticated human opens the console page, whose browser verifies every declared field, redeems the encrypted ticket outside model context and uploads chunks with Dibbla-Transfer. Agent-redeemed: the client process PUTs bounded chunks to <resource-origin>/platform/v1/file-transfers/<transfer_id>/bytes with Upload-Offset, authenticating with its own platform access token for the same grant; the first chunk redeems the ticket server-side. The URL carries no signature, ticket or session—it locates one transfer and the token decides—so a redeemed, expired or foreign-principal redemption is refused as absent, and it is a structured field the client acts on rather than prose the model repeats. Both paths share one transfer_id, one intent ledger, one resume offset and the uncached live-grant check that stops the next chunk after a revoke. Declared length or sha256 that does not hold ends the intent and produces no file. Completion publishes only the opaque source_file_id back to MCP. Abort is idempotent and never deletes a completed file. Ticket/session plaintext, presigned storage URLs and bytes are absent from the durable intent ledger, MCP results, model context, logs and audit. DIB-704 merged the four transfer steps into one tool, which DIB-724 renamed platform_files; actions prepare_upload, status and abort are this row.
platform.operations.cancel
/platform · platform_operation_cancel
Cancel a durable operationoauthliveplatform:operations:cancelShipped by DIB-539 (P-0035 Part B3) as platform_operation_cancel and extended by DIB-540 to database-operation IDs: idempotent, valid only while cancellable, CONFLICT once finished; a terminal state is never rewritten.
platform.apps.maintenance.settings
/platform · platform_app_config_update
Enable or disable an app’s scheduled maintenanceoauth—platform:apps:write`dibbla apps maintenance enable|disable` is the maintenance_enabled field of platform_app_config_update. The scope is the app write scope because a maintenance setting is app configuration, decided by the same authority as its replicas. It may be given alone or together with the other changes.
platform.domains.add
/platform · platform_app_config_update
Connect a custom domain to an applicationoauth—platform:apps:writeDIB-843. `dibbla domains add` is the domain_add field of platform_app_config_update: the hostname is registered as a custom hostname at the edge, and the answer carries — in clear text, the same words as the CLI — the one DNS record to create at the registrar (CNAME, host label, target) and the bare-domain advice (most registrars cannot put a CNAME on the apex; connect www and redirect the apex to it). The scope is the app write scope because a domain is app configuration. One hostname, one tenant: a hostname any application anywhere already holds is CONFLICT, and the answer never says whose. Adding a hostname the same application already holds hands the row back refreshed. Needs deploy-api's deploy role, like every other write on the application.
platform.domains.remove
/platform · platform_app_config_update
Disconnect a custom domain from an applicationoauth—platform:apps:writeDIB-843. `dibbla domains remove` is the domain_remove field of platform_app_config_update: the custom hostname is removed at the edge first and then from the registry; the customer's own DNS record is theirs and is never touched. Not destructive in the contract's sense — adding the hostname again starts a fresh verification and nothing that cannot be recreated is lost — so it is a write under the app write scope, not a plan/execute pair. A hostname the application does not hold is the one absence answer.
platform.notifications.add
/platform · platform_app_config_update
Subscribe to an application's alertsoauth—platform:apps:writeDIB-1102, on notify-service's /api/me/subscriptions surface. `dibbla notifications add` is the notification field of platform_app_config_update with action=add. The event type must be in the catalog - a concrete type or a group's family pattern - never free text. A personal subscription is bound to one app the caller builds (can-view, re-checked at every delivery) and goes to the caller's own address or linked Slack account, never to a typed target; an organization subscription is for owners and admins, may cover every app, and names its target. min_severity is the lowest severity delivered (default attention). Adding what already exists answers with the row updated, so a setup script can be rerun.
platform.notifications.remove
/platform · platform_app_config_update
Remove a notification subscriptionoauth—platform:apps:writeDIB-1102, on notify-service's /api/me/subscriptions surface. `dibbla notifications remove` is action=remove: the caller's own row, or for an owner or admin one of the organization's. Another member's personal row is not found, whoever asks. Not destructive in the contract's sense - adding it again restores it - so it is a write under the app write scope. A removed owner/admin default (DIB-1100) stays removed.
platform.notifications.test
/platform · platform_app_config_update
Send a test notification and see whether it arrivedoauth—platform:apps:writeDIB-1102, on notify-service's /api/me/subscriptions surface. `dibbla notifications test` is action=test: one real notification through one subscription - same adapter, rendering and ledger - sent now regardless of digest or severity floor, answered with the delivery's status and, when it did not arrive, why. Bounded per person; the idempotency key makes a retried call answer with the first send instead of sending again.
platform.deployment_proposals.decide
/platform · platform_deployment_proposals
Approve, deny or retry a deployment proposaloauth—platform:deployment-proposals:write`dibbla apps proposals approve|deny|retry` are actions of platform_deployment_proposals. DIB-537 refused an agent the power to approve, and what makes it safe now is not a rule in the connector but the one deploy-api already enforces: under admin_four_eyes and member_four_eyes the decider must be a different human from the proposal's author, checked against the acting user id, and an agent-authored proposal always needs an owner or admin other than its author. The connected agent acts as one identified human, so it can only ever carry a decision that human is entitled to make — never its own proposal's. A governance refusal is returned as FORBIDDEN naming the rule.
platform.feedback
/platform · platform_feedback
Send, list and withdraw the caller’s own product feedbackoauth—platform:feedback:write`dibbla feedback` and its list|delete subcommands are the actions of platform_feedback. The list is the organization's feedback and only the author can withdraw a message, which is what feedback-service enforces; the connector adds no rule of its own. Feedback reaches Dibbla's own team, so the tool's description tells the agent to pass on what the user actually asked it to and nothing else.
platform.tools.invoke
/platform · platform_tools
Invoke a function the organization exposes as a tooloauth—platform:workflows:readDIB-853 folded the /platform/tools mount (DIB-764, DIB-827) into /platform as ONE tool, platform_tools, with an action enum; the rows of this family are the actions. action=invoke POSTs /api/slim/functions/<server>/<name>/invoke with X-Dibbla-Source: mcp and the grant id. Exposure is the authorization (DIB-827): workflow-server re-checks registered AND exposed AND role >= min_role on every invoke, so listing and calling follow the one read scope and no second OAuth gate sits on top; platform:tools:execute stays reserved. Each call is recorded as a tool invocation — a sibling of a workflow run, never a run — and priced against the organization's quota as a narrow write. Inputs are capped at 64 KiB and a result over 16 KiB is cut in both contents. There is no idempotency key: an invocation is an attempt the ledger records once per call, and the function's own idempotency is the function's. A static API token holds only platform:identity:read at /platform and can neither list nor call.
platform.tools.expose
/platform · platform_tools
Expose or unexpose a function as a tool, and set its minimum roleoauth—platform:tools:manageDIB-853 folded the /platform/tools mount (DIB-764, DIB-827) into /platform as ONE tool, platform_tools, with an action enum; the rows of this family are the actions. action=expose is PUT /api/slim/tool-exposures/<server>/<name> with min_role and enabled (an upsert: exposing again updates the policy, so it needs no idempotency key); action=unexpose is the DELETE. The organization's exposure policy is a governance act, so it sits behind TWO gates and both must hold: the grant carries platform:tools:manage (a write scope, never a consent default) AND the person behind the grant is an owner or admin of the organization — workflow-server enforces the role on every call, and platform_tools hides the two actions from anyone else and answers FORBIDDEN naming the role on a blind call. Whether a function is exposable is the engine's rule, reported as an exposable flag (DIB-858): a platform built-in only when opted in for direct calls, an organization's own function unless it is an agent, never an engine-internal (`_`), data-source or maintenance function; a refused expose answers VALIDATION_FAILED with field_violations on name; the 101st enabled exposure is refused (EXPOSURE_LIMIT, a CONFLICT). The policy row carries a subject_kind, `function` today, so a later kind of subject (a workflow, an application) can join the same policy without a redesign — not built, deliberately.
platform.servers.tools
/platform/servers/* · *
Call one tool server's exposed functions as that server's own MCP toolsoauth—platform:servers:useDIB-1064 Phase 1, ADR 0012 amendment of 2026-09-23; own OAuth resource per server since DIB-1242 (2026-09-30). A per-server MCP address, /platform/servers/<name>, serves ONE tool server's functions as native tools, one tool per function, so a person adds only the servers they want to their client instead of reaching every exposed function through platform_tools. Each address is an OAuth protected resource of its own: its RFC 9728 document is /.well-known/oauth-protected-resource/platform/servers/<name>, it names the address as the resource and exactly one scope, platform:servers:use, and the WWW-Authenticate challenge on the address points there and nowhere else. auth-service accepts the address as the RFC 8707 resource, grants exactly that scope for it, shows a consent page that names only that server (and the app that publishes it), and binds the token's audience to the address; mcp-server verifies the audience against the path, so the token is refused on /platform and on every other server's address, a /platform token is refused on the address, and a static API token has no path onto it. The actor exchange is shared with /platform, with the assertion naming the server's resource as its issuer and auth-service holding it to the grant's resource. The grant profile policy that admits a person to /platform is not applied to a per-server resource: who may use the address is the app's access list (DIB-1222/DIB-1241), re-checked by workflow-server on every list and every invoke, and for a server no app has published the function exposures (registered AND exposed AND role >= min_role). Every tool is annotated destructiveHint and openWorldHint true (DIB-935), because what a customer's function does is not this contract's to promise. A call keeps the platform_tools invoke bounds and error mapping (64 KiB inputs, 16 KiB result cut, narrow-write quota, X-Dibbla-Source: mcp, the 400/502 mapping), through its own copy of the invoke so platform_tools is untouched, with a test pinning the two mappings equal; a field violation names the argument itself, not inputs.<field>, because a native tool's arguments are the inputs. It is recorded with surface=per_server (DIB-1067); platform_tools sends no surface, so its rows read NULL. An unknown, disconnected or nothing-exposed server answers the same empty list, so server names are not enumerable. The mount is outside parity.max_tools_full_write, which counts /platform only, and outside the directory listing: these addresses are custom connectors a person adds by hand. Shipped by DIB-1068: mcp-server serves it only behind SERVERS_MCP_ENABLED (default off; helm global.platformConnector.servers.enabled sets it and auth-service's PLATFORM_SERVERS_MCP_ENABLED from one value), so it is opt-in per installation, and /platform's tools/list, scopes, resource metadata, challenge and consent page are byte-identical with it on. A client should connect before it logs in: Claude Code's mcp login run first skips the 401 hint and looks for path-suffixed metadata that is not served (DIB-1069).

remote-destructive · 7

Exposed remotely, requires an accurate destructiveHint, a bound read-only plan and a distinct human approval.

CapabilityWhat it doesAuthIn productionScopesNotes
platform.apps.delete
/platform · platform_destructive_execute
Delete an applicationoauth—platform:apps:deleteShipped by DIB-536 (P-0035 Part B) as exactly platform_app_delete_plan and platform_app_delete_execute. Plan is read-only and binds the owner/admin OAuth subject, organization, client, grant, exact scope, role, canonical app id/alias, live generation/revision, normalized permanent effects and digest to a random 192-bit request id with a 10-minute TTL. Only the same still-owner/admin human can approve or deny on the CSRF-protected console.<domain>/connector/approvals/<request_id> page; MCP exposes no decision or direct-delete tool. Execute accepts only request_id, revalidates every binding and the live revision under the app lock, atomically redeems server-side approval before one delete/audit row, and durably replays the same terminal result (or IN_PROGRESS) without a second side effect. The request id locates state but never authenticates its holder. DIB-704 merged every destructive pair into platform_destructive_plan / platform_destructive_execute; resource=app is this row. The plan → human approval → execute shape is unchanged, and the resource enum shows only what the grant's scopes carry.
platform.databases.restore
/platform · platform_destructive_execute
Restore a database from a backupoauth—platform:databases:restoreReclassified from remote-async to remote-destructive by DIB-673 (P-0035 Part F) and rebuilt as platform_database_restore_plan and platform_database_restore_execute; the direct platform_database_restore tool and its deploy-api route are gone, because a second way in would be a way around the confirmation. The reason is what restore does, not what it is called: loading a dump into a database that already exists drops and recreates every object the dump defines, so the data held today is replaced and Dibbla cannot bring it back — the same semantics as `dibbla db restore`, which is the parity this row satisfies. The plan is read-only and names the database, how many tables and bytes are about to be overwritten, and the source file by opaque source_file_id — never a transfer id, hash, ticket, session or bytes — or states plainly that nothing is overwritten when the target does not exist yet. A human approves on the same CSRF-protected console page as every other destructive capability. Execute accepts only request_id, re-reads database and source and refuses either having changed, and returns a durable operation immediately because a large dump outlives one call; the operation records the confirmation it was redeemed under, and the confirmation records the operation, so the audit reads in both directions. The confirmation, not an idempotency key, is what makes one approved intent produce one restore. DIB-704 merged every destructive pair into platform_destructive_plan / platform_destructive_execute; resource=database_restore is this row, and it is the one that also names a source_file_id.
platform.databases.delete
/platform · platform_destructive_execute
Delete a managed databaseoauth—platform:databases:deleteShipped by DIB-673 (P-0035 Part F) as exactly platform_database_delete_plan and platform_database_delete_execute, on the same machinery as app and bucket deletion and with no shortcut of its own. Plan is read-only and binds the owner/admin OAuth subject, organization, client, grant, exact scope, role, the canonical database identity and a digest of its live metadata — size, table count, bound application — to a random request id with a 10-minute TTL, and enumerates what disappears: every table and byte, the dedicated PostgreSQL role and its credentials, the ownership record, the injected DATABASE_URL_<NAME> secret and the application left without a database. Only the same still-owner/admin human decides on the CSRF-protected console.<domain>/connector/approvals/<request_id> page; MCP exposes no decision and no direct delete. Execute accepts only request_id, re-reads the live metadata and refuses a database that changed after planning, atomically claims the approval before the single drop, and durably replays the same terminal result. A database in another organization is absent in both directions, because ownership — not the request — resolves the name. DIB-704 merged every destructive pair into platform_destructive_plan / platform_destructive_execute; resource=database is this row.
platform.storage.delete
/platform · platform_destructive_execute
Delete a bucketoauth—platform:storage:deleteShipped by DIB-541 (P-0035 Part B / P-0026) as exactly platform_storage_bucket_delete_plan and platform_storage_bucket_delete_execute. The 10-minute random request binds owner/admin subject, organization, client, grant, role, exact scope, bucket identity, current credential-free metadata, normalized force-delete effects and digest. Only the same authenticated human approves in the CSRF-protected console; MCP has no decision, direct-delete or force tool. Execute accepts only request_id, revalidates metadata, atomically claims once, then force-deletes all objects, bucket, service account, ownership and four secrets while concurrent/retried calls receive IN_PROGRESS or the same terminal result. Platform and connector buckets never enter this surface. DIB-704 merged every destructive pair into platform_destructive_plan / platform_destructive_execute; resource=storage_bucket is this row.
platform.files.delete
/platform · platform_destructive_execute
Delete a fileoauth—platform:files:deleteShipped by DIB-673 (P-0035 Part F) as exactly platform_file_delete_plan and platform_file_delete_execute. A file is named by its opaque source_file_id and never by a content hash, transfer id, ticket or session: the content hash is a dedup key shared across organizations, so it is not an identity. Plan is read-only, quotes name, size, content hash and status, and says what stops working — every capability that names the file, including database restore and file download — while stating that the content-addressed blob is deliberately left in place because it is shared with every other record holding the same bytes. The approval boundary is deploy-api's one confirmation ledger even though the records live in the broker, so there is a single audit to read and a single shape to review. Execute accepts only request_id, refuses a file renamed or replaced after planning, is single-use, durably replays its terminal result, and answers absent for another organization in both directions. No byte, object key or storage host can appear on this surface. DIB-704 merged every destructive pair into platform_destructive_plan / platform_destructive_execute; resource=file is this row.
platform.workflows.delete
/platform · platform_destructive_execute
Delete a workflowoauth—platform:workflows:delete`dibbla wf delete` is resource=workflow on platform_destructive_plan / platform_destructive_execute, behind the same plan → human approval → execute shape as every other irreversible capability. The plan says how many revisions and runs go with the definition, because that is the sentence the human approves.
platform.workflows.runs.purge
/platform · platform_destructive_execute
Permanently purge workflow run historyoauth—platform:workflows:delete`dibbla wf runs purge` is resource=workflow_runs on the destructive pair, and it is a separate resource from platform.workflows.delete on purpose: it leaves the workflow running and destroys only the record of what it did. Purging an audit trail is destructive even though nothing stops working, and a human approving it must not be shown the sentence for deleting the whole workflow. It is the one resource that also takes a `before` cutoff; runs started on or after it are kept, and running ones are never touched.

remote-async · 5

Exposed remotely, returns a durable operation ID that is polled. An MCP call never holds a long run open.

CapabilityWhat it doesAuthIn productionScopesNotes
platform.deployments.start
/platform · platform_deployment_start
Start a deploymentoauth—platform:deployments:executeShipped by DIB-671 (P-0035 Part B) and extended by DIB-679, as platform_deployment_start(alias, exactly one of archive{name, content_length, content_sha256} or source{repo, ref}, deploy options, idempotency_key). ONE call owns the whole sequence, from either source; the source is a parameter and never a second tool. With an archive, the call mints the principal-bound upload intent itself and answers with an operation id and the structured upload field a client process pushes the archive to; the deploy starts when the last chunk lands, and an interrupted upload resumes against the same intent without creating a second deploy. With repo and ref, no bytes move at all: the application may only name the repository it is already linked to, the ref is pinned to a commit sha by the call, and the platform fetches that commit itself with its own credential — which is what makes the path work from a cloud sandbox whose network reaches the provider's MCP servers and GitHub but not this origin. No credential, token or byte reaches MCP, model context, logs or audit on either path. A ref that does not exist, a repository the application is not linked to and a repository in another tenant are ONE absence answer. The build never holds the MCP call open; the generic operation tools expose status, bounded build logs, events, output (the app's URL on success, the CLI's own build error on failure) and cancel. Organization deployment governance is obeyed, not bypassed: immediate rolls out, while admin_four_eyes, member_four_eyes and admin_checkpoint instead create a pending deploy whose effective model and console decision path are named in the output, and whose console page shows the repository, ref and commit sha behind a git-sourced candidate. There is no approve, deny, apply, close or retry tool — CanDecide in deploy-api refuses the author regardless of role, and a test pins tools/list against one ever appearing. DIB-725 adds a THIRD source, files: [{file, data, encoding}] with mode replace — the complete source tree in the call itself, for a caller that has no filesystem to pack an archive from and no shell to run the CLI in. deploy-api packs the tree into the same tar.gz an upload would have carried, ingests it through the transfer plane under the caller's own binding and records the operation already holding the opaque file identity, so nothing downstream of the bytes — entitlements, governance, the runner, the ledger — can tell the three sources apart, and an inline deploy has no upload field and nothing to wait for. It is capped at 200 files and 2 MB of decoded content, refused on BOTH sides with one sentence that names the numbers and points at the archive path and the CLI; the cap is what keeps a file tree in a tool call from becoming the way large archives travel, and it is deliberately far below the source of any template Dibbla ships. Paths are relative, never absolute and never escaping, refused by the call rather than by the extractor a minute later. This is the one place source text may travel in MCP JSON: it is content the caller authored in this turn, not an archive being moved, and archive bytes still may not appear here. DIB-726 adds a fourth way in and the third thing files can mean: mode patch, where files carries only the entries that CHANGE — each as its whole new content, never a line-based diff, because a model gets line numbers and context lines wrong and a positional format needs fuzzy matching and a failure class the platform does not need — plus delete, the paths this deploy removes. deploy-api reads the app's own Dibbla-managed main branch at head through the SAME tenancy lookup the DIB-724 read surface uses, streams its `git archive`, substitutes the sent entries, drops the deleted ones and packs the result as the same tar.gz the inline path packs, so a patch is one commit on top of head and everything downstream of the bytes still cannot tell the sources apart. Every file the caller did not send is byte-identical, including its mode: a patched entrypoint.sh keeps the execute bit the repository gave it. base_sha is an OPTIONAL optimistic lock: sent, it is the revision the caller read the files at (platform_files reports it) and a patch based on anything that is no longer head is refused BASE_SHA_STALE with the current revision named — as prose and as a located detail, since the MCP edge never forwards upstream messages — so a deploy that landed while the agent was thinking is never silently overwritten; omitted, the patch applies to head, because a single-agent loop should not have to carry a revision it cannot race with. A delete that matches nothing in the base revision is refused, not ignored. A patch rolls over the running app by default (update), since a patch of an app that is not running is not a thing. Rolling back is not a tool either: DIB-809 makes it a FIFTH source on this same call, release (platform.deployments.rollback), and the history is still platform_deployments, one commit per deploy. Deploy is not a four-eyes event and DIB-725 adds no new gate: an organization that has configured deployment governance still gets a pending deploy decided in the console, and the countermeasures on the ungoverned path are the cap, the unchanged role gate (a viewer's agent may no more deploy than the viewer may), the live-grant recheck before anything moves, and attribution of every deploy to both the human subject and the client.
platform.checks.run
/platform · platform_app_run
Run an application's checks nowoauth—platform:checks:executeShipped by DIB-675 (P-0035 Del F) as platform_app_checks_run(alias, check_id, wait_seconds). It starts one manual execution of the app's already-defined checks — there is no way to run a check the application does not define — and waits a bounded time (default 300s, max 600s) for the terminal outcome, translated like history's: pass means every check held, fail names the check and its bounded reason without secrets or internal hostnames, error means the probe itself broke, indeterminate means it could not decide, and skipped_concurrent means another run holds the app's lease. Past the wait bound the call returns the still-running execution id and the caller reads the outcome from platform_app_checks_history. The one thing it deliberately does not do is hold the call open past its bound or invent an outcome it has not seen. DIB-704 renamed the tool to platform_app_run (kind=checks), which also starts a maintenance run.
platform.databases.create
/platform · platform_database_provision
Provision a managed databaseoauthliveplatform:databases:writeShipped by DIB-540 as platform_database_provision(name, app_alias?, idempotency_key). Returns a durable database_provision operation immediately; identical normalized retries replay it and changed payloads conflict. Deploy-api persists, leases and resumes the work, while the generic operation tools expose status, events, logs, output and cancel.
platform.workflows.execute
/platform · platform_workflow_execute
Execute a workflow at a pinned immutable revisionoauthliveplatform:workflows:executeShipped by DIB-539 (P-0035 Part B2/B3) as platform_workflow_execute: starts the run at HEAD asynchronously with a required idempotency_key (same key + same request replays the run, a different request under the same key is IDEMPOTENCY_KEY_CONFLICT) and returns the durable operation envelope wrapping the canonical run id. Pinning a named immutable revision is a later addition.
platform.apps.maintenance.run
/platform · platform_app_run
Start one maintenance runoauth—platform:apps:write`dibbla apps maintenance run` is platform_app_run with kind=maintenance. It returns as soon as the run is dispatched and is followed with platform_apps view=maintenance, because a maintenance run outlives one call. It requires an idempotency_key: the run spends the organization's model budget, so a retry must not spend it twice.

local-only · 20

Deliberately never remote. Requires a stated reason.

CapabilityWhat it doesAuthIn productionScopesNotes
platform.secrets.setSet or rotate a secret from the terminalnone——Takes the value as an argument or from stdin on the caller’s machine. Since DIB-928 the connector has no input path for a secret value at all: platform_secret_write action=set is gone from the schema, and a call that still carries a value or action=set is refused server-side with UNSUPPORTED before the arguments are decoded — nothing stored, echoed or logged — with the request flow named in the answer. A value in a tool argument has been read by the model and kept by the client before it reaches Dibbla, which is what OpenAI’s directory guidelines forbid and what platform.secrets.request exists to avoid. The remote counterpart of this row is therefore platform.secrets.request (the person enters the value on a signed-in Dibbla page) followed by platform.secrets.request_status; a rotation is the same request on an existing name. Until DIB-928 this row was remote-write via action=set (DIB-538, DIB-704).
cli.deploy.archiveArchive a local repository for deploynone——Reads the caller's filesystem and builds the upload archive.
cli.manifest.validateValidate dibbla.yaml offlinenone——The local walk reads a file on the caller's disk with no platform round-trip. The server-side validation of the same manifest — the deploy path's own parse, env-aware resolution and quota rules — is a different capability: platform.manifests.validate.
cli.runRun a dibbla-task.yaml pipelinenone——Executes commands on the caller's machine.
cli.contextSwitch the local CLI contextnone——Local state, and a model-controlled context switch is forbidden outright: the connector URL selects the installation and the OAuth grant selects user and org.
cli.loginAuthenticate the CLI and store credentialsnone——Uses the OS keyring and a TTY. git-credential is the git credential helper that login registers: git runs it to read the stored login for Dibbla's git host, so the credential never leaves the caller's machine (DIB-904).
cli.updateUpdate the CLI binary in placenone——Replaces a binary on the caller's machine.
cli.scaffoldScaffold a project, install a template or an agent skillnone——Materialises files in a directory on the caller’s filesystem, and reads what is already there to avoid overwriting it. Nothing remote can write a file on the caller’s machine.
cli.ai_gatewayPoint a local AI assistant at the Dibbla AI gatewaynone——Prints shell exports for the caller’s own process environment and diagnoses the caller’s network path to the gateway. Both answers are about the machine the command runs on; a remote caller would get an answer about our machine instead.
cli.mcp_client_configWrite MCP client configuration for a local agentnone——Edits the MCP client configuration file of the agent running on the caller’s machine. An agent already connected over /platform is by definition already configured; nothing remote can rewrite its config file.
cli.credentials.revealPrint a credential of the caller’s own in plaintext to the terminalnone——Returns credential material of the caller’s own, to be pasted into the caller’s shell or tools: db connect prints a connection string carrying the caller’s own API token as its password, and storage credentials mints a short-lived (1 h) access key of the caller’s own for one bucket (DIB-1344) — never the application’s STORAGE_* keys, which are secrets. No command reveals an application secret’s value any more: a secret is write-only on every surface (DIB-1337), so secrets get only says so (cli.secrets.get), env pull writes secrets by name only (cli.env.pull) and an export cannot include a value (cli.export). Keeping credential material out of a model’s context window is an invariant of P-0035, not a precaution, so this half of each family cannot have a remote form even though listing and setting can.
cli.env.pullWrite the app’s environment to a local .env.localnone——Writes .env.local in the caller’s working directory and a .env.local line into the caller’s .gitignore, merging into a file that may already be there: lines Dibbla does not know are kept, and a secret line the developer filled in is never overwritten. The app is read from the folder’s link (dibbla clone / dibbla link). The file, its merge and the .gitignore are on the calling machine. It reveals no secret value: variables arrive with their values, secrets by name only as empty NAME= lines (DIB-1337, DIB-1338), so it is not a credential reveal. Reading the same environment remotely is not a /platform capability: platform_apps keeps environment values out of every tool result.
cli.secrets.getExplain that a secret’s value cannot be read backnone——Reads nothing and calls nothing. A secret is write-only (DIB-1337): deploy-api answers GET /secrets/{name} with 410 SECRET_VALUE_NOT_READABLE to every caller, before any lookup. secrets get stays registered only so that a person who types it is told why, and pointed at secrets list and secrets set, instead of hearing “unknown command”; it prints that text from the binary on the caller’s machine and exits 1. There is no capability behind it to make remote, on any surface: no /platform tool takes or returns a secret value.
cli.secrets.importBulk-load secrets from a local .env filenone——Reads a .env-style file from the caller’s filesystem. Since DIB-928 there is no remote path for the values either: each secret goes in through platform.secrets.request, one page per secret, entered by the person. The file walk and the values both stay local.
cli.db.dumpDump a database to a local filenone——Streams the dump through the caller’s pg_dump binary into a file on the caller’s disk. Both ends — the external binary and the destination — are on the calling machine.
cli.cloneClone an app’s Dibbla-managed git repositorynone——Creates a git working copy on the caller’s filesystem using the caller’s git binary and credentials. Reading the same source remotely is a different capability and is exposed: platform.files.get.
cli.exportExport an app — source, databases, buckets, environment, manifest — to the caller’s disknone——Writes an export directory on the caller’s filesystem — a git working copy made with the caller’s git binary, pg_dump archives, one file per bucket object, env files with every variable’s value and every secret by name only, and a docker compose sketch. No secret value is part of an export: a secret is write-only (DIB-1337), and --include-secrets no longer exists as a capability — the flag is kept, hidden, only so a script that still passes it is told why it is refused (DIB-1338). The bytes it moves are the customer’s data leaving the platform (EU Data Act ch. VI, DIB-979), which has no place in a model’s context window. Each piece read remotely is its own row: platform.files.get for the source, platform.databases.rows.query for data, platform.storage.list for buckets.
cli.org.selectChoose which organization the CLI acts asnone——Writes the acting organization into the CLI’s local config. Remotely the acting organization is fixed by the OAuth grant, not chosen per call — a model-controlled org switch is forbidden for the same reason a context switch is (cli.context).
cli.adminForce one orphan-resource sweep from the operator’s machinenone——Gated by DIBBLA_ADMIN_TOKEN, a platform-operator marker that lives outside the OAuth grant model entirely: there is no scope that carries it, and inventing one would put an operator authority inside a customer consent screen. The command acts on the deploy-api instance, not on a tenant’s resources.
cli.admin.modelsManage the platform model catalog from a global admin’s machinenone——A platform-operator authority: the model catalog is global to every tenant and editable only by a Dibbla global admin (owner/admin of the global organization), checked by the AI gateway on the validated identity. No OAuth scope carries that authority, and inventing one would put an operator power inside a customer consent screen. The catalog is not a tenant’s resource.

CLI parity

What a signed-in human can do with the dibbla CLI, an OAuth grant with the right scope can do through /platform. That is a rule, not an aspiration: dibbla-cli fails its own build when a command has no capability row, and app-hosting-service fails its own build when a row claims a tool that is not in tools/list — so a command added without a decision, or a promise the platform does not deliver, cannot reach a release.

Parity is measured in capabilities, not in tools. The unit is the contract row. Several commands may name one row — apps checks enable and apps checks disable are two commands and one capability — and one row may be delivered by several tools, because a destructive capability is a read-only plan plus an execute. Checking coverage rather than a one-to-one mapping is what makes the rule strict about what matters and stops it punishing consolidation.

An exception is written down or it does not exist. A local-only row states a technical impossibility — it reads the caller’s filesystem, needs a terminal, hands over a credential in plaintext — and cites the code that makes it so. Caution is not a reason. A not-yet-available row names an owner and the work item that closes it; an owner with no item is a parking space.

What a signed-in human can do with dibbla-cli, an OAuth grant with the right scope can do through /platform. Parity is measured in capabilities, not in tools: the unit is the contract row, and several CLI commands may name the same row. An exception is a local-only row with a reason that states a technical impossibility and a source that cites the code.

The contract covers 131 dibbla commands, 92 of them reachable through /platform today. A full write grant may list at most 33 tools; the target is 33.

A full write grant must not show more AGENT-VISIBLE tools than max_tools_full_write. Agent-visible is the whole measurement: a tool declared app-only (_meta.ui.visibility: ["app"], listed in a row's mcp_app_only_tools) is reachable only by this server's own MCP Apps card, never appears in the model's context and therefore costs the agent nothing — it is outside the count, not an allowance inside it. The number is a ratchet, not a licence: it may fall, and a change that raises it is refused UNLESS the raise itself is a decision written here. There has been one. tool_budget_target is where P-0035 Part F says the surface belongs — every agent-visible tool definition costs the agent context before it has done anything. DIB-704 took it from 59 to 30 by merging families: one destructive pair for every irreversible capability instead of seven, one read tool per resource instead of a list tool and a get tool, and a parameter wherever the difference between two tools was which step of the same flow they named. DIB-753 raised it to 31 for platform_app_card, and this is the motivation the raise was allowed on: a render tool is not a data tool. Every other capability answers what is true about the platform and could be merged into a family that answers the same question; this one draws a surface for a human inside the chat, and merging it into platform_apps would make the honest read of an application also a request to display something — the one thing the budget's own rule (a parameter is cheaper than a tool) cannot buy. The surface sits exactly on its target again, so the next capability is a parameter on a tool that is already listed, an app-only tool, or it is a decision to take one away. DIB-853 raised it to 32 for platform_tools, and this is the motivation the raise was allowed on: it is the one tool that runs the customer's own code. Every other tool reads or changes something Dibbla operates; this one lists, calls and governs the functions the organization's own tool servers register and an administrator has chosen to expose — a surface that is per-organization DATA, unbounded by this contract and changeable at any moment. It replaced the /platform/tools mount (DIB-764), which had listed one native tool per exposed function on a second URL: a second client entry and a second consent on every IDE, a list hosted chat clients carried whole in context, a list the stateless edge could never refresh, and a list a directory reviewer would have snapshotted from whatever the reviewing organization happened to expose. Folding it into ONE action tool keeps the contracted surface fixed and the organization's surface as data behind it. An agent told to run a function looks for a tool named tools; folding the invoke into platform_workflow_execute or platform_catalog would have saved the raise and cost that. The surface sits on its target again, and the rule stands: the next capability is a parameter, an app-only tool, or a decision to take one away. DIB-1439 raised it to 33 for platform_data_sources, and this is the motivation the raise was allowed on: it is the one tool that answers questions from the organization's own curated DATA, and the two tools it could have been folded into each cost a promise this contract makes. platform_tools is destructive and open-world because it runs the customer's own code; a semantic search over a data source runs only Dibbla's engine (go-toolserver's adapter on the source's managed database, through a lease) and leaves nothing behind but its audit row, so it is readOnly and closed-world — the one property that lets a client allow it without asking — and folding it into platform_tools would have taken that property away from every question. platform_database_rows_query is a narrow operations tool over a database's tables by name; a data source is what an administrator curated for agents — its search configuration and filter schema decide what can come back, its exposure and min_role decide who may ask (DIB-1434), and every call is recorded as a tool invocation with subject_kind data_source (DIB-1437) — and folding it there would have made a governed question into a table read and a table read into something governed. An agent asked a question about the organization's data looks for a tool named data sources. The surface sits on its target again, and the rule stands: the next capability is a parameter, an app-only tool, or a decision to take one away.

Every command that is not remote today

Commands State Why
dibbla admin reconcile local-only Gated by DIBBLA_ADMIN_TOKEN, a platform-operator marker that lives outside the OAuth grant model entirely: there is no scope that carries it, and inventing one would put an operator authority inside a customer consent screen. The command acts on the deploy-api instance, not on a tenant’s resources.
dibbla admin models list
dibbla admin models set
dibbla admin models delete
local-only A platform-operator authority: the model catalog is global to every tenant and editable only by a Dibbla global admin (owner/admin of the global organization), checked by the AI gateway on the validated identity. No OAuth scope carries that authority, and inventing one would put an operator power inside a customer consent screen. The catalog is not a tenant’s resource.
dibbla ai env
dibbla ai url
dibbla ai test
local-only Prints shell exports for the caller’s own process environment and diagnoses the caller’s network path to the gateway. Both answers are about the machine the command runs on; a remote caller would get an answer about our machine instead.
dibbla clone local-only Creates a git working copy on the caller’s filesystem using the caller’s git binary and credentials. Reading the same source remotely is a different capability and is exposed: platform.files.get.
dibbla context current
dibbla context list
dibbla context rename
dibbla context rm
dibbla context use
local-only Local state, and a model-controlled context switch is forbidden outright: the connector URL selects the installation and the OAuth grant selects user and org.
dibbla db connect
dibbla storage credentials
local-only Returns credential material of the caller’s own, to be pasted into the caller’s shell or tools: db connect prints a connection string carrying the caller’s own API token as its password, and storage credentials mints a short-lived (1 h) access key of the caller’s own for one bucket (DIB-1344) — never the application’s STORAGE_* keys, which are secrets. No command reveals an application secret’s value any more: a secret is write-only on every surface (DIB-1337), so secrets get only says so (cli.secrets.get), env pull writes secrets by name only (cli.env.pull) and an export cannot include a value (cli.export). Keeping credential material out of a model’s context window is an invariant of P-0035, not a precaution, so this half of each family cannot have a remote form even though listing and setting can.
dibbla db dump local-only Streams the dump through the caller’s pg_dump binary into a file on the caller’s disk. Both ends — the external binary and the destination — are on the calling machine.
dibbla deploy local-only Reads the caller's filesystem and builds the upload archive.
dibbla env pull local-only Writes .env.local in the caller’s working directory and a .env.local line into the caller’s .gitignore, merging into a file that may already be there: lines Dibbla does not know are kept, and a secret line the developer filled in is never overwritten. The app is read from the folder’s link (dibbla clone / dibbla link). The file, its merge and the .gitignore are on the calling machine. It reveals no secret value: variables arrive with their values, secrets by name only as empty NAME= lines (DIB-1337, DIB-1338), so it is not a credential reveal. Reading the same environment remotely is not a /platform capability: platform_apps keeps environment values out of every tool result.
dibbla export local-only Writes an export directory on the caller’s filesystem — a git working copy made with the caller’s git binary, pg_dump archives, one file per bucket object, env files with every variable’s value and every secret by name only, and a docker compose sketch. No secret value is part of an export: a secret is write-only (DIB-1337), and --include-secrets no longer exists as a capability — the flag is kept, hidden, only so a script that still passes it is told why it is refused (DIB-1338). The bytes it moves are the customer’s data leaving the platform (EU Data Act ch. VI, DIB-979), which has no place in a model’s context window. Each piece read remotely is its own row: platform.files.get for the source, platform.databases.rows.query for data, platform.storage.list for buckets.
dibbla git-credential
dibbla login
dibbla logout
local-only Uses the OS keyring and a TTY. git-credential is the git credential helper that login registers: git runs it to read the stored login for Dibbla's git host, so the credential never leaves the caller's machine (DIB-904).
dibbla manifest validate local-only The local walk reads a file on the caller's disk with no platform round-trip. The server-side validation of the same manifest — the deploy path's own parse, env-aware resolution and quota rules — is a different capability: platform.manifests.validate.
dibbla mcp community
dibbla mcp platform
dibbla mcp server
local-only Edits the MCP client configuration file of the agent running on the caller’s machine. An agent already connected over /platform is by definition already configured; nothing remote can rewrite its config file.
dibbla org use
dibbla org clear
local-only Writes the acting organization into the CLI’s local config. Remotely the acting organization is fixed by the OAuth grant, not chosen per call — a model-controlled org switch is forbidden for the same reason a context switch is (cli.context).
dibbla run local-only Executes commands on the caller's machine.
dibbla create go-worker
dibbla template install
dibbla skills install
dibbla skills list
dibbla init
local-only Materialises files in a directory on the caller’s filesystem, and reads what is already there to avoid overwriting it. Nothing remote can write a file on the caller’s machine.
dibbla secrets get local-only Reads nothing and calls nothing. A secret is write-only (DIB-1337): deploy-api answers GET /secrets/{name} with 410 SECRET_VALUE_NOT_READABLE to every caller, before any lookup. secrets get stays registered only so that a person who types it is told why, and pointed at secrets list and secrets set, instead of hearing “unknown command”; it prints that text from the binary on the caller’s machine and exits 1. There is no capability behind it to make remote, on any surface: no /platform tool takes or returns a secret value.
dibbla secrets import local-only Reads a .env-style file from the caller’s filesystem. Since DIB-928 there is no remote path for the values either: each secret goes in through platform.secrets.request, one page per secret, entered by the person. The file walk and the values both stay local.
dibbla update
dibbla uninstall
local-only Replaces a binary on the caller's machine.
dibbla secrets set local-only Takes the value as an argument or from stdin on the caller’s machine. Since DIB-928 the connector has no input path for a secret value at all: platform_secret_write action=set is gone from the schema, and a call that still carries a value or action=set is refused server-side with UNSUPPORTED before the arguments are decoded — nothing stored, echoed or logged — with the request flow named in the answer. A value in a tool argument has been read by the model and kept by the client before it reaches Dibbla, which is what OpenAI’s directory guidelines forbid and what platform.secrets.request exists to avoid. The remote counterpart of this row is therefore platform.secrets.request (the person enters the value on a signed-in Dibbla page) followed by platform.secrets.request_status; a rotation is the same request on an existing name. Until DIB-928 this row was remote-write via action=set (DIB-538, DIB-704).

Error codes

Every surface returns the same envelope with one of these stable codes. An object in another organisation is answered as absent, never as forbidden — answering forbidden would confirm the object exists, and that confirmation is itself the disclosure.

Code HTTP Class Retryable Meaning
VALIDATION_FAILED 400 caller no The request did not satisfy the schema. Populates field_violations.
UNSUPPORTED 400 caller no A well-formed request for something this contract version does not offer.
IDEMPOTENCY_KEY_REQUIRED 400 caller no A routine write was attempted without an idempotency key.
IDEMPOTENCY_KEY_CONFLICT 409 caller no The key was reused with different parameters.
MISSING_TOKEN 401 authorization no No credential was presented. Answered with a WWW-Authenticate challenge pointing at protected-resource metadata.
INVALID_TOKEN 401 authorization no The credential failed issuer, audience, resource, client, expiry, not-before or signature validation. Deliberately one code: which check failed is not the caller's business.
INSUFFICIENT_SCOPE 403 authorization no The grant is valid but lacks the exact scope. Names the required scope, because that is what the user consents to next.
FORBIDDEN 403 authorization no Role, entitlement or per-object authorization denied the action.
CONFIRMATION_REQUIRED 403 authorization no A destructive action was attempted without a bound, redeemed confirmation.
CONFIRMATION_INVALID 403 authorization no The confirmation was changed, expired, reused, concurrently redeemed, differently authorized or revoked.
APPROVAL_REQUIRED 409 caller no The bound destructive plan exists but the same eligible human has not approved it.
APPROVAL_DENIED 409 caller no The eligible human denied or revoked the bound destructive plan.
CONFIRMATION_CHANGED 409 authorization no The principal binding, target, live generation/revision, normalized effects or digest differs from the reviewed plan.
CONFIRMATION_EXPIRED 409 caller no The destructive plan's ten-minute approval window elapsed before redemption.
CONFIRMATION_USED 409 caller no The confirmation was already consumed and this request cannot receive its bound terminal replay.
IN_PROGRESS 409 caller yes The approved destructive request is executing; retry reads its durable terminal result and never starts another side effect.
NOT_FOUND 404 absence no No such object within this principal's visibility. An object in another tenant is NOT_FOUND, never FORBIDDEN — the distinction would itself be a cross-tenant disclosure.
CONFLICT 409 caller no The object's current state does not permit this transition.
RATE_LIMITED 429 caller yes Too many requests. Retry after the indicated interval.
UPSTREAM_FAILED 502 upstream yes A dependency failed in a way that may not repeat.
UPSTREAM_UNAVAILABLE 503 upstream yes A dependency is unreachable.
TIMEOUT 504 upstream yes A dependency did not answer in time. A durable operation is returned instead wherever one exists.
NOT_CONFIGURED 503 misconfiguration no This install has not been configured to serve the capability. Operator-facing, and never implies the caller lacks access.
INTERNAL 500 upstream no An unclassified failure. Carries a correlation ID and nothing else.