Pre-release Harn is pre-1.0 — the language, standard library, and CLI may change between releases. See the release notes

Connector operator runbook

This is the release gate for Harn connector ingress. It validates the package contract first, then the HTTP or daemon path that operators actually run. Provider API mappings and credential names come from each package's harn.toml; Harn owns ingress isolation, replay/dedupe, effect policy, OAuth coordination, and runner supervision.

Never put secret values in a command argument, issue, transcript, or run receipt. Store them through the configured secret provider and refer to the package-declared secret id.

Preflight

Record the Harn commit, connector tag, environment, and operator before each validation. The generated connector parity matrix is the checked source for package versions, Harn version floors, capabilities, credential ids, setup commands, validation commands, and fixture coverage.

harn --version
harn config inspect --explain
harn check --connector-matrix --format=markdown
harn connect status --json

For every enabled connector, confirm:

  • the installed Harn version satisfies the package floor
  • only declared capabilities are enabled; connector capabilities default off
  • harn config inspect --explain identifies the winning layer
  • required secret ids are present without exposing their values
  • normalization has no network, file, process, or LLM authority

Provider setup

Install the connector tag shown in the parity matrix before running its setup command. harn connect setup-plan --connector <id> --json is the machine-readable source for the package's current secrets, scopes, health checks, and recovery copy.

GitHub App and user OAuth

Create a GitHub App owned by the example organization. Set its webhook URL to https://<public-host>/webhooks/github, generate a webhook secret, and subscribe only to the events used by the trigger. Start with read-only Metadata, Issues, Pull requests, and Contents permissions. Add the corresponding write permission only for an enabled typed mutation. Actions runner reads require administration:read (repository) or organization_self_hosted_runners:read (organization); JIT registration and cleanup require the corresponding write permission.

Download the App private key once into a permission-restricted temporary file, then let Harn move it into the secret provider:

harn connect github --app-slug harn-example --app-id 12345 \
  --private-key-file ./harn-example.private-key.pem \
  --webhook-secret-file ./github-webhook-secret --json
harn connect status --connector github --json

The stored references are github/app-private-key and github/webhook-secret; the manifest refers to those ids, never PEM or secret values. Delete the temporary files after status reports healthy. To rotate, add a new App key, rerun setup with that key, prove one signed webhook and one typed read, then revoke the old key in GitHub. Rotate the webhook secret by updating GitHub and the secret provider in one maintenance window and replaying the invalid-old/valid-new fixture pair.

GitHub user OAuth is separate from App installation authentication. Use the connector's oauth_user_device_code and oauth_user_access_token typed methods with the narrow requested scopes; do not substitute a personal access token. For concurrent clients, run the single-flight validation below before enabling token rotation.

Slack Events API and Socket Mode

Create a Slack app from the version-pinned manifest in the harn-slack-connector README. For HTTP delivery, enable Events API and set the Request URL to https://<public-host>/webhooks/slack. Store the signing secret as slack/signing-secret, authorize with harn connect slack, and store the bot token as slack/bot-token.

Minimum scopes are determined by enabled methods: app_mentions:read, channels:history, groups:history, im:history, or mpim:history for the corresponding events; chat:write for messages; reactions:write for reactions; users:read for user lookup; and files:write for uploads. Private channel reads require the matching groups:* scope and app membership in that channel. Validate that a non-member request returns a permission diagnostic and does not fall back to broader history.

Socket Mode is off by default. To opt in, create an app-level token with only connections:write, store it as slack/app-token, and set socket_mode_enabled: true in the package binding. Rotation is add-new, reconnect-and-ack-one-envelope, then revoke-old. HTTP signing-secret rotation uses the same invalid-old/valid-new proof as GitHub.

harn connect slack --scope \
  app_mentions:read,channels:history,chat:write --json
harn connect status --connector slack --json

CircleCI

In Project Settings, create a webhook at https://<public-host>/webhooks/circleci, select workflow-completed and job-completed, and configure a secret. Store it as circleci/webhook-secret. Create a personal API token only when typed reads or reruns are enabled; store it as circleci/api-token. The API token has the authority of its CircleCI user, so use a dedicated least-authority user with access only to the example project.

harn connect api-key --connector circleci \
  --secret-id circleci/webhook-secret
harn connect api-key --connector circleci --secret-id circleci/api-token
harn connect status --connector circleci --json

Rotate each value by storing the replacement through the same prompt, updating the CircleCI webhook or user token, proving a signed failure event and typed workflow read, then revoking the old value.

Buildkite

Create an organization webhook at https://<public-host>/webhooks/buildkite for build.finished and job.finished. Prefer the timestamped HMAC signature mode and store its token as buildkite/webhook-token. Create a dedicated API token with read_builds for build/job/log reads and add write_builds only when retry, rebuild, or cancel actions are enabled; store it as buildkite/api-token. GraphQL access is separately powerful and must remain disabled unless a typed method requires it.

harn connect api-key --connector buildkite \
  --secret-id buildkite/webhook-token
harn connect api-key --connector buildkite --secret-id buildkite/api-token \
  --scopes read_builds
harn connect status --connector buildkite --json

Rotate webhook and API tokens independently. A successful rotation proves one new signed event, one typed build read, and rejection of the old credential before revocation is recorded complete.

Bitbucket and secondary forges

For Bitbucket Cloud, configure the repository webhook at https://<public-host>/webhooks/bitbucket, store its HMAC secret as bitbucket/webhook-secret, and use a repository- or workspace-scoped access token as bitbucket/api-token. Grant repository read plus pull-request or commit-status write only when those typed mutations are enabled. Data Center uses the equivalent instance PAT and callback path. The provider README named by the parity matrix owns the exact event headers and scope mapping.

Secret handling check

After every setup or rotation, send a neutral fixture containing sentinel values that resemble the secret ids, inspect the trigger record, and search the state directory for the sentinel. The secret value itself must never be used as the sentinel or printed:

harn connect status --connector github --json
harn orchestrator inspect --state-dir ./.harn/orchestrator --json
rg 'github/app-private-key|github/webhook-secret' .harn

The last command may find references; it must not find credential values.

Deterministic package gate

Run the package gate from a clean checkout of each connector tag listed in the matrix. These commands exercise the package-owned verifier and normalizer against signed, invalid-signature, stale/replay, dedupe, and action-policy fixtures declared by that package.

harn package verify . --provider github
harn package verify . --provider slack
harn package verify . --provider linear
harn package verify . --provider notion --run-poll-tick
harn package verify . --provider circleci
harn package verify . --provider buildkite
harn package verify . --provider bitbucket

Do not waive a missing negative fixture. Each webhook package must demonstrate one accepted authenticated event, one authentication failure, one stale or replayed request rejection when the provider supplies a timestamp, and a stable dedupe key. Poll and streaming packages must demonstrate cursor/replay behavior at their transport boundary.

Canonical ingress gate

Create a disposable workspace that pins the connector tag and declares a provider plus trigger binding. Store the declared secrets, start the orchestrator on loopback, and send the package's signed fixture through the actual listener route.

harn connect setup-plan --connector github --json
harn connect status --connector github --json
harn orchestrator serve --state-dir ./.harn/orchestrator --public-metrics
harn orchestrator inspect --state-dir ./.harn/orchestrator --json

Repeat the listener test with the invalid signature, stale timestamp, and duplicate delivery. The required observations are:

ProbeHTTP resultDispatch result
Valid signed deliveryProvider-defined successOne inbox event and one dispatch
Invalid signature/auth401 or provider-defined rejectionNo inbox event
Stale/replayed delivery401 or 409No inbox event
Duplicate delivery idSuccess or 409, consistentlyExactly one dispatch
Normalizer attempts network/file/process/LLMRejectionNo side effect and no dispatch

Slack URL verification is the one immediate-response route: it must return the challenge only after Slack request authentication succeeds and must not enqueue an event. Notion polling must additionally prove that a restarted poll resumes from the committed cursor without re-emitting the same dedupe key.

Action and OAuth gate

For every action-capable package, exercise one read and one mutation using the typed connector method. The read must require only its declared network and secret capabilities. The mutation must produce an approval/effect-policy decision and an audit receipt; a denied mutation must make no provider call. Raw API escape hatches do not substitute for typed least-authority methods.

For OAuth connectors, run the deterministic concurrent-refresh regression before live validation. In a disposable provider account, then launch two clients against one expired credential and confirm one refresh occurs, both clients reread the committed credential, and neither persists a stale rotated refresh token.

The checked local regression is:

harn test conformance --filter oauth_client_refresh_singleflight_reread

For CI failure handling, validate these end-to-end outcomes:

ScenarioTyped readMutationRequired policy result
GitHub eventissue, pull request, check, or workflow readone matching typed actionexplicit approval before provider call
CircleCI failed jobworkflow/job lookup and artifact listrerun from faileddenied action makes zero HTTP calls
Buildkite failed jobbuild/job lookup and log fetchjob retry or rebuildwrite_builds plus explicit approval

Retry only from the persisted event or action receipt. A blind raw-API retry is not evidence because it bypasses the typed contract, dedupe key, and audit lineage.

Ephemeral runner gate

GitHub Actions just-in-time runners use std/runner_pool. Provider code mints the JIT configuration and supplies the adapter closure; the shared pool owns supervision, replacement, scaling, graceful drain, and online/busy state.

Validate:

  • pool size remains within the declared minimum and maximum
  • every slot invokes the runner with a single-use JIT configuration
  • a completed or failed runner is deregistered and replaced according to the shared restart policy
  • online/busy transitions appear in runner_pool_state
  • scaling drains the old generation before the new generation starts
  • shutdown leaves no registered runner or child process

The shared lifecycle proof is public and credential-free:

harn test conformance --filter runner_pool_lifecycle

For a live JIT proof, enable the GitHub package's typed runner methods, generate one single-use JIT configuration, and pass the resulting adapter closure to runner_pool_start. Record the allocation id, online/busy transitions, job receipt, process exit, deregistration, and replacement or terminal stop. Local runner mode uses the same pool contract but returns a local process adapter instead of minting GitHub registration material.

Observability and recovery

harn connect status --json
harn orchestrator inspect --state-dir ./.harn/orchestrator --json
curl --fail http://127.0.0.1:8080/metrics

Correlate the delivery id across the ingress receipt, trigger inbox, observability.action_graph, dispatch result, and provider action receipt. Check queue age, dispatch latency, retries, backpressure, dedupe, signature rejections, and DLQ counters. If OpenTelemetry is enabled, confirm the same trace reaches the configured collector.

Recovery is package-driven:

  1. Run harn connect status --connector <id> --json.
  2. Follow the package's recovery copy for missing_auth, expired_credentials, revoked_credentials, missing_scopes, inaccessible_resource, or transient_provider_outage.
  3. Rotate or revoke credentials only under the environment's credential procedure.
  4. Rerun the deterministic package gate and canonical ingress gate.
  5. Record the new credential version identifier, never its value.

Stable harn connect status --json states:

StatusMeaningRecovery
healthyRequired secrets, expiry, scopes, and health checks passed.Continue with the package and canonical ingress gates.
missing_authA required secret or OAuth record is absent.Run the package setup command and store the declared secret ids.
expired_credentialsThe stored credential is past its expiry.Refresh or reconnect; do not retry provider actions first.
revoked_credentialsThe credential index exists but its backing secret is gone or revoked.Revoke the stale local record, reconnect, then validate.
missing_scopesAuthentication works but required declared scopes are absent.Reauthorize with only the reported missing scopes.
inaccessible_resourceCredentials are valid but the target resource is not reachable.Grant resource access or correct the resource id.
transient_provider_outageProvider or credential-backend health could not be established.Preserve the event and retry after the dependency recovers.

Ingress rejection and denied-effect diagnostics must include the provider, delivery/dedupe id when available, stable status or diagnostic code, and trace id. They must not include raw authorization headers, signing secrets, private keys, OAuth refresh tokens, or package secret values.

Capability-change review checklist

Update this checklist in the same pull request as any connector capability:

  • package version and Harn floor match the tagged connector
  • provider capability, setup, scope, secret, health, and recovery metadata are updated in harn.toml
  • the generated parity matrix is regenerated and drift-checked
  • package README documents creation, callback, least scopes, rotation, and the exact harn package verify command
  • valid, invalid-auth, stale/replay, dedupe, disabled-by-default, missing-scope, expired-credential, and denied-effect fixtures cover the affected route
  • one typed read and every new mutation have effect-policy and zero-call denial assertions
  • HTTP, polling, streaming, or Socket Mode canonical ingress is exercised as applicable
  • event, dispatch/retry/DLQ, action receipt, and cleanup share a correlation id
  • local/JIT runner changes use std/runner_pool and prove graceful cleanup
  • all documented commands and flags pass the docs CLI checker

The cutover is releasable only when every enabled row has terminal evidence for its deterministic package gate, canonical route, negative security probes, action policy (when applicable), and recovery path.