Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Weft The book

Declaring a service

A service recipe is the service block in an access node’s metadata.json. It is pure declaration: how a credential is obtained, how a request is signed, what the permissions are, how events arrive. There is no Rust per service.

The node itself is one line:

weft::access_node!(SlackAccessNode);

The top level

KeyDefaultWhat it says
servicerequiredThe name, lowercase letters, digits and underscores
labelthe service nameWhat people see, like “Slack”
acquisitionrequiredHow a credential is obtained
auth[]How a request through it is signed. Empty for something that signs nothing, like a database
doors["own"]Which connect doors to offer
testnoneA call made at connect time to check it works, and where identity usually comes from
identitynoneThe display name, assembled from stored values, like "{team} / {user}"
permissions[]The permission catalogue people tick from
all_permissions_urlnoneWhere the provider’s full list is, when yours is a subset
verificationsilent, freeWhat the check can learn and what it costs
connection_optionalfalseThe node runs with nothing picked
callback_httpsfalseThe provider refuses plain http callbacks
own_pagenoneWhat the “your own” door shows beyond its paste fields
events{}How this service reports events, by topic
verifynoneHow a caller presenting this connection is checked, on a gated route
capabilities[]Named groups of optional fields, each unlocking one thing
grantscoexistingWhether two grants of this service can coexist

Acquisition

kindForKeys
staticPaste a keyfields
oauth2Sign ingrant, token_url, scope_delimiter, auth_params, extra_params, registration_fields, captures, token_auth, refresh
mint_jwtSign your own assertionfields, algorithm, claims, jwt_ttl_secs, exchange

The grant is authorization_code with an auth_url and PKCE on by default, or client_credentials.

There is a fourth, runtime, and you never write it. It is the stored form of a shared connect, written by the store, and declaring one is refused.

Auth steps

How a request gets signed, applied in order:

kindWhat it does
headerAdds a header
queryAdds a query parameter
basicHTTP basic auth
path_prefixPuts a prefix on the path
base_urlReplaces the scheme, host and port, keeping the node’s path
signSigns the request: SigV4, or OAuth 1.0a

Every value is a template interpolating stored values by name, like "Bearer {token}".

Permissions

{ "id": "chat:write", "label": "Send messages",
  "description": "Post messages as the app.", "default": true }

A curated catalogue, not the provider’s whole list. On a provider with hundreds of scopes, list the ones your nodes actually need and point all_permissions_url at the rest.

default: true starts it ticked. own_only: true means the runtime’s own credential can never serve it, which greys out the shared option for anyone who ticks it.

Verification

Say honestly what your check can learn:

rungMeaning
reports_permissionsThe provider states what it granted
self_introspectThe credential can describe itself
reports_validityOnly “alive”, never “what”
silentNothing knowable

Only the first two make the recorded permissions authoritative, and only those let weft block a node before it calls.

cost is free, ambiguous or paid, and only free runs on its own. A check that might bill is never run without being asked.

Today every service in the shipped catalog declares free, and nine of the thirteen declare reports_validity. So in practice the only way a connection is genuinely verified right now is the OAuth scope echo, where the provider tells weft what it granted. A probe rung exists in the type and nothing implements it.

Your own door

own_page is what that door shows:

KeyWhat it is
mintA button that creates the application for them, if the provider has an API for it
guideNumbered steps, and a pre-filled creation link
pasteThe fields, when the acquisition is not already a paste form

A paste block on a static acquisition is refused as redundant: those fields already render.

What it refuses

The validator is strict, and the messages name the fix. A service name with a capital in it. A header name that is not a legal header name. A capability listing a field the service does not declare, or listing a required field, since a required field can never be the thing that is missing. A permission with no description. A service that signs its requests offering the shared door. A token_echo caller check, which compares a token weft minted when subscribing and a route’s caller never has.

Events

Each topic under events says which facts to pull out of a payload, which account it belongs to, and how it arrives. Go and read events from a service.

Where it goes

A package root’s metadata.json, so every node in the package inherits it, or the access node’s own file.

Exactly one input in the node needs the access widget. That is the connect control, and a recipe without one is refused.