Developer Guide

Canary accounts

The shared, non-personal accounts the deployment and testing framework signs in with: who they are meant to belong to, which ones exist, how each is proven live, and the rules for adding or changing one.

Canary accounts

On this page you learn which accounts the agents, release checks and canaries sign in with, who they are meant to belong to, how to prove each one is live, and the rules for adding or changing one.

Agents sign in to other services all day: GitHub, Stripe, QuickBooks, Microsoft, Seed, Neon and our own DevStride workspace. The accounts they use are the canary accounts. The aim is that each one belongs to DevStride rather than to an engineer, can be revoked on its own, and is proven live by a check anyone with access can repeat, so that nobody leaving the team breaks the fleet. A few still depend on one person; the table below marks them. How much access each key carries is a separate decision, described in Credentials and access.

The full inventory, with identifiers, owners and where each secret is stored, is in the repository at docs/canary-accounts.md. Whoever creates or changes a canary account updates that file in the same change. The expiry dates of the Microsoft credentials and the QuickBooks refresh tokens are in the repository's KEY_ROTATION.md.

The shared identity

Canary accounts are registered to a shared identity owned by DevStride, never to a person (owner decision, October 24, 2026). Its address is in the repository's inventory. Each service it signs up to has its own password, kept in the team's 1Password; ask an admin when you need one.

Every canary account and environment is named with the word Canary (for example DevStride Canary), so it reads as the framework's at a glance and never as a person's (owner decision, October 25, 2026). A few that predate the rule still carry their old names; the inventory lists them.

The accounts

Status as of October 25, 2026. Person-owned means the account still depends on one person; replacing it is follow-up work, and those accounts come first when rotating.

AccountWhat it is forProof that it is liveStatus
GitHub tokengh, and the merge, review and deploy checksds agent check-ghlive; person-owned
DevStride API keyds work, ds team, the agents' DevStride connectionds machine ready --quicklive; person-owned
Stripe inventory keythe read-only billing inventoryds -b stripe billing-inventory --out <dir> (a folder outside the repository)live (proven on m3)
Seed tokena deliberate redeploy of a chosen commit (ds seed deploy)ds machine ready --quick; Seed has no read-only call, so the next real redeploy proves itlive
Neon keyds neonds neon projects listlive
QuickBooks app DevStride Canaryreading DevStride's own books and a sandbox companyds quickbooks --company sandbox company-info and ds quickbooks --company real company-infolive for both companies; its recovery phone is person-owned
Microsoft Graph agent appdirectory reads (ds graph read-user) and rotating its own secretds graph read-user <user>live
Teams canary readerthe hourly Teams canary confirms a notification arrivedds graph teams-canary-reader-checklive
The canary Microsoft organizationevery Microsoft and Teams test; the only test tenantds script teams-canary-status --stage prod --profile devstride-prod (production access)live; its administrator, who is also the person the hourly Teams canary notifies, is person-owned
The canary organization in productionwhere the inbound email and Teams canaries act; its items are purged every 24 hoursthe release's post-deploy health checklive
The agents' DevStride Canary Chrome profilethe sites the agents reach only through a browseran agent opens each site in the profile and none asks it to sign inlive on one agent machine, the others to follow; some of its sign-ins are person-owned until each site has a canary login

Every proof prints nothing secret. Never "prove" the Graph app with ds graph rotate-secret: that replaces the live secret.

Rules

  • A person creates the account, registered to the shared identity, never to themselves. Agents never create accounts or type passwords: they open and fill the pages around those steps, and a person does the step itself. Where a provider insists on a person (a passkey, a phone number), the inventory names that person and the follow-up.
  • Store a secret only on standard input: pbpaste | ds secrets set agent <KEY>, then clear the clipboard (pbcopy </dev/null) and run ds secrets pull on each machine (ds fleet status lists them). Never put a secret in the shared bundle, GitHub Actions, a chat, a comment, a screenshot or a file. A secret the deployed app also reads (the Teams canary reader's) additionally goes into that stage's settings store, as its setup command says.
  • An account is live only when its proof passes. The proof is a repeatable check, a ds command wherever one exists, so a new machine, a rotation and a release check all use the same test.
  • Least privilege. Each account holds exactly the permissions the inventory names; a broader one is removed, not documented.
  • Change the inventory in the same change that creates or changes an account.
  • Rotation. A shared key follows Rotating a shared key: store the new value, pull it everywhere, and only then revoke the old one. The Graph app (ds graph rotate-secret) and the Teams canary reader (ds graph teams-canary-reader-setup --rotate, then production's settings and a deploy, then --retire-old) have their own commands; both are listed in KEY_ROTATION.md.

Granting QuickBooks again

The agents hold one QuickBooks refresh token per company. Each lapses after 101 days without a run against that company, and about five years after its grant however often it is used. When ds quickbooks says Intuit no longer accepts a company's token:

  1. Run ds secrets pull (another machine may already hold a newer token) and try again.
  2. If it still fails, a person grants that company again, following the steps in the repository's inventory (docs/canary-accounts.md, "Granting QuickBooks again"). Never grant it with your own Intuit login.
  3. Store only the new refresh token, from the clipboard, and clear the clipboard:
    pbpaste | python3 -c 'import json,sys; print(json.load(sys.stdin)["refreshToken"], end="")' \
      | ds secrets set agent QUICKBOOKS_AGENT_REAL_REFRESH_TOKEN   # ..._SANDBOX_... for the sandbox
    pbcopy </dev/null
    
  4. Run ds secrets pull on each machine, then the proof for that company.

Next: back to the Overview