Repository navigation
docs(rfc-0046): design @cloudhal/release-verify and deploy --release (#227) - #230
Merged
Merged
Conversation
Madraka
added a commit
that referenced
this pull request
Nov 14, 2026
- Replace the theme/skills hash refusals, which compared placeholders
(v0.9.0 has empty lists and sha256("")), with a defined
"unmodified starter" check: template files byte for byte, no extra
src/, public/ or config files, package.json dependencies, and
cloudhal.json build-input keys from one table that a test keeps equal
to what buildSite and prepareAdminWorker read. comments.available and
media.transform (unless features.mediaTransformVar) count as build
inputs.
- Entry-path safety: an entry-path code (empty, . and .. segments,
backslash, control characters, case-fold duplicates, length), a
linear scanner (rule 13), and extraction into a fresh directory with
resolve checks, exclusive create, and module types from the manifest.
- First publish: a deprecated 0.0.0-placeholder.0, then the trusted
publisher, so the first real version has provenance; the fixed group
in .changeset/config.json.
- Attestation: an availability check first, exact flags, --bundle with
the Sigstore asset (now in releaseAssetUrls and --from), any
non-zero exit counts as failed, and it runs before extraction. The
CLI refuses without the second factor unless --allow-single-factor
is given.
- Revocation: deploy --release waits for #226's signed keys.json,
fetched on every run with a hard refusal; the package can ship first.
- Runtime dependencies stated honestly: @noble/hashes, ajv runtime
helpers and ajv-formats, guarded by a test.
- missingScopes points to §3; key-expired moves to the open points
(#226 decision 6); invariant-5 tests use the synthetic bundle; a
release-unavailable refusal when there is no public bundle.
Refs #227
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
…227) Addendum §11 before building #227: - 11a: the published verifier: runtimes (WebCrypto Ed25519, DecompressionStream, incremental SHA-256: Workers' DigestStream, else @noble/hashes, the one audited runtime dependency), generated standalone validators for the two roots, PINNED_KEYS with reduce-only trust, the two-pass tarball API (digest first, then entries verified one by one), stable error codes, missingScopes, the Node-only attestation subpath, tests on the real v0.9.0 statement/signature/manifest plus an opt-in full-tarball test in its own CI job (the 7.2 MB tarball is never committed), and the owner's first publish and trusted-publisher entry. - 11b: deploy --release: the CLI's own version only, verified bytes deployed through wrangler with no_bundle, refusals for anything the bundle bakes in (theme, settings, design, block types, skills, src/, attached or multi-site installs, Workers Builds), attestation when gh is available, --from <dir> offline, --dry-run. - 11c: four open points with defaults. Refs #227 Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- Replace the theme/skills hash refusals, which compared placeholders
(v0.9.0 has empty lists and sha256("")), with a defined
"unmodified starter" check: template files byte for byte, no extra
src/, public/ or config files, package.json dependencies, and
cloudhal.json build-input keys from one table that a test keeps equal
to what buildSite and prepareAdminWorker read. comments.available and
media.transform (unless features.mediaTransformVar) count as build
inputs.
- Entry-path safety: an entry-path code (empty, . and .. segments,
backslash, control characters, case-fold duplicates, length), a
linear scanner (rule 13), and extraction into a fresh directory with
resolve checks, exclusive create, and module types from the manifest.
- First publish: a deprecated 0.0.0-placeholder.0, then the trusted
publisher, so the first real version has provenance; the fixed group
in .changeset/config.json.
- Attestation: an availability check first, exact flags, --bundle with
the Sigstore asset (now in releaseAssetUrls and --from), any
non-zero exit counts as failed, and it runs before extraction. The
CLI refuses without the second factor unless --allow-single-factor
is given.
- Revocation: deploy --release waits for #226's signed keys.json,
fetched on every run with a hard refusal; the package can ship first.
- Runtime dependencies stated honestly: @noble/hashes, ajv runtime
helpers and ajv-formats, guarded by a test.
- missingScopes points to §3; key-expired moves to the open points
(#226 decision 6); invariant-5 tests use the synthetic bundle; a
release-unavailable refusal when there is no public bundle.
Refs #227
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
Madraka
force-pushed
the
claude/rfc46-release-verify
branch
from
November 14, 2026 08:08
9b9258b to
89fa0aa
Compare
- The unmodified-starter check no longer blocks upgrades and rollbacks.
- Files are compared with the starter that created the project,
through a committed cloudhal.starter.json written by init (a new
starter-lock root schema), or, for older projects, through a frozen
starter-history.json generated from the tags.
- Config build keys are compared with the target, which is the
running CLI.
- Dependency versions are ignored and only their names are compared,
since the bundle is self-contained.
- The real 0.9.0 to 0.10.0 case and a rollback are in the test list.
- Any file outside the base starter, minus a defined ignore list, is
refused. This catches a root skills/ directory.
- The config table now has deploy, runtime and build classes, with the
runtime overrides stated (CLOUDHAL_SITE_ID, dataDir unused by the
cloudflare target, analytics vars).
- The drift guards test the builds themselves: a static guard over
astro.config.mjs's project keys, override tests, and admin outputs
byte-identical across deploy keys.
- The gh probe requires gh 2.68.0 or later (source-ref and
source-digest) and every flag in --help; an older gh is
"unavailable", which --allow-single-factor covers.
- Open point 7: --from refuses a keys.json older than 30 days by its
signed issuedAt (needs #226's format).
Refs #227
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
The owner chose the simpler rule: --release installs the official standard theme of this CLI's version, and it does not build the project. - It refuses when src/, public/, astro.config.mjs or a root skills/ differ from this CLI version's own starter. Files are compared byte for byte after CRLF is normalised to LF, and both missing and extra files count. The refusal lists the files. - --discard-local-theme deploys the official theme anyway and lists every file that is not deployed. It is never implied. - A project whose starter files come from an older version needs the flag. `cloudhal starter sync` is named for later and is out of scope. - It still refuses on build-class cloudhal.json keys, which the flag does not cover. The deploy/runtime/build table and its drift guards stay. - It checks for a minimum wrangler version: the one in manifest.toolchain.wrangler, read from the installed wrangler. - Removed: cloudhal.starter.json, starter-history.json, the ignore list and the base-version logic. Refs #227 Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
- Skip OS metadata files (.DS_Store, Thumbs.db, desktop.ini) by exact
basename under the compared paths; test public/fonts/v1/.DS_Store.
- The key table has no fallback class. search, comments.rateLimit and
the analytics budget, retention and rate-limit keys are deploy. A type
mapped over ProjectConfig (+ media) and its mixed sub-keys, plus tests
over OWN_KEYS and the integration's option sub-keys, fail on any key
left unclassed.
- Root build inputs, checked against the installed Astro 7.4.7 and Vite
8.3.5:
- tsconfig.json is compared with the starter;
- jsconfig.json, every PostCSS config Vite loads, and a package.json
postcss key must not exist;
- vite.config.* is not loaded (Astro passes configFile: false);
- .env* cannot bake anything into the starter's code.
Refs #227
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Madraka <[email protected]>
…#230) Vite 8.3.5's resolvePostcssConfig searches upward from the project root and stops at searchForWorkspaceRoot: the nearest pnpm-workspace.yaml, lerna.json, package.json with workspaces, or deno workspace. Without one it falls back to the nearest package.json. The --release check now searches the same directories, so a monorepo's root PostCSS config cannot be dropped silently. There are test cases for a pnpm workspace root postcss.config.cjs, an intermediate package.json postcss key, and a config above the stop directory. Refs #227 Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Madraka <[email protected]>
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.
What
This is a docs-only design addendum to RFC-0046, new §11, for the two consumer tools #227 asks for. They are written up here first, and the code follows once the owner accepts this. No code, contract or package changes.
@cloudhal/release-verify: the shared reference implementation of §2's steps and §7's limits.@noble/hashesfor incremental SHA-256 where Workers'DigestStreamis absent;ajv-formatsdate-time check, because the generated validators import them;Ajvat runtime, and a test fails on any other import.PINNED_KEYSwith reduce-only trust.entry-pathsafety: a linear scanner refuses..,., empty segments, backslashes, control characters, overlong paths and case-fold duplicates.missingScopes: exact matching plus §3's one rule, that admin covers edit.releaseAssetUrls: all five assets, including the Sigstore bundle.--bundle; any non-zero exit counts asfailed.cloudhal deploy --release:It deploys the CLI's own version only.
It refuses with
release-unavailablewhen there is no published bundle, and never falls back to a local build.Revocation: it ships only after Release signing phase 2: signed keys.json and yanked.json, key expiry, KMS custody (RFC-0046 §9) #226's signed
keys.json, which it fetches on every run, refusing hard when the fetch fails.Order: verify, then the tarball digest, then the attestation, then extraction, then wrangler.
The second factor is required unless
--allow-single-factoris given.Extraction goes into a fresh directory, with resolve checks and exclusive create.
Wrangler runs with
no_bundleand module types taken from the manifest.What
--releasemeans (option B, the owner's choice): it installs the official standard theme of this CLI's version. It does not build the project.Theme check: these are compared byte for byte with this CLI version's own starter:
src/,public/,astro.config.mjsandtsconfig.json;skills/,jsconfig.json, any PostCSS config, and apostcsskey inpackage.json.The list was checked against the installed Astro 7.4.7 and Vite 8.3.5.
vite.config.*is not loaded, because Astro passesconfigFile: false..DS_Store,Thumbs.dbanddesktop.iniare skipped by exact name.--discard-local-theme: deploys the official theme anyway and lists what is not deployed. The flag is never implied.Older projects: a project whose starter files come from an older version needs the flag.
cloudhal starter syncis named for later and is out of scope.Config: build-class
cloudhal.jsonkeys are refused, and the flag does not cover them. These arecomments.available, andmedia.transformunlessfeatures.mediaTransformVaris true.searchandcomments.rateLimitaredeploy.ProjectConfigand tests overOWN_KEYSand the integration's option sub-keys fail on any key left unclassed.Wrangler: the installed wrangler must be at least
manifest.toolchain.wrangler. The starter's^4.158.0range is not trusted.The gh probe requires gh 2.68.0 or later, the first version with
--source-ref/--source-digest, plus every flag in--help. An older gh counts asunavailable, which--allow-single-factorcovers.Open points (defaults in bold)
@noble/hashesfor SHA-256 outside Workers (audited, zero-dependency, pinned exactly). Workers use the nativecrypto.DigestStream. Our own ~120-line SHA-256 would be new cryptographic code in the trust package.--releasetakes only the CLI's own version, until §10's runtime configuration lands.ghis unavailable, the CLI refuses.--allow-single-factorcontinues on the release key alone and says so; it is never implied. A failed attestation is always a refusal.CLOUDHAL_RELEASE_ASSETS), in its own CI job that downloadsv0.9.0. The 7.2 MB tarball is never committed. The committed fixtures are the real statement, signature and manifest.notAftermeans for releases a key already signed is Release signing phase 2: signed keys.json and yanked.json, key expiry, KMS custody (RFC-0046 §9) #226's decision 6. Until then there is nokey-expiredcode, andPINNED_KEYScarries no dates.theme/skillsvalues in later manifests are not part of this change. Consumers ignore the placeholders.--from, akeys.jsonmust be fresh. It is refused when its signedissuedAtis more than 30 days old, so an old list cannot replay trust from before a revocation. This needs Release signing phase 2: signed keys.json and yanked.json, key expiry, KMS custody (RFC-0046 §9) #226's format to carryissuedAt; online runs always fetch the current list.Revised done-when for #227
The issue's "
cloudhal deploy --release 0.9.x" can't be met: CLI 0.9.x has no--release, and--releasetakes only the CLI's own version. Proposed instead:@cloudhal/release-verifyis published, with provenance, fromrelease.yml. Its tests pass on the realv0.9.0statement, signature and manifest: a flipped byte, a wrong key, a revoked key, a wrong version and a wrong channel are all refused. The opt-in CI job verifies the realv0.9.0tarball.cloudhal deploy --releasein the first CLI version that ships it deploys that version's verified bundle. It refuses:release-unavailable);src/,public/,astro.config.mjsorskills/differ from the starter, unless--discard-local-themeis given;keys.jsonthat can't be fetched or doesn't verify.It depends on Release signing phase 2: signed keys.json and yanked.json, key expiry, KMS custody (RFC-0046 §9) #226's
keys.json.Owner steps when the code lands
@cloudhal/[email protected](README only,--tag placeholder), thennpm deprecateit.release.yml, thenpm-publishenvironment). The first real version then comes fromrelease.ymlwith--provenance. A version published by hand would have none, andplanPublishwould skip it afterwards.fixedgroup in.changeset/config.json(ADR-0013); that edit is part of the code PR.Notes
missingScopescites came in with fix(release): require workers-scripts.admin in the release manifest (#225) #229. This branch is rebased ontomainafter fix(release): require workers-scripts.admin in the release manifest (#225) #229 merged (c41a4d7).Refs #227
🤖 Generated with Claude Code