Durable Object transfer
Namespace ownership, callbacks, alarms and rollback for the move of the Durable Objects to the API Worker.
The stable logical IDs are ShopDatabase, ConversationHub, AccountWriter, Shop, and Api. The API declares
both classes with transferredFrom: ['Shop']. Keep those declarations indefinitely: they cover lagging stages and
are inert after transfer. The storefront binds both classes from the API through scriptName: api.worker.workerName
and never declares them. The API is uploaded first because the storefront depends on its service binding.
The hub’s callback paths are served by the API Worker:
POST /internal/conversations/{id}/turnPOST /internal/conversations/{id}/handoff-timeout
They use ctx.exports.default on the class’s current host, authenticated by Authorization: Bearer <CONVERSATION_HUB_SECRET>. Preserve the existing ConversationHubSecret value across the move. The same release must
contain the API callbacks and Fern.
Disposable stage
Use a stage named rehearsal-YYYYMMDD, dev-tenant identities and separate workers.dev origins. Never attach production
custom domains. Leave ANTHROPIC_API_KEY unset on the stage API so the read-only fallback answers conversations.
Use search tent, cart, orders, order <number or ID> or help; other messages show its limitations.
Verify that add two tents to my cart changes nothing and explains that no human handoff is available.
Start deletion of a test account before the transfer, so AccountWriter has persisted its blocked state, and check after
the move that the blocked writer still refuses. Separately verify a conversation turn and the ConversationHub
alarm on an open conversation. AccountWriter has no alarm; account deletion resumes through
explicit recovery.
Rollback
A pre-transfer application rollback cannot undo namespace ownership. Keep an owner-created rollback build containing the
earlier application behavior and the transferred topology: API class exports and callbacks, API
transferredFrom declarations, Shop cross-script bindings, no Shop class exports. Run pnpm ready on that build.
Deploy it to the disposable stage and verify again. Then try a separate emergency reverse transfer: Shop declares
transferredFrom: ['Api'], API references Shop, and both callbacks are hosted with the classes. Verify, transfer back
to the API, and verify again. Reverse transfer is never the first incident response. Destroy only the disposable stage
after recording the results. No production move is allowed until the owner has recorded all of these observations.
Owner smoke after the transfer
Use a disposable test account and the registered agent credentials described in scripts/smoke/poppy-direct.ts. Run it
with POPPY_SMOKE_CONVERSATION=1 to add a Fern conversation round trip and an open event stream. After the agent is
disconnected in the browser, the stream must end without an event: error, and a fresh read must be refused with a
challenge. The script also prompts the operator to change, reload and restore a disposable address on
/account/addresses. Retain that browser observation with the release record.
Check that the first hourly invocation after a release completes all five API jobs by reading the Worker’s JSON tail for
scheduled_job records with worker: "api" and outcome: "ran". Successful jobs delete their lease rows, so an empty
job_leases table after completion is expected.