Deploys & drift
Once Email Guard is live, HS-X keeps three separate records about it, and the dashboard vocabulary — deploys, drift, checkpoints — is just those three records by name. A deploy revision is the signed receipt of what you shipped. Drift is a recurring verdict on whether what is actually running still matches that receipt. Checkpoints are the per-invocation telemetry of what the running app is doing. This guide walks one deploy through its whole lifecycle, then shows you how to read each record from the CLI and the dashboard — including the cases that look wrong but aren't, like an installable, fully live app that still reads 'no recorded deploys'.
TL;DR — hs-x deploy ships code and records a revision: deploy id, manifest hash, bundle archived for rollback. The runtime then attests every 15 minutes; the control plane diffs the attestation against the recorded revision and publishes the verdict as drift (advisory — your app keeps serving either way). Checkpoints are the separate per-invocation stream (outcomes, latency, error fingerprints) behind the dashboard's metrics. Deploys made while unlinked are recorded locally and backfilled into your history when you hs-x link.
The three records HS-X keeps about your app
Every operational question about a deployed project reduces to one of three questions, and each has its own record:
| Question | Record | Where it lives | Who writes it |
|---|---|---|---|
| What did I ship? | Deploy revision — deploy id, signed manifest hash, archived bundle, git provenance | Control plane (Deploys tab, hs-x list) | The CLI, at deploy time |
| Is that still what's running? | Drift — a verdict comparing the latest runtime attestation against the recorded revision | Control plane (Drift tab, hs-x drift) | The runtime, every 15 minutes |
| What is it doing? | Checkpoints — per-invocation outcome, duration, error fingerprint | Analytics + sampled exemplars (Checkpoint tab) | The runtime, on every capability invocation |
The order matters. The deploy revision is the contract; drift is the verification of that contract; checkpoints are the behavior under it. A project header only reads HEALTHY when the verification has actually run — before the first attestation lands it reads awaiting first attestation, because healthy is a verdict, not a default.
Follow one deploy through its lifecycle
Run a deploy against Email Guard and watch what the control plane records:
hs-x deploy --yesBehind one command, the revision moves through explicit states:
planned— the control plane allocates the deploy id and issues short-lived credential leases for the HubSpot and Cloudflare legs. Nothing user-visible changed yet.recorded— the code is live on Cloudflare and the control plane has the receipt: manifest hash, the bundle key of the archived artifact, and provenance (CLI version, git commit and branch, who deployed). Recording is the default on control-plane-backed deploys;--no-recordopts out, and skipping it means the next attestation will report the running code as unrecorded (see step 3).promoted— the revision becomes the active deploy: the routing pointer for its environment now names it. Promotion is gated on a healthy attestation, so the usual shape ishs-x deploy --promote-when-healthy, which records, waits for the runtime's first healthy heartbeat, then promotes.retired— a later deploy was promoted over it. Retired revisions are your rollback candidates.rolled-back— it was active and you explicitly reverted away from it withhs-x rollback.
Two consequences of that state machine are worth saying out loud. First, recorded ≠ active: you can record ten revisions and promote one. The Deploys tab's "active deploy" card is the routing pointer, not the newest row. Second, retention is bounded: HS-X keeps the active revision plus the three most recent retired or rolled-back ones, and prunes older terminal revisions (the dashboard's "retention: 3 prior + current" line). Rollback re-promotes one of those retained revisions from its archived bundle:
hs-x rollback # back to the most recent retired revision
hs-x rollback --target deploy_email_guard_007 # or name one; non-adjacent targets need --forceRead your deploy history
hs-x list shows every project on your account with the timestamp of its latest recorded revision:
hs-x list$ hs-x list
■ hs-x list · acme-main
email-guard Email Guard last deploy 2026-06-10T18:21:33.009Z
pricing-cards Pricing Cards no recorded deploys
· Use an id with project-scoped commands: hs-x logs --project <id>.
· "no recorded deploys" means the control plane has no deploy revisions — deploys made while unlinked are not recorded (the app may still be live).The last-deploy column is derived from control-plane deploy records and nothing else. It is deliberately not a claim about whether the app is live in HubSpot.
That distinction is the single most common source of confusion, so here is the rule: "no recorded deploys" means the control plane never got a receipt, not that nothing was shipped. The unlinked path is fully functional — hs-x deploy without a linked HS-X account ships real workers and installable apps, and records its history in your tenant state store (a KV namespace in your own Cloudflare account), never contacting the platform. The control plane only learns about those deploys when you link:
hs-x linkbackfills history. At claim time the CLI reads your local deploy history and registers each entry as a history-only revision (source: link-backfill). Your timeline becomes truthful retroactively.- Backfilled revisions are visibility, not rollback targets. No bundle was ever archived in the control plane for them, so the Deploys tab shows them with "no bundle · backfilled from local history" and
hs-x rollbackrefuses to target them.
Read a drift verdict
A recorded revision is a claim; drift is the recurring check of that claim. At cold start and every 15 minutes after, the deployed worker sends an attestation — a small payload carrying the manifest hash it is actually running, a fingerprint of its live bindings, its SDK version, and the count of HS-X-tagged resources it can see. The control plane diffs that against the recorded revision and stores a verdict:
| State | What it means | Usual cause |
|---|---|---|
healthy | Attestation matches the recorded revision | The steady state |
drifted | A recorded deploy is attesting, but it isn't the active route, or its bindings/tags diverge | Something provisioned or removed outside HS-X |
unknown_code | The worker is running code that matches no recorded revision | A deploy that shipped without recording (--no-record), or a manual wrangler deploy |
resource_missing | Expected tagged resources (KV, DO, D1) are not reachable | Manual deletion, failed provision |
credential_revoked | The stored Cloudflare credential no longer works | Token rotated or revoked |
billing_untrusted | Heartbeats arrive but the attestation is stale or incomplete | Old SDK, partial deploy |
unknown | No heartbeat past the threshold | Worker deleted, or it never attested |
Read it from either surface:
hs-x drift --project email-guard # resource-by-resource diff
hs-x status --project email-guard # routing + drift + checkpoint in one viewThree things drift is not (the dashboard repeats these for a reason): it is not a deploy failure — the app is serving traffic; it is not a permission gate — rollback still works; it is not an error — it is a signal that live state diverged from the recorded contract. The project header shows ADVISORY, your users see nothing.
The repair is almost always the same: run hs-x deploy again. The deploy records a fresh revision matching what is now live, the next attestation matches it, and the verdict returns to healthy — then promote it (or deploy with --promote-when-healthy in the first place). If the drift was caused by something added or deleted by hand in Cloudflare, remove or restore it and let the next heartbeat confirm.
Read the checkpoint stream
Deploy revisions and drift describe the app at rest. Checkpoints describe it under load: every capability invocation — each validate-email workflow-action run, each email-health card fetch — emits one checkpoint with its outcome, duration, and (on failure) a stable error fingerprint.
The stream has two tiers, and the dashboard's Checkpoint tab reads both:
- Metrics, unsampled. Every invocation lands in the analytics store. This is what the success-rate number, the p50/p95/p99 latency percentiles, and the hourly trend charts are computed from — and the same stream backs the usage and billing reads, so the number you see is the number you're billed against.
- Exemplars, sampled. A sample of concrete invocations (errors by default) is kept with redacted payloads, grouped by error fingerprint. This is the "show me one failing request" layer under the recent-failures table.
One naming collision to defuse: the project header's awaiting first attestation line is about drift (the runtime hasn't heartbeated yet), not about this tab. Checkpoints start flowing with the first real invocation; attestation starts with the worker's cold start. A freshly deployed, never-invoked app will attest healthy while its Checkpoint tab is still empty — both are telling the truth.
For the layer below checkpoints — raw structured logs, request tracing, alerts — continue to the monitoring guide.
Resolve the common confusions
My app is live and installable, but hs-x list says "no recorded deploys"
It was deployed while unlinked (or with --no-record), so the control plane never got the receipt. The app is fine. Run hs-x link — the claim backfills your local deploy history into the control plane and the timeline fills in.
The header says ADVISORY with unknown_code right after I deployed
The worker is attesting a deploy id the control plane has no record of. Since 0.2.5, control-plane-backed deploys record by default, so the usual cause today is an explicit --no-record or a manual wrangler deploy outside HS-X. Re-run hs-x deploy (and promote when healthy); the next heartbeat clears it.
The dashboard shows five deploys but only two rollback candidates
Candidates are retained terminal revisions (retired or rolled-back) that still have an archived bundle. Revisions in recorded state that were never promoted, and history-only backfilled revisions, are listed for truth but are not rollback targets.
The header says "awaiting first attestation"
The runtime hasn't heartbeated yet. Right after a first deploy this clears within one cold start; if it persists, the worker either isn't running or was deployed without heartbeat wiring (--no-heartbeat). hs-x status --project <id> shows whether any attestation has ever landed.
Where next
- How to · Operate → Monitoring — the layer below checkpoints: structured logs, correlation ids, live tails, and alerts.
- How to · Ship → Environments — the routing pointer is per-environment; how staging and production revisions coexist.
- How to · Start → Getting started — where Email Guard, the app these records describe, gets built and first deployed.
