Skip to content

Reference

alter design

Validate the ALTER_INTEGRATION.md design document before provisioning.

The design document (ALTER_INTEGRATION.md) records how an integration uses Alter — the goal, the runtime identity (app vs agent), the key type, principals, providers, and how grants are acquired — in machine-checkable YAML frontmatter. alter design validate runs a deterministic rule set over it so identity mistakes (for example, an app runtime key serving an agent workload) fail before anything is provisioned. Pair with alter verify, which re-validates the design and then checks the implementation against it.

alter design validate [--design <path>] [--remote] [--app <app-id-or-name>] [--output json|jsonl|table]

ALTER_INTEGRATION.md is a file committed at the repository root. YAML frontmatter carries the machine-checkable fields; the prose below it explains the intent. The Alter Docs MCP server returns a complete, already-valid starter template for each setup flow, so the frontmatter never has to be written from scratch: get_started includes it in the flow plan, and next_step repeats it at the design step.

Each template below is the exact artifact that server returns. Copy the one matching the goal and replace its values.

goal: user-data — call an API as the signed-in user

Section titled “goal: user-data — call an API as the signed-in user”
---
use_case: "Post as our Slack app bot in a workspace a user connects"
goal: user-data # user-data | backend-secret | agent
runtime_identity: app # app | agent — WHO makes the provider call
key_type: runtime # runtime (app key, alter_rk_…) | agent (the agent's own key) — must match runtime_identity
principals: [user] # user | group | system | agent — never mix user and agent.
# Names the Alter grant OWNER, not the upstream token principal.
providers: [slack] # the OAuth providers this design calls
grant_acquisition: connect-consent # user-data always acquires grants via browser consent
idp: none # none | jwt-resolution (jwt-resolution must be listed in dashboard_steps)
dashboard_steps: [] # operator-owned human actions, listed honestly
language: typescript # python | typescript — the two languages with SDK wiring
---
Why: the signed-in user approves the workspace installation, and Alter binds
the grant to that user. Slack API calls use the installed Slack app bot; this
flow does not request a Slack user token or post as the human.

Here, principals: [user] identifies the Alter grant owner. It does not claim that every provider issues a human-user token: Slack’s standard Alter adapter stores an installed app-bot token, while other providers may issue a credential whose upstream principal is the user. The provider reference is authoritative.

goal: backend-secret — a service holds its own credential

Section titled “goal: backend-secret — a service holds its own credential”
---
use_case: "Call the Stripe API from our billing worker"
goal: backend-secret # user-data | backend-secret | agent
runtime_identity: app # app | agent — WHO makes the provider call
key_type: runtime # runtime (app key, alter_rk_…) | agent (the agent's own key) — must match runtime_identity
principals: [system] # system | user | group — an agent-bound secret is the agent flow
providers: [] # OAuth providers only; a managed secret is not listed here
grant_acquisition: managed-secret-grant # backend-secret always acquires via a managed-secret grant
idp: none # none | jwt-resolution (jwt-resolution must be listed in dashboard_steps)
dashboard_steps: [] # operator-owned human actions, listed honestly
language: typescript # python | typescript — the two languages with SDK wiring
---
Why: the billing worker calls Stripe as itself. There is no end user in the
loop, so the credential is a managed secret bound to the workload.

goal: agent — an agent calls under its own identity

Section titled “goal: agent — an agent calls under its own identity”
---
use_case: "Let our support agent read threads available to an installed Slack app bot"
goal: agent # user-data | backend-secret | agent
runtime_identity: agent # app | agent — WHO makes the provider call
key_type: agent # the agent's own key from alter agents create — an agent identity must NOT use an app runtime key
principals: [agent] # never mix user and agent on one credential path
providers: [slack] # the OAuth providers this design calls
grant_acquisition: agent-delegation # agent-delegation | managed-secret-grant (the latter is CLI/portal-only)
idp: none # none | jwt-resolution (jwt-resolution must be listed in dashboard_steps)
dashboard_steps: [] # operator-owned human actions, listed honestly
language: typescript # python | typescript — the two languages with SDK wiring
---
Why: the agent acts under its own Alter identity so the audit trail attributes
each call to the agent, not to the application. The delegated Slack credential
still represents the installed app bot, not the human who approved it.

Each template’s inline comments are scoped to its own goal, because the accepted value depends on the goal. The tables below give the full range.

FieldRequiredAccepted values
use_caseyesFree text — one sentence describing the integration.
goalyesuser-data, backend-secret, agent
runtime_identityyesapp, agent — which identity makes the provider call.
key_typeyesruntime, agent — must match runtime_identity.
grant_acquisitionyesconnect-consent, managed-secret-grant, agent-delegation
idpyesnone, jwt-resolution
languageyespython, typescript
principalslist — required for every goaluser, group, system, agent — user and agent never share one credential path. Each goal constrains the set; see the table below.
providerslistOAuth provider ids this design calls. Required for goal: user-data. A managed secret is not listed here.
dashboard_stepslistHuman, operator-owned steps (for example identity-provider setup).

The frontmatter is standard YAML. Every list field (principals, providers, dashboard_steps) accepts either YAML list form, and both produce the same design:

---
use_case: "Read the signed-in user's QuickBooks invoices"
goal: user-data
runtime_identity: app
key_type: runtime
principals:
- user
providers:
- quickbooks
grant_acquisition: connect-consent
idp: jwt-resolution
dashboard_steps:
- Configure IDP # an operator confirms the identity provider
language: typescript
---

is equivalent to principals: [user], providers: [quickbooks], and dashboard_steps: ["Configure IDP"]. A single bare value is a one-item list (providers: quickbooks), and an empty value, ~, or [] is an empty list. Quote a value that contains : or starts with a YAML indicator character (for example use_case: "Sync: nightly").

The file fails with rule parse (exit 1), naming the problem, when:

  • the YAML is malformed or repeats a key — the message gives the line and column in the file;
  • a value or list item contains an unquoted : — YAML reads it as a nested mapping, and the message says to quote it;
  • a line between the --- markers is not a key: value field, a list item or a # comment (for example free prose) — the message says to move it below the closing --- or make it a comment;
  • the frontmatter is not a mapping of key: value fields;
  • a single-value field (for example goal) holds a list, mapping, number, or boolean instead of text;
  • a list field holds something other than a list of text, or an item is empty or nested — the message names the field and the zero-based item index, as in dashboard_steps[0]: list items must be strings, found a mapping.

CLI releases up to and including 0.10.0 used a smaller parser that accepted unquoted : values and prose lines inside the frontmatter, so a design file written for them may need values quoted or prose moved when the CLI is upgraded.

When the frontmatter fails to parse, no rule-set violations are reported for it; fix the parse errors first. Fields the validator does not know are ignored.

The goal decides which identity makes the provider call, which key that identity carries, and how the credential is acquired. Every combination outside this table is a rule violation:

goalruntime_identitykey_typeprincipalsgrant_acquisition
user-dataappruntimemust include userconnect-consent
backend-secretappruntimeat least one of system, user, group; never agentmanaged-secret-grant
agentagentagentexactly [agent]agent-delegation, or managed-secret-grant — the latter is valid but emits a warning, because an agent-bound managed-secret grant can only be created with alter managed-secrets grants create --principal-type agent or the dashboard, never the SDK. Exit code stays 0.

A user-delegated OAuth grant consumed by an agent is still goal: agent with principals: [agent]. principals records the identity on the runtime credential path, not who consented — the user’s consent is recorded by grant_acquisition: agent-delegation.

Validates the design document. Local by default — no network and no login required, so it can gate automation.

Offline exit codes: 0 passed (warnings allowed); 1 rule violations, or a design file that exists but cannot be read; 2 a bad flag or --output value; 4 the design file was not found. Adding --remote can also produce the standard authenticated codes — 3 not signed in, 7 forbidden (e.g. insufficient PAT scope) — plus 5/6 from the backend, and 2/4 when the --remote app itself cannot be resolved or found. See exit codes before branching on them in CI.

FlagDefaultDescription
--design <path>./ALTER_INTEGRATION.mdPath to the design document.
--remoteoffAdditionally verify the design’s providers against the app’s actual configuration, when the design lists any. With an empty providers list the remote check is a no-op and nothing is contacted. Otherwise requires alter auth login and an app (pass --app, link a workspace, or set ALTER_APP_ID).
--app <app-id-or-name>resolved appApp for --remote; falls back to ALTER_APP_ID or the linked workspace.
--output <format>jsonjson, jsonl, or table. Because the report is one object, jsonl falls back to pretty-printed JSON.

The rules enforced include:

  • Identity ↔ key type — runtime_identity: agent requires key_type: agent: the named agent’s own key, returned by alter agents create (or minted later with alter agents mint-key). Per-agent keys currently carry the legacy alter_key_… prefix. runtime_identity: app requires key_type: runtime (alter_rk_…, from alter keys mint). An alter_ak_… key from alter keys mint --key-type agent is not bound to any agent, so it cannot reach agent-bound grants or be attributed to an agent; do not use it for key_type: agent.
  • Goal ↔ runtime identity — user-data and backend-secret run as the app; agent runs as the named agent. Identity and key agreeing with each other is not enough: an internally consistent app + runtime pair still cannot implement goal: agent.
  • Agent principal — goal: agent requires exactly principals: [agent]. User, group, and system principals are separate credential paths and belong to their own design.
  • Backend-secret principal — goal: backend-secret requires at least one system, user, or group principal and never agent. A design that names no principal never says who holds the secret.
  • Goal ↔ grant acquisition — user-data acquires grants via browser consent; backend-secret via managed-secret grants; agent via delegation or an agent-bound managed-secret grant (created via the CLI or dashboard — never the SDK).
  • Supported SDK language — language must be python or typescript. Any other value is an error because the next onboarding step cannot produce SDK wiring for it.
  • Principal hygiene — user and agent principals never share one credential path.
  • Human-owned trust steps declared — idp: jwt-resolution requires identity-provider setup to be listed in dashboard_steps. The field records that an operator must confirm this trust decision; the operator may run alter identity-providers create or use the dashboard. Identity-provider update and deletion remain dashboard-only.

JSON output reports status, design_path, the rules_version, each violation’s rule, severity, and message, and — only when --remote could not complete — a remote_note explaining why. A --remote run whose authenticated phase failed reports status: "fail" with no remote-provider-configured violation, so check remote_note before treating that as a design problem.

Two rule ids in the violations array come from the command rather than the versioned rule set: parse (the file has no readable YAML frontmatter, or a field has the wrong shape — see Frontmatter syntax) and remote-provider-configured (a designed provider is not configured on the app). A consumer switching on rule ids should expect them alongside the rule-set ids below.

rules_version identifies the rule set that produced a report. A stored report is only comparable against the version that emitted it:

rules_versionCLI versionRules
1v0.7.0 – v0.7.3Initial set: identity ↔ key type, goal ↔ grant acquisition, principal hygiene, IDP dashboard step, user-data principal and provider, the backend-secret agent exclusion (already reporting as backend-secret-principal), and the agent managed-secret acquisition warning.
2never releasedlanguage and Gate B’s empty-SDK-scan became errors. Superseded before any release, so no published binary reports 2.
30.8.0Adds goal ↔ runtime identity, the agent principal requirement, and the backend-secret principal presence requirement (its agent-exclusion half has reported as backend-secret-principal since 1). Goal-coherence rules now stay silent for a field that already failed its required or enum check, so one typo reports once.

Rules that reject a previously-passing design ship in a new CLI version, so pin the CLI version in CI if a design must keep validating unchanged.

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.