Repository navigation
Conversation
…emetry, no env vars
- remoteLoader follows sites/<site>/latest.json { revision, schemaHash,
publishedAt } and swaps revisions/<sha>.json only when the revision differs
from the one last swapped in and schemaHash matches the bundled content's.
Channel manifest, generations, content-hash verification, bundle
comparison and the Authorization header are gone; site alone turns it on.
- deco content writes schemaHash = sha256Hex(canonicalJson(schema.gen.json)).
- Drafts are { set, delete } on the CDN, revalidated with If-None-Match on
every read (200 or 304 only); the view revision is keyed by the ETag.
- Telemetry: top-level token -> otel.decocms.com Bearer; telemetry.endpoint
wins; telemetry: { site, token } removed; new telemetry.resource.
- No environment reads except NODE_ENV=development in remoteLoader:
DECO_CONTENT_INTERVAL, DECO_PREVIEW_API_DOMAINS (-> preview.apiDomains),
OTEL_*, DECO_OTEL_*, commit-SHA variables removed; guardrail test added.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ftHosts, token needs site
- Remove cms.forRevision(revision) and the served-revisions map behind it;
the CMS reads only through forRelease() and forDraft(pointer).
- Rename preview.apiDomains to preview.draftHosts (the hosts a draft
pointer may point at; defaults to v7's list).
- createCMS({ token }) without site throws "token needs site: pass both,
or site alone".
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…lete name
The no-env guard now also catches process?.env, globalThis.process?.env,
destructured { env } = process and Bun.env. The draft parser no longer
rejects a delete list that repeats a name (not in the contract).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…builtAt) deco content stamps the content module with builtAt, the build machine's clock (ISO 8601; no git, no env). The background release check swaps in the CDN revision only when latest.json's publishedAt is later than the bundle's builtAt and its schemaHash matches; otherwise it keeps serving the bundle. A bundle without builtAt counts as the oldest. Studio writes publishedAt on Publish, Make current/rollback and Resync, so a rollback wins over older bundles and a later deploy wins over the rollback. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…mittedAt) deco content now stamps the content module with committedAt, the committer date of git HEAD in the root being built (git log -1 --format=%cI), instead of the build machine's clock. With build time, a slow build of an older commit that finished after a newer publish would win and content would go backwards. Without git (not a repo, no commits, git missing) it writes no stamp and prints one line saying so; the SDK still treats a bundle without a stamp as the oldest. The SDK serves the CDN revision when latest.json's publishedAt is later than committedAt and the schemaHash matches; otherwise the bundle. Co-Authored-By: Claude Opus 5.5 <[email protected]>
esbuild leaves /dev/stdout empty on Linux, so the browser-bundle import check failed there with 'Unexpected end of JSON input'. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WNwbSEePYNcY5YCgqZURig
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #619 (
v8-16-draft-overlays). This is the SDK side of the hosted contract (2026-10-06) plus the PO amendments. Studio, the telemetry ingest, the analytics collector and the docs ship as separate PRs.Contract summary (SDK side)
Delivery (
remoteLoader.ts, rewritten). The PO rules are "do not try to be smart" and, between the bundle and the CDN, "whoever is newer wins":load()serves the bundled content and never touches the network. Nothing persists across restarts.https://delivery.decocms.com/sites/<site>/latest.json={ revision, schemaHash, publishedAt }, with no credentials.sites/<site>/revisions/<revision>.json={ revision, schemaHash, blocks }only when all three hold:revisiondiffers from the one this process last swapped in;schemaHashequals the bundled content module's;publishedAtis later than the bundle'scommittedAt(equal keeps the bundle). A bundle withoutcommittedAt(a custom loader, a content module generated before this change, a build outside git) counts as the oldest, so the CDN wins when the schema matches.publishedAtthat doesn't parse as a date makeslatest.jsonmalformed.revisionis the git commit SHA (40 lowercase hex). The revision body must carry the samerevisionandschemaHashaslatest.json.generationordering, snapshot paths;Authorizationheader;site && tokengate.publishedAtvscommittedAt.NODE_ENV=developmentit never swaps, so local files win. This is the only env read left in the SDK.schemaHash(cli/content.ts):deco contentreads.deco/schema.gen.jsonand writesschemaHash: sha256Hex(canonicalJson(JSON.parse(text)))into.deco/blocks.gen.ts, using the@decocms/blocks/protocolhelpers Studio already imports.schemaHash, and the SDK never swaps.00b083655ee7af02aa92dbff85e402857bb1e50c253a579dbab23498a7842c98(incli/content.test.ts).SnapshotgainsschemaHash?: string.committedAt(cli/content.ts, PO: stamp with commit time, not build time):deco contentwritescommittedAtinto.deco/blocks.gen.ts: the committer date of git HEAD in the root being built, ISO 8601 (git log -1 --format=%cI). No env is read.SnapshotgainscommittedAt?: string.Studio-side contract (for the Studio PR; the SDK only reads it):
latest.json.publishedAtmust be written with the current time on Publish, on "Make current"/rollback, and on Resync. A rollback then wins over bundles of commits made before it, and a deploy of a later commit wins over the rollback.Drafts on the CDN (
draftChanges.ts,content.ts):delivery.decocms.com/sites/<site>/drafts/<slug>.json@<version>, already admitted by.decocms.com.{ set, delete }, withsetanddeletedisjoint.format: 1is gone, and adeletelist may repeat a name.If-None-Matchwith the held ETag. Only a200(new body) or a304to that ETag is accepted; anything else isLOADER_FAILED, never production.<base>~<ETag>, so each save re-keys caches.Telemetry (
telemetry.ts):createCMS({ token })sends tohttps://otel.decocms.com/v1/<signal>withauthorization: Bearer <token>.sitelabelsservice.nameanddeco.site.tokenrequiressite:createCMS({ token })withoutsitethrows (PO createCMS surface, 2026-10-06).telemetry: { endpoint, headers }wins over the token.telemetry: falseis off.telemetry: { site, token }form is deleted.No env vars (PO: "do NEVER EVER use envvars"):
DECO_CONTENT_INTERVALintervalDECO_PREVIEW_API_DOMAINSpreview.draftHosts(default: v7's list)OTEL_EXPORTER_OTLP_ENDPOINT/_HEADERS,DECO_OTEL_HEADERS,DECO_OTEL_AUTH_TOKENtelemetry.endpoint/telemetry.headersOTEL_EXPORTER_OTLP_{METRICS,LOGS,TRACES}_ENDPOINT,DECO_OTEL_*_ENDPOINTOTEL_RESOURCE_ATTRIBUTES,OTEL_SERVICE_NAME, commit SHA variables (DECO_COMMIT_SHA,WORKERS_CI_COMMIT_SHA,CF_PAGES_COMMIT_SHA,VERCEL_GIT_COMMIT_SHA,GITHUB_SHA,RENDER_GIT_COMMIT,SOURCE_VERSION,COMMIT_SHA),VERCEL_ENV,NODE_ENV(the environment label)telemetry.resource(covers the service version)readEnv()helperpreview.draftHostsandtelemetry.resource.sitealone turns on hosted releases;token(withsite) turns on hosted telemetry.cms.forRevision(). The CMS reads only throughforRelease()andforDraft(pointer).NODE_ENV === "development"inremoteLoader.ts.Site token format (the SDK only forwards it; Studio signs it and the ingest verifies it, so there is no shared helper in this package). It's a JWS Compact token with EdDSA/Ed25519 and no expiry:
The edge checks four things:
crypto.subtle.verify("Ed25519", publicKey, sig, ASCII(seg0 "." seg1)).site,kidandiatpresent with those types, and norevoked:<kid>in the KV denylist. Akill:<site>key answers 403.Changes
src/v8/remoteLoader.ts: the rewrite above, plus a test-onlySymbol.for("decocms.blocks.test.deliveryOrigin")origin override (undocumented, not exported).src/v8/cms.ts:sitealone wraps the content;sitewith no token hash;tokenwarns as an option conflict;resolveInterval;preview.draftHosts.src/v8/draftChanges.ts:{ set, delete }only,parseDraftHosts, ETag/304 infetchDraftChanges, and the domains passed in as a param.src/v8/content.ts: ETag revalidation, shared in-flight reads, andisSnapshotacceptsschemaHashandcommittedAt.src/v8/telemetry.ts:resolveDestination(config, site, token),telemetry.resource, and every env read deleted.src/v8/identity.ts:readEnvdeleted.src/v8/types.ts:Snapshot.schemaHash?,Snapshot.committedAt?,TelemetryConfig { endpoint?, headers?, resource?, limits? },preview.draftHosts, and newsite/tokendocs.src/v8/cli/content.ts: writesschemaHashandcommittedAt.src/v8/cli/run.ts: thedeco publishsignpost names Studio's Publish/Resync.conformance/observability/*.ts: the doc snippets pass params, with notelemetry: { site, token }..agents/skills/deco-v7-to-v8-migration/reference/{gotchas,faststore}.md: these said the SDK reads env; they now say the site reads its own env and passes params.Tests
remoteLoader.test.ts(rewritten, 31 tests)committedAt→ CDN, an older commit built after a publish → CDN, a fallback loader'scommittedAt, an unparseablepublishedAtrefused; no network at boot; latest → revision → swap; no credentials; the first check downloads even content equal to the bundle; the same revision isn't downloaded again; rollback vialatest.jsonfollowed with no ordering; schema mismatch keeps the bundle or the last swap; a bundle withoutschemaHashnever fetches; 404/500/network errors keep memory; malformedlatest.jsonor revision body refused;NODE_ENV=developmentnever fetches; fallback loadersnoEnv.test.ts(new guardrail)packages/blocks/srcin any spelling (process.env,process?.env,globalThis.process?.env,{ env } = process,Bun.env,Deno.env,import.meta.env,.env[) except the oneNODE_ENVline; a self-test pins each spellingcli/content.test.tscommittedAtstamped from git HEAD's commit time (same commit → unchanged module, later commit → new stamp), no stamp and one line outside git,schemaHashwritten, the shared vector, formatting-independent, absent without a schema file, invalid JSON fails, a schema change rewrites the modulecms.test.tspreview.draftHosts(replaces the defaults, no env, validation), noDECO_CONTENT_INTERVAL, the token isn't in the keytelemetry.test.tsOTEL_*/DECO_OTEL_*reads,telemetry.resource, a resource default that ignores envdelivery,delivery.snippets,sdk,observability,guides,extra,cli)latest.json/revisionscontract (CD-1/3/4–10/12, HRI-6/8, new HRI-16..22 for the newer-wins scenarios (HRI-22: an older commit built after a publish loses) plus an SDK no-git/no-env guard, cli-05 checks nocommittedAtoutside git, H-3/6/12, HP-6/9/16, DP-1/4/6/7/11/13/14/14b/17, HD-2/8, AR-05–08/22/57, si-09/10, tel-03–07/33, mig-08/09). The env cases became "no env is read" cases. New snippet:preview.draftHostsbun run checkpasses: typecheck, biome and knip.bun run test(packages/blocks) passes: 1733 passed, 64 skipped.cli-11(deco schema --watch, which this PR doesn't touch) timed out once under full-suite load and passes on its own.OPEN items (smallest choice taken, each marked
// OPEN:in code)remoteLoader.ts): a process that already swapped a release in keeps it if a laterlatest.jsonturns out older than the bundle. Memory stays as it is, like the schema-mismatch case; it doesn't swap back to the bundle.remoteLoader.ts): acommittedAtthat doesn't parse as a date counts as missing (the oldest).remoteLoader.ts): the revision is validated as/^[0-9a-f]{40}$/(a SHA-1 commit). SHA-256 object-format repos aren't accepted.telemetry.ts): per-signal OTLP URLs (v7OTEL_EXPORTER_OTLP_<SIGNAL>_ENDPOINT/DECO_OTEL_<SIGNAL>_ENDPOINT) have no param. Every signal goes to<endpoint>/v1/<signal>. No site in~/code/next-majoruses them.telemetry.ts):deployment.environment.namedefaults to"production"andtelemetry.resourceoverrides it. It no longer readsNODE_ENV, because the only allowedNODE_ENVread is the dev rule.content.ts): the draft view's revision is<base>~<response ETag>, falling back to the pointer's<version>when the response has no ETag.preview.draftHosts: []means no draft host at all. Only an omitted list gives the defaults.schemaHash, so that check doesn't swap. Before, it downloaded anyway.Follow-ups (not in this PR)
deco-sites/docs-tanstack): hosted-releases-internals should state the newer-wins rule (HRI-16..22, by commit time) in place of "no comparison with the bundle"; delete the env tables and channel pages, then documentpreview.draftHosts,telemetry.resourceand the newsite/tokenmeaning. The conformance claim IDs above are what those pages should state.publishedAt= now on Publish, Make current/rollback and Resync.8.1.0-next.7is published (needs 2FA).🤖 Generated with Claude Code