Preview is not staging with a cute URL

A preview environment is a runnable instance of an unmerged change. In the simplest case, it is a static site built from a pull request and published at a temporary URL. In the more interesting case, it is an application stack: frontend, API, database branch, feature flags, secrets, workers, webhooks, logs, and a cleanup rule tied to the lifecycle of the branch.

That sounds like staging, but the job is different. Staging is an integration environment. It answers, “Is the integrated system ready for promotion?” Preview is a change environment. It answers, “Does this specific proposal deserve to enter the integrated system?” Staging is shared by time; preview is scoped by change. Staging tests the candidate release; preview tests the candidate contribution.

The distinction is not academic. A shared staging environment can only hold one version of the truth at a time. If three pull requests are in flight, staging either serializes them, mixes them, or lies about which one is being reviewed. A preview environment gives each change its own place to stand. The reviewer can click the URL, exercise the behavior, inspect logs, compare deploy metadata, and decide whether the proposal is real enough to merge.

Key Takeaway

Preview is the first environment whose primary user is the reviewer, not the deployer. Design it around evidence, not convenience.

This matters for product work, but it matters even more for operational software. A map zoom change, a tenant setting, a new ECO status, a webhook retry path, a permission rule, or a dashboard filter is hard to evaluate from a diff alone. The reviewer needs to see behavior under something that looks like the real system without giving the unmerged branch real production authority.

Why this matters more with agents

Agents make pull requests cheaper to produce and easier to overtrust. A human can ask for a migration, a UI update, tests, docs, and a cleanup script, then receive a plausible branch in minutes. The code may compile. The tests may pass. The prose may sound confident. None of that proves the behavior is correct or that the blast radius is understood.

The recent cairn on CI/CD maturity in agentic engineering argued that agentic workflows raise throughput before they raise confidence. Preview environments are one answer to that imbalance. They give the human reviewer a live artifact to interrogate. They also give review agents, smoke tests, accessibility checks, screenshot comparisons, and product acceptance scripts a common target.

Text review still matters. It is where architecture, maintainability, security, and long-term ownership get considered. But text review is weakest when the diff is large, cross-layer, or generated by a system that can imitate confidence. A preview environment shifts part of the review from “does this look right?” to “what evidence do we have that this behaves right?”

Example: Review Moves From Diff Guessing To Evidence
@Q Opened PR #184. Preview: preview-eco-map-pr-184.example. Commit 8f31c2a. Smoke tests passed. Two warnings: migration is additive, but the rollback script was not exercised; tenant setting defaults changed for demo only.
@Maya I can verify the demo tenant view from the preview. Please add the rollback rehearsal output before I approve the API change.

That exchange is healthier than “looks good” on a 1,200-line diff. The preview did not make the decision. It made the decision inspectable.

The contract has seven clauses

A preview environment is a contract because it makes promises. When those promises are implicit, every reviewer invents their own mental model. One person assumes the database is isolated. Another assumes the URL follows the latest branch. Another assumes production secrets are absent. Another assumes logs will exist for a week. The failure is not usually malice; it is unstated semantics.

Write the contract down. A useful preview environment answers seven questions:

  1. Code identity: What commit, branch, artifact, or image is running?
  2. Data boundary: What database, fixtures, snapshots, object storage, queues, and external systems can it touch?
  3. Identity boundary: Who can open it, and what roles do they have inside it?
  4. Secret boundary: Which credentials exist, and are they preview-scoped?
  5. Migration boundary: How are schema and data migrations applied, reviewed, and reversed?
  6. Evidence boundary: What logs, traces, checks, screenshots, and health signals attach to this preview?
  7. Lifecycle boundary: When is it created, updated, paused, expired, deleted, and audited?

Those clauses turn a preview from a nice-to-have into a reusable piece of engineering infrastructure. They also keep the team from confusing a partial preview with a full rehearsal. A static documentation preview can ignore database semantics. A full-stack preview cannot.

Definition

A preview contract is the explicit policy that defines what a pull-request environment is allowed to know, touch, prove, and leave behind.

The contract should be visible where review happens. A pull request comment, deployment status, or generated checklist should tell the reviewer what kind of preview they are looking at. “Static only; no API” is useful. “API connected to synthetic fixtures; outbound webhooks disabled” is useful. “Looks like prod” is not.

Code identity: immutable enough to trust

The first promise is identity. When a reviewer clicks a preview URL, what exactly are they seeing? A moving branch? A frozen commit? A rebuild of the same source with different dependencies? A production deploy candidate? A locally generated artifact pushed by a human?

Modern platforms expose several useful patterns. Cloudflare Pages supports preview deployments for new versions before production and lets teams configure branch build behavior. Vercel creates preview deployments for non-production branches and pull requests by default. Heroku Review Apps create disposable apps for GitHub pull requests. GitHub deployment environments can record and gate deployments in the place where code review already happens.

The important design choice is not the vendor. It is the identity model.

Stable branch URLs are collaboration-friendly. They let product, QA, and reviewers keep one link as the branch changes. The cost is that the link moves. A comment from yesterday may refer to a different artifact than the one currently visible.

Commit URLs are evidence-friendly. They freeze the reviewed artifact. The cost is that they multiply links and can confuse less technical reviewers who just want “the latest PR version.”

Deployment records connect either style back to the repository. They should include commit SHA, build time, build status, test status, environment class, and who or what initiated the deployment.

flowchart TD
    PR[Pull Request] --> Build[Build Artifact]
    Build --> CommitPreview[Commit Preview URL]
    Build --> BranchPreview[Branch Preview URL]
    Build --> DeployRecord[Deployment Record]
    DeployRecord --> Review[Human and Agent Review]
    Review --> MergeDecision[Merge Decision]

For agentic work, the deployment record matters. Agents can update a branch several times while responding to review comments. The human reviewer needs to know whether the URL they just tested corresponds to the latest commit and whether the final approved commit is the one that passed the preview checks.

Data isolation is the hard part

Static previews flatter us. A docs site, marketing page, or pure frontend prototype can often be previewed safely with no persistent state at all. Full-stack applications are less forgiving. They need data, and data is where preview semantics become expensive.

There are four common data models.

Model Use when Main risk
Synthetic fixtures UI flows, deterministic tests, demos Too clean to reveal production-like edge cases
Seeded disposable database Feature work needing backend behavior Setup cost and fixture drift
Sanitized snapshot Complex workflows requiring realistic shape Privacy, refresh cost, and stale assumptions
Database branch Migration review and realistic relational behavior Provider coupling and lifecycle discipline

Neon, Supabase, and PlanetScale all document variants of database branching or deploy-request workflows. The common idea is attractive: make a cheap branch of database state, apply the change there, review the result, then discard or promote with more confidence. It is not magic. Branching helps with schema review and realistic testing; it does not eliminate privacy constraints, production migration planning, or the need to reason about side effects.

The Osprey Strike production architecture already treats environment boundaries seriously: main and demo have separate namespaces, load balancers, and databases, while Cloudflare and AWS layers enforce access and ingress rules. Preview environments should inherit that habit, not flatten it. A preview that points an unmerged branch at a shared mutable database teaches reviewers the wrong lesson: the URL looks isolated, but the state is communal. This is why “just use staging” becomes brittle as the team gets faster. A shared staging database is sometimes fine for release rehearsal, but it is a poor substrate for independent review of multiple unmerged changes.

Warning

The preview boundary is only as strong as the state boundary. If every branch writes to the same database, you do not have isolated previews; you have many front doors into one shared experiment.

Secrets and identity cannot be copied blindly

The fastest way to make a preview dangerous is to give it production authority. It is tempting because production secrets make everything work. The third-party APIs respond. Webhooks deliver. Object storage has files. Email sends. Maps load. The problem is that an unmerged branch should not be allowed to behave like production by default.

Preview credentials should be preview-scoped. That means separate API keys where the provider supports them, separate OAuth clients when callback URLs differ, separate buckets or prefixes for object storage, disabled or sandboxed outbound messaging, and explicit deny-by-default rules for irreversible actions. If a preview needs to call a real external system, that exception should be visible in the deployment record.

Identity has the same shape. Who can view a preview? Internal engineers only? Product and operations? External stakeholders? A customer? The answer changes the risk model. Cloudflare Access, Vercel Deployment Protection, GitHub environments, and similar controls exist because a preview URL is still a deployed system. A secret link is not an access policy.

This is where the Constructured static-site pattern is instructive. Cairns and Conduit were designed as private sites protected by Cloudflare Access with Google Workspace SAML. The internal source spec chose Cloudflare’s native GitHub integration for deployment simplicity, but it did not treat “static” as synonymous with “public.” Preview environments should carry the same instinct: first decide who should reach the thing, then publish the URL.

Migrations need rehearsal, not hope

Migrations are the preview stress test. A UI-only preview can be convincing while hiding the hardest part of the change. A backend preview that applies migrations to an isolated branch starts to answer the questions production will ask later: does the schema change apply cleanly, does the application run against the new shape, does old code tolerate the transition, and is rollback or roll-forward plausible?

Preview cannot prove every production migration safe. Production has scale, locks, concurrent writes, old workers, long-running jobs, real integrations, and users doing surprising things. But preview can catch a class of mistakes while they are still cheap: missing migration files, destructive changes disguised as refactors, incompatible assumptions between API and web, fixtures that no longer load, generated clients that were not rebuilt, and data backfills that need more thought.

PlanetScale’s deploy-request model is a useful reference because it separates schema review from application deploy. Neon and Supabase branching are useful references because they make database copies cheap enough to attach to review workflows. The broader lesson is independent of provider: schema has to become reviewable evidence, not a footnote in the pull request.

sequenceDiagram
    participant Dev as Branch or PR
    participant CI as CI Pipeline
    participant DB as Preview Database Branch
    participant App as Preview App
    participant Reviewer as Reviewer

    Dev->>CI: Push commit
    CI->>DB: Create or update isolated state
    CI->>DB: Apply migrations
    CI->>App: Deploy app with preview credentials
    CI->>App: Run smoke tests
    App-->>Reviewer: URL plus evidence
    Reviewer->>App: Exercise behavior
    Reviewer->>Dev: Approve, request changes, or block
Tip

Treat migration preview as rehearsal, not certification. The useful output is evidence: diff, apply result, compatibility notes, smoke-test output, and known limits.

Observability turns preview into evidence

A preview without observability is mainly a screenshot generator. It lets someone click around, but it does not leave much behind. That is fine for a static copy change. It is weak for an agent-generated workflow change, a new permission rule, or an integration path.

The minimum useful evidence bundle is small:

  • commit SHA and branch name
  • deployment timestamp and build status
  • environment class and data model
  • smoke-test result
  • application logs for the preview window
  • error report link or trace query
  • cleanup status

This does not require enterprise ceremony. A pull request comment can carry the bundle. A GitHub deployment can link to it. A bot can update it after each push. The point is that evidence should travel with the review, not live only in the deploy platform’s dashboard.

Osprey Strike’s production article emphasized traceability from the browser through GraphQL to the database. Preview environments need a smaller version of the same discipline. If a reviewer finds a bug, they should be able to say which preview, which commit, which tenant or fixture, which request, and which logs show the failure. That turns review into a reproducible report instead of a Slack memory.

Example: Evidence Attached To The PR
@Dana The preview shows the right empty state, but creating a second ECO leaves the map centered on the first one.
@Q Confirmed on preview pr-211, commit 4c9a61e, fixture tenant demo-rural. Trace eco-create-7f2 shows the create response has the new coordinates; the web state reducer keeps the previous map center.

The second message is possible only if preview has enough observability to make the behavior inspectable. That is the difference between “I saw a weird thing” and “here is the failing path.”

Cleanup is part of the feature

Disposable does not mean temporary unless deletion actually happens. Teams are good at creating environments and bad at retiring them. Preview systems that do not clean up become a hidden estate: stale URLs, orphaned buckets, forgotten credentials, zombie databases, old deployment records, surprise costs, and unclear access paths.

The lifecycle contract should be explicit:

  1. Create on pull request open or first push.
  2. Update on every pushed commit.
  3. Pause or scale down after inactivity if the platform supports it.
  4. Delete on merge or close.
  5. Revoke preview credentials and external callbacks.
  6. Retain only the audit record needed for review history.

Supabase’s current branching docs, for example, describe preview branches as ephemeral and suited for focused testing, with automatic pause or deletion behavior tied to inactivity or pull request lifecycle. Heroku Review Apps similarly center the idea of disposable apps tied to PRs. Those defaults are healthy because they make cleanup part of the product, not an afterthought.

Key Takeaway

If preview cleanup is manual, preview creation should be rare. If preview creation is automatic, cleanup must be automatic too.

Cost matters, but security matters more. A forgotten preview with no traffic can still hold credentials. A stale database branch can still contain sensitive shape. A closed-PR URL can still reveal an internal workflow. Deletion is not housekeeping; it is part of the trust boundary.

The Constructured shape

Constructured already has several pieces of the preview story, even where we have not named them that way. Cairns is built from the Constructured/cairns repository and deployed by Cloudflare Pages behind Cloudflare Access. Conduit receives content from participating repos, writes to its own repository, and lets Cloudflare Pages deploy from there. The internal design doc deliberately chose native Pages integration over CI-managed credentials because the simpler contract was good enough for a private static site.

That static-site pattern is a useful baseline. It has clear code identity, simple deployment, strong access control, low state, and easy rollback through git. The preview contract is naturally small: build this branch, publish a URL, protect it behind Access, and delete or supersede it when the branch moves.

Application work is more complex. Osprey Strike has real tenants, auth layers, event-sourced state, projections, deadlines, webhooks, and production/demo environment boundaries. A serious preview environment for that kind of system cannot be just “run the app somewhere.” It needs a declared data model, tenant fixtures, disabled or sandboxed outbound integrations, preview credentials, migration rules, and logs.

That does not mean the team should build a perfect full-stack preview platform before using previews at all. It means each preview class should be honest about its contract.

Preview class Good for Contract
Static/docs preview Cairns, Conduit pages, copy and navigation Branch or commit identity, access control, build result
Frontend fixture preview UI states, layout, interaction review Synthetic data, no production API, screenshot or smoke evidence
API sandbox preview Resolver behavior, auth rules, integration boundaries Disposable database, mock external systems, preview-scoped secrets
Full-stack rehearsal Cross-layer features and migrations Isolated state, migration evidence, logs/traces, lifecycle cleanup

This ladder keeps the team honest. Not every pull request deserves the most expensive preview. Every pull request deserves clarity about what its preview proves.

A practical maturity path

The right path is incremental. A team can get most of the cultural value from simple previews before it solves the hardest state problems. The trick is to advance only when the current preview class is boring.

Stage 1: Build previews for static surfaces. Every documentation or static-site PR gets a URL. The URL is protected if the site is private. The deployment record includes commit, status, and build logs. This is where Cairns and Conduit naturally fit.

Stage 2: Attach preview evidence to PRs. The PR shows the preview URL, commit SHA, smoke-test result, and known limitations. Reviewers stop asking “where is the thing?” and start asking better questions about behavior.

Stage 3: Add frontend fixture previews. UI-heavy application branches deploy against deterministic fixtures or mock APIs. Product and operations can review states that are hard to infer from screenshots.

Stage 4: Add isolated backend state for selected changes. Backend or cross-layer PRs get disposable databases or database branches when the value justifies the cost. Migrations are applied in preview, and the result is visible in the PR.

Stage 5: Govern full-stack previews. The team treats preview environments as a small platform: quotas, cleanup, access policy, preview secrets, audit logs, provider costs, and exception handling all have owners.

This path pairs well with the CI/CD maturity model. Preview environments belong between trustworthy CI and continuous delivery. They exercise deployment automation earlier, improve review quality, and reduce the pressure to make staging carry every kind of validation.

Failure modes

The dangerous preview is the one everyone treats as stronger than it is. A green preview means the branch built and whatever checks exist passed. It does not mean the change is secure, production-ready, scalable, accessible, or migration-safe.

Watch for these failure modes:

  • Production secrets in preview. The branch can send real email, mutate real external systems, or access production-only APIs.
  • Shared mutable databases. Multiple previews write into the same state and reviewers cannot tell which branch caused which behavior.
  • Stale branch URLs. A reviewer approves behavior from an older commit because the URL or PR comment did not make identity clear.
  • No cleanup. Closed PRs leave compute, credentials, buckets, database branches, or callbacks behind.
  • Access by obscurity. Anyone with the link can open an internal preview.
  • Staging drift in disguise. Preview has different config, flags, auth, or fixtures from the environment it claims to approximate.
  • Click-through review. Reviewers treat “I opened the URL” as equivalent to acceptance criteria.
  • Agent self-certification. The same agent that wrote the change also declares the preview sufficient without independent checks or human judgment.
Warning

Preview environments reduce ambiguity. They do not reduce accountability. The reviewer still owns the decision to merge.

Most failures are contract failures. The team did not say what preview was allowed to do, what it proved, or when it expired. Fixing that usually starts with a small generated preview summary in the pull request.

Summary

Preview environments are review infrastructure. They are not a replacement for tests, staging, feature flags, production observability, or human judgment. They are the place where those systems start to meet before merge.

  1. Preview and staging answer different questions — staging evaluates an integrated candidate release; preview evaluates an unmerged candidate contribution.
  2. The contract matters more than the platform — every preview should declare code identity, data boundary, identity policy, secret scope, migration behavior, evidence, and lifecycle.
  3. Data isolation is the hard boundary — static previews are easy; full-stack previews require deliberate choices about fixtures, snapshots, database branches, and side effects.
  4. Agentic work raises the stakes — when branches appear faster, reviewers need runnable evidence, not just plausible diffs and passing tests.
  5. Cleanup is security work — automatic preview creation requires automatic teardown, credential revocation, and a retained audit trail.

Discussion prompts

  • Which current Constructured surface would benefit most from a generated PR preview summary: Cairns/Conduit, Osprey Strike web, Osprey Strike API, or infrastructure changes?
  • For Osprey Strike, what is the smallest useful backend preview contract: synthetic fixtures only, disposable database, sanitized snapshot, or true database branching?
  • What actions should preview environments be categorically unable to perform, even when a branch needs realistic integration behavior?

References

  1. Cloudflare Pages: Preview deployments — Official documentation for previewing project changes before production in the platform already used for Cairns and Conduit.
  2. Cloudflare Pages: Branch build controls — Shows how production and preview branch behavior can be configured instead of assumed.
  3. GitHub Docs: Managing environments for deployment — Defines GitHub environments, environment secrets, and deployment protection rules that can attach policy to deployment targets.
  4. Vercel Docs: Environments — Useful reference for the mainstream preview/production split and automatic preview deployments on branch or pull request activity.
  5. Heroku Dev Center: Review Apps — Long-running review-app model for disposable pull-request applications with shareable URLs and lifecycle tied to GitHub.
  6. Neon Docs: Branching — Explains copy-on-write database branches, a core option when previews need realistic relational state without mutating production.
  7. Supabase Docs: Branching — Documents preview branches as separate environments with their own instance and credentials, including lifecycle behavior for focused testing.
  8. Cairns: CI/CD Maturity in the Age of Agentic Engineering — The adjacent maturity model; this cairn expands the preview-environment stage into an operational contract.