Adding preview slots
This runbook adds one or more PR-preview slots. It was exercised while adding
preview_10–preview_19; examples for the next rehearsal use SLOT=20.
The important rule is simple: do not add a Semaphore lease until the slot has been provisioned, deployed, and tested. A lease makes the slot available to CI; from then on, any eligible PR may claim and erase it.
The safest automation model#
Use one agent and one durable checklist for the whole expansion. The agent may resume completed steps, but it must never infer permission for a purchase, overwrite, rotation, or production write.
Work in four phases:
- Plan: inspect git, Doppler metadata, Cloudflare, GitHub, and Slack without changing them. Produce the exact slot list, domains, expected app names, existing objects, prices, and intended writes.
- Approve: a human approves the concrete batch. Domain approval includes exact names and a maximum total price. External-app approval names the GitHub organization and Slack workspace. No open-ended approval.
- Apply: create only missing objects. Every operation must be idempotent or stop when an object with the intended name already exists.
- Verify: read the resulting state back from each system. Creation output alone is not evidence that a slot works.
The approval boundary is:
| Action | Agent may do it during planning? | Apply requirement |
|---|---|---|
| Read inventories, validate manifests, check domain availability and price | Yes | None |
| Create branch configs or cloud resources with new slot names | No | Approve the exact slot batch |
| Create GitHub or Slack apps | No | Approve organization/workspace and app names |
| Register domains | No | Approve exact domains, current prices, and maximum total |
| Write new per-slot Doppler secrets | No | Approve project, configs, and secret names |
| Seed production Semaphore leases | No | Separate approval after all slots pass |
| Overwrite, rotate, delete, reclaim, or change production integrations | No | Stop and obtain specific approval |
Do not paste credentials into chat, markdown, command arguments, screenshots,
or git. Pipe API responses directly into Doppler and verify only their shape.
Temporary credential files belong in a mode-0700 directory from mktemp -d
outside the repository and must be removed after the write.
Browser sessions#
Use a dedicated, headed Chrome for Testing profile as described in Browser testing. A human signs into GitHub, Slack, and Cloudflare once; the agent can then drive the approved batch and the human can watch it.
Do not import a personal Chrome profile. Playwriter can control an already-open personal Chrome tab, so use it only when the human explicitly permits that for this task. The dedicated automation profile is the default because its cookies and permissions are isolated and disposable.
When personal Chrome is explicitly approved, the human only needs to enable Playwriter on one harmless anchor tab. The agent can create and retain its own working tab from that browser context; do not make the human create a new tab for every provider. Record which tabs are user-owned, leave the anchor and unrelated tabs untouched, and re-identify the working tab by exact URL after an OAuth redirect.
Browser automation does not weaken the approval boundary. The agent stops on 2FA, CAPTCHA, a changed price, a different workspace or organization, a name collision, or any page whose final action is outside the approved batch.
The missing orchestrator#
The repository has good idempotent leaf commands, but no durable expansion orchestrator. For this batch, the agent can run those commands and maintain the ledger at the end of this document. Before a later expansion, it would be worth adding a first-class command with this shape:
pnpm preview expand plan --slots 10-19 --out expansion.json
pnpm preview expand apply --plan expansion.json --approve <plan-sha256>
pnpm preview expand verify --plan expansion.json
pnpm preview expand activate --plan expansion.jsonplan would be read-only and contain no secrets. apply would reject a stale
plan, require the hash of the reviewed plan, run sequentially, and resume from
verified checkpoints. Domain registration would additionally require the
approved price ceiling in the plan. activate would remain separate because
adding production Semaphore leases changes who can use and erase the slots.
The command should call the existing provisioners rather than reimplement
them. Its value is durable state, precondition checks, direct secret piping,
and safe resumption after browser authorization or a provider outage. It
should never gain generic --force, --rotate, or deletion flags.
Single-slot rehearsal#
For preview_20, keep one ledger row and use SLOT=20 throughout. Do not turn
a single-slot rehearsal into another range expansion.
The order is:
- Add
preview_20toenvs.tswithUNPROVISIONEDIDs. - Confirm both zones, provider names, capacity, prices, and the exact writes.
- Create the five Doppler configs and provision Auth without
--rotate. - Create the dedicated GitHub App and Slack bootstrap app.
- Run every create-only Cloudflare ensure, record the returned IDs, then run every ensure again and require no changes.
- Test and merge the repository change. A merge deploys production; it does not deploy the new preview slot.
- Deploy the five preview apps from current
main. - Upgrade Slack to the full manifest and verify its URLs. Add the two Google OAuth redirect URIs. Verify the GitHub App through its API.
- Present the ledger and obtain separate approval for the production Semaphore lease write.
- Open a canary PR whose body contains exactly
preview_environment=preview-20, then prove deploy, e2e, one real Google/GitHub/Slack round trip, and cleanup. Close the canary after cleanup so it cannot immediately reacquire a slot.
Each failed checkpoint stops the rehearsal. Fix the runbook or automation at the point of failure before retrying the slot.
What one slot contains#
| Layer | Per-slot state |
|---|---|
| Repository | OS and Semaphore resource IDs; derived Auth, Streams, Dummy Petshop, OAuth-audience, mobile, and lease projections |
| Doppler | preview_N in os, auth, semaphore, streams-example-app, and dummy-petshop |
| Cloudflare | Two zones, seven Workers, two D1 databases, two KV namespaces, two R2 buckets, one Queue, DNS, routes, six container apps, and email routing |
| External apps | One GitHub App and one Slack app for full integration parity |
| Lease fleet | One production Semaphore environment-config-lease resource |
The seven Workers are OS, its typechecker and worker-bundler sidecars, Auth, Semaphore, Streams, and Dummy Petshop. OS deploys six sandbox container classes. AI Search and the container-backed builder were removed in July 2026; old account objects may still exist and are not a slot template.
0. Build the expansion plan#
Start from current main in a branch and worktree. Record the commit SHA and
run the read-only fleet checks:
doppler run --project _shared --config prd -- pnpm preview status
doppler run --project _shared --config prd -- pnpm preview reconcilereconcile checks existing Semaphore entries, five Doppler configs, and two
active zones. It does not check envs.ts, secret shape, deployed Workers,
resource IDs, integration apps, or end-to-end health.
For every proposed slot, the plan must contain:
- both domain names and current zone status;
- every existing Doppler config, Cloudflare object, GitHub App, and Slack app with the intended name;
- the exact missing objects to create;
- current account capacity and projected capacity;
- the GitHub organization and Slack workspace IDs;
- any domain price and the maximum approved total;
- a
not-started,created, orverifiedstate for each stage.
On 2026-07-20, iterate-preview-10 through iterate-preview-19 had active
.com and .app zones but no matching DNS records or Cloudflare resources.
Recheck; live state wins over this note.
Capacity#
Ten slots currently add 70 Workers, 20 D1 databases, 20 KV namespaces, 20 R2 buckets, and 10 Queues. The 2026-07-20 preview account held 123 Workers, 27 D1 databases, 18 KV namespaces, 29 R2 buckets, and 11 Queues before expansion. Some are retired objects, which explains differences from slot-count maths.
The current caps reserve 67 GiB memory, 15.25 vCPU, and 180 GB disk per slot.
Nineteen slots reserve 1,273 GiB, 289.75 vCPU, and 3,420 GB. Recalculate from
SANDBOX_MAX_INSTANCES and Cloudflare's current limits immediately before
applying the plan.
References: Workers, Containers, D1, KV, R2, and Queues.
1. Teach the repository about slots 10–19#
In envs.ts, add preview_10–preview_19 to envs using
previewSlot(N, ...), with all three resource IDs set to UNPROVISIONED. Add
the same names to semaphoreEnvs using
semaphorePreviewSlot(N, UNPROVISIONED).
Never invent IDs or copy them from another slot. OS and Auth intentionally share the Auth D1 ID; Semaphore has its own D1 ID.
The resource-free maps and consumers derive from envs.ts:
authEnvs,dummyPetshopEnvs, andstreamsExampleEnvs;- the preview provisioner and Semaphore inventory;
- Auth audiences and per-slot OAuth client targets;
- mobile server presets.
Streams keeps previewDependencies: ["auth"] so the orchestrator selects and
tests one coherent Auth + relying-party revision. This is selection, not deploy
ordering: both derive the same signing key from Doppler and deploy concurrently.
Do not add another numeric slot list or an exact-range test.
Search for operational prose and hidden ranges before moving on:
rg -n 'nine slots|all nine|1–9|1\.\.9|length: 9|\[1, 2, 3, 4, 5, 6, 7, 8, 9\]' \
envs.ts scripts apps docs .depotHistorical incident notes do not need rewriting. Live procedures do.
2. Confirm domains#
ensure-resources creates records in an existing zone. It does not register a
domain or create a zone.
If a domain is missing, use Cloudflare Registrar's domain-check API to confirm availability and the real-time price immediately before approval. Registration is billable and non-refundable. The agent may call the registration API only after a human approves the exact domain and price ceiling. A changed name or price invalidates that approval.
Cloudflare registration requires a Registrar write token, a default payment
method, registrant contact, and acceptance of the registration agreement. A
successful registration normally creates the authoritative Cloudflare zone;
read it back and wait for active before continuing.
References: Registrar API and register a domain.
3. Create Doppler configs#
Do not create _shared/preview_11–preview_19. App configs inherit shared
Cloudflare credentials from their project-level preview root, which inherits
_shared/preview. _shared/preview_10 is old residue, not a template.
After approval, create the two configs the provisioner does not create:
for project in os dummy-petshop; do
for n in $(seq 10 19); do
config="preview_$n"
doppler configs get "$config" --project "$project" --json >/dev/null 2>&1 ||
doppler configs create "$config" --project "$project" --environment preview
done
doneThen run:
pnpm preview provision-auth-preview-configsDo not pass --rotate. Without it, existing client secrets, Better Auth
secrets, and service tokens are preserved. The command still writes live
Doppler state, so its exact config list belongs in the approved plan.
Verify names, inheritance, and required-secret presence without printing
values. Auth must have its OAuth seed and runtime secrets; OS, Semaphore, and
Streams must have matching per-slot Auth client IDs and secrets; Semaphore and
Streams must have AUTH_FORGE_ES256_PRIVATE_JWK. OS must also have
APP_CONFIG_INTEGRATIONS__PETSHOP; an OS deploy cannot infer the Dummy Petshop
client.
APP_CONFIG_PROJECT_APP_SESSION_SECRET is one non-production value. Auth and
OS dev must contain the same value; _shared/preview contains that value once
for every preview child to inherit. Do not copy it into auth/preview,
os/preview, or a preview_N child. Doppler does not allow an inheritable
config to inherit another config, so an app-level preview root cannot sit
between _shared/preview and the children without breaking the existing shared
secret chain. The provisioner verifies the dev pair and shared preview value,
then fails if a child does not resolve the common value.
If an older fleet has per-slot overrides, migrate it as a separately approved operation:
- Verify
auth/devandos/devmatch without printing either value. - Set that value on
_shared/previewand verify every Auth/OS child inherits_shared.previewin its config metadata. - Remove only the named child overrides, one slot at a time, and verify both
effective values still match
_shared/previewbefore touching Workers. - Update the deployed Auth and OS secret bindings together. For a leased slot,
use
wrangler secret putagainst the existing Workers so another PR's code is not replaced; require both health probes afterward. An unleased OS Worker is deliberately parked at HTTP 503, so verify the paired secret uploads and leave that modeled state intact.
This invalidates existing preview project-app sessions. Never remove a child
override before _shared/preview is present and inherited by that child.
Do not run pnpm auth:sync-clients. That older command can point every target
at the Auth config wrapping the command; it is not the isolated preview-stack
provisioner.
4. Create integration apps#
The Workers can deploy without Slack or GitHub config, but that is not a complete slot. Slack e2e skips without a signing secret, and GitHub e2e uses Dummy Petshop instead of the slot's real GitHub App.
Slack: API first, browser for authorization#
Use the Slack preview-app runbook. The preferred path is Slack's App Manifest API, not ten rounds of form entry:
- A human generates an app configuration token for the approved test workspace. It is user-and-workspace scoped, not app scoped, and normally expires after 12 hours.
- The agent renders and validates all ten manifests before creating anything.
- After approval, call
apps.manifest.createsequentially; its Tier 1 rate limit is at least one request per minute. - Pipe
client_id,client_secret, andsigning_secretfrom each response directly intoos/preview_NasAPP_CONFIG_INTEGRATIONS__SLACK. - After OS is live, use the dedicated browser profile to install each app through OS's Connect Slack flow. This captures the bot token and claims the workspace in OS; installing only from Slack's dashboard is insufficient.
Use the bootstrap manifest until OS can answer Slack's URL verification. Then
apply the full manifest with apps.manifest.update. Slack's editor may save the
manifest while still showing URL isn't verified; click Click here to
verify and wait until that state disappears. Read back the Events API URL,
five bot events, Interactivity URL, Agent View, OAuth callback, and scopes.
Never store the configuration token in git or a long-lived shared preview
config.
Do not install every preview bot into a shared workspace at once. Broad
message.* subscriptions make duplicate delivery and duplicate replies likely.
For fleet verification, claim one slot through an OS test project, run the real
Slack round trip, then disconnect it before moving to the next slot.
GitHub: manifest flow with one approved browser batch#
Use the GitHub preview-app runbook.
GitHub has no POST /apps; the supported manifest flow includes a GitHub review
screen. That does not require ten manual handoffs:
- Start a local callback receiver and render the ten manifests with a unique
anti-CSRF
statefor each slot. - Open the organization manifest forms in the dedicated browser profile.
- Confirm the review screen shows
iterate, the exact ten app names,.comcallback/webhook URLs, and the approved permissions. - Once the human approves that batch, the agent may click each Create button,
capture its one-time code, and call
POST /app-manifests/{code}/conversions. - Pipe each conversion response directly into
os/preview_N. The runtime key iswebhookSecret, notwebhookSigningSecret.
Stop on an existing app name. Inspect and reconcile it; never create a near-duplicate or overwrite its settings by guesswork.
Authenticate as each created App and require:
GET /appreturns the exactiterate-preview-Nslug;GET /app/hook/configreturnshttps://os.iterate-preview-N.com/api/integrations/github/webhook.
Creation output is not verification. The final smoke still installs the App through OS and delivers a signed webhook.
Google: update the shared preview OAuth client#
The Auth sign-in flow and OS Google integration use the same non-production Google OAuth client across preview slots. Add both redirect URIs for every new slot:
https://auth.iterate-preview-N.com/api/auth/callback/google
https://os.iterate-preview-N.com/api/integrations/google/callbackThis is a Google Cloud Console write and may require a different signed-in account from GitHub or Slack. Record the owning project/client in the plan and stop if the selected account cannot see it. Do not modify the separate production client.
The Google Console can display rapidly bulk-filled rows that its form model has not registered. A save then returns HTTP 400 and restores the previous list. Add redirects in small batches with normal typed input. After every save, reload the client and require the new values to persist. The slots 10–19 rehearsal succeeded in batches of six after one nineteen-row bulk fill failed.
5. Create Cloudflare resources and record IDs#
Run management calls sequentially so retries and rate limits remain legible:
for n in $(seq 10 19); do
pnpm --dir apps/auth ensure-resources --env "preview_$n"
pnpm --dir apps/semaphore ensure-resources --env "preview_$n"
pnpm --dir apps/dummy-petshop ensure-resources --env "preview_$n"
pnpm --dir apps/os ensure-resources --env "preview_$n"
doneStreams has no ensure-resources; its deploy creates its DNS record. OS also
installs R2 lifecycle rules, creates its Queue and exact-repo event
subscriptions, and enables inbound Email Routing. On a brand-new slot the
Email Routing catch-all is explicitly deferred because Cloudflare rejects a
Worker action until that script exists; the first OS deploy installs and
verifies the catch-all after uploading the Worker.
Outbound Email Service onboarding remains a Cloudflare dashboard step. Within the approved batch, an agent may drive it using the dedicated browser profile, but must stop before onboarding a different sender domain or changing existing DNS. Verify each sender after saving.
Paste these IDs into the branch:
- OS
projectDirectoryKvIdandworkerBuildCacheKvId; - the shared OS/Auth
authDbId; - Semaphore
resourcesDbId.
Run every ensure-resources command again. The second pass must match the
recorded IDs and create nothing. Before the first OS deploy, its one expected
deferred result is Email Routing catch-all ... deferred until worker ... deploys; any collision, warning, different ID, or other new object is a failed
checkpoint.
6. Test and merge the repository change#
Run:
pnpm --dir apps/os exec vitest --root ../.. run \
scripts/preview/preview.test.ts \
apps/os/scripts/generate-wrangler-config.test.ts \
apps/dummy-petshop/src/generate-wrangler-config.test.ts
pnpm --dir apps/auth test
pnpm typecheck
pnpm lint
pnpm format:checkInspect generated Wrangler config for every new environment. Names, routes, D1/KV IDs, and container caps must be slot-specific.
Merge this branch before deployment or leasing. Never seed production Semaphore from an unmerged expansion branch.
The normal merge-to-main workflows deploy production Auth/OS, Semaphore,
Streams, and Tunnels. They do not deploy newly added preview environments.
7. Deploy from current main#
Pull current main after the expansion merges. Deploy while the slots are
still absent from Semaphore, so there is no lease holder to race.
for n in $(seq 10 19); do
target_env="preview_$n"
pnpm --dir apps/auth run deploy --env "$target_env"
pnpm --dir apps/dummy-petshop run deploy --env "$target_env"
pnpm --dir apps/semaphore run deploy --env "$target_env"
pnpm --dir apps/streams-example-app run deploy --env "$target_env"
pnpm --dir apps/os run deploy --env "$target_env"
doneAuth, OS, Semaphore, and Streams may deploy concurrently; they derive the same public signing key from Doppler and do not fetch one another's JWKS. Dummy Petshop must precede OS e2e. Do not replace a failed deploy with a curl-only health check; the deploy command validates secrets, resources, migrations, routes, and smoke probes.
Fresh hostnames can return Cloudflare 522 while edge certificates propagate. The deploy smoke owns that bounded retry. Auth's post-deploy OAuth-client seed retries only Cloudflare 522–526 on a bounded 78-second schedule; an unclassified status such as 500 remains an immediate failure.
Preview orchestration resolves every public app origin from envs.ts, the
same source that generates Worker routes. It reads Doppler only for readiness
bearer secrets, merges those with the repository-owned origin, and injects
that origin as APP_CONFIG_BASE_URL into each app's e2e process. Do not add
APP_CONFIG_BASE_URL duplicates to each Doppler child: OS and Semaphore
intentionally never had them, and requiring them made a correctly provisioned
slot fail before its deploy command started.
Exact-repository subscription lookup is part of config-repo creation, so a
transient Cloudflare listing failure must not poison the repository's terminal
state. Idempotent Cloudflare API reads retry 408, 429, and 5xx responses twice
with bounded delay and a warning for each absorbed attempt. Mutations are never
replayed. If project creation still records repos/create-failed, inspect that
fact instead of rerunning the same deterministic smoke project: the failure is
durable and the project will correctly remain unready.
Now update Slack from bootstrap to full manifests, complete Slack installation
through OS, and verify each GitHub App's /app identity and webhook URL.
The lifecycle canary in step 9 must exercise one claimed slot through a disposable OS project. Provider dashboard state is insufficient:
- Connect Google through OS, then call a metadata-only endpoint such as Gmail
/users/me/profile. The Auth callback and OS integration callback prove two different Google redirect URIs. A historical connection row can remain after disconnect; requiregetConnection()or a real API call, not the row. - Install the slot's GitHub App on the dedicated private smoke repository, then
read that exact repository through
itx.integrations.github.get(connection).octokit. This proves the App installation credential, not only its manifest and callback. - Connect Slack through OS, require
auth.test, join#slack-agent-e2e-test, and post a uniquely marked mention from the separateSLACK_CI_BOT_TOKENactor. Requireslack/webhook-received,slack/thread-route-configured, a Slack-thread agent, and a reply by the preview bot in the same thread. See Slack testing.
Use project-scoped admin claims when minting the browser session for a disposable project. Strip terminal colour codes before copying a printed mint URL; ANSI bytes inside the query string corrupt it. Do not print provider tokens. Disconnect or let normal preview cleanup erase the project connections after retaining only non-secret evidence.
8. Approve and add Semaphore leases#
This is a separate production write. Present the completed verification ledger
and obtain approval immediately before running it from current main:
doppler run --project semaphore --config prd -- \
pnpm --dir apps/semaphore seed:environment-config-leases
doppler run --project _shared --config prd -- pnpm preview status
doppler run --project _shared --config prd -- pnpm preview reconcileStop unless status reports nineteen slots and reconcile reports zero
issues.
Slot handover attempts its atomic Durable Object retirement before looking at
other Workers. Cloudflare rejects the retirement and names every Worker whose
external binding blocks it; cleanup then inspects and detaches only those named
Workers, verifies their settings, and retries. Do not restore an eager account-
wide settings scan: concurrent preview handovers multiplied that scan across
roughly 183 Workers, made every chunk receive a 120-second Retry-After, and
could not fit inside CI even after a five-minute cooldown. An unclassified
retirement failure remains fatal, so this optimisation does not bypass the
handover erase.
9. Prove the normal lifecycle#
Use a small canary PR that touches a preview-shared path. The normal CI path is a standalone body directive:
preview_environment=preview-20Markdown examples and comments do not count. For a fleet sweep, pin, run, and clean one new slot at a time:
PR=<canary-pr-number>
for n in $(seq 10 19); do
doppler run --project _shared --config prd -- \
pnpm preview assign --pull-request-number "$PR" --slot "$n"
doppler run --project _shared --config prd -- \
pnpm preview run --pull-request-number "$PR" --all-apps
doppler run --project _shared --config prd -- \
pnpm preview cleanup --pull-request-number "$PR"
doneAfter each slot, inspect the managed PR table, CI logs, Cloudflare traces, and
pnpm preview status. An unexplained error, skipped Slack test, unhealthy
storage shard, unreleased lease, or mismatched final state fails the slot. Do
not keep feeding work to a sick slot; leave it unavailable and record the
reason until automatic health quarantine exists.
Classify error-level telemetry rather than treating green checks as the final barrier. Intentional failure-path tests must be identifiable as such. Durable Object storage resets, network loss, Worker hangs/cancellations, and default project-worker readiness failures are not normal background noise; retain the event window and investigate or explicitly track them before declaring the expansion complete.
Close the canary only after all ten slots have passed. Run status and
reconcile once more. Any open PR reacquires a slot on its next preview run,
so keep that in mind if the canary is a real work PR rather than a disposable
one.
Resuming safely#
Keep this ledger in the expansion PR. created is not verified; mark the
cell only after reading the state back from the owning system.
| Slot | Domains | Doppler | Cloudflare + second ensure | GitHub | Slack | Five apps | Lease | Lifecycle |
|---|---|---|---|---|---|---|---|---|
| 10 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 11 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 12 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 13 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 14 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 15 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 16 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 17 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 18 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
| 19 | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
Before resuming, rerun the planning inventory and compare it with this ledger. If the systems disagree, trust the read-back and investigate. Never “finish” a half-created object by rotating or replacing credentials unless that action is explicitly approved.
Drift found while writing this runbook#
_shared/preview_10exists without an app-level stack. It is residue, not a template.- Existing OS preview configs inherited one preview-1 GitHub credential even though a GitHub App has only one webhook URL.
- A 2026-07-22
auth.testrecheck found the preview 3 and preview 6 product-bot fallback tokens healthy. The preview-14 config has noSLACK_CI_BOT_TOKEN, which is correct:_shared/prdowns the live CI trigger actor. An earlier ad-hoc smoke sentBearer undefinedand misclassified Slack'sinvalid_authresponse as a stale preview token. - Slots 10–19 initially had matching Auth/OS session secrets per slot, but the
values differed across slots and shadowed
_shared/preview. The app-level preview roots cannot own this value because Doppler forbids an inheritable config from also inheriting the existing shared preview config. - New OS configs lacked
APP_CONFIG_INTEGRATIONS__PETSHOP; provisioning now writes the fixed Dummy Petshop client before deployment. - Slack bootstrap manifests preserve OAuth callbacks and scopes but omit Event subscriptions, Interactivity, Agent View, and App Home until OS can pass URL verification.
- A green merge-to-main production rollout does not deploy preview workers or publish new Semaphore leases.
- The old GitHub runbook used
.appOS URLs, the wrong webhook key, and a broken callback-capture script. - The old Slack bulk guide duplicated manifests and asked humans to paste secrets into chat. It has been replaced by the Manifest API workflow.
- Dummy Petshop's deploy comment claimed there was no route or DNS, while its generated config uses both.
- Streams lacked its Auth dependency; the expansion change fixes the deploy graph.
- AI Search and the container-backed builder were removed after the first audit. Their retired account objects demonstrate why code and live state must both be checked immediately before expansion.