An AI coding agent can build a development app from a reviewed specification. The owner still has to identify a real customer job, decide the offer, inspect the implementation, observe the denied paths and authorize the public release. This guide walks through that whole task using a paid team checklist workspace as a selected reference. Use that model only if it matches your actual permitted customer problem. BIG CHANGE did not interview customers, run Agent, build the app, test a payment or deploy it.

The big change

  • What changed: Replit Agent Plan Mode lets you review a plan before code or data changes, so the brief can cover the complete customer journey.
  • Why it matters: The saved checklist, tenant boundary, payment decision and release can be tested as one product.
  • What to watch: A completed Agent task, successful build or Checkout return does not establish demand, authorization, settled card payment or a safe public service.

1. Decide whether to build

Begin with evidence you are permitted to use: consenting customer conversations, an existing task you own, support requests or lawful requirements. Record who performs the job, what they do now, how often, what fails and which saved result would help. Compare a configurable existing tool with a new app. If the task, customer authority or useful first result is unclear, investigate before commissioning code. Never ask an agent to supply imaginary interviews, customers, market size or revenue.

Fill the charter below. Define an actor, input, action, saved result and next user. The selected app has one workspace owner who creates a recurring checklist and assigns a dated run; an invited member completes it; the owner accepts or returns it. A booking or document product needs a different contract, even if the same build and release sequence applies. Choose the actual price, currency, tax treatment, trial, cancellation, failed payment and refund policy before taking live money. Decide data rights, retention, export, support and spending authority.

2. Choose and plan the development route

The selected route uses React, Express, PostgreSQL, Replit-managed Clerk, Stripe-hosted subscription Checkout and Billing Portal. Check the current Node runtime, package versions, generated auth proxy, account capabilities and deployment type. Replit separates development and production databases; keep development identities, Stripe test keys and synthetic records separate too. Check Agent billing, hosting, database, identity and Stripe costs against your own accounts. Preparing this specification requires no paid Agent run.

Give Agent the filled charter and the complete build brief, application contract, SQL and billing contract below. Use Plan Mode first. Require a route/file map, migration, permission matrix, provider actions, environment split, cost-sensitive steps and tests. Review the plan before permitting changes to a controlled development project. Reject a dashboard or a payment page that replaces the useful assignment and review job. Replit's task board tracks Agent work; its Done state does not count as owner acceptance.

Implement in reviewable slices: verified enrollment and membership, template/version and assignment, member completion and owner review, scoped history/export, then hosted test billing. Inspect each diff and migration. The service must let the whole job survive refresh; a collection of disconnected forms is incomplete.

3. Observe the allowed and denied journeys

Use two real test identities in workspace A and a separate owner in workspace B. Complete template creation, invitation, assignment, item completion, submission, return or acceptance, then reload lists, details and history. Confirm that a new template version does not rewrite an assigned run. Send direct API requests with B's resource IDs from A. Test a member against owner routes, a wrong or expired invite, revocation, replay, removal with an existing provider session, stale versions, duplicate mutations and state races. Inspect database rows and history counts on denials; a hidden button proves little.

Configure a Stripe test Product and fixed monthly card Price only after deciding the offer. The server creates one mapped Customer and at most one unresolved Checkout attempt, sends the browser to hosted Checkout, verifies signed raw-body webhooks and computes paid access from current provider evidence. The success URL reports a return, not an entitlement. Test lost responses, duplicate requests, webhook order, failed renewal, period end, wrong Customer or Price, an extra subscription and non-card or zero payment. Existing scoped records and export remain available after lapse under the disclosed policy; new run creation is the paid action. Stripe's Portal manages the mapped Customer on demand.

The accepted reference pins Stripe request API 2026-09-30.endive and checks the webhook endpoint event version separately. Confirm that the actual SDK and account expose these documented fields before coding. If they differ, stop and rederive the adapter. The strict predicate can temporarily deny new runs during payment failure. Explain that outcome in customer terms.

4. Approve a separate public release

Inspect the complete generated code, lockfile, migration history, auth proxy order, secrets, audience and rollback route. Configure distinct production Clerk, PostgreSQL and Stripe resources only after a separate owner decision for the offer, live payment, database change and public audience. Replit publishing controls must support the server and webhook; a static page cannot run this contract. Repeat a small permitted and denied journey at the actual production URL. If a live transaction is not authorized, record live billing as unverified and hold that paid launch.

Assign support, access removal, failed-billing, cost, retention, backup and recovery owners. Check the host's data recovery options and rehearse a restore before calling it tested. Reconcile uncertain Stripe events against their exact IDs and current provider objects. A code rollback cannot undo a migration or payment.

Related BIG CHANGE guides cover a two-plan subscription manager, finishing and releasing an existing Replit app, and a restricted internal staff tool. Those answer narrower or different jobs; this guide follows a newly scoped paid SaaS product from customer problem through operation.

The material below is a copyable proposed implementation, not tested application code. Keep bracketed charter answers private and replace them with actual owner evidence. If the real job differs from recurring assignment and review, redesign the entities, routes, schema and checks before implementation.

Copyable product charter

Decision

Owner's entry and evidence

Person and problem

[Specific permitted customer role], [recurring task], [current method], [observed friction and source/date].

Evidence and rights

[Consent or lawful requirement], [notes location], [permission for any data or quotation].

Existing alternative

[Actual tool or process], [why configuring it does not solve this job].

First valuable action

[Actor], [input], [action], [saved result], [next user].

Acceptance

[Permitted user] completes [whole action] with [authorized test data]; [denied actor] cannot read or change it.

Customer boundary

[Workspace and roles], [invitation], [retention and export], [access removal owner].

v1 exclusions

[Deferred features] and [requirements that would make this version unsuitable].

Commercial choice

[Real price, currency, tax, terms, trial, refund and cancellation policy], [live-mode approver].

Resource and release owner

[Repository, identity, database, Stripe, host], [budget], [release and support owners].

Hold the build if the first valuable action, lawful data use or customer authority is unresolved. Hold live payment and public release until the offer and operations decisions are made.

Selected application contract

This is a selected reference, appropriate only when the charter's real valuable action is assigning and reviewing recurring work. It is not a claim that checklist software is a validated market. A tenant is one paying organization with one or more verified users. The React browser calls the Express API; Replit-managed Clerk authenticates people; PostgreSQL holds app state; Stripe hosts card collection and billing management. The Express process is the sole database and Stripe caller. Use separate development and production identity, database, Stripe and host settings. Never send a database password, Stripe secret or webhook secret to the browser or an agent prompt.

Complete v1 journey

An owner signs in and creates a workspace. The server creates the workspace and owner membership atomically from the verified server identity, without accepting ownerUserId from the browser. The owner defines a checklist template with a name, instructions and at least one item, invites a member by email, and assigns a dated run. The invited person accepts using the intended verified email address, opens only their workspace's run, marks items done with a note, and submits it. The owner sees the submitted run and either accepts it or returns it with a reason. Both users can inspect the saved status/history after refresh. This is the useful core action, including its feedback loop.

Only one workspace membership per verified user and one paid subscription per workspace are supported in this reference. A real product needing cross-workspace consultants, ownership transfer, team billing splits, files, offline work, public sharing or regulated evidence needs a different approved contract. Set a real data retention and export policy before using real records.

Identity and access matrix

The server verifies the Clerk session on every app route except the signed Stripe webhook and deliberately public read-only pages. Three session-only, unenrolled-user exceptions are GET /api/me, POST /api/workspaces and POST /api/invitations/accept: they require a verified session (and the shared Origin/CSRF/body controls for the two mutations), but cannot require membership before creating it. All other private routes require a current active PostgreSQL membership. Tenant ID in a path is a selector, never authority. Scope every read and write to authenticated membership and the row's workspace ID. Mutations use the common workspace transaction/lock protocol below. Do not trust browser role, email, price or workspace metadata. Cross-tenant resource IDs return the same 404 as nonexistent IDs. Signed-out requests get 401; a known member without the role gets 403.

Action

Owner

Member

Signed out / other workspace

View private workspace, template and run

Own workspace

Own workspace only

Denied

Create/update template and assignment

Own workspace

Denied

Denied

Mark assigned run item done; submit run

Only if assigned

Only if assigned

Denied

Accept or return submitted run

Own workspace

Denied

Denied

Create/revoke invitation; deactivate member

Own workspace

Denied

Denied

Create hosted Checkout or billing portal session

Own workspace owner

Denied

Denied

Receive Stripe webhook

Verified Stripe signature only

No browser route

No browser route

The invitation stores a SHA-256 hash of a cryptographically random 32-byte token, a normalized invited email, workspace, expiry and consumed/revoked timestamps. Return the one-time URL only to the authenticated creating owner for sharing through an authorized channel; never display it again or log it. An unused, unrevoked and unexpired invitation for the same workspace/email returns 409 rather than silently creating another. Invitation issuance is a one-time-secret exception to generic response replay: keep only its hash, not the token in mutation_keys. If creation response is lost, the owner reads the token-free list, revokes that exact unused invite and issues a new one under the workspace lock. On acceptance, verify the signed-in Clerk user's verified primary email equals the invited email; an email query parameter is never authority. Under the user-then-workspace lock protocol, reject expired/used/revoked tokens or membership in another workspace. An already-active same-workspace member returns 409 without consuming the token. A formerly inactive same-workspace member may rejoin only through a fresh owner invitation: reactivate that retained membership row, append membership history and consume the token in one transaction. A removed member's old provider session never restores app access. The owner may not deactivate themselves; ownership transfer is outside v1.

Persistence and state

Use the SQL below as the new-project reference. It defines workspaces, members, invitations, versioned templates/items, runs/items, run and membership history, mutation keys, billing mappings and webhook states. Each tenant-owned table carries workspace_id; compound foreign keys prevent a row from pointing at another tenant's parent. Browser code never connects to SQL or reads billing/event tables. All application state changes use the one-connection transaction protocol below. Check affected-row count and expected current status/version before commit. A stale version returns 409 and the browser reloads the scoped record.

Run state: assigned -> in_progress -> submitted -> accepted, with submitted -> returned -> in_progress for a review correction; owner cancellation from assigned, in_progress or returned moves to terminal cancelled. Only an active assigned member may edit items while the run is assigned, in_progress or returned. The first successful item edit moves assigned/returned to in_progress; an explicit resume moves returned to in_progress even if items remain complete; submitted, accepted and cancelled items are immutable. Submission requires every required item complete and only in_progress; only an active owner reviews submitted, with a nonempty reason for return. A successful state change writes one run-history row and increments run version in the same transaction; failed or repeated requests write none. Owner deactivation of a member is blocked with 409 while that member has an assigned, in_progress, returned or submitted run. Cancel the first three with a reason and create a fresh assignment for an active member; first accept or return a submitted run, then cancel if returned. Historical accepted/cancelled rows remain scoped to the workspace; deactivation does not rewrite them.

API routes and response contract

GET /health exposes only service readiness. Public landing/plans and provider-owned sign-in, callback and auth transport remain outside app membership while carrying no app records. Place Replit's generated Clerk proxy/middleware before Express parsers/private routes, preserving the provider's required transport/bootstrap behavior. Mount POST /api/stripe/webhook with raw body before general JSON parsing; it is outside app session/CSRF but requires a valid Stripe signature. Every browser state change, including the two unenrolled exceptions, accepts a bounded JSON body and an allowed Origin/CSRF policy matched to the actual cookie/session transport; disallow simple cross-origin form mutation, unsafe GET writes and credentialed wildcard CORS. Each private response, including export and errors, sets Cache-Control: no-store. Public/auth transport responses have their separate provider controls.

Route

Input

Output / invariant

GET /api/me

Verified session, membership optional

No-store active workspace ID/role or workspaceId:null; recovers lost bootstrap/accept response without token replay.

POST /api/workspaces

Reviewed name; verified session, no prior membership

Session-only bootstrap. Under user lock, create one workspace/owner membership atomically. Same active owner and identical name returns existing workspace; changed name or member/inactive row returns 409.

GET /api/workspaces/:workspaceId

Path selector

Active member gets only workspace ID/name and own role; never billing secret or other member email.

GET /api/workspaces/:workspaceId/members

Bounded page

Owner gets member IDs, roles, active flags and safe display names; member gets only self. No invitation tokens.

POST /api/workspaces/:workspaceId/invitations

Email, bounded expiry

Owner gets 201 invitation ID/expiry and one-time URL; active duplicate email gets 409. Token never in list/replay store/log.

GET /api/workspaces/:workspaceId/invitations

Bounded page

Owner gets ID, redacted/approved email, expiry and used/revoked status only.

POST /api/workspaces/:workspaceId/invitations/:invitationId/revoke

Mutation key

Owner atomically revokes an unused invitation; consumed/revoked returns 409.

POST /api/invitations/accept

One-time token; verified session without membership

Session-only exception. Verified-email match, user/workspace locks, atomic consume and insert/reactivate same-workspace membership; 409 for other-workspace or active membership.

POST /api/workspaces/:workspaceId/members/:userId/deactivate

Reason, mutation key

Owner only, never self; 409 until assignee's assigned/in_progress/returned runs are cancelled and submitted runs accepted or returned then cancelled. Sets active=false and appends membership history atomically.

GET /api/workspaces/:workspaceId/templates and GET .../templates/:templateId/versions/:version

Bounded list or exact ID/version

Active member gets ID/version/name/instructions and ordered item position/title/required only; owner also sees archive status.

POST /api/workspaces/:workspaceId/templates and POST .../templates/:templateId/versions

Name/instructions/ordered items, mutation key; expected latest version for update

Owner creates immutable version 1 or appends latest+1 under workspace lock; no in-place rewrite of earlier versions or assigned runs.

POST /api/workspaces/:workspaceId/runs

Exact template ID/version, active assignee, due date, mutation key

Owner only; 201 run with copied items and initial history. Requires billing-contract paid check immediately before guarded commit.

GET /api/workspaces/:workspaceId/runs and GET .../runs/:runId

Bounded filters/page or exact ID

Owner sees tenant runs; member sees assigned runs only. Detail includes item position (not an invented item ID), title, required/done/note, run status/version/due date.

GET /api/workspaces/:workspaceId/runs/:runId/history

Bounded page

Same scoped run visibility; allowlisted transition/time/actor/reason, no tokens or billing payload.

PATCH /api/workspaces/:workspaceId/runs/:runId/items/:position

Positive integer position, done, bounded note, expected run version, mutation key

Active assigned member, editable statuses only; scoped item update, run version increment and first-edit transition/history atomically.

POST /api/workspaces/:workspaceId/runs/:runId/resume

Expected version, mutation key

Assigned active member moves only returned to in_progress; one history/version change.

POST /api/workspaces/:workspaceId/runs/:runId/submit

Expected version, mutation key

Assigned member, only in_progress with all required items done; one submitted state/history.

POST /api/workspaces/:workspaceId/runs/:runId/review

accept/return, return reason, expected version, mutation key

Owner only, submitted only; atomic state/version/history.

POST /api/workspaces/:workspaceId/runs/:runId/cancel

Reason, expected version, mutation key

Owner cancels only assigned/in_progress/returned work; history preserved; new run required for reassignment.

GET /api/workspaces/:workspaceId/export

Owner; bounded cursor and format json or csv

Even after payment lapse, owner exports only this workspace's templates, runs, items, history and membership audit; no invitation tokens, payment secrets or another tenant. Stream in bounded pages, no-store; audit export privately.

POST /api/workspaces/:workspaceId/billing/checkout

Owner; fixed plan code and mutation key

Uses billing-contract durable attempt and current-subscription guard; response is validated hosted URL, same-key replay, or pending/hold. No browser Price/Customer ID.

POST /api/workspaces/:workspaceId/billing/portal

Owner; no Price/Customer input

On-demand short-lived URL for stored workspace Customer only.

POST /api/stripe/webhook

Raw payload/signature

Verify exact endpoint version/signature; idempotent state and serialized current-resource reconciliation. No callback grant.

The React UI must expose the whole route table coherently: public landing/plans and provider sign-in; a verified new-user setup screen with create-workspace and accept-invitation alternatives; owner template list/detail/version editor, members/invitations with revoke/deactivate and stranded-work guidance, run list/detail/review/cancel, billing/account/export; and member assigned-run list/detail/item completion/resume/submission with returned reason. Every private view handles loading, empty, 401, 403, 404, 409 reload, payment 403 and provider 503 explicitly. A hidden control is convenience, not the authorization check. After refresh, each view reads the saved API state; a disabled payment action and informational return page never claim payment success. The selected app has no public customer-record view.

One mutation order and result contract

Use a single checked-out PostgreSQL connection for BEGIN ISOLATION LEVEL READ COMMITTED, lock, all reads/writes and COMMIT; never let a pool send these statements on different connections. For bootstrap and invitation acceptance, take a transaction advisory lock keyed by the verified Clerk user ID first. Bootstrap then checks the unique membership user ID and inserts workspace/member or returns its existing immutable workspace after a retry. Invitation acceptance next reads the token hash only to locate its workspace, takes the workspace advisory lock, then freshly locks/re-reads invitation and membership and applies the email/expiry/active rules, comparing clock_timestamp() to expires_at under the lock. All other tenant mutations, including invitation issue/revoke, deactivation, template/run/item/review/cancel, billing mapping and webhook reconciliation, take the same workspace advisory lock before any membership/resource decision; then freshly re-read active membership and target rows with FOR UPDATE when writing. Never acquire a user lock after a workspace lock. PostgreSQL READ COMMITTED gives the post-wait statement a fresh snapshot; transaction isolation documents the distinction. The common lock serializes deactivation with later app writes: if removal commits first, the writer's fresh active-membership check fails; if a writer commits first, removal sees its new run state and may return 409. Keep lock scope small and use a bounded statement/lock timeout.

For every ordinary post-bootstrap mutation except invitation issuance, under that lock insert a (workspace, actor, route, idempotency key) row with a hash of normalized request fields before effects. A committed same-key/same-hash row returns its stored HTTP status and allowlisted response without repeating an effect; same key with different hash returns 409. A visible committed processing row violates this atomic contract: HOLD and inspect the exact record/history rather than executing a retry; an uncommitted row rolls back on crash. Store result status/body and state/history in the same commit. Check expected version and allowed from_status in the actual UPDATE ... WHERE workspace_id=? AND id=? AND version=? AND status IN (...) RETURNING statement; require one returned row. Item PATCH additionally uses the positive position composite key and must not write after submission. A failed transaction rolls back key, item and history together. A lost HTTP response is resolved by fresh read of its key and row/history; on deadlock/serialization error retry the whole transaction at most twice with the same key after re-reading state, then return 503/409 for operator or user recovery. These are proposed invariants; the owner's real concurrency tests determine whether generated code meets them.

Read routes use exact workspace filters and a fresh active-member lookup on each request. Response objects are explicit allowlists: workspace ID/name/own role; template IDs/version/text and item position; run IDs/status/version/due date/assignee, item fields and history actor/reason; invitation list without token; export without secrets or Stripe payloads. Return only JSON or CSV encoded safely for the chosen export format, with bounded cursor pagination, no-store and content disposition. Do not spread SQL SELECT * or provider objects to the browser.

The success and cancel return routes are informational. Payment entitlement follows the billing contract below using the stored workspace-to-Customer mapping and current matching subscription, plan, invoice and card-payment evidence. For the reference v1, support one fixed monthly card plan, automatic collection and no trials, coupons, prorations or delayed methods. Stripe active alone and checkout.session.completed alone do not prove a settled current card invoice. Access may pause during payment failure; decide and disclose this policy to customers. A later refund/dispute requires explicit policy and reconciliation. Do not directly set paid=true from a client callback. Keep the owner able to view/export existing records when billing lapses under the product's disclosed policy; paid creation is the gated action here.

Build and release boundaries

Development uses provider test identities, Stripe test mode, a development database and synthetic checklist data. Record actual package versions and supported Node runtime; verify Replit's generated identity setup and deployment type against them. The owner reviews migration and code, tests all routes directly and through the UI, and compares Stripe test Dashboard objects with app billing state. Production uses the separately provisioned Replit-managed Clerk production instance, its own database, Stripe live keys/webhook endpoint and fixed live Price, configured only after business, tax, terms, email, data and payment decisions. Review any automatic deployment branch and host audience before publishing. A successful build, preview URL or Checkout redirect is not proof of a safe SaaS release.

Single-plan card billing contract

The checked Stripe API version guide calls 2026-09-30.endive current on 22 October 2026. Pin this request version in the installed Node SDK and verify that SDK accepts it; separately create/read the test webhook endpoint and verify its event api_version is also 2026-09-30.endive. These are two version settings. If the actual account or SDK cannot use this shape, HOLD and rederive/test the adapter from that account's version; never silently accept an older field layout. No Stripe account, test payment or generated server was run for this packet.

First customer and Checkout attempt

All owner billing mutations use the app contract's single checked-out READ COMMITTED transaction and workspace advisory lock; authorize active owner after taking it. Before Checkout, ensure one Stripe Customer for this workspace. Insert billing_accounts with an immutable random customer_setup_key, exact stored request digest/parameters, random customer_call_owner, customer_call_generation=1, immutable customer_first_call_at and a separate short customer_call_deadline, in reserved state; commit before calling stripe.customers.create with that key and server-set workspace metadata. The first-call timestamp starts the provider-key retention window and never resets on takeover; the deadline only bounds local worker ownership. The owner may persist a returned Customer ID only through the conditional-write protocol below, after checking live mode, ID and that it is not mapped elsewhere. A lost response or first 500 is uncertain, not proof of absence. Automatic recovery may repeat only the identical endpoint, parameters and key while clock_timestamp() < customer_first_call_at + interval '24 hours'; after that, HOLD for operator readback of the exact provider request/customer. Never issue a fresh Customer creation key to clear uncertainty; no Checkout starts until the mapping is proven. Stripe idempotent requests documents saved first results and possible pruning after 24 hours.

Under the workspace lock, list the mapped Customer's subscriptions with status:'all', a bounded limit:100, and reject has_more/provider uncertainty. Treat every status except canceled and incomplete_expired as nonterminal, including incomplete, trialing, active, past_due, unpaid and paused; if any exists, refuse a new Checkout with 409 and direct the owner to the current billing/recovery path. Also refuse while any attempt is reserved, open, unknown or hold, enforced by SQL's partial unique index. One explicit attempt ID and request digest bind workspace, verified owner, Customer, fixed server Price, one-month interval/quantity, card-only method, allowed origin/success/cancel URLs and mode. A new key with changed or existing unresolved binding is 409; an identical key/hash replays its stored open Session URL only after retrieving it and confirming its exact ID/Customer/Price/binding and to_timestamp(session.expires_at) > clock_timestamp() on the SQL server. An identical reserved attempt returns 202 pending while its owner deadline has not passed; other keys return 409. After a crash/deadline, a recovery worker may take over only with the same immutable binding and key, under the workspace lock, while still inside the immutable first-call retention window. It atomically replaces call_owner, increments call_generation, and sets a new short call_deadline only if state is reserved or unknown, the old owner/generation still match and clock_timestamp() >= call_deadline. A zero-row update means read back and stop. Customer setup uses the identical takeover rule on its own fields; mapped, open, completed, expired and hold cannot be taken over.

Commit reserved with random call_owner, call_generation=1, immutable first_call_at and separate short call_deadline before Stripe create. The single worker owning it calls stripe.checkout.sessions.create in subscription mode with stored Customer, line_items:[{price: fixedPriceId, quantity:1}], payment_method_types:['card'] (and verify account behavior), client_reference_id equal to the unpredictable attempt_id, metadata containing that same ID, and only approved return origins; use a Stripe idempotency key derived from that immutable attempt. A worker carrying its captured owner UUID and generation may turn reserved or unknown into open on a verified matching response, or reserved into unknown on timeout/error, only by the guarded conditional write below. An unknown response retains the original key, Customer, attempt ID and first-call time. Retry the identical provider request/key only while clock_timestamp() < first_call_at + interval '24 hours'; after that, stop automatic retries and use Stripe request logs plus exact signed Session event/readback to resolve it. No new paid attempt is allowed until the unknown one is shown expired or completed. A known open Session becomes expired only after retrieving that exact Session and confirming expiry/expiration; a completed Session is terminal only after its subscription/Customer mapping is reconciled. A saved URL or browser return alone never grants access.

Local completion fence. Every Customer or Checkout provider-call success, timeout and error finishes in a new one-connection workspace-locked transaction and attempts a single UPDATE ... RETURNING against that exact row. The WHERE must include workspace and immutable request binding (Customer setup key/digest, or attempt ID/key/digest/Customer/Price/origin and the mode captured by the digest), the worker's captured call_owner and call_generation, and its permitted prior state. Customer success permits only reserved/unknown -> mapped; Customer timeout/error permits only reserved -> unknown (an already unknown result is read back). Checkout success permits only reserved/unknown -> open; Checkout timeout/error permits only reserved -> unknown. The success update checks the verified response ID against any already stored ID and writes URL/expiry only for that exact Session; a different ID or Customer becomes hold only while the same active owner/generation/source state still qualifies, never an overwrite. The corresponding Customer ID uniqueness check and terminal mapping occur in this transaction. The owner writes unknown only for uncertain failures; definitive provider errors still require exact readback before a safe terminal disposition and may remain hold rather than authorize another key. If RETURNING has zero rows, do not write again: read back the current row and return its state/result or pending/hold. In particular a webhook-completed, expired, mapped or hold row and a row claimed by another generation cannot be changed by a late success/error/timeout. No worker starts a second provider call on the old generation after takeover. A matching replay against a terminal exact Customer/Session is read-only and idempotent; a different identity on a terminal row leaves that row unchanged and creates a separate operator incident/held event, never a new obligation or entitlement. Never change completed or expired to open/unknown, mapped to unknown, or any hold automatically. The webhook may prove and commit completed before the create response arrives; the delayed worker then gets zero rows and reads back completed. A takeover may happen before an old worker returns; its old UUID/generation gets zero rows even if the request binding matches.

For example, with $ placeholders bound from the committed reservation, the Checkout response transition has the following shape. Check RETURNING before issuing any follow-up write; the other outcomes use the same full WHERE and their narrower state sets. The Customer update substitutes customer_setup_key, customer_request_sha256, customer_call_owner, customer_call_generation, and customer_setup_state on billing_accounts. Use clock_timestamp() for takeover/deadline predicates, never transaction-start now().

SQL
update checkout_attempts
set state = 'open', stripe_session_id = $8, stripe_session_url = $9,
    stripe_expires_at = to_timestamp($10::double precision), updated_at = clock_timestamp()
where workspace_id = $1 and attempt_id = $2 and idempotency_key = $3
  and request_sha256 = $4 and stripe_customer_id = $5
  and stripe_price_id = $6 and return_origin = $7
  and call_owner = $11 and call_generation = $12
  and state in ('reserved','unknown')
  and (stripe_session_id is null or stripe_session_id = $8)
returning attempt_id, state, stripe_session_id;

A signed checkout.session.completed or async event may arrive before the Session ID response was saved. Retrieve its exact Session ID from Stripe. It can bind only to one reserved/open/unknown attempt with the signed Session's unpredictable client_reference_id/metadata attempt_id, same mapped Customer, server Price/quantity, subscription mode, deployment livemode, return origin and the stored immutable attempt digest; reject a conflicting stored Session ID or a Customer/Subscription already mapped to another workspace. Lock that workspace and attempt, conditionally persist the exact Session ID once and reconcile the current Subscription in the same transaction. This webhook transition uses the immutable binding and allowed source states, but need not match the in-flight create worker's owner token; it must never overwrite a different ID. A matching completed replay is read-only/idempotent; expired or hold is not reopened by a late response/event. Conflicting identities leave a terminal row unchanged and keep the event/incident held for operator comparison; a nonterminal conflict may transition conditionally to hold without entitlement. Missing/mismatched evidence cannot enable access. This is a bounded recovery for a lost response, not permission to attach a Customer by email or search alone.

Signed event processing

Mount express.raw({type:'application/json',limit:'512kb'}) at the webhook before express.json(), after the project's required managed-auth proxy placement. stripe.webhooks.constructEvent(raw, stripeSignature, endpointSecret) must succeed; check event livemode and api_version against this deployment's pinned endpoint version. Invalid signature/version/mode alters no billing mapping and returns an error for investigation. Allow checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, customer.subscription.created/updated/deleted, invoice.paid, invoice.payment_failed, invoice.voided and invoice.marked_uncollectible as reconciliation triggers, never direct grants. Unrelated valid events are recorded ignored. Stripe's subscription webhook guide and delivery guide describe asynchronous and unordered delivery.

Insert event.id as pending once. A concurrent delivery locks that event row so only one processing owner proceeds; a committed processed/ignored duplicate returns 200. pending/hold is retried from the same event after exact readback, not discarded because its ID exists. Derive candidate workspace only from a verified stored Checkout attempt or a previously immutable Customer mapping. Subscription/invoice events for that Customer arriving before the exact Checkout Session has been proven stay pending: they may not bind a Subscription or process entitlement by themselves. Acquire its workspace advisory lock, then fetch the current Subscription/Invoice from Stripe after the wait and verify Customer, mode and mapping before updating local UI/cache state. Thus two events cannot commit stale state in reverse order. Persist event result, billing mapping and latest snapshot in one SQL commit. If provider read or mapping is uncertain, leave pending/hold, return 5xx and let Stripe retry. After delivery retries cease, an owner reconciles stored event ID, exact Session/Customer/Subscription in Stripe Workbench and reprocesses that same record; no new attempt/key or local paid=true shortcut. A crashed processing transaction rolls back to pending when pending was committed before work; a committed hold remains visible for recovery. Log only IDs/error classes, never webhook payload, signature or card/email data.

Strict paid-run predicate on 2026-09-30.endive

The paid action is only POST /api/workspaces/:workspaceId/runs; existing scoped reads/export remain available on a disclosed lapse. At the start of the run mutation, take the workspace lock, fresh-check active owner membership and the immutable mapped Customer, and read SELECT extract(epoch FROM clock_timestamp()) AS current_epoch_seconds on that same connection. PostgreSQL returns a numeric Unix-seconds value here; compare it directly with Stripe's integer Unix-second period.start/end, retaining fractional seconds rather than flooring or rounding. For timestamptz deadlines (invitation expiry, Customer/Checkout call deadline, Stripe Session expiry), compare to clock_timestamp() on the SQL server. Do not use now(), CURRENT_TIMESTAMP or transaction_timestamp() for any elapsed-time gate: those are fixed at transaction start, even after a provider wait. Ordinary created_at DEFAULT now() audit values can remain. The PostgreSQL date/time functions document the advancing clock and transaction-start alternatives. Perform the following current provider reads while the workspace lock is held with a bounded timeout; then fresh-check mapping, membership, advancing clock and expected template/assignee state immediately before the guarded run insert. Holding the lock does not make Stripe and PostgreSQL an atomic system: a provider change just after the read may race with commit. Keep the window bounded, fail closed on provider error, and monitor signed events; claim only the observed decision at this request, never guaranteed irreversible payment.

  1. stripe.subscriptions.list({customer: storedCustomerId, status:'all', limit:100}): reject has_more, unexpected Customer/mode or more than one nonterminal subscription across all Prices. Require exactly one, then retrieve that ID. Its status must be active, collection_method==='charge_automatically', pause_collection===null, pending_update===null, no trial, and exactly one subscription item (items.has_more===false) with quantity one and the configured fixed Price. Do not filter by Price before counting: an extra unrelated active subscription still blocks this one-subscription v1. The current Subscription object lists statuses and item current_period_start/end.
  2. Resolve latest_invoice to its exact ID and retrieve it. Require matching customer, livemode, collection_method, status==='paid', amount_paid>0, amount_remaining===0, expected currency and no credit/discount/manual path outside this reference. Require one unambiguous relevant subscription line with parent.type==='subscription_item_details', parent.subscription_item_details.subscription===subscription.id, subscription_item===currentItem.id, pricing.price_details.price===configuredPriceId, quantity one, and line.period.start <= current_epoch_seconds < line.period.end with the same start/end as the current subscription item period. Require exactly one invoice line for this selected no-proration/no-extra-item plan. Reject lines.has_more, extra invoice lines, missing parent/pricing fields or an invoice only for a previous period. A period boundary at end is denied; after a renewal, a new current paid invoice must qualify. The Invoice and Invoice Line Item references define these fields.
  3. stripe.invoicePayments.list({invoice: latestInvoiceId, limit:100}): reject has_more or ambiguous entries. Require positive amount_paid values for status==='paid' on exactly that invoice that sum to the Invoice's amount_paid, each with payment.type==='payment_intent' and an exact PaymentIntent ID. Retrieve each stripe.paymentIntents.retrieve(id), require same Customer/mode and status==='succeeded'; resolve its exact payment_method through stripe.paymentMethods.retrieve(id) and require type==='card'. Deny null/missing ID, zero-charge, out-of-band paid mark, customer-balance/credit settlement, delayed method, failed/pending intent or unsupported payment. The Invoice Payment, list method, PaymentIntent and PaymentMethod references define the relations.

After all three checks, fresh-read in-transaction membership, mapping, template/version and assignee, then take a new SELECT extract(epoch FROM clock_timestamp()) AS current_epoch_seconds immediately before the conditional run insert. Require line.period.start <= current_epoch_seconds < line.period.end and the same bound for the subscription item's current period. If a provider wait crossed end, deny this request and start a new current-state check on a later request; never commit from the earlier clock value. Repeating SELECT now() would not detect this crossing. Then insert the run, copied items, initial history and committed mutation-key result atomically. Known ineligibility returns 403 without a run; a provider timeout, page overflow, unknown field or changed mapping returns 503/409 without a run. The browser never receives a Stripe secret, raw provider object or local entitlement flag as authority. A later refund/dispute or reversal needs an explicit owner policy and monitoring. The test matrix must include time just before/at/after period end, a single transaction whose provider wait crosses period end with unchanged run count, old paid invoice after failed renewal, extra nonterminal subscription, mismatched Price/customer, manual/credit/zero/non-card payment, changing membership/mapping during provider read, provider outage, duplicate/out-of-order event and unchanged run count after every denial.

Keep test/live keys, webhook endpoint/version, fixed Price, Customer and return origin separate. The portal URL uses only the mapped Customer and is created on demand for the active owner, never cached/shared. If the account cannot enforce this selected card-only one-plan flow, HOLD the paid release and redesign the policy before taking live money. Actual account, SDK response, taxes, cancellation setting and consumer terms remain owner decisions; none were tested here.

New-project PostgreSQL schema

Review this reference against the generated app and apply it only through a reviewed versioned migration. History reads order by the server-generated run_history.id or membership_history.id, allocated by an insert after the common workspace lock. These identities define committed event sequence within each workspace; occurred_at DEFAULT now() describes transaction-start time and is not a transaction-order key. A rollback can leave gaps. Use workspace-scoped ID cursors and never infer sequence from timestamp alone.

SQL
-- New-project PostgreSQL reference for the selected checklist SaaS contract.
-- Review against the actual generated app and apply through a versioned migration.
-- The Express server is the only database client; never expose its connection string.
create extension if not exists pgcrypto;

create table workspaces (
  id uuid primary key default gen_random_uuid(),
  name text not null check (char_length(name) between 1 and 120),
  created_by text not null,
  created_at timestamptz not null default now(),
  unique (id, created_by)
);

create table memberships (
  workspace_id uuid not null references workspaces(id) on delete restrict,
  user_id text not null,
  role text not null check (role in ('owner', 'member')),
  active boolean not null default true,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  primary key (workspace_id, user_id),
  unique (user_id)
);
create unique index one_owner_per_workspace on memberships (workspace_id)
  where role = 'owner' and active;

create table membership_history (
  id bigint generated always as identity primary key,
  workspace_id uuid not null,
  member_user_id text not null,
  actor_user_id text not null,
  action text not null check (action in ('owner_created','invited','reactivated','deactivated')),
  reason text,
  occurred_at timestamptz not null default now(),
  foreign key (workspace_id, member_user_id) references memberships(workspace_id, user_id),
  foreign key (workspace_id, actor_user_id) references memberships(workspace_id, user_id)
);

create table invitations (
  id uuid primary key default gen_random_uuid(),
  workspace_id uuid not null references workspaces(id) on delete restrict,
  email_normalized text not null check (char_length(email_normalized) between 3 and 254),
  token_hash bytea not null unique,
  expires_at timestamptz not null,
  consumed_at timestamptz,
  revoked_at timestamptz,
  invited_by text not null,
  created_at timestamptz not null default now(),
  foreign key (workspace_id, invited_by) references memberships(workspace_id, user_id),
  check (not (consumed_at is not null and revoked_at is not null))
);

create table templates (
  workspace_id uuid not null references workspaces(id) on delete restrict,
  id uuid not null default gen_random_uuid(),
  version integer not null default 1 check (version > 0),
  name text not null check (char_length(name) between 1 and 120),
  instructions text not null default '' check (char_length(instructions) <= 4000),
  archived_at timestamptz,
  created_by text not null,
  created_at timestamptz not null default now(),
  primary key (workspace_id, id, version),
  foreign key (workspace_id, created_by) references memberships(workspace_id, user_id)
);
create table template_items (
  workspace_id uuid not null,
  template_id uuid not null,
  template_version integer not null,
  position integer not null check (position > 0),
  title text not null check (char_length(title) between 1 and 240),
  required boolean not null default true,
  primary key (workspace_id, template_id, template_version, position),
  foreign key (workspace_id, template_id, template_version)
    references templates(workspace_id, id, version) on delete restrict
);

create table runs (
  workspace_id uuid not null references workspaces(id) on delete restrict,
  id uuid not null default gen_random_uuid(),
  template_id uuid not null,
  template_version integer not null,
  assignee_user_id text not null,
  due_at timestamptz,
  status text not null default 'assigned'
    check (status in ('assigned','in_progress','submitted','returned','accepted','cancelled')),
  version integer not null default 1 check (version > 0),
  created_by text not null,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  primary key (workspace_id, id),
  foreign key (workspace_id, template_id, template_version)
    references templates(workspace_id, id, version) on delete restrict,
  foreign key (workspace_id, assignee_user_id)
    references memberships(workspace_id, user_id) on delete restrict,
  foreign key (workspace_id, created_by)
    references memberships(workspace_id, user_id) on delete restrict
);
create index runs_assignee_status on runs(workspace_id, assignee_user_id, status);

create table run_items (
  workspace_id uuid not null,
  run_id uuid not null,
  position integer not null check (position > 0),
  title text not null check (char_length(title) between 1 and 240),
  required boolean not null,
  done boolean not null default false,
  note text not null default '' check (char_length(note) <= 2000),
  updated_at timestamptz not null default now(),
  primary key (workspace_id, run_id, position),
  foreign key (workspace_id, run_id) references runs(workspace_id, id) on delete restrict
);

create table run_history (
  id bigint generated always as identity primary key,
  workspace_id uuid not null,
  run_id uuid not null,
  from_status text,
  to_status text not null,
  reason text,
  actor_user_id text not null,
  occurred_at timestamptz not null default now(),
  foreign key (workspace_id, run_id) references runs(workspace_id, id) on delete restrict,
  foreign key (workspace_id, actor_user_id)
    references memberships(workspace_id, user_id) on delete restrict
);
create index run_history_tenant_run on run_history(workspace_id, run_id, id);

create table mutation_keys (
  workspace_id uuid not null references workspaces(id) on delete restrict,
  actor_user_id text not null,
  route_key text not null,
  idempotency_key text not null,
  request_sha256 bytea not null,
  result_status integer,
  result_json jsonb,
  state text not null check (state in ('processing','committed')),
  created_at timestamptz not null default now(),
  completed_at timestamptz,
  primary key (workspace_id, actor_user_id, route_key, idempotency_key),
  foreign key (workspace_id, actor_user_id)
    references memberships(workspace_id, user_id) on delete restrict
);

-- These tables are never returned through general workspace-record endpoints.
create table billing_accounts (
  workspace_id uuid primary key references workspaces(id) on delete restrict,
  customer_setup_key text not null unique,
  customer_request_sha256 bytea not null,
  customer_setup_state text not null check (customer_setup_state in ('reserved','unknown','mapped','hold')),
  customer_call_owner uuid,
  customer_call_generation bigint not null default 1 check (customer_call_generation > 0),
  customer_first_call_at timestamptz,
  customer_call_deadline timestamptz,
  stripe_customer_id text unique,
  stripe_subscription_id text unique,
  stripe_price_id text,
  subscription_status text,
  paid_period_end timestamptz,
  current_invoice_id text,
  entitlement_checked_at timestamptz,
  updated_at timestamptz not null default now(),
  unique (workspace_id, stripe_customer_id)
);
create table checkout_attempts (
  workspace_id uuid not null references workspaces(id) on delete restrict,
  attempt_id uuid not null default gen_random_uuid() unique,
  owner_user_id text not null,
  idempotency_key text not null,
  request_sha256 bytea not null,
  stripe_customer_id text not null,
  stripe_price_id text not null,
  return_origin text not null,
  stripe_session_id text unique,
  stripe_session_url text,
  state text not null check (state in ('reserved','open','unknown','completed','expired','hold')),
  call_owner uuid,
  call_generation bigint not null default 1 check (call_generation > 0),
  first_call_at timestamptz,
  call_deadline timestamptz,
  stripe_expires_at timestamptz,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  primary key (workspace_id, idempotency_key),
  foreign key (workspace_id, owner_user_id) references memberships(workspace_id, user_id),
  foreign key (workspace_id, stripe_customer_id) references billing_accounts(workspace_id, stripe_customer_id)
);
create unique index one_unresolved_checkout_per_workspace on checkout_attempts(workspace_id)
  where state in ('reserved','open','unknown','hold');
create table stripe_events (
  stripe_event_id text primary key,
  event_type text not null,
  stripe_created_at timestamptz not null,
  received_at timestamptz not null default now(),
  processed_at timestamptz,
  attempt_count integer not null default 0 check (attempt_count >= 0),
  last_error_code text,
  result text not null check (result in ('pending','processing','processed','ignored','hold'))
);

-- SQL keys preserve tenant relations; they do not replace server authorization.
-- Every private query must derive workspace and role from current verified membership.
-- Migrations, least-privilege database roles, indexes and retention require owner review.

Copyable Replit Agent build brief

Give the agent the filled charter above, this brief, the application contract above, the billing contract above and the SQL above only after the owner has decided that the selected checklist workflow fits the real problem. Keep customer interviews, payment credentials and private records out of the prompt. Replace bracketed decisions with approved facts; do not let the agent fill them imaginatively.

Text
Build a first version of a paid team checklist workspace for [real, owner-validated customer role and task]. The first valuable job is: an owner creates a recurring checklist, assigns a dated run to a verified member, that member completes and submits it, and the owner accepts or returns it with a reason. Use the filled charter and the application, billing and SQL contracts in this guide as binding requirements. Copy those complete sections into this controlled project with the prompt. If the charter contradicts this selected job, STOP and ask for a redesigned contract; do not relabel a form or invent customers, demand, prices, legal terms or test results.

Use React for the interface, an Express server, PostgreSQL, the Replit project's supported managed Clerk authentication, and Stripe-hosted Checkout and Billing Portal. Check the project's generated auth/proxy setup, installed packages, Node runtime and account capabilities before writing code. Plan first. Return a file/route/data map, the proposed migration, the permission matrix, environment list, test plan, budget-sensitive provider actions and explicit unknowns. Wait for my review of that plan before implementation.

Implement the entire selected v1 journey, including session-only unenrolled bootstrap and invitation acceptance, one-workspace membership, invitation revoke/rejoin, member deactivation with stranded-run cancellation, complete template/run/history/export views, versioned templates, copied run items, member submit, owner accept/return, immutable history, server-side fixed Price lookup, hosted billing, signed idempotent webhooks and server-computed paid creation access. Browser role, tenant ID, email, Stripe Customer ID and Price ID are never authority. Use the app contract's single-connection READ COMMITTED user-then-workspace lock order for mutations and fresh active-membership checks after locking. Every other private read/write except session-only `GET /api/me` requires current active membership and scoped SQL. Do not expose the database connection or Stripe secrets in browser code. Follow the exact UI/API/state and response allowlists in the application contract above. Use separate development and production identity, database and Stripe settings. Do not connect a production account, apply a production migration, publish, send real invitations or charge a card without my separate approval.

Use a versioned SQL migration based on the SQL above. Present the whole generated diff and tests. Add direct API tests for signed-out, cross-workspace, member-on-owner-route, removed member, wrong invitation email, expired/replayed invitation, stale version, repeated idempotency key, submitted/accepted item immutability, both orders of revocation versus assignment/item/submit/review, unknown Checkout and duplicate-subscription prevention, Customer/Checkout owner-generation fencing with webhook-before-response/timeout and takeover-before-old-result order tests, unpaid/failed/current-period card entitlement with a provider wait crossing period end, and duplicate/out-of-order Stripe events. Pin and verify request API `2026-09-30.endive` separately from the webhook endpoint event version; if the actual SDK/account differs, stop and redesign the adapter. Include both browser journey and state/readback checks, with unchanged-row/run evidence on every denial. Report which tests actually ran, their environment and results; mark unrun tests as unrun. Do not claim a security audit or live launch.

At the end, give me the exact files changed, migration history, local/development URLs, provider configuration still needed, actual observed tests, unresolved risks, account costs/limits to inspect and a reversible release/rollback checklist. Stop at a reviewable development build.

Plan approval is only permission to let the reader's agent edit its controlled development project. The owner separately approves provider accounts, live payment configuration, production migration, production data and publication.

Owner-run acceptance and release record

Fill results from the real development project. unrun is an honest result. Record environment, build/commit, test identities and Stripe test object IDs in the owner's private system; do not paste credentials or customer data into a public document.

Check

Required observation

Actual result / evidence

Customer task fit

Named permitted users can complete the full charter action; the selected checklist model genuinely represents it

[pass/fail/unrun, notes]

Plan and diff

Owner reads all changed files, dependencies, migrations, generated auth middleware order and secret exposure

Identity bootstrap

Verified person creates one workspace; another person cannot claim it

Bootstrap concurrency

Same verified user makes concurrent/retried creates: one workspace/owner membership, same ID returned; changed name is 409. Unenrolled invite acceptance still works behind session/Origin/body guard

Invitation

Intended verified email accepts once; wrong email, expired, revoked and replayed token all fail

Membership lifecycle

Duplicate active invite is 409; revoke blocks acceptance; removed same-workspace member rejoins only with fresh owner invite; other-workspace member is denied; owner cannot remove self

Tenant reads

Two real test workspaces A/B; A user cannot read B template/run/history using direct IDs/API

Tenant writes

A user cannot modify B item or assign B member; forged browser role/workspace fails

Role and removal

Member cannot call owner routes; revoked member's existing session cannot use private API

Complete core action

Owner template and assignment → member item completion/submission → owner accept/return → saved state/history after refresh

Complete navigation/export

Template list/detail/new version, run detail/items/history, invitation/member views and owner-only scoped JSON/CSV export work after reload and billing lapse; private responses are no-store allowlists

Unhappy core paths

Missing required item, stale version, double-click/replayed mutation and concurrent review produce no extra transition

Revocation races

Run deactivation before/after assignment, item edit, submit and review in both commit orders. Deactivation blocks on open assigned work until owner cancels/reassigns by new run; no post-removal write; submitted/accepted/cancelled item edit denied with unchanged row/history

Mutation recovery

Same-key/same-body returns exact stored response, changed body 409; lost response readback finds one effect; deadlock/timeout bounded retry leaves no extra item/run/history

Test billing

Checkout creates intended single test Customer/subscription; portal routes to its Customer; no browser card capture

Duplicate Checkout

Existing nonterminal subscription blocks new key; reserved/open/unknown/hold attempt blocks concurrent and sequential new keys. Same key replays only a proven unexpired Session; lost Session ID reconciles through exact attempt/Customer/signed event or remains HOLD

Provider-call ownership orders

Deterministically pause/release Customer and Checkout creates: webhook maps/completes before original success and before timeout; takeover commits before old success and before old error; matching terminal event/response replays. Exact binding plus owner UUID/generation and allowed source state fence every worker update. Late worker returns readback only. Assert unchanged mapped Customer/Session/subscription, terminal state, event result and one obligation; conflicts remain held without a new key or entitlement

First-call and deadline clocks

Immutable first-call time stays fixed across owner takeovers; only short call deadline and generation advance. Repeat identical provider request/key strictly before first call + 24 hours; at/after retention expiry require operator readback. Compare clock_timestamp() to SQL deadlines/expiry

Payment boundary

Return URL alone grants nothing; paid current period grants chosen action; failed/unpaid/expired period blocks it; webhook replay/out-of-order reconciles without duplicate entitlement

Provider-wait period crossing

Start one READ COMMITTED run transaction before period.end, pause a provider read through the boundary, then release it. The fresh extract(epoch FROM clock_timestamp()) numeric seconds value is at/after end and denies the run; run, copied items, history, mutation result and count remain unchanged. now()/CURRENT_TIMESTAMP would falsely keep the transaction-start value

Payment adapter boundaries

Pinned request/event versions match; test before/at/after period end, extra nonterminal subscription, wrong Price/customer, manual/credit/zero/non-card payment, changed mapping, provider error and duplicate/out-of-order events. Every denial leaves run count unchanged

Separate production

Production Clerk, database, Stripe keys, fixed live Price and webhook endpoint are independently verified; no development data or secret copied

Release

Owner approves terms, tax/payment policy, audience, migration and exact build; public URL sign-in/private route/paid action and denial checks repeated

Operations

Support contact, monitoring, cancellation handling, export/retention, backup/restore route, rollback owner and cost alerts recorded

If any identity, tenant or payment denial fails, stop the release, preserve the failed request and server trace without secrets, repair the exact route/query/event mapping, then repeat the direct and browser checks. If a migration is uncertain, compare the actual target and migration history before another write. If the deploy is unhealthy, return traffic to the last known good release under the host's supported procedure, preserve schema compatibility and investigate; do not assume code rollback reverses a database migration or a Stripe charge. Label a restore untested until rehearsed on an authorized separate target.

Sources and further reading

The official manuals document platform controls and object fields. The charter, application and billing contracts are proposed designs; the owner must observe the tests in the real account.