Connectors
A connector is a provider definition that OutcomeCI ships: the API's base URL,
the kinds of credential it accepts, the operations a step can be granted, and
the rules no grant can override. A workflow binds a connector to a secret under apis:
secrets:
github: vault:github
apis:
github: {uses: github, auth: secrets.github}The connector never holds a secret or makes a request itself. The runtime's broker sends every call, authenticates it with the credential, enforces the step's grants, and records the call in the run's journal. The agent sees an operation name, its inputs, and the projected result, never a secret or a token.
Two connectors are available: slack and github. Each workflow revision
records the exact connector definition it compiled against, so a run always
uses the operations its revision was built with.
Accepted credentials
auth: secrets.<name> never says how to authenticate: the
Vault credential does. Each connector
lists the kinds its API accepts and fills in what the provider fixes, such as
its token endpoint:
| Connector | Credential | Store it as |
|---|---|---|
github |
Personal access token, classic or fine-grained | A plain value |
github |
GitHub App installation | app_installation with --app-id, --installation-id, and the app's private key |
slack |
Bot token (xoxb-) |
A plain value |
slack |
Slack app with token rotation | oauth2 with --client-id, --grant-type refresh_token, and the client_secret and refresh_token |
A GitHub App installation signs a short-lived JWT with the app's key and exchanges it for an installation token. A Slack app with token rotation exchanges its refresh token for a 12-hour bot token; Slack issues a new refresh token each time, and the runtime saves it back to the Vault before the run continues. Both tokens are reused until they expire.
A credential the connector does not accept fails before any request, naming the connector, the Vault entry, and the kinds it accepts.
Request a connector
New connectors arrive through GitHub issues on outcomeci/connectors. Open an issue that names the API, the operations your workflow needs, and how the API authenticates. The pull request that answers it follows the connector authoring guide, which people and coding agents both use: it defines the operations, the grants and refusals, the credential kinds the API accepts, and the tests a connector needs before it ships.
GitHub
uses: github calls https://api.github.com with a personal access token or
a GitHub App installation token. A step can send up to 100 GitHub requests.
| Operation | What it does | Grant arguments |
|---|---|---|
read |
Sends a GET to any path under the GitHub REST API, such as /repos/{owner}/{repo}/contents/{path} |
repo |
write |
Reads, creates branches, commits files, and opens pull requests with GET, POST, PUT, and PATCH |
repo |
A repo grant limits every request to paths under /repos/{owner}/{name}.
Write it as a literal owner/name or as a reference to an {owner, name}
object, such as trigger.repo.
write always refuses, whatever the grant:
- changes outside a repository;
- repository settings and administration, such as collaborators, hooks, keys, Actions, environments, and rulesets;
- branch protection;
- merging a pull request or branch; and
- moving an existing branch.
Slack
uses: slack calls the Slack Web API with a bot token, or with the token a
Slack app with token rotation issues. A step can send up to 50 Slack requests.
| Operation | What it does | Grant arguments |
|---|---|---|
post |
Posts one message to a channel, or a thread reply with thread_ts. Returns the message's channel, ts, and thread_ts |
channel, thread_ts |
thread |
Reads a message's thread: the root message and every reply | channel |
reactions |
Reads the reactions on one message | channel |
file |
Opens one file shared in a conversation, such as a screenshot | channel |
file downloads the file with the same credential, only from files.slack.com,
and saves it with the run's artifacts. It returns the file's
name, mimetype, size, and a local file.path the agent can open. Files
larger than 2 MiB are refused. A channel grant on file is checked against
the response: the file must be shared in that channel.
Grant operations to a step
A step's can: lists the operations it may call. A bare entry grants the
operation with no argument rules:
can:
- github.readA mapping fixes grant arguments on every call:
can:
- slack.post: {channel: trigger.channel, thread_ts: trigger.ts}
- github.write: {repo: target.repo}An argument is a literal or a reference to data the step can read. The broker
enforces it on every call, fills it in when the agent leaves it out, and
refuses a call that names something else. When a step is granted the same
operation twice, name each grant with as so later steps can tell the calls
apart:
can:
- slack.post: {channel: build, as: plan_post}A later step reads the recorded call as <step>.calls.plan_post, or the last
call of an operation as <step>.calls.slack.post.
Add rules a grant cannot express
Grant arguments are exact. For rules about intent, such as "one branch, one
pull request, no force-push", add a policy to the step:
- fix:
reason: fix-and-open-pr.md
can:
- github.write: {repo: triage.repo}
policy: "One new branch from the default branch, one PR, only files the fix needs. No force-push."A reviewer checks each call that changes something against the policy before the broker sends it. Reads are not reviewed. See Review calls with a policy.
Wait for people
The Slack connector defines two watchers, which await and converse steps
use to wait for a person:
| Watcher | Written as | Waits for |
|---|---|---|
reaction |
await: {slack.reaction: {...}} |
An emoji reaction on a recorded message, optionally from one person |
reply |
converse: slack.thread(<recorded message>) |
Human replies in the message's thread; bot messages are ignored |
The runtime owns the polling, the timeout, and the record of what it saw. See await steps and converse steps.
Start runs from provider events
The Slack connector also has a receiver, which a webhook trigger uses to
verify Slack's signed Events API requests:
trigger:
webhook: {uses: slack, auth: secrets.slack_signing, events: [mention, dm]}| Event | Starts a run for |
|---|---|
mention |
A top-level message that mentions the app, in a channel it is in |
dm |
A top-level direct message to the app |
The receiver answers Slack's URL verification handshake, rejects requests whose signature does not match or whose timestamp is more than five minutes old, and deduplicates redeliveries by Slack's event ID. Thread replies do not start runs. See Webhooks.
oci integration slack setup creates a Slack app with only the scopes the
operations use and an event subscription to your workflow's webhook URL. See
the CLI reference.