From 3ffdf291a5530a1905816ab5870a1344554414d7 Mon Sep 17 00:00:00 2001 From: Madraka <8898640+Madraka@users.noreply.github.com> Date: Sat, 14 Nov 2026 15:15:54 +0300 Subject: [PATCH 1/2] =?UTF-8?q?feat(release-verify):=20add=20@cloudhal/rel?= =?UTF-8?q?ease-verify=20(RFC-0046=20=C2=A711a,=20#227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The one verifier of Cloudhal release bundles, Worker-safe: - verifyRelease runs RFC-0046 §2's six steps in order (sizes, Ed25519 with a trusted key, the request binding, both precompiled validators and the invariants, the manifest's digest and listings, the tarball's digest). entries() then streams the tarball a second time and yields each entry only once its path is safe and its size and SHA-256 match its listing. - entry-path refuses empty, . and .. segments, backslashes, control characters, overlong paths and case-fold duplicates. It is a linear scanner with no regex (rule 13). - 19 stable error codes; PINNED_KEYS (frozen, equal to the key doc, trust can only be reduced); missingScopes with §3's admin-covers-edit rule; releaseAssetUrls for the five assets. - ./attestation (Node only): probes for gh 2.68.0 or newer with every flag and a signed-in session, else "unavailable"; runs the exact §11a command with --bundle; any non-zero exit, signal or timeout is "failed". - Runtime dependencies: @noble/hashes (when there is no DigestStream), plus the ajv runtime helpers and ajv-formats that the generated validators import. The validators come from the package's own gen:ts (after core-contracts', via turbo ^gen:ts) and are drift-checked by a test, by the CI contract-drift job and by pnpm drift. - Tests: the committed real v0.9.0 statement, signature and manifest, and synthetic bundles signed with a throwaway key. The opt-in real-tarball test runs in a new CI job that downloads v0.9.0. - The package joins the fixed group (PACKAGES, tsconfig.build.json, .changeset/config.json), with a changeset. - scripts/release-bundle/verify.ts runs it for signed bundles. Refs #227 Co-Authored-By: Claude Opus 5.5 Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com> --- .changeset/config.json | 1 + .changeset/release-verify-package.md | 21 + .github/workflows/ci.yml | 26 +- AGENTS.md | 1 + biome.json | 3 +- docs/security/release-signing-keys.md | 4 + package.json | 2 +- packages/cli/test/release-plan.test.ts | 2 +- packages/release-verify/README.md | 59 + packages/release-verify/package.json | 58 + .../release-verify/scripts/gen-validators.ts | 113 + packages/release-verify/src/attestation.ts | 178 + packages/release-verify/src/crypto.ts | 73 + packages/release-verify/src/documents.ts | 160 + packages/release-verify/src/errors.ts | 41 + .../src/generated/release-manifest.ts | 237 + .../src/generated/release-statement.ts | 66 + .../src/generated/validators.d.ts | 18 + .../src/generated/validators.js | 4324 ++++++++++++ packages/release-verify/src/index.ts | 31 + packages/release-verify/src/keys.ts | 54 + packages/release-verify/src/limits.ts | 30 + packages/release-verify/src/paths.ts | 57 + packages/release-verify/src/scopes.ts | 21 + packages/release-verify/src/tar.ts | 278 + packages/release-verify/src/urls.ts | 37 + packages/release-verify/src/verify.ts | 157 + .../release-verify/test/attestation.test.ts | 174 + packages/release-verify/test/crypto.test.ts | 111 + .../test/fixtures/v0.9.0/.gitattributes | 2 + .../test/fixtures/v0.9.0/manifest.json | 5933 +++++++++++++++++ .../fixtures/v0.9.0/release-statement.json | 24 + .../fixtures/v0.9.0/release-statement.sig | 1 + packages/release-verify/test/helpers.ts | 170 + packages/release-verify/test/package.test.ts | 154 + .../release-verify/test/real-tarball.test.ts | 44 + packages/release-verify/test/tar.test.ts | 90 + .../release-verify/test/verify-real.test.ts | 94 + .../test/verify-synthetic.test.ts | 240 + packages/release-verify/tsconfig.json | 9 + packages/release-verify/vitest.config.ts | 11 + pnpm-lock.yaml | 31 + pnpm-workspace.yaml | 1 + scripts/build-packages.ts | 6 +- scripts/code-size/measure.ts | 1 + scripts/release-bundle/verify.ts | 39 + tsconfig.build.json | 1 + turbo.json | 1 + 48 files changed, 13184 insertions(+), 5 deletions(-) create mode 100644 .changeset/release-verify-package.md create mode 100644 packages/release-verify/README.md create mode 100644 packages/release-verify/package.json create mode 100644 packages/release-verify/scripts/gen-validators.ts create mode 100644 packages/release-verify/src/attestation.ts create mode 100644 packages/release-verify/src/crypto.ts create mode 100644 packages/release-verify/src/documents.ts create mode 100644 packages/release-verify/src/errors.ts create mode 100644 packages/release-verify/src/generated/release-manifest.ts create mode 100644 packages/release-verify/src/generated/release-statement.ts create mode 100644 packages/release-verify/src/generated/validators.d.ts create mode 100644 packages/release-verify/src/generated/validators.js create mode 100644 packages/release-verify/src/index.ts create mode 100644 packages/release-verify/src/keys.ts create mode 100644 packages/release-verify/src/limits.ts create mode 100644 packages/release-verify/src/paths.ts create mode 100644 packages/release-verify/src/scopes.ts create mode 100644 packages/release-verify/src/tar.ts create mode 100644 packages/release-verify/src/urls.ts create mode 100644 packages/release-verify/src/verify.ts create mode 100644 packages/release-verify/test/attestation.test.ts create mode 100644 packages/release-verify/test/crypto.test.ts create mode 100644 packages/release-verify/test/fixtures/v0.9.0/.gitattributes create mode 100644 packages/release-verify/test/fixtures/v0.9.0/manifest.json create mode 100644 packages/release-verify/test/fixtures/v0.9.0/release-statement.json create mode 100644 packages/release-verify/test/fixtures/v0.9.0/release-statement.sig create mode 100644 packages/release-verify/test/helpers.ts create mode 100644 packages/release-verify/test/package.test.ts create mode 100644 packages/release-verify/test/real-tarball.test.ts create mode 100644 packages/release-verify/test/tar.test.ts create mode 100644 packages/release-verify/test/verify-real.test.ts create mode 100644 packages/release-verify/test/verify-synthetic.test.ts create mode 100644 packages/release-verify/tsconfig.json create mode 100644 packages/release-verify/vitest.config.ts diff --git a/.changeset/config.json b/.changeset/config.json index 0083cccd..5492b5e6 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -5,6 +5,7 @@ "fixed": [ [ "@cloudhal/core-contracts", + "@cloudhal/release-verify", "@cloudhal/storage", "@cloudhal/analytics", "@cloudhal/core-ts", diff --git a/.changeset/release-verify-package.md b/.changeset/release-verify-package.md new file mode 100644 index 00000000..c75f86f6 --- /dev/null +++ b/.changeset/release-verify-package.md @@ -0,0 +1,21 @@ +--- +"@cloudhal/release-verify": patch +--- + +New package `@cloudhal/release-verify` (RFC-0046 §11a). It is the one verifier of Cloudhal release bundles, and it is Worker-safe. + +`verifyRelease` runs these checks in order, and refuses on the first failure: +- the Ed25519 signature on `release-statement.json`, against the pinned release keys; +- that the statement is for the version and channel you asked for; +- both precompiled validators; +- every digest and §7 limit; +- the tarball, in two passes. + +The tarball is never held whole. The first pass checks its digest, and the second yields each entry only once it is verified. Unsafe entry paths (`..`, `.`, empty segments, backslashes, control characters, case-fold duplicates) are refused with `entry-path`. Every refusal carries a stable `code`. + +The package also exports: +- `missingScopes`, which applies §3's rule that `workers-scripts.admin` covers `workers-scripts.edit`; +- `releaseAssetUrls`; +- `PINNED_KEYS`, which can only be reduced. + +The Node-only subpath `@cloudhal/release-verify/attestation` checks the GitHub build attestation with `gh` 2.68.0 or newer. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e668f28..469ba5f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,31 @@ jobs: cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm --filter @cloudhal/core-contracts gen + - run: pnpm --filter @cloudhal/release-verify gen:ts - name: Generated code must match the committed code run: | - git diff --exit-code -- packages/core-contracts/src/generated crates/cloudhal-schema/src/generated \ + git diff --exit-code -- packages/core-contracts/src/generated packages/release-verify/src/generated crates/cloudhal-schema/src/generated \ || { echo "::error::Generated types are out of date. Run 'pnpm gen' and commit."; exit 1; } + + release-verify-v0-9-0: + # RFC-0046 §11a: @cloudhal/release-verify against the real, immutable v0.9.0 assets, tarball + # included. Ordinary test runs skip this test (no network); only this job downloads them. + name: Release verifier on the real v0.9.0 bundle + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Download the v0.9.0 release assets + env: + GH_TOKEN: ${{ github.token }} + run: gh release download v0.9.0 --repo cloudhal-dev/cloudhal --dir "$RUNNER_TEMP/v0.9.0" + - name: Verify them (statement, manifest, every tarball entry) + working-directory: packages/release-verify + env: + CLOUDHAL_RELEASE_ASSETS: ${{ runner.temp }}/v0.9.0 + run: pnpm exec vitest run test/real-tarball.test.ts diff --git a/AGENTS.md b/AGENTS.md index 68e6fc1a..84400864 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,7 @@ node scripts/release-bundle.ts build|verify … # RFC-0046 release bundle: det - `packages/core-contracts/schemas/*.schema.json` — **the only hand-edited contract source**. `common.schema.json` holds shared `$defs`. - `packages/core-contracts/src/generated/`, `crates/cloudhal-schema/src/generated/` — generated; never edit. - `packages/core-contracts/fixtures/{valid,invalid}//` — golden fixtures used by both languages. +- `packages/release-verify` — RFC-0046 §11a's published verifier of release bundles (Worker-safe: WebCrypto Ed25519, `DecompressionStream`, `DigestStream` else `@noble/hashes`): `verifyRelease` (§2's six steps, the two-pass tarball, `entry-path`), stable error codes, `PINNED_KEYS` (kept equal to the key doc), `missingScopes`; `./attestation` is the Node-only `gh attestation verify` check. Its validators for the two release roots are generated by its own `gen:ts` (after core-contracts'); `scripts/release-bundle/verify.ts` calls it for signed bundles. - `packages/storage` — `SqlDriver`/`BlobStore` + adapters (`sqlite`, `d1`, `do-sqlite`, `fs`, `r2`; `SqlBlobStore`, `LayeredBlobStore`, `FetcherBlobStore` at the root) + migrations (`migrations/*.ts`, plain SQL). Conformance suites in `src/testing.ts`. - `packages/core-ts` — `SiteGraphStore` (CAS on `rev`, snapshots, diff, publish, rollback). Behavioural suite in `src/testing.ts` runs on sqlite and Miniflare D1. `./local` opens the offline store; `seedStarterSite` seeds a demo. `./surfaces` is RFC-0020's generative UI: the catalog (primitives + block types), eval-free surface checks, bindings, repairs, JSON Patch (`patchPage`), `catalogPrompt`; `./interop` converts A2UI v0.9 and json-render to and from surfaces; `./design` is RFC-0021's DESIGN.md lint and guidance. `./fields` is RFC-0041's typed custom fields (definition and value checks, `resolveFields`, `formatFieldValue`, the agents' JSON Schema, the conversion table); `src/fields-operations/` runs rename/retype/localize/remove. `search.ts` is RFC-0019's FTS5 site search (index written with the publish batch; articles, pages, terms and authors, RFC-0029); `search-provider.ts` is RFC-0029's `SearchProvider` (`fts5`, `memory`, `withFallback`), set with `SiteGraphStoreOptions.search`; `search-ai.ts` is the experimental Cloudflare AI Search provider (`hybridProvider`, `searchProviderFromEnv`, `cloudhal.json` `search`, docs/guides/search.md). - `packages/schedules` — RFC-0008: time model (only `temporal-polyfill/implementation`, `now` injected, never `Temporal.Now`), RRULE subset, release model (`rl1:`), automation/run state, taint and budgets, `runDue`; `./sql` is `SqlScheduleStore`, `./engine` the Temporal-free part core-ts and the site Worker use. Worker-safe; conformance suites in `src/testing.ts` on sqlite and Miniflare D1. diff --git a/biome.json b/biome.json index 7bd5fffa..48d0834d 100644 --- a/biome.json +++ b/biome.json @@ -16,7 +16,8 @@ "!**/.turbo", "!**/node_modules", "!apps/studio/src/assets/geo", - "!packages/cli/test/fixtures/theme-*" + "!packages/cli/test/fixtures/theme-*", + "!packages/release-verify/test/fixtures/v0.9.0" ] }, "formatter": { diff --git a/docs/security/release-signing-keys.md b/docs/security/release-signing-keys.md index 0fb0306b..be7eee58 100644 --- a/docs/security/release-signing-keys.md +++ b/docs/security/release-signing-keys.md @@ -49,6 +49,10 @@ Releases are immutable and can never gain an asset, so a release signed by a rev 4. Set the old key's status to `retiring` in this file. Keep it pinned by consumers for as long as releases it signed are installed. 5. A compromised key goes straight to `revoked` here, with an advisory under [SECURITY.md](../../SECURITY.md), and the next release is signed with the new key. +## Verifying a release + +[`@cloudhal/release-verify`](../../packages/release-verify/README.md) does all of the steps below in one call, on Node, Workers or a browser. Its `PINNED_KEYS` is kept equal to the table above, and its `./attestation` subpath runs step 3. To check by hand instead, follow these steps. + ## Verifying a release by hand Download the release's assets into an empty directory, for example with `gh release download v --repo cloudhal-dev/cloudhal --dir `. diff --git a/package.json b/package.json index 96d6c9c5..b3e07a53 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "typecheck": "turbo run typecheck", "test": "turbo run test", "gen": "turbo run gen:ts gen:rs", - "drift": "pnpm gen && git diff --exit-code -- packages/core-contracts/src/generated crates/cloudhal-schema/src/generated apps/studio/src/i18n/generated", + "drift": "pnpm gen && git diff --exit-code -- packages/core-contracts/src/generated packages/release-verify/src/generated crates/cloudhal-schema/src/generated apps/studio/src/i18n/generated", "cargo:test": "cargo test --workspace --locked", "cargo:clippy": "cargo clippy --workspace --all-targets --locked -- -D warnings", "changeset": "changeset", diff --git a/packages/cli/test/release-plan.test.ts b/packages/cli/test/release-plan.test.ts index 08280058..f15e09d5 100644 --- a/packages/cli/test/release-plan.test.ts +++ b/packages/cli/test/release-plan.test.ts @@ -16,7 +16,7 @@ const pkg = (name: string, version: string): Pkg => ({ dir: name, name, version describe("release plan", () => { it("the repo's publishable packages share one version (changesets fixed group)", () => { const pkgs = readPackages(ROOT); - expect(pkgs).toHaveLength(13); + expect(pkgs).toHaveLength(14); expect(sharedVersion(pkgs)).toMatch(/^\d+\.\d+\.\d+$/); }); diff --git a/packages/release-verify/README.md b/packages/release-verify/README.md new file mode 100644 index 00000000..f3a8f104 --- /dev/null +++ b/packages/release-verify/README.md @@ -0,0 +1,59 @@ +# @cloudhal/release-verify + +Verifies a [Cloudhal](https://cloudhal.dev) release bundle before you deploy it. It follows [RFC-0046](https://github.com/cloudhal-dev/cloudhal/blob/main/docs/rfcs/0046-release-bundles.md) §2, §7 and §11a: hosts, tools and the `cloudhal` CLI share this one verifier. It runs on Workers, Node 24 and browsers. + +```ts +import { releaseAssetUrls, verifyRelease } from "@cloudhal/release-verify"; + +const release = await verifyRelease({ + request: { version: "0.9.0" }, // what you asked for: a signed older release is refused + statement, signature, manifest, // the exact downloaded bytes (Uint8Array) + tarball: () => fetchTarballStream(), // opened twice: digest first, then entries + planVersion: 1, // the highest plan version your deployer supports +}); +for await (const entry of release.entries()) { + // { worker, kind, path, type?, bytes }: verified against the signed manifest +} +// Activate the deployment only after the loop has finished without an error. +``` + +## What it checks + +1. **Sizes.** The statement is at most 4096 bytes, and the signature is exactly 64. +2. **Signature.** Ed25519 over the exact statement bytes, with a trusted key for `signing.keyId`. +3. **Request.** The statement is for the requested version and channel. +4. **Statement.** Its schema, through the precompiled validator, plus RFC-0046 §2 invariants 1 to 3 and 6. +5. **Manifest.** + - Its size and SHA-256 equal the statement's. + - Its schema and invariants 4 and 5. + - The `release-statement.sig` signature form. + - `planVersion`. + - Every listed path, and §7's limits. +6. **Tarball.** Its size and SHA-256 equal the statement's. + +Then `entries()` re-reads the tarball. It refuses: +- an entry the manifest does not list, or one whose size or SHA-256 differs from its listing; +- a missing entry; +- anything outside §7's tar format; +- an unsafe path: `..`, `.`, an empty segment, a backslash, a control character, more than 512 bytes, or two paths that collide after case folding. + +There is no soft-fail mode. Every refusal is a `ReleaseVerifyError` with a stable `code` (`ERROR_CODES`). + +## Keys + +`PINNED_KEYS` holds the keys of [docs/security/release-signing-keys.md](https://github.com/cloudhal-dev/cloudhal/blob/main/docs/security/release-signing-keys.md) as they were when this version was published. It cannot learn about later revocations by itself. + +You can only reduce trust: pass `revoked: [""]` to withdraw a key. Passing your own `keys` map replaces the table, so treat that as a reviewed change. + +## The second factor + +The Node-only subpath `@cloudhal/release-verify/attestation` checks the GitHub build-provenance attestation of the tarball, the manifest and the statement. It needs `gh` 2.68.0 or newer, signed in. It returns `verified` or `failed`, or `unavailable` when `gh` is missing, too old, or signed out. + +```ts +import { verifyAttestation } from "@cloudhal/release-verify/attestation"; +const r = await verifyAttestation({ dir, version: "0.9.0", commit: release.statement.commit }); +``` + +## Scopes + +`missingScopes(manifest, granted)` compares Cloudflare scope ids exactly, with one exception: `workers-scripts.admin` covers `workers-scripts.edit`, which manifests up to v0.9.0 name (RFC-0046 §3). diff --git a/packages/release-verify/package.json b/packages/release-verify/package.json new file mode 100644 index 00000000..4449a626 --- /dev/null +++ b/packages/release-verify/package.json @@ -0,0 +1,58 @@ +{ + "name": "@cloudhal/release-verify", + "version": "0.9.0", + "description": "Verifies a Cloudhal release bundle (RFC-0046 §2, §7, §11a): the Ed25519 release statement against pinned keys, the manifest, and every part of the tarball. Worker-safe; the attestation check is a Node-only subpath.", + "license": "Apache-2.0", + "homepage": "https://cloudhal.dev", + "repository": { + "type": "git", + "url": "git+https://github.com/cloudhal-dev/cloudhal.git", + "directory": "packages/release-verify" + }, + "bugs": { + "url": "https://github.com/cloudhal-dev/cloudhal/issues" + }, + "type": "module", + "exports": { + ".": "./src/index.ts", + "./attestation": "./src/attestation.ts" + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "gen:ts": "node scripts/gen-validators.ts", + "typecheck": "tsc --noEmit -p tsconfig.json", + "test": "vitest run", + "check": "pnpm typecheck && pnpm test" + }, + "dependencies": { + "@noble/hashes": "catalog:", + "ajv": "catalog:", + "ajv-formats": "catalog:" + }, + "devDependencies": { + "@cloudflare/workers-types": "catalog:", + "@cloudhal/core-contracts": "workspace:*", + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "engines": { + "node": ">=24" + }, + "publishConfig": { + "access": "public", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "default": "./dist/src/index.js" + }, + "./attestation": { + "types": "./dist/src/attestation.d.ts", + "default": "./dist/src/attestation.js" + } + } + } +} diff --git a/packages/release-verify/scripts/gen-validators.ts b/packages/release-verify/scripts/gen-validators.ts new file mode 100644 index 00000000..3f519611 --- /dev/null +++ b/packages/release-verify/scripts/gen-validators.ts @@ -0,0 +1,113 @@ +/** + * Writes this package's generated files (RFC-0046 §11a) from the core-contracts sources: + * + * src/generated/validators.js ajv standalone validators for exactly the two release roots + * src/generated/validators.d.ts + * src/generated/release-manifest.ts, release-statement.ts the generated contract types + * + * The package never depends on `@cloudhal/core-contracts` at runtime, so a Worker deployer does + * not pull in every validator; the validators import only ajv's runtime helpers and the + * `date-time` format of ajv-formats (test/generated.test.ts). Run by `pnpm gen` (turbo `gen:ts`, + * after core-contracts'); test/generated.test.ts fails when the committed files are stale. + * + * node scripts/gen-validators.ts # write + */ +import { readFileSync } from "node:fs"; +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { schemas } from "@cloudhal/core-contracts"; +import { Ajv2020 } from "ajv/dist/2020.js"; +import standaloneModule from "ajv/dist/standalone/index.js"; +import addFormatsModule from "ajv-formats"; + +const addFormats = addFormatsModule.default; +const standaloneCode = standaloneModule.default; + +const here = path.dirname(fileURLToPath(import.meta.url)); +export const OUT_DIR = path.resolve(here, "../src/generated"); +const CONTRACTS_GENERATED = path.resolve(here, "../../core-contracts/src/generated"); + +const BANNER = `/* eslint-disable */ +/* biome-ignore-all lint: generated */ +/** + * GENERATED FILE — DO NOT EDIT. + * Source: packages/core-contracts/schemas/release-{manifest,statement}.schema.json + * (run \`pnpm gen\`; packages/release-verify/scripts/gen-validators.ts) + */ +`; + +const ROOTS = { + validateReleaseManifest: "release-manifest", + validateReleaseStatement: "release-statement", +} as const; + +/** + * ajv's standalone output uses `require()` for its runtime helpers even in ESM mode: rewrite those + * into static imports with explicit `.js` extensions (the same transform as core-contracts'). + */ +function toEsm(src: string): string { + const imports: string[] = []; + let n = 0; + const body = src.replace(/require\("([^"]+)"\)/g, (_m, spec: string) => { + const id = `__req${n++}`; + const withExt = /\.[cm]?js$/.test(spec) ? spec : `${spec}.js`; + imports.push(`import ${id} from "${withExt}";`); + return id; + }); + if (/\brequire\(/.test(body)) + throw new Error("gen-validators: unhandled require() in ajv standalone output"); + const interop = body.replace(/\b(__req\d+)\.default\b/g, "($1.default ?? $1)"); + return `${imports.join("\n")}\n${interop}`; +} + +/** The generated files, by name, as they must be committed. */ +export function generate(): Map { + const ajv = new Ajv2020({ + strict: true, + allErrors: true, + code: { source: true, esm: true, lines: true }, + }); + addFormats(ajv); + const exportsMap: Record = {}; + for (const [fn, name] of Object.entries(ROOTS)) { + const schema = schemas[name] as { $id: string }; + ajv.addSchema(schema, name); + exportsMap[fn] = schema.$id; + } + const out = new Map(); + out.set("validators.js", `${BANNER}${toEsm(standaloneCode(ajv, exportsMap))}\n`); + out.set( + "validators.d.ts", + [ + BANNER, + 'import type { ReleaseManifest } from "./release-manifest.ts";', + 'import type { ReleaseStatement } from "./release-statement.ts";', + "", + "export interface StandaloneValidator {", + " (data: unknown): data is T;", + " errors?: { instancePath: string; message?: string }[] | null;", + "}", + "", + "export declare const validateReleaseManifest: StandaloneValidator;", + "export declare const validateReleaseStatement: StandaloneValidator;", + "", + ].join("\n"), + ); + for (const name of Object.values(ROOTS)) { + const types = readFileSync(path.join(CONTRACTS_GENERATED, `${name}.ts`), "utf8"); + // core-contracts' own banner goes; this package's names both sources. + const body = types.slice(types.indexOf("*/\n") + 3); + out.set(`${name}.ts`, `${BANNER}${body}`); + } + return out; +} + +if (import.meta.url === `file://${process.argv[1]}`) { + await mkdir(OUT_DIR, { recursive: true }); + const files = generate(); + for (const [name, text] of files) await writeFile(path.join(OUT_DIR, name), text); + console.log( + `generated ${[...files.keys()].join(", ")} → ${path.relative(process.cwd(), OUT_DIR)}`, + ); +} diff --git a/packages/release-verify/src/attestation.ts b/packages/release-verify/src/attestation.ts new file mode 100644 index 00000000..d8949883 --- /dev/null +++ b/packages/release-verify/src/attestation.ts @@ -0,0 +1,178 @@ +/** + * The second factor (RFC-0046 §2, §9, §11a): the GitHub build-provenance attestation of a + * release's files, checked with `gh attestation verify`. Node only (it runs `gh`). + * + * - `unavailable` (nothing verified): `gh` is missing, older than 2.68.0 (the first release with + * `--source-ref` and `--source-digest`), lacks one of the flags below in its `--help`, or is not + * signed in. Callers decide what that means; the CLI refuses unless told to rely on one factor. + * - `failed`: any non-zero exit, a signal, or no exit within the timeout. Always a refusal. + * - `verified`: all three files (tarball, manifest, statement) verified. + */ +import { spawn } from "node:child_process"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { MANIFEST_FILE, STATEMENT_FILE, sigstoreName, tarballName } from "./limits.ts"; +import { RELEASE_REPOSITORY, releaseAssetNames } from "./urls.ts"; + +export const MIN_GH_VERSION = "2.68.0"; +export const SIGNER_WORKFLOW = `${RELEASE_REPOSITORY}/.github/workflows/bundle-build.yml`; +/** Every flag the verify call passes; the probe requires each in `gh attestation verify --help`. */ +export const ATTESTATION_FLAGS = [ + "--repo", + "--signer-workflow", + "--source-ref", + "--source-digest", + "--deny-self-hosted-runners", + "--bundle", +] as const; + +export type AttestationResult = + | { readonly status: "verified" } + | { readonly status: "failed"; readonly file: string; readonly reason: string } + | { readonly status: "unavailable"; readonly reason: string }; + +export interface RunResult { + readonly code: number | null; + readonly signal: string | null; + readonly timedOut: boolean; + readonly stdout: string; + readonly stderr: string; +} + +export type Runner = ( + cmd: string, + args: readonly string[], + timeoutMs: number, +) => Promise; + +export interface AttestationOptions { + /** The directory holding the downloaded assets. */ + readonly dir: string; + readonly version: string; + /** The verified statement's `commit` (40 hex), pinned with `--source-digest`. */ + readonly commit: string; + readonly timeoutMs?: number; + /** For tests: how commands run (default: spawn, no shell). */ + readonly run?: Runner; +} + +/** The argument vector of the verify call for `file` (exact flags of §11a). */ +export function attestationArgs(file: string, commit: string, bundle?: string): string[] { + return [ + "attestation", + "verify", + file, + "--repo", + RELEASE_REPOSITORY, + "--signer-workflow", + SIGNER_WORKFLOW, + "--source-ref", + "refs/heads/main", + "--source-digest", + commit, + "--deny-self-hosted-runners", + ...(bundle ? ["--bundle", bundle] : []), + ]; +} + +/** `a.b.c` ≥ `min`, comparing the numeric parts. */ +export function versionAtLeast(version: string, min: string): boolean { + const a = version.split(".").map((x) => Number.parseInt(x, 10)); + const b = min.split(".").map((x) => Number.parseInt(x, 10)); + for (let i = 0; i < 3; i++) { + const x = a[i] ?? 0; + const y = b[i] ?? 0; + if (Number.isNaN(x)) return false; + if (x !== y) return x > y; + } + return true; +} + +/** The version `gh --version` prints first (`gh version 2.102.0 (2026-09-30)`). */ +export function ghVersionOf(stdout: string): string | undefined { + const first = stdout.split("\n", 1)[0] ?? ""; + const words = first.trim().split(" "); + return words[0] === "gh" && words[1] === "version" ? words[2] : undefined; +} + +const ok = (r: RunResult) => r.code === 0 && r.signal === null && !r.timedOut; + +async function probe(run: Runner, timeoutMs: number): Promise { + const need = `gh ${MIN_GH_VERSION} or newer, signed in (gh auth login)`; + const v = await run("gh", ["--version"], timeoutMs).catch(() => undefined); + if (!v || !ok(v)) return `gh is not installed: install ${need}`; + const version = ghVersionOf(v.stdout); + if (!version || !versionAtLeast(version, MIN_GH_VERSION)) + return `gh ${version ?? "(unknown version)"} is too old: install ${need}`; + const help = await run("gh", ["attestation", "verify", "--help"], timeoutMs).catch( + () => undefined, + ); + const text = help ? `${help.stdout}\n${help.stderr}` : ""; + const lacking = ATTESTATION_FLAGS.filter((f) => !text.includes(f)); + if (!help || !ok(help) || lacking.length) + return `gh ${version} lacks gh attestation verify ${lacking.join(" ")}: install ${need}`; + const auth = await run("gh", ["auth", "status"], timeoutMs).catch(() => undefined); + if (!auth || !ok(auth)) return `gh is not signed in: run gh auth login`; + return undefined; +} + +/** Verifies the attestation of the tarball, the manifest and the statement in `o.dir`. */ +export async function verifyAttestation(o: AttestationOptions): Promise { + releaseAssetNames(o.version); // refuses a malformed version before anything runs + if (o.commit.length !== 40 || [...o.commit].some((c) => !"0123456789abcdef".includes(c))) + throw new TypeError("commit must be 40 lowercase hex characters"); + const run = o.run ?? spawnRunner; + const timeoutMs = o.timeoutMs ?? 60_000; + const why = await probe(run, timeoutMs); + if (why) return { status: "unavailable", reason: why }; + const bundle = path.join(o.dir, sigstoreName(o.version)); + const withBundle = existsSync(bundle) ? bundle : undefined; + for (const name of [tarballName(o.version), MANIFEST_FILE, STATEMENT_FILE]) { + const file = path.join(o.dir, name); + const r = await run("gh", attestationArgs(file, o.commit, withBundle), timeoutMs).catch( + (e: Error): RunResult => ({ + code: null, + signal: null, + timedOut: false, + stdout: "", + stderr: e.message, + }), + ); + if (!ok(r)) { + const reason = r.timedOut + ? `no answer within ${timeoutMs / 1000} s` + : r.signal + ? `stopped by ${r.signal}` + : (r.stderr.trim().split("\n").pop() ?? `exit ${r.code}`); + return { status: "failed", file: name, reason }; + } + } + return { status: "verified" }; +} + +/** Runs `cmd` without a shell; never throws for a non-zero exit. */ +export const spawnRunner: Runner = (cmd, args, timeoutMs) => + new Promise((resolve, reject) => { + const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] }); + let stdout = ""; + let stderr = ""; + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + child.kill("SIGKILL"); + }, timeoutMs); + child.stdout.on("data", (c: Buffer) => { + stdout += c.toString(); + }); + child.stderr.on("data", (c: Buffer) => { + stderr += c.toString(); + }); + child.once("error", (e) => { + clearTimeout(timer); + reject(e); + }); + child.once("close", (code, signal) => { + clearTimeout(timer); + resolve({ code, signal, timedOut, stdout, stderr }); + }); + }); diff --git a/packages/release-verify/src/crypto.ts b/packages/release-verify/src/crypto.ts new file mode 100644 index 00000000..5f86cb80 --- /dev/null +++ b/packages/release-verify/src/crypto.ts @@ -0,0 +1,73 @@ +/** + * Ed25519 and SHA-256 on every runtime (RFC-0046 §11a): WebCrypto for Ed25519 and one-shot + * digests; for the streamed tarball, Workers' native `crypto.DigestStream` where it exists and + * otherwise `@noble/hashes` (audited, zero dependencies), because WebCrypto's `digest` cannot + * stream. test/crypto.test.ts checks both paths against the NIST vectors and `node:crypto`. + */ +import { sha256 as nobleSha256 } from "@noble/hashes/sha2.js"; + +const toHex = (bytes: Uint8Array): string => { + let s = ""; + for (const b of bytes) s += b.toString(16).padStart(2, "0"); + return s; +}; + +/** Pure Ed25519 over the exact `message` bytes. */ +export async function verifyEd25519( + publicKey: Uint8Array, + message: Uint8Array, + signature: Uint8Array, +): Promise { + try { + const key = await crypto.subtle.importKey("raw", publicKey, { name: "Ed25519" }, false, [ + "verify", + ]); + return await crypto.subtle.verify({ name: "Ed25519" }, key, signature, message); + } catch { + // A key that is not a valid Ed25519 point verifies nothing. + return false; + } +} + +/** SHA-256 of `bytes`, lowercase hex. */ +export async function sha256Hex(bytes: Uint8Array): Promise { + return toHex(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))); +} + +export interface IncrementalSha256 { + update(chunk: Uint8Array): Promise; + /** Lowercase hex; call once, after the last `update`. */ + digest(): Promise; +} + +/** Workers' `crypto.DigestStream` (a WritableStream whose `digest` resolves on close). */ +export type DigestStreamConstructor = new ( + algorithm: string, +) => WritableStream & { readonly digest: Promise }; + +const nativeDigestStream = (): DigestStreamConstructor | undefined => + (globalThis as { crypto?: { DigestStream?: DigestStreamConstructor } }).crypto?.DigestStream; + +/** An incremental SHA-256: `crypto.DigestStream` when the runtime has one, else @noble/hashes. */ +export function incrementalSha256( + DigestStream: DigestStreamConstructor | undefined = nativeDigestStream(), +): IncrementalSha256 { + if (DigestStream) { + const stream = new DigestStream("SHA-256"); + const writer = stream.getWriter(); + return { + update: (chunk) => writer.write(chunk), + async digest() { + await writer.close(); + return toHex(new Uint8Array(await stream.digest)); + }, + }; + } + const h = nobleSha256.create(); + return { + update: async (chunk) => { + h.update(chunk); + }, + digest: async () => toHex(h.digest()), + }; +} diff --git a/packages/release-verify/src/documents.ts b/packages/release-verify/src/documents.ts new file mode 100644 index 00000000..c3bd6161 --- /dev/null +++ b/packages/release-verify/src/documents.ts @@ -0,0 +1,160 @@ +/** + * The statement and the manifest (RFC-0046 §2 steps 1 to 5): sizes, the Ed25519 signature with a + * trusted key, the binding to the request (invariant 7), both precompiled validators, the + * invariants the schemas cannot express, and the manifest's listings (paths and §7 limits). + */ +import { refuse } from "./errors.ts"; +import type { ReleaseManifest } from "./generated/release-manifest.ts"; +import type { ReleaseStatement } from "./generated/release-statement.ts"; +import { validateReleaseManifest, validateReleaseStatement } from "./generated/validators.js"; +import { channelOf, LIMITS, MANIFEST_FILE, SIGNATURE_FILE, tarballName } from "./limits.ts"; +import { checkNoFoldedDuplicates, checkPath } from "./paths.ts"; + +const utf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }); + +/** The JSON document in `bytes`, or the refusal `code`. */ +export function parseJson( + bytes: Uint8Array, + code: "statement-invalid" | "manifest-invalid", +): unknown { + try { + return JSON.parse(utf8.decode(bytes)); + } catch { + return refuse( + code, + `${code === "statement-invalid" ? "the statement" : "the manifest"} is not UTF-8 JSON`, + ); + } +} + +const firstError = (errors?: { instancePath: string; message?: string }[] | null) => + errors?.[0] ? `${errors[0].instancePath || "/"} ${errors[0].message ?? ""}`.trim() : "invalid"; + +/** §2 step 4: the schema, then invariants 1, 2, 3 and 6. */ +export function checkStatementDoc(doc: unknown, serialisedBytes: number): ReleaseStatement { + if (!validateReleaseStatement(doc)) + refuse("statement-invalid", `the statement: ${firstError(validateReleaseStatement.errors)}`); + const s = doc; + if (s.manifest.name !== MANIFEST_FILE) + refuse("statement-invalid", `statement.manifest.name is not ${MANIFEST_FILE}`); + if (s.tarball.name !== tarballName(s.version)) + refuse("statement-invalid", `statement.tarball.name is not ${tarballName(s.version)}`); + if (s.manifest.bytes > LIMITS.manifestBytes) + refuse("statement-invalid", `the manifest is above ${LIMITS.manifestBytes} bytes`); + if (s.channel !== channelOf(s.version)) + refuse("statement-invalid", `channel ${s.channel} does not match version ${s.version}`); + if (serialisedBytes > LIMITS.statementBytes) + refuse("statement-too-large", `the statement is above ${LIMITS.statementBytes} bytes`); + return s; +} + +/** §2 step 3 (invariant 7): the statement is for the version and channel that were asked for. */ +export function checkRequested(doc: unknown, version: string, channel?: "stable" | "next"): void { + const s = (doc ?? {}) as { version?: unknown; channel?: unknown }; + if (s.version !== version) + refuse("wrong-version", `the statement is for ${String(s.version)}, not ${version}`); + const want = channel ?? channelOf(version); + if (s.channel !== want) + refuse("wrong-channel", `the statement is for channel ${String(s.channel)}, not ${want}`); +} + +/** §2 step 5 after the digest: the schema, invariants 4 and 5, the signature form, planVersion. */ +export function checkManifestDoc( + doc: unknown, + statement: ReleaseStatement, + planVersion: number, +): ReleaseManifest { + if (!validateReleaseManifest(doc)) + refuse("manifest-invalid", `the manifest: ${firstError(validateReleaseManifest.errors)}`); + const m = doc; + if (m.signing.keyId !== statement.signing.keyId) + refuse("statement-manifest-mismatch", "the statement and the manifest name different keys"); + for (const k of ["version", "channel", "securityRelease", "commit", "createdAt"] as const) + if (m[k] !== statement[k]) + refuse("statement-manifest-mismatch", `the statement's ${k} is not the manifest's`); + // A project key never signs the legacy manifest.sig form (§1). + if (m.signing.signature !== SIGNATURE_FILE) + refuse("manifest-invalid", `the manifest does not name ${SIGNATURE_FILE}`); + if (m.planVersion > planVersion) + refuse("plan-version-unsupported", `planVersion ${m.planVersion} is above ${planVersion}`); + checkListings(m); + return m; +} + +export type Worker = "site" | "admin"; + +/** One part the manifest lists, by its tar path. */ +export interface ListedPart { + readonly worker: Worker | null; + readonly kind: "module" | "asset" | "starter"; + /** The module path, the asset path (`/x`), or the starter's `starter/`. */ + readonly path: string; + readonly type?: string; + readonly bytes: number; + readonly sha256: string; +} + +/** Every part the manifest lists, keyed by its tar path (`site/modules/x.mjs`, …). */ +export function listedParts(m: ReleaseManifest): Map { + const out = new Map(); + for (const worker of ["site", "admin"] as const) { + const w = m.workers[worker]; + for (const mod of w.modules) + out.set(`${worker}/modules/${mod.path}`, { worker, kind: "module", ...mod }); + for (const f of w.assets.files) + out.set(`${worker}/assets${f.path}`, { + worker, + kind: "asset", + path: f.path, + bytes: f.bytes, + sha256: f.sha256, + }); + } + if (m.starter.kind === "bundle") { + const s = m.starter; + out.set(s.path, { + worker: null, + kind: "starter", + path: s.path, + bytes: s.bytes, + sha256: s.sha256, + }); + } + return out; +} + +/** Paths (§11a) and §7's limits over every listing, before the tarball is read. */ +function checkListings(m: ReleaseManifest): void { + let parts = 0; + for (const worker of ["site", "admin"] as const) { + const w = m.workers[worker]; + for (const mod of w.modules) { + checkPath(mod.path, "module", `${worker} module`); + if (mod.bytes > LIMITS.partBytes) + refuse("limit-exceeded", `${worker} module ${mod.path} is above ${LIMITS.partBytes} bytes`); + } + checkNoFoldedDuplicates( + w.modules.map((x) => x.path), + `${worker} modules`, + ); + const files = w.assets.files; + if (files.length > LIMITS.assetsPerWorker) + refuse("limit-exceeded", `${worker}: more than ${LIMITS.assetsPerWorker} assets`); + for (const f of files) { + checkPath(f.path, "asset", `${worker} asset`); + if (f.bytes > LIMITS.assetBytes) + refuse("limit-exceeded", `${worker} asset ${f.path} is above ${LIMITS.assetBytes} bytes`); + } + checkNoFoldedDuplicates( + files.map((x) => x.path), + `${worker} assets`, + ); + parts += w.modules.length + files.length; + } + if (m.starter.kind === "bundle") { + checkPath(m.starter.path, "entry", "starter"); + parts++; + } + if (parts > LIMITS.tarEntries) + refuse("limit-exceeded", `the manifest lists more than ${LIMITS.tarEntries} parts`); +} diff --git a/packages/release-verify/src/errors.ts b/packages/release-verify/src/errors.ts new file mode 100644 index 00000000..f99cfa20 --- /dev/null +++ b/packages/release-verify/src/errors.ts @@ -0,0 +1,41 @@ +/** + * The refusals of RFC-0046 §11a. Each `code` is public API: consumers branch on it, so a code is + * never renamed or reused. There is no warning level: every one of them refuses the release. + */ +export const ERROR_CODES = [ + "statement-too-large", + "signature-size", + "key-unknown", + "key-revoked", + "signature-invalid", + "wrong-version", + "wrong-channel", + "statement-invalid", + "manifest-mismatch", + "manifest-invalid", + "statement-manifest-mismatch", + "plan-version-unsupported", + "tarball-mismatch", + "tar-format", + "entry-path", + "entry-unlisted", + "entry-mismatch", + "entry-missing", + "limit-exceeded", +] as const; + +export type ReleaseVerifyErrorCode = (typeof ERROR_CODES)[number]; + +export class ReleaseVerifyError extends Error { + override readonly name = "ReleaseVerifyError"; + readonly code: ReleaseVerifyErrorCode; + constructor(code: ReleaseVerifyErrorCode, message: string) { + super(message); + this.code = code; + } +} + +/** Throws the refusal `code`; typed `never` so checks read as guards. */ +export function refuse(code: ReleaseVerifyErrorCode, message: string): never { + throw new ReleaseVerifyError(code, message); +} diff --git a/packages/release-verify/src/generated/release-manifest.ts b/packages/release-verify/src/generated/release-manifest.ts new file mode 100644 index 00000000..b5ea6402 --- /dev/null +++ b/packages/release-verify/src/generated/release-manifest.ts @@ -0,0 +1,237 @@ +/* eslint-disable */ +/* biome-ignore-all lint: generated */ +/** + * GENERATED FILE — DO NOT EDIT. + * Source: packages/core-contracts/schemas/release-{manifest,statement}.schema.json + * (run `pnpm gen`; packages/release-verify/scripts/gen-validators.ts) + */ +/* biome-ignore-all lint: generated */ +/** + * GENERATED FILE — DO NOT EDIT. + * Source: packages/core-contracts/schemas/*.schema.json (run `pnpm gen`) + */ + +/** + * Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time. + * + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "SchemaVersion". + */ +export type SchemaVersion = 1; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "CloudhalVersion". + */ +export type CloudhalVersion = string; +/** + * RFC 3339 / ISO 8601 instant in UTC. + * + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "Timestamp". + */ +export type Timestamp = string; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "MigrationName". + */ +export type MigrationName = string; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "BindingName". + */ +export type BindingName = string; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "Sha256Hex". + */ +export type Sha256Hex = string; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "SecretName". + */ +export type SecretName = + | "BETTER_AUTH_SECRET" + | "MCP_STATE_KEY" + | "CLOUDHAL_PREVIEW_SECRET" + | "CLOUDHAL_PURGE_TOKEN" + | "CLOUDHAL_DEPLOY_TOKEN" + | "CLOUDHAL_SETUP_TOKEN" + | "CLOUDHAL_COMMENTS_KEY"; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "ScopeId". + */ +export type ScopeId = string; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "Capability". + */ +export type Capability = "account" | "workers" | "d1" | "r2" | "analytics" | "domains"; +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "SiteId". + */ +export type SiteId = string; + +/** + * cloudhal.release-manifest@1 (RFC-0046): the manifest of one Cloudhal release bundle, published as the release asset manifest.json next to the gzip tarball cloudhal-release-.tar.gz. A project release is signed through release-statement.json, which pins this file's SHA-256 and size (signing.signature = release-statement.sig); the legacy form manifest.sig, a raw 64-byte Ed25519 signature over these exact bytes, is for development keys only. It describes every built Worker module and static asset by size and SHA-256, the binding templates, feature levels, migration range and rollback facts a remote deployer needs to install, update or refuse a version without running a build. Portable on purpose: it references common only for SchemaVersion and Timestamp; every other definition is local. + */ +export interface ReleaseManifest { + schemaVersion: SchemaVersion; + format: "cloudhal.release-manifest@1"; + version: CloudhalVersion; + channel: "stable" | "next"; + securityRelease: boolean; + commit: string; + createdAt: Timestamp; + contracts: { + schemaVersion: 1; + }; + levels: { + schedules: number; + gates: number; + }; + migrations: { + first: MigrationName; + last: MigrationName; + count: number; + /** + * @minItems 1 + */ + names: [MigrationName, ...MigrationName[]]; + compatibleWithPrevious: boolean; + }; + durableObjects: { + bindings: { + name: BindingName; + className: string; + migration: string; + }[]; + lifecycleChange: boolean; + }; + runtime: { + compatibilityDate: string; + compatibilityFlags: string[]; + }; + toolchain: { + [k: string]: string | undefined; + }; + theme: { + name: string; + version: string; + blockTypes: string[]; + settingsSha256: Sha256Hex; + designSha256: Sha256Hex; + }; + skills: { + name: string; + sha256: Sha256Hex; + }[]; + workers: { + site: WorkerArtifact; + admin: WorkerArtifact; + }; + bindingTemplates: { + site: BindingTemplate; + admin: BindingTemplate; + }; + requiredScopes: ScopeId[]; + optionalScopes: ScopeId[]; + requiredCapabilities: Capability[]; + optionalCapabilities: Capability[]; + starter: + | { + kind: "admin-api"; + } + | { + kind: "bundle"; + path: string; + bytes: number; + sha256: Sha256Hex; + siteId: SiteId; + }; + features: { + adminSitesAcceptsSiteId: boolean; + healthReportsVersion: boolean; + mediaTransformVar: boolean; + [k: string]: boolean | undefined; + }; + planVersion: number; + signing: { + keyId: string; + algorithm: "Ed25519"; + signature: "manifest.sig" | "release-statement.sig"; + }; +} +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "WorkerArtifact". + */ +export interface WorkerArtifact { + mainModule: string; + /** + * @minItems 1 + */ + modules: [ + { + path: string; + type: "esm" | "cjs" | "wasm" | "text" | "data"; + bytes: number; + sha256: Sha256Hex; + }, + ...{ + path: string; + type: "esm" | "cjs" | "wasm" | "text" | "data"; + bytes: number; + sha256: Sha256Hex; + }[], + ]; + assets: { + count: number; + bytes: number; + /** + * @maxItems 20000 + */ + files: { + path: string; + bytes: number; + sha256: Sha256Hex; + assetHash: string; + contentType: string; + }[]; + headers: string; + config?: { + not_found_handling?: "none" | "404-page" | "single-page-application"; + run_worker_first?: string[]; + }; + }; + bindingTemplate?: "site" | "admin"; +} +/** + * This interface was referenced by `ReleaseManifest`'s JSON-Schema + * via the `definition` "BindingTemplate". + */ +export interface BindingTemplate { + bindings: { + name: BindingName; + type: + | "d1" + | "r2_bucket" + | "assets" + | "service" + | "durable_object_namespace" + | "analytics_engine" + | "analytics_sql" + | "ratelimit" + | "ai_search" + | "send_email" + | "ai" + | "browser"; + required: boolean; + aliases?: BindingName[]; + }[]; + vars: BindingName[]; + secrets: SecretName[]; + cache?: boolean; + cron?: string; +} diff --git a/packages/release-verify/src/generated/release-statement.ts b/packages/release-verify/src/generated/release-statement.ts new file mode 100644 index 00000000..609a1b92 --- /dev/null +++ b/packages/release-verify/src/generated/release-statement.ts @@ -0,0 +1,66 @@ +/* eslint-disable */ +/* biome-ignore-all lint: generated */ +/** + * GENERATED FILE — DO NOT EDIT. + * Source: packages/core-contracts/schemas/release-{manifest,statement}.schema.json + * (run `pnpm gen`; packages/release-verify/scripts/gen-validators.ts) + */ +/* biome-ignore-all lint: generated */ +/** + * GENERATED FILE — DO NOT EDIT. + * Source: packages/core-contracts/schemas/*.schema.json (run `pnpm gen`) + */ + +/** + * Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time. + * + * This interface was referenced by `ReleaseStatement`'s JSON-Schema + * via the `definition` "SchemaVersion". + */ +export type SchemaVersion = 1; +/** + * This interface was referenced by `ReleaseStatement`'s JSON-Schema + * via the `definition` "CloudhalVersion". + */ +export type CloudhalVersion = string; +/** + * RFC 3339 / ISO 8601 instant in UTC. + * + * This interface was referenced by `ReleaseStatement`'s JSON-Schema + * via the `definition` "Timestamp". + */ +export type Timestamp = string; +/** + * This interface was referenced by `ReleaseStatement`'s JSON-Schema + * via the `definition` "Sha256Hex". + */ +export type Sha256Hex = string; + +/** + * cloudhal.release-statement@1 (RFC-0046): the small canonical document a Cloudhal release signs, published as the release asset release-statement.json next to release-statement.sig (a raw 64-byte Ed25519 signature over the exact bytes, at most 4096 of them). It names the version, channel, commit and key id and pins the SHA-256 and size of manifest.json and of the tarball, so a remote deployer verifies one small file and then checks every other asset by digest. Canonical bytes: keys sorted recursively, 2-space indent, a trailing newline; a consumer verifies the bytes it downloaded and never re-serialises them. Portable on purpose: it references common only for SchemaVersion and Timestamp. + */ +export interface ReleaseStatement { + schemaVersion: SchemaVersion; + format: "cloudhal.release-statement@1"; + version: CloudhalVersion; + channel: "stable" | "next"; + securityRelease: boolean; + commit: string; + createdAt: Timestamp; + signing: { + keyId: string; + algorithm: "Ed25519"; + signature: "release-statement.sig"; + }; + manifest: StatementAsset; + tarball: StatementAsset; +} +/** + * This interface was referenced by `ReleaseStatement`'s JSON-Schema + * via the `definition` "StatementAsset". + */ +export interface StatementAsset { + name: string; + sha256: Sha256Hex; + bytes: number; +} diff --git a/packages/release-verify/src/generated/validators.d.ts b/packages/release-verify/src/generated/validators.d.ts new file mode 100644 index 00000000..a499a390 --- /dev/null +++ b/packages/release-verify/src/generated/validators.d.ts @@ -0,0 +1,18 @@ +/* eslint-disable */ +/* biome-ignore-all lint: generated */ +/** + * GENERATED FILE — DO NOT EDIT. + * Source: packages/core-contracts/schemas/release-{manifest,statement}.schema.json + * (run `pnpm gen`; packages/release-verify/scripts/gen-validators.ts) + */ + +import type { ReleaseManifest } from "./release-manifest.ts"; +import type { ReleaseStatement } from "./release-statement.ts"; + +export interface StandaloneValidator { + (data: unknown): data is T; + errors?: { instancePath: string; message?: string }[] | null; +} + +export declare const validateReleaseManifest: StandaloneValidator; +export declare const validateReleaseStatement: StandaloneValidator; diff --git a/packages/release-verify/src/generated/validators.js b/packages/release-verify/src/generated/validators.js new file mode 100644 index 00000000..5bf9e7c8 --- /dev/null +++ b/packages/release-verify/src/generated/validators.js @@ -0,0 +1,4324 @@ +/* eslint-disable */ +/* biome-ignore-all lint: generated */ +/** + * GENERATED FILE — DO NOT EDIT. + * Source: packages/core-contracts/schemas/release-{manifest,statement}.schema.json + * (run `pnpm gen`; packages/release-verify/scripts/gen-validators.ts) + */ +import __req0 from "ajv/dist/runtime/equal.js"; +import __req1 from "ajv/dist/runtime/ucs2length.js"; +import __req2 from "ajv-formats/dist/formats.js"; +"use strict"; +export const validateReleaseManifest = validate20; +const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/release-manifest.schema.json","title":"ReleaseManifest","description":"cloudhal.release-manifest@1 (RFC-0046): the manifest of one Cloudhal release bundle, published as the release asset manifest.json next to the gzip tarball cloudhal-release-.tar.gz. A project release is signed through release-statement.json, which pins this file's SHA-256 and size (signing.signature = release-statement.sig); the legacy form manifest.sig, a raw 64-byte Ed25519 signature over these exact bytes, is for development keys only. It describes every built Worker module and static asset by size and SHA-256, the binding templates, feature levels, migration range and rollback facts a remote deployer needs to install, update or refuse a version without running a build. Portable on purpose: it references common only for SchemaVersion and Timestamp; every other definition is local.","type":"object","additionalProperties":false,"required":["schemaVersion","format","version","channel","securityRelease","commit","createdAt","contracts","levels","migrations","durableObjects","runtime","toolchain","theme","skills","workers","bindingTemplates","requiredScopes","optionalScopes","requiredCapabilities","optionalCapabilities","starter","features","planVersion","signing"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"format":{"type":"string","enum":["cloudhal.release-manifest@1"]},"version":{"$ref":"#/$defs/CloudhalVersion"},"channel":{"type":"string","enum":["stable","next"]},"securityRelease":{"type":"boolean"},"commit":{"type":"string","pattern":"^[0-9a-f]{40}$"},"createdAt":{"$ref":"#/$defs/Timestamp"},"contracts":{"type":"object","additionalProperties":false,"required":["schemaVersion"],"properties":{"schemaVersion":{"type":"integer","enum":[1]}}},"levels":{"type":"object","additionalProperties":false,"required":["schedules","gates"],"properties":{"schedules":{"type":"integer","minimum":0},"gates":{"type":"integer","minimum":0}}},"migrations":{"type":"object","additionalProperties":false,"required":["first","last","count","names","compatibleWithPrevious"],"properties":{"first":{"$ref":"#/$defs/MigrationName"},"last":{"$ref":"#/$defs/MigrationName"},"count":{"type":"integer","minimum":1},"names":{"type":"array","minItems":1,"uniqueItems":true,"items":{"$ref":"#/$defs/MigrationName"}},"compatibleWithPrevious":{"type":"boolean"}}},"durableObjects":{"type":"object","additionalProperties":false,"required":["bindings","lifecycleChange"],"properties":{"bindings":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","className","migration"],"properties":{"name":{"$ref":"#/$defs/BindingName"},"className":{"type":"string","pattern":"^[A-Z][A-Za-z0-9]*$"},"migration":{"type":"string","pattern":"^v[1-9][0-9]*$"}}}},"lifecycleChange":{"type":"boolean"}}},"runtime":{"type":"object","additionalProperties":false,"required":["compatibilityDate","compatibilityFlags"],"properties":{"compatibilityDate":{"type":"string","pattern":"^[0-9]{4}-[0-9]{2}-[0-9]{2}$"},"compatibilityFlags":{"type":"array","uniqueItems":true,"items":{"type":"string","pattern":"^[a-z0-9_]+$"}}}},"toolchain":{"type":"object","additionalProperties":{"type":"string","maxLength":64}},"theme":{"type":"object","additionalProperties":false,"required":["name","version","blockTypes","settingsSha256","designSha256"],"properties":{"name":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,63}$"},"version":{"type":"string","maxLength":64},"blockTypes":{"type":"array","uniqueItems":true,"items":{"type":"string"}},"settingsSha256":{"$ref":"#/$defs/Sha256Hex"},"designSha256":{"$ref":"#/$defs/Sha256Hex"}}},"skills":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","sha256"],"properties":{"name":{"type":"string","pattern":"^[a-z][a-z0-9-]{0,63}$"},"sha256":{"$ref":"#/$defs/Sha256Hex"}}}},"workers":{"type":"object","additionalProperties":false,"required":["site","admin"],"properties":{"site":{"$ref":"#/$defs/WorkerArtifact"},"admin":{"$ref":"#/$defs/WorkerArtifact"}}},"bindingTemplates":{"type":"object","additionalProperties":false,"required":["site","admin"],"properties":{"site":{"$ref":"#/$defs/BindingTemplate"},"admin":{"$ref":"#/$defs/BindingTemplate"}}},"requiredScopes":{"type":"array","uniqueItems":true,"items":{"$ref":"#/$defs/ScopeId"}},"optionalScopes":{"type":"array","uniqueItems":true,"items":{"$ref":"#/$defs/ScopeId"}},"requiredCapabilities":{"type":"array","uniqueItems":true,"items":{"$ref":"#/$defs/Capability"}},"optionalCapabilities":{"type":"array","uniqueItems":true,"items":{"$ref":"#/$defs/Capability"}},"starter":{"oneOf":[{"type":"object","additionalProperties":false,"required":["kind"],"properties":{"kind":{"type":"string","enum":["admin-api"]}}},{"type":"object","additionalProperties":false,"required":["kind","path","bytes","sha256","siteId"],"properties":{"kind":{"type":"string","enum":["bundle"]},"path":{"type":"string","pattern":"^starter/[A-Za-z0-9._-]+$"},"bytes":{"type":"integer","minimum":1},"sha256":{"$ref":"#/$defs/Sha256Hex"},"siteId":{"$ref":"#/$defs/SiteId"}}}]},"features":{"type":"object","additionalProperties":{"type":"boolean"},"required":["adminSitesAcceptsSiteId","healthReportsVersion","mediaTransformVar"],"properties":{"adminSitesAcceptsSiteId":{"type":"boolean"},"healthReportsVersion":{"type":"boolean"},"mediaTransformVar":{"type":"boolean"}}},"planVersion":{"type":"integer","minimum":1},"signing":{"type":"object","additionalProperties":false,"required":["keyId","algorithm","signature"],"properties":{"keyId":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{2,63}$"},"algorithm":{"type":"string","enum":["Ed25519"]},"signature":{"type":"string","enum":["manifest.sig","release-statement.sig"]}}}},"$defs":{"BindingName":{"type":"string","pattern":"^[A-Z][A-Z0-9_]*(_\\*)?$"},"BindingTemplate":{"type":"object","additionalProperties":false,"required":["bindings","vars","secrets"],"properties":{"bindings":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","type","required"],"properties":{"name":{"$ref":"#/$defs/BindingName"},"type":{"type":"string","enum":["d1","r2_bucket","assets","service","durable_object_namespace","analytics_engine","analytics_sql","ratelimit","ai_search","send_email","ai","browser"]},"required":{"type":"boolean"},"aliases":{"type":"array","items":{"$ref":"#/$defs/BindingName"}}}}},"vars":{"type":"array","uniqueItems":true,"items":{"$ref":"#/$defs/BindingName"}},"secrets":{"type":"array","uniqueItems":true,"items":{"$ref":"#/$defs/SecretName"}},"cache":{"type":"boolean"},"cron":{"type":"string","pattern":"^[0-9*/,\\- ]+$"}}},"Capability":{"type":"string","enum":["account","workers","d1","r2","analytics","domains"]},"CloudhalVersion":{"type":"string","pattern":"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"},"MigrationName":{"type":"string","pattern":"^[0-9]{4}_[a-z0-9_]+$"},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"ScopeId":{"type":"string","pattern":"^[a-z0-9-]+(\\.[a-z0-9-]+)+$"},"SecretName":{"type":"string","enum":["BETTER_AUTH_SECRET","MCP_STATE_KEY","CLOUDHAL_PREVIEW_SECRET","CLOUDHAL_PURGE_TOKEN","CLOUDHAL_DEPLOY_TOKEN","CLOUDHAL_SETUP_TOKEN","CLOUDHAL_COMMENTS_KEY"]},"Sha256Hex":{"type":"string","pattern":"^[0-9a-f]{64}$"},"SiteId":{"type":"string","pattern":"^site_[A-Za-z0-9]{6,32}$"},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"},"WorkerArtifact":{"type":"object","additionalProperties":false,"required":["mainModule","modules","assets"],"properties":{"mainModule":{"type":"string","pattern":"^[A-Za-z0-9._/-]+$"},"modules":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["path","type","bytes","sha256"],"properties":{"path":{"type":"string","pattern":"^[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)*$"},"type":{"type":"string","enum":["esm","cjs","wasm","text","data"]},"bytes":{"type":"integer","minimum":1},"sha256":{"$ref":"#/$defs/Sha256Hex"}}}},"assets":{"type":"object","additionalProperties":false,"required":["count","bytes","files","headers"],"properties":{"count":{"type":"integer","minimum":0,"maximum":20000},"bytes":{"type":"integer","minimum":0},"files":{"type":"array","maxItems":20000,"items":{"type":"object","additionalProperties":false,"required":["path","bytes","sha256","assetHash","contentType"],"properties":{"path":{"type":"string","pattern":"^/[^\\s]*$","maxLength":1024},"bytes":{"type":"integer","minimum":0,"maximum":26214400},"sha256":{"$ref":"#/$defs/Sha256Hex"},"assetHash":{"type":"string","pattern":"^[0-9a-f]{32}$"},"contentType":{"type":"string","maxLength":128}}}},"headers":{"type":"string","maxLength":100000},"config":{"type":"object","additionalProperties":false,"properties":{"not_found_handling":{"type":"string","enum":["none","404-page","single-page-application"]},"run_worker_first":{"type":"array","items":{"type":"string","pattern":"^/"}}}}}},"bindingTemplate":{"type":"string","enum":["site","admin"]}}}}}; +const schema32 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema33 = {"type":"string","pattern":"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"}; +const schema34 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema35 = {"type":"string","pattern":"^[0-9]{4}_[a-z0-9_]+$"}; +const schema38 = {"type":"string","pattern":"^[A-Z][A-Z0-9_]*(_\\*)?$"}; +const schema39 = {"type":"string","pattern":"^[0-9a-f]{64}$"}; +const schema50 = {"type":"string","pattern":"^[a-z0-9-]+(\\.[a-z0-9-]+)+$"}; +const schema52 = {"type":"string","enum":["account","workers","d1","r2","analytics","domains"]}; +const schema55 = {"type":"string","pattern":"^site_[A-Za-z0-9]{6,32}$"}; +const func1 = Object.prototype.hasOwnProperty; +const func0 = (__req0.default ?? __req0); +const func3 = (__req1.default ?? __req1); +const pattern4 = new RegExp("^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$", "u"); +const pattern5 = new RegExp("^[0-9a-f]{40}$", "u"); +const pattern6 = new RegExp("^[0-9]{4}_[a-z0-9_]+$", "u"); +const pattern9 = new RegExp("^[A-Z][A-Z0-9_]*(_\\*)?$", "u"); +const pattern10 = new RegExp("^[A-Z][A-Za-z0-9]*$", "u"); +const pattern11 = new RegExp("^v[1-9][0-9]*$", "u"); +const pattern12 = new RegExp("^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "u"); +const pattern13 = new RegExp("^[a-z0-9_]+$", "u"); +const pattern14 = new RegExp("^[a-z][a-z0-9-]{0,63}$", "u"); +const pattern15 = new RegExp("^[0-9a-f]{64}$", "u"); +const pattern30 = new RegExp("^[a-z0-9-]+(\\.[a-z0-9-]+)+$", "u"); +const pattern32 = new RegExp("^starter/[A-Za-z0-9._-]+$", "u"); +const pattern34 = new RegExp("^site_[A-Za-z0-9]{6,32}$", "u"); +const pattern35 = new RegExp("^[a-z0-9][a-z0-9-]{2,63}$", "u"); +const formats0 = __req2.fullFormats["date-time"]; +const schema42 = {"type":"object","additionalProperties":false,"required":["mainModule","modules","assets"],"properties":{"mainModule":{"type":"string","pattern":"^[A-Za-z0-9._/-]+$"},"modules":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["path","type","bytes","sha256"],"properties":{"path":{"type":"string","pattern":"^[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)*$"},"type":{"type":"string","enum":["esm","cjs","wasm","text","data"]},"bytes":{"type":"integer","minimum":1},"sha256":{"$ref":"#/$defs/Sha256Hex"}}}},"assets":{"type":"object","additionalProperties":false,"required":["count","bytes","files","headers"],"properties":{"count":{"type":"integer","minimum":0,"maximum":20000},"bytes":{"type":"integer","minimum":0},"files":{"type":"array","maxItems":20000,"items":{"type":"object","additionalProperties":false,"required":["path","bytes","sha256","assetHash","contentType"],"properties":{"path":{"type":"string","pattern":"^/[^\\s]*$","maxLength":1024},"bytes":{"type":"integer","minimum":0,"maximum":26214400},"sha256":{"$ref":"#/$defs/Sha256Hex"},"assetHash":{"type":"string","pattern":"^[0-9a-f]{32}$"},"contentType":{"type":"string","maxLength":128}}}},"headers":{"type":"string","maxLength":100000},"config":{"type":"object","additionalProperties":false,"properties":{"not_found_handling":{"type":"string","enum":["none","404-page","single-page-application"]},"run_worker_first":{"type":"array","items":{"type":"string","pattern":"^/"}}}}}},"bindingTemplate":{"type":"string","enum":["site","admin"]}}}; +const pattern19 = new RegExp("^[A-Za-z0-9._/-]+$", "u"); +const pattern20 = new RegExp("^[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)*$", "u"); +const pattern22 = new RegExp("^/[^\\s]*$", "u"); +const pattern24 = new RegExp("^[0-9a-f]{32}$", "u"); +const pattern25 = new RegExp("^/", "u"); + +function validate21(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ +let vErrors = null; +let errors = 0; +const evaluated0 = validate21.evaluated; +if(evaluated0.dynamicProps){ +evaluated0.props = undefined; +} +if(evaluated0.dynamicItems){ +evaluated0.items = undefined; +} +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.mainModule === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "mainModule"},message:"must have required property '"+"mainModule"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.modules === undefined){ +const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "modules"},message:"must have required property '"+"modules"+"'"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +if(data.assets === undefined){ +const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "assets"},message:"must have required property '"+"assets"+"'"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +for(const key0 in data){ +if(!((((key0 === "mainModule") || (key0 === "modules")) || (key0 === "assets")) || (key0 === "bindingTemplate"))){ +const err3 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +} +if(data.mainModule !== undefined){ +let data0 = data.mainModule; +if(typeof data0 === "string"){ +if(!pattern19.test(data0)){ +const err4 = {instancePath:instancePath+"/mainModule",schemaPath:"#/properties/mainModule/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9._/-]+$"},message:"must match pattern \""+"^[A-Za-z0-9._/-]+$"+"\""}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +} +} +else { +const err5 = {instancePath:instancePath+"/mainModule",schemaPath:"#/properties/mainModule/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +} +if(data.modules !== undefined){ +let data1 = data.modules; +if(Array.isArray(data1)){ +if(data1.length < 1){ +const err6 = {instancePath:instancePath+"/modules",schemaPath:"#/properties/modules/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +const len0 = data1.length; +for(let i0=0; i0=", limit: 1},message:"must be >= 1"}; +if(vErrors === null){ +vErrors = [err17]; +} +else { +vErrors.push(err17); +} +errors++; +} +} +} +if(data2.sha256 !== undefined){ +let data6 = data2.sha256; +if(typeof data6 === "string"){ +if(!pattern15.test(data6)){ +const err18 = {instancePath:instancePath+"/modules/" + i0+"/sha256",schemaPath:"#/$defs/Sha256Hex/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; +if(vErrors === null){ +vErrors = [err18]; +} +else { +vErrors.push(err18); +} +errors++; +} +} +else { +const err19 = {instancePath:instancePath+"/modules/" + i0+"/sha256",schemaPath:"#/$defs/Sha256Hex/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err19]; +} +else { +vErrors.push(err19); +} +errors++; +} +} +} +else { +const err20 = {instancePath:instancePath+"/modules/" + i0,schemaPath:"#/properties/modules/items/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err20]; +} +else { +vErrors.push(err20); +} +errors++; +} +} +} +else { +const err21 = {instancePath:instancePath+"/modules",schemaPath:"#/properties/modules/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err21]; +} +else { +vErrors.push(err21); +} +errors++; +} +} +if(data.assets !== undefined){ +let data7 = data.assets; +if(data7 && typeof data7 == "object" && !Array.isArray(data7)){ +if(data7.count === undefined){ +const err22 = {instancePath:instancePath+"/assets",schemaPath:"#/properties/assets/required",keyword:"required",params:{missingProperty: "count"},message:"must have required property '"+"count"+"'"}; +if(vErrors === null){ +vErrors = [err22]; +} +else { +vErrors.push(err22); +} +errors++; +} +if(data7.bytes === undefined){ +const err23 = {instancePath:instancePath+"/assets",schemaPath:"#/properties/assets/required",keyword:"required",params:{missingProperty: "bytes"},message:"must have required property '"+"bytes"+"'"}; +if(vErrors === null){ +vErrors = [err23]; +} +else { +vErrors.push(err23); +} +errors++; +} +if(data7.files === undefined){ +const err24 = {instancePath:instancePath+"/assets",schemaPath:"#/properties/assets/required",keyword:"required",params:{missingProperty: "files"},message:"must have required property '"+"files"+"'"}; +if(vErrors === null){ +vErrors = [err24]; +} +else { +vErrors.push(err24); +} +errors++; +} +if(data7.headers === undefined){ +const err25 = {instancePath:instancePath+"/assets",schemaPath:"#/properties/assets/required",keyword:"required",params:{missingProperty: "headers"},message:"must have required property '"+"headers"+"'"}; +if(vErrors === null){ +vErrors = [err25]; +} +else { +vErrors.push(err25); +} +errors++; +} +for(const key2 in data7){ +if(!(((((key2 === "count") || (key2 === "bytes")) || (key2 === "files")) || (key2 === "headers")) || (key2 === "config"))){ +const err26 = {instancePath:instancePath+"/assets",schemaPath:"#/properties/assets/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err26]; +} +else { +vErrors.push(err26); +} +errors++; +} +} +if(data7.count !== undefined){ +let data8 = data7.count; +if(!(((typeof data8 == "number") && (!(data8 % 1) && !isNaN(data8))) && (isFinite(data8)))){ +const err27 = {instancePath:instancePath+"/assets/count",schemaPath:"#/properties/assets/properties/count/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err27]; +} +else { +vErrors.push(err27); +} +errors++; +} +if((typeof data8 == "number") && (isFinite(data8))){ +if(data8 > 20000 || isNaN(data8)){ +const err28 = {instancePath:instancePath+"/assets/count",schemaPath:"#/properties/assets/properties/count/maximum",keyword:"maximum",params:{comparison: "<=", limit: 20000},message:"must be <= 20000"}; +if(vErrors === null){ +vErrors = [err28]; +} +else { +vErrors.push(err28); +} +errors++; +} +if(data8 < 0 || isNaN(data8)){ +const err29 = {instancePath:instancePath+"/assets/count",schemaPath:"#/properties/assets/properties/count/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err29]; +} +else { +vErrors.push(err29); +} +errors++; +} +} +} +if(data7.bytes !== undefined){ +let data9 = data7.bytes; +if(!(((typeof data9 == "number") && (!(data9 % 1) && !isNaN(data9))) && (isFinite(data9)))){ +const err30 = {instancePath:instancePath+"/assets/bytes",schemaPath:"#/properties/assets/properties/bytes/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err30]; +} +else { +vErrors.push(err30); +} +errors++; +} +if((typeof data9 == "number") && (isFinite(data9))){ +if(data9 < 0 || isNaN(data9)){ +const err31 = {instancePath:instancePath+"/assets/bytes",schemaPath:"#/properties/assets/properties/bytes/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err31]; +} +else { +vErrors.push(err31); +} +errors++; +} +} +} +if(data7.files !== undefined){ +let data10 = data7.files; +if(Array.isArray(data10)){ +if(data10.length > 20000){ +const err32 = {instancePath:instancePath+"/assets/files",schemaPath:"#/properties/assets/properties/files/maxItems",keyword:"maxItems",params:{limit: 20000},message:"must NOT have more than 20000 items"}; +if(vErrors === null){ +vErrors = [err32]; +} +else { +vErrors.push(err32); +} +errors++; +} +const len1 = data10.length; +for(let i1=0; i1 1024){ +const err39 = {instancePath:instancePath+"/assets/files/" + i1+"/path",schemaPath:"#/properties/assets/properties/files/items/properties/path/maxLength",keyword:"maxLength",params:{limit: 1024},message:"must NOT have more than 1024 characters"}; +if(vErrors === null){ +vErrors = [err39]; +} +else { +vErrors.push(err39); +} +errors++; +} +if(!pattern22.test(data12)){ +const err40 = {instancePath:instancePath+"/assets/files/" + i1+"/path",schemaPath:"#/properties/assets/properties/files/items/properties/path/pattern",keyword:"pattern",params:{pattern: "^/[^\\s]*$"},message:"must match pattern \""+"^/[^\\s]*$"+"\""}; +if(vErrors === null){ +vErrors = [err40]; +} +else { +vErrors.push(err40); +} +errors++; +} +} +else { +const err41 = {instancePath:instancePath+"/assets/files/" + i1+"/path",schemaPath:"#/properties/assets/properties/files/items/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err41]; +} +else { +vErrors.push(err41); +} +errors++; +} +} +if(data11.bytes !== undefined){ +let data13 = data11.bytes; +if(!(((typeof data13 == "number") && (!(data13 % 1) && !isNaN(data13))) && (isFinite(data13)))){ +const err42 = {instancePath:instancePath+"/assets/files/" + i1+"/bytes",schemaPath:"#/properties/assets/properties/files/items/properties/bytes/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err42]; +} +else { +vErrors.push(err42); +} +errors++; +} +if((typeof data13 == "number") && (isFinite(data13))){ +if(data13 > 26214400 || isNaN(data13)){ +const err43 = {instancePath:instancePath+"/assets/files/" + i1+"/bytes",schemaPath:"#/properties/assets/properties/files/items/properties/bytes/maximum",keyword:"maximum",params:{comparison: "<=", limit: 26214400},message:"must be <= 26214400"}; +if(vErrors === null){ +vErrors = [err43]; +} +else { +vErrors.push(err43); +} +errors++; +} +if(data13 < 0 || isNaN(data13)){ +const err44 = {instancePath:instancePath+"/assets/files/" + i1+"/bytes",schemaPath:"#/properties/assets/properties/files/items/properties/bytes/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err44]; +} +else { +vErrors.push(err44); +} +errors++; +} +} +} +if(data11.sha256 !== undefined){ +let data14 = data11.sha256; +if(typeof data14 === "string"){ +if(!pattern15.test(data14)){ +const err45 = {instancePath:instancePath+"/assets/files/" + i1+"/sha256",schemaPath:"#/$defs/Sha256Hex/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; +if(vErrors === null){ +vErrors = [err45]; +} +else { +vErrors.push(err45); +} +errors++; +} +} +else { +const err46 = {instancePath:instancePath+"/assets/files/" + i1+"/sha256",schemaPath:"#/$defs/Sha256Hex/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err46]; +} +else { +vErrors.push(err46); +} +errors++; +} +} +if(data11.assetHash !== undefined){ +let data15 = data11.assetHash; +if(typeof data15 === "string"){ +if(!pattern24.test(data15)){ +const err47 = {instancePath:instancePath+"/assets/files/" + i1+"/assetHash",schemaPath:"#/properties/assets/properties/files/items/properties/assetHash/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{32}$"},message:"must match pattern \""+"^[0-9a-f]{32}$"+"\""}; +if(vErrors === null){ +vErrors = [err47]; +} +else { +vErrors.push(err47); +} +errors++; +} +} +else { +const err48 = {instancePath:instancePath+"/assets/files/" + i1+"/assetHash",schemaPath:"#/properties/assets/properties/files/items/properties/assetHash/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err48]; +} +else { +vErrors.push(err48); +} +errors++; +} +} +if(data11.contentType !== undefined){ +let data16 = data11.contentType; +if(typeof data16 === "string"){ +if(func3(data16) > 128){ +const err49 = {instancePath:instancePath+"/assets/files/" + i1+"/contentType",schemaPath:"#/properties/assets/properties/files/items/properties/contentType/maxLength",keyword:"maxLength",params:{limit: 128},message:"must NOT have more than 128 characters"}; +if(vErrors === null){ +vErrors = [err49]; +} +else { +vErrors.push(err49); +} +errors++; +} +} +else { +const err50 = {instancePath:instancePath+"/assets/files/" + i1+"/contentType",schemaPath:"#/properties/assets/properties/files/items/properties/contentType/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err50]; +} +else { +vErrors.push(err50); +} +errors++; +} +} +} +else { +const err51 = {instancePath:instancePath+"/assets/files/" + i1,schemaPath:"#/properties/assets/properties/files/items/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err51]; +} +else { +vErrors.push(err51); +} +errors++; +} +} +} +else { +const err52 = {instancePath:instancePath+"/assets/files",schemaPath:"#/properties/assets/properties/files/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err52]; +} +else { +vErrors.push(err52); +} +errors++; +} +} +if(data7.headers !== undefined){ +let data17 = data7.headers; +if(typeof data17 === "string"){ +if(func3(data17) > 100000){ +const err53 = {instancePath:instancePath+"/assets/headers",schemaPath:"#/properties/assets/properties/headers/maxLength",keyword:"maxLength",params:{limit: 100000},message:"must NOT have more than 100000 characters"}; +if(vErrors === null){ +vErrors = [err53]; +} +else { +vErrors.push(err53); +} +errors++; +} +} +else { +const err54 = {instancePath:instancePath+"/assets/headers",schemaPath:"#/properties/assets/properties/headers/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err54]; +} +else { +vErrors.push(err54); +} +errors++; +} +} +if(data7.config !== undefined){ +let data18 = data7.config; +if(data18 && typeof data18 == "object" && !Array.isArray(data18)){ +for(const key4 in data18){ +if(!((key4 === "not_found_handling") || (key4 === "run_worker_first"))){ +const err55 = {instancePath:instancePath+"/assets/config",schemaPath:"#/properties/assets/properties/config/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err55]; +} +else { +vErrors.push(err55); +} +errors++; +} +} +if(data18.not_found_handling !== undefined){ +let data19 = data18.not_found_handling; +if(typeof data19 !== "string"){ +const err56 = {instancePath:instancePath+"/assets/config/not_found_handling",schemaPath:"#/properties/assets/properties/config/properties/not_found_handling/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err56]; +} +else { +vErrors.push(err56); +} +errors++; +} +if(!(((data19 === "none") || (data19 === "404-page")) || (data19 === "single-page-application"))){ +const err57 = {instancePath:instancePath+"/assets/config/not_found_handling",schemaPath:"#/properties/assets/properties/config/properties/not_found_handling/enum",keyword:"enum",params:{allowedValues: schema42.properties.assets.properties.config.properties.not_found_handling.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err57]; +} +else { +vErrors.push(err57); +} +errors++; +} +} +if(data18.run_worker_first !== undefined){ +let data20 = data18.run_worker_first; +if(Array.isArray(data20)){ +const len2 = data20.length; +for(let i2=0; i2 1){ +outer0: +for(;i3--;){ +for(j0 = i3; j0--;){ +if(func0(data7[i3], data7[j0])){ +const err20 = {instancePath:instancePath+"/vars",schemaPath:"#/properties/vars/uniqueItems",keyword:"uniqueItems",params:{i: i3, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i3+" are identical)"}; +if(vErrors === null){ +vErrors = [err20]; +} +else { +vErrors.push(err20); +} +errors++; +break outer0; +} +} +} +} +} +else { +const err21 = {instancePath:instancePath+"/vars",schemaPath:"#/properties/vars/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err21]; +} +else { +vErrors.push(err21); +} +errors++; +} +} +if(data.secrets !== undefined){ +let data9 = data.secrets; +if(Array.isArray(data9)){ +const len3 = data9.length; +for(let i4=0; i4 1){ +outer1: +for(;i5--;){ +for(j1 = i5; j1--;){ +if(func0(data9[i5], data9[j1])){ +const err24 = {instancePath:instancePath+"/secrets",schemaPath:"#/properties/secrets/uniqueItems",keyword:"uniqueItems",params:{i: i5, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i5+" are identical)"}; +if(vErrors === null){ +vErrors = [err24]; +} +else { +vErrors.push(err24); +} +errors++; +break outer1; +} +} +} +} +} +else { +const err25 = {instancePath:instancePath+"/secrets",schemaPath:"#/properties/secrets/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err25]; +} +else { +vErrors.push(err25); +} +errors++; +} +} +if(data.cache !== undefined){ +if(typeof data.cache !== "boolean"){ +const err26 = {instancePath:instancePath+"/cache",schemaPath:"#/properties/cache/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err26]; +} +else { +vErrors.push(err26); +} +errors++; +} +} +if(data.cron !== undefined){ +let data12 = data.cron; +if(typeof data12 === "string"){ +if(!pattern29.test(data12)){ +const err27 = {instancePath:instancePath+"/cron",schemaPath:"#/properties/cron/pattern",keyword:"pattern",params:{pattern: "^[0-9*/,\\- ]+$"},message:"must match pattern \""+"^[0-9*/,\\- ]+$"+"\""}; +if(vErrors === null){ +vErrors = [err27]; +} +else { +vErrors.push(err27); +} +errors++; +} +} +else { +const err28 = {instancePath:instancePath+"/cron",schemaPath:"#/properties/cron/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err28]; +} +else { +vErrors.push(err28); +} +errors++; +} +} +} +else { +const err29 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err29]; +} +else { +vErrors.push(err29); +} +errors++; +} +validate24.errors = vErrors; +return errors === 0; +} +validate24.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; + + +function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ +/*# sourceURL="https://cloudhal.dev/schemas/v1/release-manifest.schema.json" */; +let vErrors = null; +let errors = 0; +const evaluated0 = validate20.evaluated; +if(evaluated0.dynamicProps){ +evaluated0.props = undefined; +} +if(evaluated0.dynamicItems){ +evaluated0.items = undefined; +} +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.schemaVersion === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.format === undefined){ +const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "format"},message:"must have required property '"+"format"+"'"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +if(data.version === undefined){ +const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +if(data.channel === undefined){ +const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "channel"},message:"must have required property '"+"channel"+"'"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +if(data.securityRelease === undefined){ +const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "securityRelease"},message:"must have required property '"+"securityRelease"+"'"}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +} +if(data.commit === undefined){ +const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "commit"},message:"must have required property '"+"commit"+"'"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +if(data.createdAt === undefined){ +const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "createdAt"},message:"must have required property '"+"createdAt"+"'"}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +if(data.contracts === undefined){ +const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "contracts"},message:"must have required property '"+"contracts"+"'"}; +if(vErrors === null){ +vErrors = [err7]; +} +else { +vErrors.push(err7); +} +errors++; +} +if(data.levels === undefined){ +const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "levels"},message:"must have required property '"+"levels"+"'"}; +if(vErrors === null){ +vErrors = [err8]; +} +else { +vErrors.push(err8); +} +errors++; +} +if(data.migrations === undefined){ +const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "migrations"},message:"must have required property '"+"migrations"+"'"}; +if(vErrors === null){ +vErrors = [err9]; +} +else { +vErrors.push(err9); +} +errors++; +} +if(data.durableObjects === undefined){ +const err10 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "durableObjects"},message:"must have required property '"+"durableObjects"+"'"}; +if(vErrors === null){ +vErrors = [err10]; +} +else { +vErrors.push(err10); +} +errors++; +} +if(data.runtime === undefined){ +const err11 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "runtime"},message:"must have required property '"+"runtime"+"'"}; +if(vErrors === null){ +vErrors = [err11]; +} +else { +vErrors.push(err11); +} +errors++; +} +if(data.toolchain === undefined){ +const err12 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "toolchain"},message:"must have required property '"+"toolchain"+"'"}; +if(vErrors === null){ +vErrors = [err12]; +} +else { +vErrors.push(err12); +} +errors++; +} +if(data.theme === undefined){ +const err13 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "theme"},message:"must have required property '"+"theme"+"'"}; +if(vErrors === null){ +vErrors = [err13]; +} +else { +vErrors.push(err13); +} +errors++; +} +if(data.skills === undefined){ +const err14 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "skills"},message:"must have required property '"+"skills"+"'"}; +if(vErrors === null){ +vErrors = [err14]; +} +else { +vErrors.push(err14); +} +errors++; +} +if(data.workers === undefined){ +const err15 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "workers"},message:"must have required property '"+"workers"+"'"}; +if(vErrors === null){ +vErrors = [err15]; +} +else { +vErrors.push(err15); +} +errors++; +} +if(data.bindingTemplates === undefined){ +const err16 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "bindingTemplates"},message:"must have required property '"+"bindingTemplates"+"'"}; +if(vErrors === null){ +vErrors = [err16]; +} +else { +vErrors.push(err16); +} +errors++; +} +if(data.requiredScopes === undefined){ +const err17 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "requiredScopes"},message:"must have required property '"+"requiredScopes"+"'"}; +if(vErrors === null){ +vErrors = [err17]; +} +else { +vErrors.push(err17); +} +errors++; +} +if(data.optionalScopes === undefined){ +const err18 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "optionalScopes"},message:"must have required property '"+"optionalScopes"+"'"}; +if(vErrors === null){ +vErrors = [err18]; +} +else { +vErrors.push(err18); +} +errors++; +} +if(data.requiredCapabilities === undefined){ +const err19 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "requiredCapabilities"},message:"must have required property '"+"requiredCapabilities"+"'"}; +if(vErrors === null){ +vErrors = [err19]; +} +else { +vErrors.push(err19); +} +errors++; +} +if(data.optionalCapabilities === undefined){ +const err20 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "optionalCapabilities"},message:"must have required property '"+"optionalCapabilities"+"'"}; +if(vErrors === null){ +vErrors = [err20]; +} +else { +vErrors.push(err20); +} +errors++; +} +if(data.starter === undefined){ +const err21 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "starter"},message:"must have required property '"+"starter"+"'"}; +if(vErrors === null){ +vErrors = [err21]; +} +else { +vErrors.push(err21); +} +errors++; +} +if(data.features === undefined){ +const err22 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "features"},message:"must have required property '"+"features"+"'"}; +if(vErrors === null){ +vErrors = [err22]; +} +else { +vErrors.push(err22); +} +errors++; +} +if(data.planVersion === undefined){ +const err23 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "planVersion"},message:"must have required property '"+"planVersion"+"'"}; +if(vErrors === null){ +vErrors = [err23]; +} +else { +vErrors.push(err23); +} +errors++; +} +if(data.signing === undefined){ +const err24 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "signing"},message:"must have required property '"+"signing"+"'"}; +if(vErrors === null){ +vErrors = [err24]; +} +else { +vErrors.push(err24); +} +errors++; +} +for(const key0 in data){ +if(!(func1.call(schema31.properties, key0))){ +const err25 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err25]; +} +else { +vErrors.push(err25); +} +errors++; +} +} +if(data.schemaVersion !== undefined){ +let data0 = data.schemaVersion; +if(!(((typeof data0 == "number") && (!(data0 % 1) && !isNaN(data0))) && (isFinite(data0)))){ +const err26 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err26]; +} +else { +vErrors.push(err26); +} +errors++; +} +if(!(data0 === 1)){ +const err27 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema32.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err27]; +} +else { +vErrors.push(err27); +} +errors++; +} +} +if(data.format !== undefined){ +let data1 = data.format; +if(typeof data1 !== "string"){ +const err28 = {instancePath:instancePath+"/format",schemaPath:"#/properties/format/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err28]; +} +else { +vErrors.push(err28); +} +errors++; +} +if(!(data1 === "cloudhal.release-manifest@1")){ +const err29 = {instancePath:instancePath+"/format",schemaPath:"#/properties/format/enum",keyword:"enum",params:{allowedValues: schema31.properties.format.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err29]; +} +else { +vErrors.push(err29); +} +errors++; +} +} +if(data.version !== undefined){ +let data2 = data.version; +if(typeof data2 === "string"){ +if(!pattern4.test(data2)){ +const err30 = {instancePath:instancePath+"/version",schemaPath:"#/$defs/CloudhalVersion/pattern",keyword:"pattern",params:{pattern: "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"},message:"must match pattern \""+"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"+"\""}; +if(vErrors === null){ +vErrors = [err30]; +} +else { +vErrors.push(err30); +} +errors++; +} +} +else { +const err31 = {instancePath:instancePath+"/version",schemaPath:"#/$defs/CloudhalVersion/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err31]; +} +else { +vErrors.push(err31); +} +errors++; +} +} +if(data.channel !== undefined){ +let data3 = data.channel; +if(typeof data3 !== "string"){ +const err32 = {instancePath:instancePath+"/channel",schemaPath:"#/properties/channel/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err32]; +} +else { +vErrors.push(err32); +} +errors++; +} +if(!((data3 === "stable") || (data3 === "next"))){ +const err33 = {instancePath:instancePath+"/channel",schemaPath:"#/properties/channel/enum",keyword:"enum",params:{allowedValues: schema31.properties.channel.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err33]; +} +else { +vErrors.push(err33); +} +errors++; +} +} +if(data.securityRelease !== undefined){ +if(typeof data.securityRelease !== "boolean"){ +const err34 = {instancePath:instancePath+"/securityRelease",schemaPath:"#/properties/securityRelease/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err34]; +} +else { +vErrors.push(err34); +} +errors++; +} +} +if(data.commit !== undefined){ +let data5 = data.commit; +if(typeof data5 === "string"){ +if(!pattern5.test(data5)){ +const err35 = {instancePath:instancePath+"/commit",schemaPath:"#/properties/commit/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{40}$"},message:"must match pattern \""+"^[0-9a-f]{40}$"+"\""}; +if(vErrors === null){ +vErrors = [err35]; +} +else { +vErrors.push(err35); +} +errors++; +} +} +else { +const err36 = {instancePath:instancePath+"/commit",schemaPath:"#/properties/commit/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err36]; +} +else { +vErrors.push(err36); +} +errors++; +} +} +if(data.createdAt !== undefined){ +let data6 = data.createdAt; +if(typeof data6 === "string"){ +if(!(formats0.validate(data6))){ +const err37 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}; +if(vErrors === null){ +vErrors = [err37]; +} +else { +vErrors.push(err37); +} +errors++; +} +} +else { +const err38 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err38]; +} +else { +vErrors.push(err38); +} +errors++; +} +} +if(data.contracts !== undefined){ +let data7 = data.contracts; +if(data7 && typeof data7 == "object" && !Array.isArray(data7)){ +if(data7.schemaVersion === undefined){ +const err39 = {instancePath:instancePath+"/contracts",schemaPath:"#/properties/contracts/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"}; +if(vErrors === null){ +vErrors = [err39]; +} +else { +vErrors.push(err39); +} +errors++; +} +for(const key1 in data7){ +if(!(key1 === "schemaVersion")){ +const err40 = {instancePath:instancePath+"/contracts",schemaPath:"#/properties/contracts/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err40]; +} +else { +vErrors.push(err40); +} +errors++; +} +} +if(data7.schemaVersion !== undefined){ +let data8 = data7.schemaVersion; +if(!(((typeof data8 == "number") && (!(data8 % 1) && !isNaN(data8))) && (isFinite(data8)))){ +const err41 = {instancePath:instancePath+"/contracts/schemaVersion",schemaPath:"#/properties/contracts/properties/schemaVersion/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err41]; +} +else { +vErrors.push(err41); +} +errors++; +} +if(!(data8 === 1)){ +const err42 = {instancePath:instancePath+"/contracts/schemaVersion",schemaPath:"#/properties/contracts/properties/schemaVersion/enum",keyword:"enum",params:{allowedValues: schema31.properties.contracts.properties.schemaVersion.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err42]; +} +else { +vErrors.push(err42); +} +errors++; +} +} +} +else { +const err43 = {instancePath:instancePath+"/contracts",schemaPath:"#/properties/contracts/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err43]; +} +else { +vErrors.push(err43); +} +errors++; +} +} +if(data.levels !== undefined){ +let data9 = data.levels; +if(data9 && typeof data9 == "object" && !Array.isArray(data9)){ +if(data9.schedules === undefined){ +const err44 = {instancePath:instancePath+"/levels",schemaPath:"#/properties/levels/required",keyword:"required",params:{missingProperty: "schedules"},message:"must have required property '"+"schedules"+"'"}; +if(vErrors === null){ +vErrors = [err44]; +} +else { +vErrors.push(err44); +} +errors++; +} +if(data9.gates === undefined){ +const err45 = {instancePath:instancePath+"/levels",schemaPath:"#/properties/levels/required",keyword:"required",params:{missingProperty: "gates"},message:"must have required property '"+"gates"+"'"}; +if(vErrors === null){ +vErrors = [err45]; +} +else { +vErrors.push(err45); +} +errors++; +} +for(const key2 in data9){ +if(!((key2 === "schedules") || (key2 === "gates"))){ +const err46 = {instancePath:instancePath+"/levels",schemaPath:"#/properties/levels/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err46]; +} +else { +vErrors.push(err46); +} +errors++; +} +} +if(data9.schedules !== undefined){ +let data10 = data9.schedules; +if(!(((typeof data10 == "number") && (!(data10 % 1) && !isNaN(data10))) && (isFinite(data10)))){ +const err47 = {instancePath:instancePath+"/levels/schedules",schemaPath:"#/properties/levels/properties/schedules/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err47]; +} +else { +vErrors.push(err47); +} +errors++; +} +if((typeof data10 == "number") && (isFinite(data10))){ +if(data10 < 0 || isNaN(data10)){ +const err48 = {instancePath:instancePath+"/levels/schedules",schemaPath:"#/properties/levels/properties/schedules/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err48]; +} +else { +vErrors.push(err48); +} +errors++; +} +} +} +if(data9.gates !== undefined){ +let data11 = data9.gates; +if(!(((typeof data11 == "number") && (!(data11 % 1) && !isNaN(data11))) && (isFinite(data11)))){ +const err49 = {instancePath:instancePath+"/levels/gates",schemaPath:"#/properties/levels/properties/gates/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err49]; +} +else { +vErrors.push(err49); +} +errors++; +} +if((typeof data11 == "number") && (isFinite(data11))){ +if(data11 < 0 || isNaN(data11)){ +const err50 = {instancePath:instancePath+"/levels/gates",schemaPath:"#/properties/levels/properties/gates/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"}; +if(vErrors === null){ +vErrors = [err50]; +} +else { +vErrors.push(err50); +} +errors++; +} +} +} +} +else { +const err51 = {instancePath:instancePath+"/levels",schemaPath:"#/properties/levels/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err51]; +} +else { +vErrors.push(err51); +} +errors++; +} +} +if(data.migrations !== undefined){ +let data12 = data.migrations; +if(data12 && typeof data12 == "object" && !Array.isArray(data12)){ +if(data12.first === undefined){ +const err52 = {instancePath:instancePath+"/migrations",schemaPath:"#/properties/migrations/required",keyword:"required",params:{missingProperty: "first"},message:"must have required property '"+"first"+"'"}; +if(vErrors === null){ +vErrors = [err52]; +} +else { +vErrors.push(err52); +} +errors++; +} +if(data12.last === undefined){ +const err53 = {instancePath:instancePath+"/migrations",schemaPath:"#/properties/migrations/required",keyword:"required",params:{missingProperty: "last"},message:"must have required property '"+"last"+"'"}; +if(vErrors === null){ +vErrors = [err53]; +} +else { +vErrors.push(err53); +} +errors++; +} +if(data12.count === undefined){ +const err54 = {instancePath:instancePath+"/migrations",schemaPath:"#/properties/migrations/required",keyword:"required",params:{missingProperty: "count"},message:"must have required property '"+"count"+"'"}; +if(vErrors === null){ +vErrors = [err54]; +} +else { +vErrors.push(err54); +} +errors++; +} +if(data12.names === undefined){ +const err55 = {instancePath:instancePath+"/migrations",schemaPath:"#/properties/migrations/required",keyword:"required",params:{missingProperty: "names"},message:"must have required property '"+"names"+"'"}; +if(vErrors === null){ +vErrors = [err55]; +} +else { +vErrors.push(err55); +} +errors++; +} +if(data12.compatibleWithPrevious === undefined){ +const err56 = {instancePath:instancePath+"/migrations",schemaPath:"#/properties/migrations/required",keyword:"required",params:{missingProperty: "compatibleWithPrevious"},message:"must have required property '"+"compatibleWithPrevious"+"'"}; +if(vErrors === null){ +vErrors = [err56]; +} +else { +vErrors.push(err56); +} +errors++; +} +for(const key3 in data12){ +if(!(((((key3 === "first") || (key3 === "last")) || (key3 === "count")) || (key3 === "names")) || (key3 === "compatibleWithPrevious"))){ +const err57 = {instancePath:instancePath+"/migrations",schemaPath:"#/properties/migrations/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key3},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err57]; +} +else { +vErrors.push(err57); +} +errors++; +} +} +if(data12.first !== undefined){ +let data13 = data12.first; +if(typeof data13 === "string"){ +if(!pattern6.test(data13)){ +const err58 = {instancePath:instancePath+"/migrations/first",schemaPath:"#/$defs/MigrationName/pattern",keyword:"pattern",params:{pattern: "^[0-9]{4}_[a-z0-9_]+$"},message:"must match pattern \""+"^[0-9]{4}_[a-z0-9_]+$"+"\""}; +if(vErrors === null){ +vErrors = [err58]; +} +else { +vErrors.push(err58); +} +errors++; +} +} +else { +const err59 = {instancePath:instancePath+"/migrations/first",schemaPath:"#/$defs/MigrationName/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err59]; +} +else { +vErrors.push(err59); +} +errors++; +} +} +if(data12.last !== undefined){ +let data14 = data12.last; +if(typeof data14 === "string"){ +if(!pattern6.test(data14)){ +const err60 = {instancePath:instancePath+"/migrations/last",schemaPath:"#/$defs/MigrationName/pattern",keyword:"pattern",params:{pattern: "^[0-9]{4}_[a-z0-9_]+$"},message:"must match pattern \""+"^[0-9]{4}_[a-z0-9_]+$"+"\""}; +if(vErrors === null){ +vErrors = [err60]; +} +else { +vErrors.push(err60); +} +errors++; +} +} +else { +const err61 = {instancePath:instancePath+"/migrations/last",schemaPath:"#/$defs/MigrationName/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err61]; +} +else { +vErrors.push(err61); +} +errors++; +} +} +if(data12.count !== undefined){ +let data15 = data12.count; +if(!(((typeof data15 == "number") && (!(data15 % 1) && !isNaN(data15))) && (isFinite(data15)))){ +const err62 = {instancePath:instancePath+"/migrations/count",schemaPath:"#/properties/migrations/properties/count/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err62]; +} +else { +vErrors.push(err62); +} +errors++; +} +if((typeof data15 == "number") && (isFinite(data15))){ +if(data15 < 1 || isNaN(data15)){ +const err63 = {instancePath:instancePath+"/migrations/count",schemaPath:"#/properties/migrations/properties/count/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"}; +if(vErrors === null){ +vErrors = [err63]; +} +else { +vErrors.push(err63); +} +errors++; +} +} +} +if(data12.names !== undefined){ +let data16 = data12.names; +if(Array.isArray(data16)){ +if(data16.length < 1){ +const err64 = {instancePath:instancePath+"/migrations/names",schemaPath:"#/properties/migrations/properties/names/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"}; +if(vErrors === null){ +vErrors = [err64]; +} +else { +vErrors.push(err64); +} +errors++; +} +const len0 = data16.length; +for(let i0=0; i0 1){ +outer0: +for(;i1--;){ +for(j0 = i1; j0--;){ +if(func0(data16[i1], data16[j0])){ +const err67 = {instancePath:instancePath+"/migrations/names",schemaPath:"#/properties/migrations/properties/names/uniqueItems",keyword:"uniqueItems",params:{i: i1, j: j0},message:"must NOT have duplicate items (items ## "+j0+" and "+i1+" are identical)"}; +if(vErrors === null){ +vErrors = [err67]; +} +else { +vErrors.push(err67); +} +errors++; +break outer0; +} +} +} +} +} +else { +const err68 = {instancePath:instancePath+"/migrations/names",schemaPath:"#/properties/migrations/properties/names/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err68]; +} +else { +vErrors.push(err68); +} +errors++; +} +} +if(data12.compatibleWithPrevious !== undefined){ +if(typeof data12.compatibleWithPrevious !== "boolean"){ +const err69 = {instancePath:instancePath+"/migrations/compatibleWithPrevious",schemaPath:"#/properties/migrations/properties/compatibleWithPrevious/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err69]; +} +else { +vErrors.push(err69); +} +errors++; +} +} +} +else { +const err70 = {instancePath:instancePath+"/migrations",schemaPath:"#/properties/migrations/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err70]; +} +else { +vErrors.push(err70); +} +errors++; +} +} +if(data.durableObjects !== undefined){ +let data19 = data.durableObjects; +if(data19 && typeof data19 == "object" && !Array.isArray(data19)){ +if(data19.bindings === undefined){ +const err71 = {instancePath:instancePath+"/durableObjects",schemaPath:"#/properties/durableObjects/required",keyword:"required",params:{missingProperty: "bindings"},message:"must have required property '"+"bindings"+"'"}; +if(vErrors === null){ +vErrors = [err71]; +} +else { +vErrors.push(err71); +} +errors++; +} +if(data19.lifecycleChange === undefined){ +const err72 = {instancePath:instancePath+"/durableObjects",schemaPath:"#/properties/durableObjects/required",keyword:"required",params:{missingProperty: "lifecycleChange"},message:"must have required property '"+"lifecycleChange"+"'"}; +if(vErrors === null){ +vErrors = [err72]; +} +else { +vErrors.push(err72); +} +errors++; +} +for(const key4 in data19){ +if(!((key4 === "bindings") || (key4 === "lifecycleChange"))){ +const err73 = {instancePath:instancePath+"/durableObjects",schemaPath:"#/properties/durableObjects/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key4},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err73]; +} +else { +vErrors.push(err73); +} +errors++; +} +} +if(data19.bindings !== undefined){ +let data20 = data19.bindings; +if(Array.isArray(data20)){ +const len1 = data20.length; +for(let i2=0; i2 1){ +const indices0 = {}; +for(;i4--;){ +let item0 = data28[i4]; +if(typeof item0 !== "string"){ +continue; +} +if(typeof indices0[item0] == "number"){ +j1 = indices0[item0]; +const err95 = {instancePath:instancePath+"/runtime/compatibilityFlags",schemaPath:"#/properties/runtime/properties/compatibilityFlags/uniqueItems",keyword:"uniqueItems",params:{i: i4, j: j1},message:"must NOT have duplicate items (items ## "+j1+" and "+i4+" are identical)"}; +if(vErrors === null){ +vErrors = [err95]; +} +else { +vErrors.push(err95); +} +errors++; +break; +} +indices0[item0] = i4; +} +} +} +else { +const err96 = {instancePath:instancePath+"/runtime/compatibilityFlags",schemaPath:"#/properties/runtime/properties/compatibilityFlags/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err96]; +} +else { +vErrors.push(err96); +} +errors++; +} +} +} +else { +const err97 = {instancePath:instancePath+"/runtime",schemaPath:"#/properties/runtime/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err97]; +} +else { +vErrors.push(err97); +} +errors++; +} +} +if(data.toolchain !== undefined){ +let data30 = data.toolchain; +if(data30 && typeof data30 == "object" && !Array.isArray(data30)){ +for(const key7 in data30){ +let data31 = data30[key7]; +if(typeof data31 === "string"){ +if(func3(data31) > 64){ +const err98 = {instancePath:instancePath+"/toolchain/" + key7.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/toolchain/additionalProperties/maxLength",keyword:"maxLength",params:{limit: 64},message:"must NOT have more than 64 characters"}; +if(vErrors === null){ +vErrors = [err98]; +} +else { +vErrors.push(err98); +} +errors++; +} +} +else { +const err99 = {instancePath:instancePath+"/toolchain/" + key7.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/toolchain/additionalProperties/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err99]; +} +else { +vErrors.push(err99); +} +errors++; +} +} +} +else { +const err100 = {instancePath:instancePath+"/toolchain",schemaPath:"#/properties/toolchain/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err100]; +} +else { +vErrors.push(err100); +} +errors++; +} +} +if(data.theme !== undefined){ +let data32 = data.theme; +if(data32 && typeof data32 == "object" && !Array.isArray(data32)){ +if(data32.name === undefined){ +const err101 = {instancePath:instancePath+"/theme",schemaPath:"#/properties/theme/required",keyword:"required",params:{missingProperty: "name"},message:"must have required property '"+"name"+"'"}; +if(vErrors === null){ +vErrors = [err101]; +} +else { +vErrors.push(err101); +} +errors++; +} +if(data32.version === undefined){ +const err102 = {instancePath:instancePath+"/theme",schemaPath:"#/properties/theme/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"}; +if(vErrors === null){ +vErrors = [err102]; +} +else { +vErrors.push(err102); +} +errors++; +} +if(data32.blockTypes === undefined){ +const err103 = {instancePath:instancePath+"/theme",schemaPath:"#/properties/theme/required",keyword:"required",params:{missingProperty: "blockTypes"},message:"must have required property '"+"blockTypes"+"'"}; +if(vErrors === null){ +vErrors = [err103]; +} +else { +vErrors.push(err103); +} +errors++; +} +if(data32.settingsSha256 === undefined){ +const err104 = {instancePath:instancePath+"/theme",schemaPath:"#/properties/theme/required",keyword:"required",params:{missingProperty: "settingsSha256"},message:"must have required property '"+"settingsSha256"+"'"}; +if(vErrors === null){ +vErrors = [err104]; +} +else { +vErrors.push(err104); +} +errors++; +} +if(data32.designSha256 === undefined){ +const err105 = {instancePath:instancePath+"/theme",schemaPath:"#/properties/theme/required",keyword:"required",params:{missingProperty: "designSha256"},message:"must have required property '"+"designSha256"+"'"}; +if(vErrors === null){ +vErrors = [err105]; +} +else { +vErrors.push(err105); +} +errors++; +} +for(const key8 in data32){ +if(!(((((key8 === "name") || (key8 === "version")) || (key8 === "blockTypes")) || (key8 === "settingsSha256")) || (key8 === "designSha256"))){ +const err106 = {instancePath:instancePath+"/theme",schemaPath:"#/properties/theme/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key8},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err106]; +} +else { +vErrors.push(err106); +} +errors++; +} +} +if(data32.name !== undefined){ +let data33 = data32.name; +if(typeof data33 === "string"){ +if(!pattern14.test(data33)){ +const err107 = {instancePath:instancePath+"/theme/name",schemaPath:"#/properties/theme/properties/name/pattern",keyword:"pattern",params:{pattern: "^[a-z][a-z0-9-]{0,63}$"},message:"must match pattern \""+"^[a-z][a-z0-9-]{0,63}$"+"\""}; +if(vErrors === null){ +vErrors = [err107]; +} +else { +vErrors.push(err107); +} +errors++; +} +} +else { +const err108 = {instancePath:instancePath+"/theme/name",schemaPath:"#/properties/theme/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err108]; +} +else { +vErrors.push(err108); +} +errors++; +} +} +if(data32.version !== undefined){ +let data34 = data32.version; +if(typeof data34 === "string"){ +if(func3(data34) > 64){ +const err109 = {instancePath:instancePath+"/theme/version",schemaPath:"#/properties/theme/properties/version/maxLength",keyword:"maxLength",params:{limit: 64},message:"must NOT have more than 64 characters"}; +if(vErrors === null){ +vErrors = [err109]; +} +else { +vErrors.push(err109); +} +errors++; +} +} +else { +const err110 = {instancePath:instancePath+"/theme/version",schemaPath:"#/properties/theme/properties/version/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err110]; +} +else { +vErrors.push(err110); +} +errors++; +} +} +if(data32.blockTypes !== undefined){ +let data35 = data32.blockTypes; +if(Array.isArray(data35)){ +const len3 = data35.length; +for(let i5=0; i5 1){ +const indices1 = {}; +for(;i6--;){ +let item1 = data35[i6]; +if(typeof item1 !== "string"){ +continue; +} +if(typeof indices1[item1] == "number"){ +j2 = indices1[item1]; +const err112 = {instancePath:instancePath+"/theme/blockTypes",schemaPath:"#/properties/theme/properties/blockTypes/uniqueItems",keyword:"uniqueItems",params:{i: i6, j: j2},message:"must NOT have duplicate items (items ## "+j2+" and "+i6+" are identical)"}; +if(vErrors === null){ +vErrors = [err112]; +} +else { +vErrors.push(err112); +} +errors++; +break; +} +indices1[item1] = i6; +} +} +} +else { +const err113 = {instancePath:instancePath+"/theme/blockTypes",schemaPath:"#/properties/theme/properties/blockTypes/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err113]; +} +else { +vErrors.push(err113); +} +errors++; +} +} +if(data32.settingsSha256 !== undefined){ +let data37 = data32.settingsSha256; +if(typeof data37 === "string"){ +if(!pattern15.test(data37)){ +const err114 = {instancePath:instancePath+"/theme/settingsSha256",schemaPath:"#/$defs/Sha256Hex/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; +if(vErrors === null){ +vErrors = [err114]; +} +else { +vErrors.push(err114); +} +errors++; +} +} +else { +const err115 = {instancePath:instancePath+"/theme/settingsSha256",schemaPath:"#/$defs/Sha256Hex/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err115]; +} +else { +vErrors.push(err115); +} +errors++; +} +} +if(data32.designSha256 !== undefined){ +let data38 = data32.designSha256; +if(typeof data38 === "string"){ +if(!pattern15.test(data38)){ +const err116 = {instancePath:instancePath+"/theme/designSha256",schemaPath:"#/$defs/Sha256Hex/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; +if(vErrors === null){ +vErrors = [err116]; +} +else { +vErrors.push(err116); +} +errors++; +} +} +else { +const err117 = {instancePath:instancePath+"/theme/designSha256",schemaPath:"#/$defs/Sha256Hex/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err117]; +} +else { +vErrors.push(err117); +} +errors++; +} +} +} +else { +const err118 = {instancePath:instancePath+"/theme",schemaPath:"#/properties/theme/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err118]; +} +else { +vErrors.push(err118); +} +errors++; +} +} +if(data.skills !== undefined){ +let data39 = data.skills; +if(Array.isArray(data39)){ +const len4 = data39.length; +for(let i7=0; i7 1){ +outer1: +for(;i9--;){ +for(j3 = i9; j3--;){ +if(func0(data49[i9], data49[j3])){ +const err138 = {instancePath:instancePath+"/requiredScopes",schemaPath:"#/properties/requiredScopes/uniqueItems",keyword:"uniqueItems",params:{i: i9, j: j3},message:"must NOT have duplicate items (items ## "+j3+" and "+i9+" are identical)"}; +if(vErrors === null){ +vErrors = [err138]; +} +else { +vErrors.push(err138); +} +errors++; +break outer1; +} +} +} +} +} +else { +const err139 = {instancePath:instancePath+"/requiredScopes",schemaPath:"#/properties/requiredScopes/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err139]; +} +else { +vErrors.push(err139); +} +errors++; +} +} +if(data.optionalScopes !== undefined){ +let data51 = data.optionalScopes; +if(Array.isArray(data51)){ +const len6 = data51.length; +for(let i10=0; i10 1){ +outer2: +for(;i11--;){ +for(j4 = i11; j4--;){ +if(func0(data51[i11], data51[j4])){ +const err142 = {instancePath:instancePath+"/optionalScopes",schemaPath:"#/properties/optionalScopes/uniqueItems",keyword:"uniqueItems",params:{i: i11, j: j4},message:"must NOT have duplicate items (items ## "+j4+" and "+i11+" are identical)"}; +if(vErrors === null){ +vErrors = [err142]; +} +else { +vErrors.push(err142); +} +errors++; +break outer2; +} +} +} +} +} +else { +const err143 = {instancePath:instancePath+"/optionalScopes",schemaPath:"#/properties/optionalScopes/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err143]; +} +else { +vErrors.push(err143); +} +errors++; +} +} +if(data.requiredCapabilities !== undefined){ +let data53 = data.requiredCapabilities; +if(Array.isArray(data53)){ +const len7 = data53.length; +for(let i12=0; i12 1){ +outer3: +for(;i13--;){ +for(j5 = i13; j5--;){ +if(func0(data53[i13], data53[j5])){ +const err146 = {instancePath:instancePath+"/requiredCapabilities",schemaPath:"#/properties/requiredCapabilities/uniqueItems",keyword:"uniqueItems",params:{i: i13, j: j5},message:"must NOT have duplicate items (items ## "+j5+" and "+i13+" are identical)"}; +if(vErrors === null){ +vErrors = [err146]; +} +else { +vErrors.push(err146); +} +errors++; +break outer3; +} +} +} +} +} +else { +const err147 = {instancePath:instancePath+"/requiredCapabilities",schemaPath:"#/properties/requiredCapabilities/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err147]; +} +else { +vErrors.push(err147); +} +errors++; +} +} +if(data.optionalCapabilities !== undefined){ +let data55 = data.optionalCapabilities; +if(Array.isArray(data55)){ +const len8 = data55.length; +for(let i14=0; i14 1){ +outer4: +for(;i15--;){ +for(j6 = i15; j6--;){ +if(func0(data55[i15], data55[j6])){ +const err150 = {instancePath:instancePath+"/optionalCapabilities",schemaPath:"#/properties/optionalCapabilities/uniqueItems",keyword:"uniqueItems",params:{i: i15, j: j6},message:"must NOT have duplicate items (items ## "+j6+" and "+i15+" are identical)"}; +if(vErrors === null){ +vErrors = [err150]; +} +else { +vErrors.push(err150); +} +errors++; +break outer4; +} +} +} +} +} +else { +const err151 = {instancePath:instancePath+"/optionalCapabilities",schemaPath:"#/properties/optionalCapabilities/type",keyword:"type",params:{type: "array"},message:"must be array"}; +if(vErrors === null){ +vErrors = [err151]; +} +else { +vErrors.push(err151); +} +errors++; +} +} +if(data.starter !== undefined){ +let data57 = data.starter; +const _errs138 = errors; +let valid51 = false; +let passing0 = null; +const _errs139 = errors; +if(data57 && typeof data57 == "object" && !Array.isArray(data57)){ +if(data57.kind === undefined){ +const err152 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/0/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"}; +if(vErrors === null){ +vErrors = [err152]; +} +else { +vErrors.push(err152); +} +errors++; +} +for(const key12 in data57){ +if(!(key12 === "kind")){ +const err153 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/0/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key12},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err153]; +} +else { +vErrors.push(err153); +} +errors++; +} +} +if(data57.kind !== undefined){ +let data58 = data57.kind; +if(typeof data58 !== "string"){ +const err154 = {instancePath:instancePath+"/starter/kind",schemaPath:"#/properties/starter/oneOf/0/properties/kind/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err154]; +} +else { +vErrors.push(err154); +} +errors++; +} +if(!(data58 === "admin-api")){ +const err155 = {instancePath:instancePath+"/starter/kind",schemaPath:"#/properties/starter/oneOf/0/properties/kind/enum",keyword:"enum",params:{allowedValues: schema31.properties.starter.oneOf[0].properties.kind.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err155]; +} +else { +vErrors.push(err155); +} +errors++; +} +} +} +else { +const err156 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/0/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err156]; +} +else { +vErrors.push(err156); +} +errors++; +} +var _valid0 = _errs139 === errors; +if(_valid0){ +valid51 = true; +passing0 = 0; +var props0 = true; +} +const _errs144 = errors; +if(data57 && typeof data57 == "object" && !Array.isArray(data57)){ +if(data57.kind === undefined){ +const err157 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/1/required",keyword:"required",params:{missingProperty: "kind"},message:"must have required property '"+"kind"+"'"}; +if(vErrors === null){ +vErrors = [err157]; +} +else { +vErrors.push(err157); +} +errors++; +} +if(data57.path === undefined){ +const err158 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/1/required",keyword:"required",params:{missingProperty: "path"},message:"must have required property '"+"path"+"'"}; +if(vErrors === null){ +vErrors = [err158]; +} +else { +vErrors.push(err158); +} +errors++; +} +if(data57.bytes === undefined){ +const err159 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/1/required",keyword:"required",params:{missingProperty: "bytes"},message:"must have required property '"+"bytes"+"'"}; +if(vErrors === null){ +vErrors = [err159]; +} +else { +vErrors.push(err159); +} +errors++; +} +if(data57.sha256 === undefined){ +const err160 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/1/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"}; +if(vErrors === null){ +vErrors = [err160]; +} +else { +vErrors.push(err160); +} +errors++; +} +if(data57.siteId === undefined){ +const err161 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/1/required",keyword:"required",params:{missingProperty: "siteId"},message:"must have required property '"+"siteId"+"'"}; +if(vErrors === null){ +vErrors = [err161]; +} +else { +vErrors.push(err161); +} +errors++; +} +for(const key13 in data57){ +if(!(((((key13 === "kind") || (key13 === "path")) || (key13 === "bytes")) || (key13 === "sha256")) || (key13 === "siteId"))){ +const err162 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/1/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key13},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err162]; +} +else { +vErrors.push(err162); +} +errors++; +} +} +if(data57.kind !== undefined){ +let data59 = data57.kind; +if(typeof data59 !== "string"){ +const err163 = {instancePath:instancePath+"/starter/kind",schemaPath:"#/properties/starter/oneOf/1/properties/kind/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err163]; +} +else { +vErrors.push(err163); +} +errors++; +} +if(!(data59 === "bundle")){ +const err164 = {instancePath:instancePath+"/starter/kind",schemaPath:"#/properties/starter/oneOf/1/properties/kind/enum",keyword:"enum",params:{allowedValues: schema31.properties.starter.oneOf[1].properties.kind.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err164]; +} +else { +vErrors.push(err164); +} +errors++; +} +} +if(data57.path !== undefined){ +let data60 = data57.path; +if(typeof data60 === "string"){ +if(!pattern32.test(data60)){ +const err165 = {instancePath:instancePath+"/starter/path",schemaPath:"#/properties/starter/oneOf/1/properties/path/pattern",keyword:"pattern",params:{pattern: "^starter/[A-Za-z0-9._-]+$"},message:"must match pattern \""+"^starter/[A-Za-z0-9._-]+$"+"\""}; +if(vErrors === null){ +vErrors = [err165]; +} +else { +vErrors.push(err165); +} +errors++; +} +} +else { +const err166 = {instancePath:instancePath+"/starter/path",schemaPath:"#/properties/starter/oneOf/1/properties/path/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err166]; +} +else { +vErrors.push(err166); +} +errors++; +} +} +if(data57.bytes !== undefined){ +let data61 = data57.bytes; +if(!(((typeof data61 == "number") && (!(data61 % 1) && !isNaN(data61))) && (isFinite(data61)))){ +const err167 = {instancePath:instancePath+"/starter/bytes",schemaPath:"#/properties/starter/oneOf/1/properties/bytes/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err167]; +} +else { +vErrors.push(err167); +} +errors++; +} +if((typeof data61 == "number") && (isFinite(data61))){ +if(data61 < 1 || isNaN(data61)){ +const err168 = {instancePath:instancePath+"/starter/bytes",schemaPath:"#/properties/starter/oneOf/1/properties/bytes/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"}; +if(vErrors === null){ +vErrors = [err168]; +} +else { +vErrors.push(err168); +} +errors++; +} +} +} +if(data57.sha256 !== undefined){ +let data62 = data57.sha256; +if(typeof data62 === "string"){ +if(!pattern15.test(data62)){ +const err169 = {instancePath:instancePath+"/starter/sha256",schemaPath:"#/$defs/Sha256Hex/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; +if(vErrors === null){ +vErrors = [err169]; +} +else { +vErrors.push(err169); +} +errors++; +} +} +else { +const err170 = {instancePath:instancePath+"/starter/sha256",schemaPath:"#/$defs/Sha256Hex/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err170]; +} +else { +vErrors.push(err170); +} +errors++; +} +} +if(data57.siteId !== undefined){ +let data63 = data57.siteId; +if(typeof data63 === "string"){ +if(!pattern34.test(data63)){ +const err171 = {instancePath:instancePath+"/starter/siteId",schemaPath:"#/$defs/SiteId/pattern",keyword:"pattern",params:{pattern: "^site_[A-Za-z0-9]{6,32}$"},message:"must match pattern \""+"^site_[A-Za-z0-9]{6,32}$"+"\""}; +if(vErrors === null){ +vErrors = [err171]; +} +else { +vErrors.push(err171); +} +errors++; +} +} +else { +const err172 = {instancePath:instancePath+"/starter/siteId",schemaPath:"#/$defs/SiteId/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err172]; +} +else { +vErrors.push(err172); +} +errors++; +} +} +} +else { +const err173 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf/1/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err173]; +} +else { +vErrors.push(err173); +} +errors++; +} +var _valid0 = _errs144 === errors; +if(_valid0 && valid51){ +valid51 = false; +passing0 = [passing0, 1]; +} +else { +if(_valid0){ +valid51 = true; +passing0 = 1; +if(props0 !== true){ +props0 = true; +} +} +} +if(!valid51){ +const err174 = {instancePath:instancePath+"/starter",schemaPath:"#/properties/starter/oneOf",keyword:"oneOf",params:{passingSchemas: passing0},message:"must match exactly one schema in oneOf"}; +if(vErrors === null){ +vErrors = [err174]; +} +else { +vErrors.push(err174); +} +errors++; +} +else { +errors = _errs138; +if(vErrors !== null){ +if(_errs138){ +vErrors.length = _errs138; +} +else { +vErrors = null; +} +} +} +} +if(data.features !== undefined){ +let data64 = data.features; +if(data64 && typeof data64 == "object" && !Array.isArray(data64)){ +if(data64.adminSitesAcceptsSiteId === undefined){ +const err175 = {instancePath:instancePath+"/features",schemaPath:"#/properties/features/required",keyword:"required",params:{missingProperty: "adminSitesAcceptsSiteId"},message:"must have required property '"+"adminSitesAcceptsSiteId"+"'"}; +if(vErrors === null){ +vErrors = [err175]; +} +else { +vErrors.push(err175); +} +errors++; +} +if(data64.healthReportsVersion === undefined){ +const err176 = {instancePath:instancePath+"/features",schemaPath:"#/properties/features/required",keyword:"required",params:{missingProperty: "healthReportsVersion"},message:"must have required property '"+"healthReportsVersion"+"'"}; +if(vErrors === null){ +vErrors = [err176]; +} +else { +vErrors.push(err176); +} +errors++; +} +if(data64.mediaTransformVar === undefined){ +const err177 = {instancePath:instancePath+"/features",schemaPath:"#/properties/features/required",keyword:"required",params:{missingProperty: "mediaTransformVar"},message:"must have required property '"+"mediaTransformVar"+"'"}; +if(vErrors === null){ +vErrors = [err177]; +} +else { +vErrors.push(err177); +} +errors++; +} +for(const key14 in data64){ +if(!(((key14 === "adminSitesAcceptsSiteId") || (key14 === "healthReportsVersion")) || (key14 === "mediaTransformVar"))){ +if(typeof data64[key14] !== "boolean"){ +const err178 = {instancePath:instancePath+"/features/" + key14.replace(/~/g, "~0").replace(/\//g, "~1"),schemaPath:"#/properties/features/additionalProperties/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err178]; +} +else { +vErrors.push(err178); +} +errors++; +} +} +} +if(data64.adminSitesAcceptsSiteId !== undefined){ +if(typeof data64.adminSitesAcceptsSiteId !== "boolean"){ +const err179 = {instancePath:instancePath+"/features/adminSitesAcceptsSiteId",schemaPath:"#/properties/features/properties/adminSitesAcceptsSiteId/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err179]; +} +else { +vErrors.push(err179); +} +errors++; +} +} +if(data64.healthReportsVersion !== undefined){ +if(typeof data64.healthReportsVersion !== "boolean"){ +const err180 = {instancePath:instancePath+"/features/healthReportsVersion",schemaPath:"#/properties/features/properties/healthReportsVersion/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err180]; +} +else { +vErrors.push(err180); +} +errors++; +} +} +if(data64.mediaTransformVar !== undefined){ +if(typeof data64.mediaTransformVar !== "boolean"){ +const err181 = {instancePath:instancePath+"/features/mediaTransformVar",schemaPath:"#/properties/features/properties/mediaTransformVar/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err181]; +} +else { +vErrors.push(err181); +} +errors++; +} +} +} +else { +const err182 = {instancePath:instancePath+"/features",schemaPath:"#/properties/features/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err182]; +} +else { +vErrors.push(err182); +} +errors++; +} +} +if(data.planVersion !== undefined){ +let data69 = data.planVersion; +if(!(((typeof data69 == "number") && (!(data69 % 1) && !isNaN(data69))) && (isFinite(data69)))){ +const err183 = {instancePath:instancePath+"/planVersion",schemaPath:"#/properties/planVersion/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err183]; +} +else { +vErrors.push(err183); +} +errors++; +} +if((typeof data69 == "number") && (isFinite(data69))){ +if(data69 < 1 || isNaN(data69)){ +const err184 = {instancePath:instancePath+"/planVersion",schemaPath:"#/properties/planVersion/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"}; +if(vErrors === null){ +vErrors = [err184]; +} +else { +vErrors.push(err184); +} +errors++; +} +} +} +if(data.signing !== undefined){ +let data70 = data.signing; +if(data70 && typeof data70 == "object" && !Array.isArray(data70)){ +if(data70.keyId === undefined){ +const err185 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/required",keyword:"required",params:{missingProperty: "keyId"},message:"must have required property '"+"keyId"+"'"}; +if(vErrors === null){ +vErrors = [err185]; +} +else { +vErrors.push(err185); +} +errors++; +} +if(data70.algorithm === undefined){ +const err186 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/required",keyword:"required",params:{missingProperty: "algorithm"},message:"must have required property '"+"algorithm"+"'"}; +if(vErrors === null){ +vErrors = [err186]; +} +else { +vErrors.push(err186); +} +errors++; +} +if(data70.signature === undefined){ +const err187 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/required",keyword:"required",params:{missingProperty: "signature"},message:"must have required property '"+"signature"+"'"}; +if(vErrors === null){ +vErrors = [err187]; +} +else { +vErrors.push(err187); +} +errors++; +} +for(const key15 in data70){ +if(!(((key15 === "keyId") || (key15 === "algorithm")) || (key15 === "signature"))){ +const err188 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key15},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err188]; +} +else { +vErrors.push(err188); +} +errors++; +} +} +if(data70.keyId !== undefined){ +let data71 = data70.keyId; +if(typeof data71 === "string"){ +if(!pattern35.test(data71)){ +const err189 = {instancePath:instancePath+"/signing/keyId",schemaPath:"#/properties/signing/properties/keyId/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9][a-z0-9-]{2,63}$"},message:"must match pattern \""+"^[a-z0-9][a-z0-9-]{2,63}$"+"\""}; +if(vErrors === null){ +vErrors = [err189]; +} +else { +vErrors.push(err189); +} +errors++; +} +} +else { +const err190 = {instancePath:instancePath+"/signing/keyId",schemaPath:"#/properties/signing/properties/keyId/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err190]; +} +else { +vErrors.push(err190); +} +errors++; +} +} +if(data70.algorithm !== undefined){ +let data72 = data70.algorithm; +if(typeof data72 !== "string"){ +const err191 = {instancePath:instancePath+"/signing/algorithm",schemaPath:"#/properties/signing/properties/algorithm/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err191]; +} +else { +vErrors.push(err191); +} +errors++; +} +if(!(data72 === "Ed25519")){ +const err192 = {instancePath:instancePath+"/signing/algorithm",schemaPath:"#/properties/signing/properties/algorithm/enum",keyword:"enum",params:{allowedValues: schema31.properties.signing.properties.algorithm.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err192]; +} +else { +vErrors.push(err192); +} +errors++; +} +} +if(data70.signature !== undefined){ +let data73 = data70.signature; +if(typeof data73 !== "string"){ +const err193 = {instancePath:instancePath+"/signing/signature",schemaPath:"#/properties/signing/properties/signature/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err193]; +} +else { +vErrors.push(err193); +} +errors++; +} +if(!((data73 === "manifest.sig") || (data73 === "release-statement.sig"))){ +const err194 = {instancePath:instancePath+"/signing/signature",schemaPath:"#/properties/signing/properties/signature/enum",keyword:"enum",params:{allowedValues: schema31.properties.signing.properties.signature.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err194]; +} +else { +vErrors.push(err194); +} +errors++; +} +} +} +else { +const err195 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err195]; +} +else { +vErrors.push(err195); +} +errors++; +} +} +} +else { +const err196 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err196]; +} +else { +vErrors.push(err196); +} +errors++; +} +validate20.errors = vErrors; +return errors === 0; +} +validate20.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; + +export const validateReleaseStatement = validate27; +const schema56 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://cloudhal.dev/schemas/v1/release-statement.schema.json","title":"ReleaseStatement","description":"cloudhal.release-statement@1 (RFC-0046): the small canonical document a Cloudhal release signs, published as the release asset release-statement.json next to release-statement.sig (a raw 64-byte Ed25519 signature over the exact bytes, at most 4096 of them). It names the version, channel, commit and key id and pins the SHA-256 and size of manifest.json and of the tarball, so a remote deployer verifies one small file and then checks every other asset by digest. Canonical bytes: keys sorted recursively, 2-space indent, a trailing newline; a consumer verifies the bytes it downloaded and never re-serialises them. Portable on purpose: it references common only for SchemaVersion and Timestamp.","type":"object","additionalProperties":false,"required":["schemaVersion","format","version","channel","securityRelease","commit","createdAt","signing","manifest","tarball"],"properties":{"schemaVersion":{"$ref":"#/$defs/SchemaVersion"},"format":{"type":"string","enum":["cloudhal.release-statement@1"]},"version":{"$ref":"#/$defs/CloudhalVersion"},"channel":{"type":"string","enum":["stable","next"]},"securityRelease":{"type":"boolean"},"commit":{"type":"string","pattern":"^[0-9a-f]{40}$"},"createdAt":{"$ref":"#/$defs/Timestamp"},"signing":{"type":"object","additionalProperties":false,"required":["keyId","algorithm","signature"],"properties":{"keyId":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{2,63}$"},"algorithm":{"type":"string","enum":["Ed25519"]},"signature":{"type":"string","enum":["release-statement.sig"]}}},"manifest":{"$ref":"#/$defs/StatementAsset"},"tarball":{"$ref":"#/$defs/StatementAsset"}},"$defs":{"CloudhalVersion":{"type":"string","pattern":"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"},"SchemaVersion":{"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]},"Sha256Hex":{"type":"string","pattern":"^[0-9a-f]{64}$"},"StatementAsset":{"type":"object","additionalProperties":false,"required":["name","sha256","bytes"],"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"},"sha256":{"$ref":"#/$defs/Sha256Hex"},"bytes":{"type":"integer","minimum":1,"maximum":104857600}}},"Timestamp":{"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}}}; +const schema57 = {"type":"integer","description":"Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.","enum":[1]}; +const schema58 = {"type":"string","pattern":"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"}; +const schema59 = {"type":"string","description":"RFC 3339 / ISO 8601 instant in UTC.","format":"date-time"}; +const schema60 = {"type":"object","additionalProperties":false,"required":["name","sha256","bytes"],"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"},"sha256":{"$ref":"#/$defs/Sha256Hex"},"bytes":{"type":"integer","minimum":1,"maximum":104857600}}}; +const schema61 = {"type":"string","pattern":"^[0-9a-f]{64}$"}; +const pattern39 = new RegExp("^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$", "u"); + +function validate28(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ +let vErrors = null; +let errors = 0; +const evaluated0 = validate28.evaluated; +if(evaluated0.dynamicProps){ +evaluated0.props = undefined; +} +if(evaluated0.dynamicItems){ +evaluated0.items = undefined; +} +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.name === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "name"},message:"must have required property '"+"name"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.sha256 === undefined){ +const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "sha256"},message:"must have required property '"+"sha256"+"'"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +if(data.bytes === undefined){ +const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "bytes"},message:"must have required property '"+"bytes"+"'"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +for(const key0 in data){ +if(!(((key0 === "name") || (key0 === "sha256")) || (key0 === "bytes"))){ +const err3 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +} +if(data.name !== undefined){ +let data0 = data.name; +if(typeof data0 === "string"){ +if(!pattern39.test(data0)){ +const err4 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/pattern",keyword:"pattern",params:{pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"},message:"must match pattern \""+"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"+"\""}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +} +} +else { +const err5 = {instancePath:instancePath+"/name",schemaPath:"#/properties/name/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +} +if(data.sha256 !== undefined){ +let data1 = data.sha256; +if(typeof data1 === "string"){ +if(!pattern15.test(data1)){ +const err6 = {instancePath:instancePath+"/sha256",schemaPath:"#/$defs/Sha256Hex/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{64}$"},message:"must match pattern \""+"^[0-9a-f]{64}$"+"\""}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +} +else { +const err7 = {instancePath:instancePath+"/sha256",schemaPath:"#/$defs/Sha256Hex/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err7]; +} +else { +vErrors.push(err7); +} +errors++; +} +} +if(data.bytes !== undefined){ +let data2 = data.bytes; +if(!(((typeof data2 == "number") && (!(data2 % 1) && !isNaN(data2))) && (isFinite(data2)))){ +const err8 = {instancePath:instancePath+"/bytes",schemaPath:"#/properties/bytes/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err8]; +} +else { +vErrors.push(err8); +} +errors++; +} +if((typeof data2 == "number") && (isFinite(data2))){ +if(data2 > 104857600 || isNaN(data2)){ +const err9 = {instancePath:instancePath+"/bytes",schemaPath:"#/properties/bytes/maximum",keyword:"maximum",params:{comparison: "<=", limit: 104857600},message:"must be <= 104857600"}; +if(vErrors === null){ +vErrors = [err9]; +} +else { +vErrors.push(err9); +} +errors++; +} +if(data2 < 1 || isNaN(data2)){ +const err10 = {instancePath:instancePath+"/bytes",schemaPath:"#/properties/bytes/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"}; +if(vErrors === null){ +vErrors = [err10]; +} +else { +vErrors.push(err10); +} +errors++; +} +} +} +} +else { +const err11 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err11]; +} +else { +vErrors.push(err11); +} +errors++; +} +validate28.errors = vErrors; +return errors === 0; +} +validate28.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; + + +function validate27(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){ +/*# sourceURL="https://cloudhal.dev/schemas/v1/release-statement.schema.json" */; +let vErrors = null; +let errors = 0; +const evaluated0 = validate27.evaluated; +if(evaluated0.dynamicProps){ +evaluated0.props = undefined; +} +if(evaluated0.dynamicItems){ +evaluated0.items = undefined; +} +if(data && typeof data == "object" && !Array.isArray(data)){ +if(data.schemaVersion === undefined){ +const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schemaVersion"},message:"must have required property '"+"schemaVersion"+"'"}; +if(vErrors === null){ +vErrors = [err0]; +} +else { +vErrors.push(err0); +} +errors++; +} +if(data.format === undefined){ +const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "format"},message:"must have required property '"+"format"+"'"}; +if(vErrors === null){ +vErrors = [err1]; +} +else { +vErrors.push(err1); +} +errors++; +} +if(data.version === undefined){ +const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "version"},message:"must have required property '"+"version"+"'"}; +if(vErrors === null){ +vErrors = [err2]; +} +else { +vErrors.push(err2); +} +errors++; +} +if(data.channel === undefined){ +const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "channel"},message:"must have required property '"+"channel"+"'"}; +if(vErrors === null){ +vErrors = [err3]; +} +else { +vErrors.push(err3); +} +errors++; +} +if(data.securityRelease === undefined){ +const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "securityRelease"},message:"must have required property '"+"securityRelease"+"'"}; +if(vErrors === null){ +vErrors = [err4]; +} +else { +vErrors.push(err4); +} +errors++; +} +if(data.commit === undefined){ +const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "commit"},message:"must have required property '"+"commit"+"'"}; +if(vErrors === null){ +vErrors = [err5]; +} +else { +vErrors.push(err5); +} +errors++; +} +if(data.createdAt === undefined){ +const err6 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "createdAt"},message:"must have required property '"+"createdAt"+"'"}; +if(vErrors === null){ +vErrors = [err6]; +} +else { +vErrors.push(err6); +} +errors++; +} +if(data.signing === undefined){ +const err7 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "signing"},message:"must have required property '"+"signing"+"'"}; +if(vErrors === null){ +vErrors = [err7]; +} +else { +vErrors.push(err7); +} +errors++; +} +if(data.manifest === undefined){ +const err8 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "manifest"},message:"must have required property '"+"manifest"+"'"}; +if(vErrors === null){ +vErrors = [err8]; +} +else { +vErrors.push(err8); +} +errors++; +} +if(data.tarball === undefined){ +const err9 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "tarball"},message:"must have required property '"+"tarball"+"'"}; +if(vErrors === null){ +vErrors = [err9]; +} +else { +vErrors.push(err9); +} +errors++; +} +for(const key0 in data){ +if(!(func1.call(schema56.properties, key0))){ +const err10 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err10]; +} +else { +vErrors.push(err10); +} +errors++; +} +} +if(data.schemaVersion !== undefined){ +let data0 = data.schemaVersion; +if(!(((typeof data0 == "number") && (!(data0 % 1) && !isNaN(data0))) && (isFinite(data0)))){ +const err11 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/type",keyword:"type",params:{type: "integer"},message:"must be integer"}; +if(vErrors === null){ +vErrors = [err11]; +} +else { +vErrors.push(err11); +} +errors++; +} +if(!(data0 === 1)){ +const err12 = {instancePath:instancePath+"/schemaVersion",schemaPath:"#/$defs/SchemaVersion/enum",keyword:"enum",params:{allowedValues: schema57.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err12]; +} +else { +vErrors.push(err12); +} +errors++; +} +} +if(data.format !== undefined){ +let data1 = data.format; +if(typeof data1 !== "string"){ +const err13 = {instancePath:instancePath+"/format",schemaPath:"#/properties/format/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err13]; +} +else { +vErrors.push(err13); +} +errors++; +} +if(!(data1 === "cloudhal.release-statement@1")){ +const err14 = {instancePath:instancePath+"/format",schemaPath:"#/properties/format/enum",keyword:"enum",params:{allowedValues: schema56.properties.format.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err14]; +} +else { +vErrors.push(err14); +} +errors++; +} +} +if(data.version !== undefined){ +let data2 = data.version; +if(typeof data2 === "string"){ +if(!pattern4.test(data2)){ +const err15 = {instancePath:instancePath+"/version",schemaPath:"#/$defs/CloudhalVersion/pattern",keyword:"pattern",params:{pattern: "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"},message:"must match pattern \""+"^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?$"+"\""}; +if(vErrors === null){ +vErrors = [err15]; +} +else { +vErrors.push(err15); +} +errors++; +} +} +else { +const err16 = {instancePath:instancePath+"/version",schemaPath:"#/$defs/CloudhalVersion/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err16]; +} +else { +vErrors.push(err16); +} +errors++; +} +} +if(data.channel !== undefined){ +let data3 = data.channel; +if(typeof data3 !== "string"){ +const err17 = {instancePath:instancePath+"/channel",schemaPath:"#/properties/channel/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err17]; +} +else { +vErrors.push(err17); +} +errors++; +} +if(!((data3 === "stable") || (data3 === "next"))){ +const err18 = {instancePath:instancePath+"/channel",schemaPath:"#/properties/channel/enum",keyword:"enum",params:{allowedValues: schema56.properties.channel.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err18]; +} +else { +vErrors.push(err18); +} +errors++; +} +} +if(data.securityRelease !== undefined){ +if(typeof data.securityRelease !== "boolean"){ +const err19 = {instancePath:instancePath+"/securityRelease",schemaPath:"#/properties/securityRelease/type",keyword:"type",params:{type: "boolean"},message:"must be boolean"}; +if(vErrors === null){ +vErrors = [err19]; +} +else { +vErrors.push(err19); +} +errors++; +} +} +if(data.commit !== undefined){ +let data5 = data.commit; +if(typeof data5 === "string"){ +if(!pattern5.test(data5)){ +const err20 = {instancePath:instancePath+"/commit",schemaPath:"#/properties/commit/pattern",keyword:"pattern",params:{pattern: "^[0-9a-f]{40}$"},message:"must match pattern \""+"^[0-9a-f]{40}$"+"\""}; +if(vErrors === null){ +vErrors = [err20]; +} +else { +vErrors.push(err20); +} +errors++; +} +} +else { +const err21 = {instancePath:instancePath+"/commit",schemaPath:"#/properties/commit/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err21]; +} +else { +vErrors.push(err21); +} +errors++; +} +} +if(data.createdAt !== undefined){ +let data6 = data.createdAt; +if(typeof data6 === "string"){ +if(!(formats0.validate(data6))){ +const err22 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/format",keyword:"format",params:{format: "date-time"},message:"must match format \""+"date-time"+"\""}; +if(vErrors === null){ +vErrors = [err22]; +} +else { +vErrors.push(err22); +} +errors++; +} +} +else { +const err23 = {instancePath:instancePath+"/createdAt",schemaPath:"#/$defs/Timestamp/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err23]; +} +else { +vErrors.push(err23); +} +errors++; +} +} +if(data.signing !== undefined){ +let data7 = data.signing; +if(data7 && typeof data7 == "object" && !Array.isArray(data7)){ +if(data7.keyId === undefined){ +const err24 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/required",keyword:"required",params:{missingProperty: "keyId"},message:"must have required property '"+"keyId"+"'"}; +if(vErrors === null){ +vErrors = [err24]; +} +else { +vErrors.push(err24); +} +errors++; +} +if(data7.algorithm === undefined){ +const err25 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/required",keyword:"required",params:{missingProperty: "algorithm"},message:"must have required property '"+"algorithm"+"'"}; +if(vErrors === null){ +vErrors = [err25]; +} +else { +vErrors.push(err25); +} +errors++; +} +if(data7.signature === undefined){ +const err26 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/required",keyword:"required",params:{missingProperty: "signature"},message:"must have required property '"+"signature"+"'"}; +if(vErrors === null){ +vErrors = [err26]; +} +else { +vErrors.push(err26); +} +errors++; +} +for(const key1 in data7){ +if(!(((key1 === "keyId") || (key1 === "algorithm")) || (key1 === "signature"))){ +const err27 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"}; +if(vErrors === null){ +vErrors = [err27]; +} +else { +vErrors.push(err27); +} +errors++; +} +} +if(data7.keyId !== undefined){ +let data8 = data7.keyId; +if(typeof data8 === "string"){ +if(!pattern35.test(data8)){ +const err28 = {instancePath:instancePath+"/signing/keyId",schemaPath:"#/properties/signing/properties/keyId/pattern",keyword:"pattern",params:{pattern: "^[a-z0-9][a-z0-9-]{2,63}$"},message:"must match pattern \""+"^[a-z0-9][a-z0-9-]{2,63}$"+"\""}; +if(vErrors === null){ +vErrors = [err28]; +} +else { +vErrors.push(err28); +} +errors++; +} +} +else { +const err29 = {instancePath:instancePath+"/signing/keyId",schemaPath:"#/properties/signing/properties/keyId/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err29]; +} +else { +vErrors.push(err29); +} +errors++; +} +} +if(data7.algorithm !== undefined){ +let data9 = data7.algorithm; +if(typeof data9 !== "string"){ +const err30 = {instancePath:instancePath+"/signing/algorithm",schemaPath:"#/properties/signing/properties/algorithm/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err30]; +} +else { +vErrors.push(err30); +} +errors++; +} +if(!(data9 === "Ed25519")){ +const err31 = {instancePath:instancePath+"/signing/algorithm",schemaPath:"#/properties/signing/properties/algorithm/enum",keyword:"enum",params:{allowedValues: schema56.properties.signing.properties.algorithm.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err31]; +} +else { +vErrors.push(err31); +} +errors++; +} +} +if(data7.signature !== undefined){ +let data10 = data7.signature; +if(typeof data10 !== "string"){ +const err32 = {instancePath:instancePath+"/signing/signature",schemaPath:"#/properties/signing/properties/signature/type",keyword:"type",params:{type: "string"},message:"must be string"}; +if(vErrors === null){ +vErrors = [err32]; +} +else { +vErrors.push(err32); +} +errors++; +} +if(!(data10 === "release-statement.sig")){ +const err33 = {instancePath:instancePath+"/signing/signature",schemaPath:"#/properties/signing/properties/signature/enum",keyword:"enum",params:{allowedValues: schema56.properties.signing.properties.signature.enum},message:"must be equal to one of the allowed values"}; +if(vErrors === null){ +vErrors = [err33]; +} +else { +vErrors.push(err33); +} +errors++; +} +} +} +else { +const err34 = {instancePath:instancePath+"/signing",schemaPath:"#/properties/signing/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err34]; +} +else { +vErrors.push(err34); +} +errors++; +} +} +if(data.manifest !== undefined){ +if(!(validate28(data.manifest, {instancePath:instancePath+"/manifest",parentData:data,parentDataProperty:"manifest",rootData,dynamicAnchors}))){ +vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors); +errors = vErrors.length; +} +} +if(data.tarball !== undefined){ +if(!(validate28(data.tarball, {instancePath:instancePath+"/tarball",parentData:data,parentDataProperty:"tarball",rootData,dynamicAnchors}))){ +vErrors = vErrors === null ? validate28.errors : vErrors.concat(validate28.errors); +errors = vErrors.length; +} +} +} +else { +const err35 = {instancePath,schemaPath:"#/type",keyword:"type",params:{type: "object"},message:"must be object"}; +if(vErrors === null){ +vErrors = [err35]; +} +else { +vErrors.push(err35); +} +errors++; +} +validate27.errors = vErrors; +return errors === 0; +} +validate27.evaluated = {"props":true,"dynamicProps":false,"dynamicItems":false}; + diff --git a/packages/release-verify/src/index.ts b/packages/release-verify/src/index.ts new file mode 100644 index 00000000..9b3294d3 --- /dev/null +++ b/packages/release-verify/src/index.ts @@ -0,0 +1,31 @@ +/** + * `@cloudhal/release-verify` (RFC-0046 §11a): the one verifier of Cloudhal release bundles for + * hosts, tools and the CLI. Worker-safe: WebCrypto Ed25519, `DecompressionStream`, and an + * incremental SHA-256 (`crypto.DigestStream`, else @noble/hashes). The second factor, the GitHub + * build attestation, is the Node-only subpath `@cloudhal/release-verify/attestation`. + */ +export { type IncrementalSha256, incrementalSha256, sha256Hex, verifyEd25519 } from "./crypto.ts"; +export type { ListedPart, Worker } from "./documents.ts"; +export { ERROR_CODES, ReleaseVerifyError, type ReleaseVerifyErrorCode } from "./errors.ts"; +export type { ReleaseManifest } from "./generated/release-manifest.ts"; +export type { ReleaseStatement } from "./generated/release-statement.ts"; +export { type KeyStatus, PINNED_KEYS, type ReleaseKey, type ReleaseKeys } from "./keys.ts"; +export { + channelOf, + LIMITS, + MANIFEST_FILE, + SIGNATURE_FILE, + STATEMENT_FILE, + sigstoreName, + tarballName, +} from "./limits.ts"; +export { foldPath, type PathKind, unsafePathReason } from "./paths.ts"; +export { IMPLIED_SCOPES, missingScopes } from "./scopes.ts"; +export type { ByteSource } from "./tar.ts"; +export { RELEASE_REPOSITORY, releaseAssetNames, releaseAssetUrls } from "./urls.ts"; +export { + type VerifiedEntry, + type VerifiedRelease, + type VerifyReleaseInput, + verifyRelease, +} from "./verify.ts"; diff --git a/packages/release-verify/src/keys.ts b/packages/release-verify/src/keys.ts new file mode 100644 index 00000000..b3f0a09b --- /dev/null +++ b/packages/release-verify/src/keys.ts @@ -0,0 +1,54 @@ +/** + * The release keys this package trusts (RFC-0046 §9, §11a): the table of + * docs/security/release-signing-keys.md, kept equal to it by test/keys.test.ts. + * + * `PINNED_KEYS` is frozen into each published version and cannot learn about a revocation by + * itself. A caller may only reduce trust: `revoked` withdraws key ids. A caller that trusts a key + * outside this table passes a whole `keys` map, which is a reviewed change on its side. Once the + * signed `keys.json` of #226 exists, it feeds the same reduce-only path. + */ +import { refuse } from "./errors.ts"; + +export type KeyStatus = "active" | "retiring" | "revoked"; + +export interface ReleaseKey { + /** The raw 32-byte Ed25519 public key, base64. */ + readonly publicKey: string; + readonly status: KeyStatus; +} + +export type ReleaseKeys = Readonly>; + +export const PINNED_KEYS: ReleaseKeys = Object.freeze({ + "cloudhal-release-2026a": Object.freeze({ + publicKey: "7az3pPc4oT0AOfRcdfOZZQkM6XAGHo2jXEdDZ1qEVR8=", + status: "active", + }), +}); + +/** The raw public key `keyId` verifies with, or a refusal (unknown, revoked, malformed). */ +export function trustedKey( + keys: ReleaseKeys, + revoked: readonly string[], + keyId: string, +): Uint8Array { + const key = Object.hasOwn(keys, keyId) ? keys[keyId] : undefined; + if (!key) return refuse("key-unknown", `no trusted release key ${keyId}`); + if (key.status === "revoked" || revoked.includes(keyId)) + return refuse("key-revoked", `release key ${keyId} is revoked`); + const raw = fromBase64(key.publicKey); + if (raw?.length !== 32) + return refuse("key-unknown", `the trusted key ${keyId} is not a 32-byte Ed25519 key`); + return raw; +} + +function fromBase64(s: string): Uint8Array | undefined { + try { + const bin = atob(s); + const out = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); + return out; + } catch { + return undefined; + } +} diff --git a/packages/release-verify/src/limits.ts b/packages/release-verify/src/limits.ts new file mode 100644 index 00000000..54de1a2d --- /dev/null +++ b/packages/release-verify/src/limits.ts @@ -0,0 +1,30 @@ +/** + * RFC-0046 §1 asset names and §7's normative limits. `scripts/release-bundle/manifest.ts` (the + * producer) holds the same numbers; test/limits.test.ts keeps the two equal. + */ +export const LIMITS = { + manifestBytes: 1_900_000, + statementBytes: 4096, + tarballBytes: 104_857_600, + partBytes: 64 * 1024 * 1024, + assetBytes: 25 * 1024 * 1024, + assetsPerWorker: 20_000, + tarEntries: 30_000, +} as const; + +/** The largest decompressed tar a release may hold: every part at its limit is far beyond it. */ +export const MAX_TAR_BYTES = 1024 * 1024 * 1024; + +/** The longest path, in UTF-8 bytes, a manifest listing or a tar entry may carry (§11a). */ +export const MAX_PATH_BYTES = 512; + +export const STATEMENT_FILE = "release-statement.json"; +export const SIGNATURE_FILE = "release-statement.sig"; +export const MANIFEST_FILE = "manifest.json"; +export const tarballName = (version: string): string => `cloudhal-release-${version}.tar.gz`; +export const sigstoreName = (version: string): string => + `cloudhal-release-${version}.sigstore.json`; + +/** `X.Y.Z-
` ⇔ `next` (§2 invariant 3, §6). */
+export const channelOf = (version: string): "stable" | "next" =>
+  version.includes("-") ? "next" : "stable";
diff --git a/packages/release-verify/src/paths.ts b/packages/release-verify/src/paths.ts
new file mode 100644
index 00000000..eae13402
--- /dev/null
+++ b/packages/release-verify/src/paths.ts
@@ -0,0 +1,57 @@
+/**
+ * Entry-path safety (RFC-0046 §11a): every path in the manifest's listings and every tar entry
+ * name is checked before any byte of that entry is used. One pass over the characters plus a
+ * `split("/")`, no regular expression, so the check is linear in the path's length (AGENTS.md
+ * rule 13). A failure is the refusal `entry-path`.
+ */
+import { refuse } from "./errors.ts";
+import { MAX_PATH_BYTES } from "./limits.ts";
+
+/** `module` and `entry` paths are relative; an `asset` path is `/` (§3). */
+export type PathKind = "module" | "asset" | "entry";
+
+const utf8 = new TextEncoder();
+
+/** Why `path` is unsafe as a `kind` path, or undefined when it is safe. */
+export function unsafePathReason(path: string, kind: PathKind): string | undefined {
+  if (utf8.encode(path).length > MAX_PATH_BYTES) return `longer than ${MAX_PATH_BYTES} bytes`;
+  for (let i = 0; i < path.length; i++) {
+    const c = path.charCodeAt(i);
+    if (c < 0x20 || c === 0x7f) return "a control character";
+    if (c === 0x5c) return "a backslash";
+  }
+  let rel = path;
+  if (kind === "asset") {
+    if (!path.startsWith("/")) return "an asset path that does not start with /";
+    rel = path.slice(1);
+  } else if (path.startsWith("/")) return "a leading /";
+  for (const segment of rel.split("/")) {
+    if (segment === "") return "an empty segment";
+    if (segment === "." || segment === "..") return `a ${segment} segment`;
+  }
+  return undefined;
+}
+
+/** Refuses `path` (named `where` in the message) unless it is safe as a `kind` path. */
+export function checkPath(path: string, kind: PathKind, where: string): void {
+  const reason = unsafePathReason(path, kind);
+  if (reason) refuse("entry-path", `${where}: ${JSON.stringify(path)} has ${reason}`);
+}
+
+/**
+ * Case folding as case-insensitive file systems apply it, approximately: NFC, then upper and
+ * lower case (so "ß" and "SS" meet). Two listed paths that fold equal would collide on disk.
+ */
+export const foldPath = (path: string): string => path.normalize("NFC").toUpperCase().toLowerCase();
+
+/** Refuses two `paths` that are equal after case folding. */
+export function checkNoFoldedDuplicates(paths: Iterable, where: string): void {
+  const seen = new Map();
+  for (const p of paths) {
+    const f = foldPath(p);
+    const other = seen.get(f);
+    if (other !== undefined)
+      refuse("entry-path", `${where}: ${JSON.stringify(other)} and ${JSON.stringify(p)} collide`);
+    seen.set(f, p);
+  }
+}
diff --git a/packages/release-verify/src/scopes.ts b/packages/release-verify/src/scopes.ts
new file mode 100644
index 00000000..b08ce3fc
--- /dev/null
+++ b/packages/release-verify/src/scopes.ts
@@ -0,0 +1,21 @@
+/**
+ * The scopes rule of RFC-0046 §3: scope ids are compared exactly, with one exception. Workers
+ * Admin (`workers-scripts.admin`) covers Workers Editor (`workers-scripts.edit`), which manifests
+ * up to and including v0.9.0 name; an Editor cannot create the Workers a release installs (#225).
+ */
+export const IMPLIED_SCOPES: Readonly> = Object.freeze({
+  "workers-scripts.admin": Object.freeze(["workers-scripts.edit"]),
+});
+
+/** The manifest's `requiredScopes` that `granted` does not hold, in the manifest's order. */
+export function missingScopes(
+  manifest: { readonly requiredScopes: readonly string[] },
+  granted: Iterable,
+): string[] {
+  const held = new Set();
+  for (const s of granted) {
+    held.add(s);
+    for (const implied of IMPLIED_SCOPES[s] ?? []) held.add(implied);
+  }
+  return manifest.requiredScopes.filter((s) => !held.has(s));
+}
diff --git a/packages/release-verify/src/tar.ts b/packages/release-verify/src/tar.ts
new file mode 100644
index 00000000..aeefbfd9
--- /dev/null
+++ b/packages/release-verify/src/tar.ts
@@ -0,0 +1,278 @@
+/**
+ * Streams a release tarball (RFC-0046 §7) without holding it whole. It accepts exactly what the
+ * producer (`scripts/release-bundle/tar.ts`) writes and refuses everything else (`tar-format`):
+ *
+ * - gzip with the fixed header (no flags, mtime 0, OS 255), decompressed by `DecompressionStream`;
+ * - ustar headers with a valid checksum, `ustar\0` + `00`, mode 0644, uid/gid 0, no owner names,
+ *   link name or prefix, the release's mtime, and typeflag `0` (or `x` with a single PAX `path`
+ *   record for the entry that follows); links, directories and every other type are refused;
+ * - two or more zero blocks, and nothing but zeros, at the end.
+ */
+import { ReleaseVerifyError, refuse } from "./errors.ts";
+import { LIMITS, MAX_TAR_BYTES } from "./limits.ts";
+
+export type ByteSource = ReadableStream | AsyncIterable | Uint8Array;
+
+/** The chunks of `source`, whatever its form. */
+export async function* chunksOf(source: ByteSource): AsyncGenerator {
+  if (source instanceof Uint8Array) {
+    yield source;
+    return;
+  }
+  if (typeof (source as ReadableStream).getReader === "function") {
+    const reader = (source as ReadableStream).getReader();
+    try {
+      for (;;) {
+        const { done, value } = await reader.read();
+        if (done) return;
+        yield value;
+      }
+    } finally {
+      await reader.cancel().catch(() => {});
+    }
+  }
+  yield* source as AsyncIterable;
+}
+
+const GZIP_FIXED = [0x1f, 0x8b, 8, 0, 0, 0, 0, 0];
+
+/** Refuses a gzip stream whose first ten bytes are not the fixed header (§7). */
+export function checkGzipHeader(head: Uint8Array): void {
+  if (head.length < 10) refuse("tar-format", "the tarball is not gzip");
+  for (let i = 0; i < GZIP_FIXED.length; i++)
+    if (head[i] !== GZIP_FIXED[i]) refuse("tar-format", "the tarball's gzip header is not fixed");
+  if (head[9] !== 255) refuse("tar-format", "the tarball's gzip OS byte is not 255");
+}
+
+/** The decompressed bytes of a gzip `source` whose header is the fixed one. */
+export async function* gunzip(
+  source: AsyncIterable,
+  max: number = MAX_TAR_BYTES,
+): AsyncGenerator {
+  const ds = new DecompressionStream("gzip");
+  const writer = ds.writable.getWriter();
+  const pump = (async () => {
+    const head: number[] = [];
+    try {
+      for await (const chunk of source) {
+        if (head.length < 10) {
+          for (const b of chunk.subarray(0, 10 - head.length)) head.push(b);
+          if (head.length === 10) checkGzipHeader(Uint8Array.from(head));
+        }
+        await writer.write(chunk);
+      }
+      if (head.length < 10) checkGzipHeader(Uint8Array.from(head));
+      await writer.close();
+    } catch (e) {
+      await writer.abort(e).catch(() => {});
+      throw e;
+    }
+  })();
+  pump.catch(() => {});
+  const reader = ds.readable.getReader();
+  let total = 0;
+  try {
+    for (;;) {
+      let next: ReadableStreamReadResult;
+      try {
+        next = await reader.read();
+      } catch (e) {
+        await pump; // a header refusal from the pump wins over the decompressor's error
+        throw e;
+      }
+      if (next.done) break;
+      total += next.value.length;
+      if (total > max)
+        refuse("limit-exceeded", `the tarball decompresses to more than ${max} bytes`);
+      yield next.value;
+    }
+    await pump;
+  } catch (e) {
+    if (e instanceof ReleaseVerifyError) throw e;
+    refuse("tar-format", `the tarball is not valid gzip (${(e as Error).message})`);
+  } finally {
+    await reader.cancel().catch(() => {});
+  }
+}
+
+/** A FIFO of byte chunks that hands out exact lengths. */
+class ByteQueue {
+  private chunks: Uint8Array[] = [];
+  private offset = 0;
+  length = 0;
+  push(chunk: Uint8Array): void {
+    if (chunk.length === 0) return;
+    this.chunks.push(chunk);
+    this.length += chunk.length;
+  }
+  take(n: number): Uint8Array {
+    const out = new Uint8Array(n);
+    let at = 0;
+    while (at < n) {
+      const head = this.chunks[0] as Uint8Array;
+      const part = head.subarray(this.offset, this.offset + (n - at));
+      out.set(part, at);
+      at += part.length;
+      this.offset += part.length;
+      if (this.offset === head.length) {
+        this.chunks.shift();
+        this.offset = 0;
+      }
+    }
+    this.length -= n;
+    return out;
+  }
+}
+
+const dec = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
+
+function str(h: Uint8Array, at: number, len: number): string {
+  const s = h.subarray(at, at + len);
+  const end = s.indexOf(0);
+  try {
+    return dec.decode(end >= 0 ? s.subarray(0, end) : s);
+  } catch {
+    return refuse("tar-format", "a tar header field is not UTF-8");
+  }
+}
+
+/** An octal field: 1 to 11 digits, NUL- or space-terminated (anchored and bounded: linear). */
+function num(h: Uint8Array, at: number, len: number, what: string): number {
+  const s = str(h, at, len).trim();
+  if (!/^[0-7]{1,11}$/.test(s)) refuse("tar-format", `a tar header has a bad ${what} field`);
+  return Number.parseInt(s, 8);
+}
+
+function checksumOk(h: Uint8Array): boolean {
+  let sum = 0;
+  for (let i = 0; i < 512; i++) sum += i >= 148 && i < 156 ? 0x20 : (h[i] as number);
+  return num(h, 148, 8, "checksum") === sum;
+}
+
+interface Header {
+  readonly name: string;
+  readonly size: number;
+  readonly flag: "0" | "x";
+}
+
+function checkHeader(h: Uint8Array, mtime: number): Header {
+  if (!checksumOk(h)) refuse("tar-format", "a tar header has a bad checksum");
+  if (str(h, 257, 6) !== "ustar" || h[262] !== 0 || str(h, 263, 2) !== "00")
+    refuse("tar-format", "a tar header is not ustar");
+  const flag = h[156] === 0 ? "0" : String.fromCharCode(h[156] as number);
+  if (flag !== "0" && flag !== "x") refuse("tar-format", `a tar entry has type ${flag}`);
+  if (num(h, 100, 8, "mode") !== 0o644) refuse("tar-format", "a tar entry's mode is not 0644");
+  if (num(h, 108, 8, "uid") !== 0 || num(h, 116, 8, "gid") !== 0)
+    refuse("tar-format", "a tar entry's uid or gid is not 0");
+  if (num(h, 136, 12, "mtime") !== mtime)
+    refuse("tar-format", "a tar entry's mtime is not the release's createdAt");
+  if (str(h, 157, 100) || str(h, 265, 32) || str(h, 297, 32) || str(h, 345, 155))
+    refuse("tar-format", "a tar entry has a link name, an owner name or a prefix");
+  return { name: str(h, 0, 100), size: num(h, 124, 12, "size"), flag };
+}
+
+/** The `path` of a PAX header holding exactly one `path` record. */
+function paxPath(data: Uint8Array): string {
+  let text: string;
+  try {
+    text = dec.decode(data);
+  } catch {
+    return refuse("tar-format", "a PAX header is not UTF-8");
+  }
+  const sp = text.indexOf(" ");
+  const len = Number(text.slice(0, sp));
+  if (sp < 1 || len !== data.length || !text.endsWith("\n") || !text.startsWith(`${len} path=`))
+    refuse("tar-format", "a PAX header is not a single path record");
+  return text.slice(sp + 6, -1);
+}
+
+export interface TarEntry {
+  readonly path: string;
+  readonly bytes: Uint8Array;
+}
+
+const padded = (size: number) => Math.ceil(size / 512) * 512;
+const isZero = (b: Uint8Array) => b.every((x) => x === 0);
+
+/** A tar stream read in exact lengths. */
+class TarReader {
+  private readonly q = new ByteQueue();
+  private readonly it: AsyncIterator;
+  constructor(source: AsyncIterable) {
+    this.it = source[Symbol.asyncIterator]();
+  }
+  /** Buffers at least `n` bytes; false when the stream ends first. */
+  async fill(n: number): Promise {
+    while (this.q.length < n) {
+      const next = await this.it.next();
+      if (next.done) return false;
+      this.q.push(next.value);
+    }
+    return true;
+  }
+  /** Exactly `n` bytes, or the refusal of a truncated archive. */
+  async take(n: number): Promise {
+    if (!(await this.fill(n))) refuse("tar-format", "the tar archive is truncated");
+    return this.q.take(n);
+  }
+  /** Everything left in the stream must be zeros, and with `zeros` already read, 1024 or more. */
+  async checkEnd(zeros: number): Promise {
+    let trailing = zeros;
+    do {
+      const rest = this.q.take(this.q.length);
+      if (!isZero(rest)) refuse("tar-format", "the tar archive has data after its end");
+      trailing += rest.length;
+    } while (await this.fill(1));
+    if (trailing < 1024) refuse("tar-format", "the tar archive does not end with two zero blocks");
+  }
+}
+
+/** The next file header with its PAX path applied, or null at the first zero block. */
+async function nextHeader(
+  r: TarReader,
+  mtime: number,
+): Promise<{ path: string; size: number } | null> {
+  let pax: string | null = null;
+  for (;;) {
+    const h = await r.take(512);
+    if (isZero(h)) {
+      if (pax !== null) refuse("tar-format", "a PAX header ends the archive");
+      return null;
+    }
+    const { name, size, flag } = checkHeader(h, mtime);
+    if (size > LIMITS.partBytes)
+      refuse("limit-exceeded", `a tar entry is above ${LIMITS.partBytes} bytes`);
+    if (flag === "0") {
+      if (pax !== null && pax.slice(-100) !== name)
+        refuse("tar-format", `${pax}: the ustar name is not the end of the PAX path`);
+      return { path: pax ?? name, size };
+    }
+    if (pax !== null) refuse("tar-format", "two PAX headers in a row");
+    if (size > 4096) refuse("tar-format", "a PAX header is too large");
+    pax = paxPath((await r.take(padded(size))).subarray(0, size));
+  }
+}
+
+/**
+ * The entries of a decompressed tar stream, in archive order. `admit(path, size)` runs on each
+ * entry's header, before its bytes are buffered, so an unlisted or oversized entry is refused
+ * without reading it into memory.
+ */
+export async function* readTar(
+  source: AsyncIterable,
+  mtime: number,
+  admit: (path: string, size: number) => void,
+): AsyncGenerator {
+  const r = new TarReader(source);
+  let count = 0;
+  for (;;) {
+    const header = await nextHeader(r, mtime);
+    if (!header) break;
+    if (++count > LIMITS.tarEntries)
+      refuse("limit-exceeded", `the tarball has more than ${LIMITS.tarEntries} entries`);
+    admit(header.path, header.size);
+    const data = await r.take(padded(header.size));
+    yield { path: header.path, bytes: data.subarray(0, header.size) };
+  }
+  await r.checkEnd(512);
+}
diff --git a/packages/release-verify/src/urls.ts b/packages/release-verify/src/urls.ts
new file mode 100644
index 00000000..60bb9817
--- /dev/null
+++ b/packages/release-verify/src/urls.ts
@@ -0,0 +1,37 @@
+/**
+ * Where a release's five assets (RFC-0046 §1) are downloaded from. Downloading is discovery only,
+ * never a trust factor: `verifyRelease` decides.
+ */
+import {
+  MANIFEST_FILE,
+  SIGNATURE_FILE,
+  STATEMENT_FILE,
+  sigstoreName,
+  tarballName,
+} from "./limits.ts";
+
+export const RELEASE_REPOSITORY = "cloudhal-dev/cloudhal";
+
+/** `CloudhalVersion` of the contracts: anchored, bounded alternatives, no overlapping runs. */
+const VERSION =
+  /^(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})\.(0|[1-9][0-9]{0,8})(-[0-9A-Za-z.-]{1,64})?$/;
+
+/** The five assets of `version`, in §1's order (tarball, manifest, statement, signature, Sigstore). */
+export function releaseAssetNames(version: string): string[] {
+  if (!VERSION.test(version)) throw new TypeError(`not a Cloudhal version: ${version}`);
+  return [
+    tarballName(version),
+    MANIFEST_FILE,
+    STATEMENT_FILE,
+    SIGNATURE_FILE,
+    sigstoreName(version),
+  ];
+}
+
+/** `{ name, url }` of each asset of `version` on the GitHub release `v`. */
+export function releaseAssetUrls(version: string): { name: string; url: string }[] {
+  return releaseAssetNames(version).map((name) => ({
+    name,
+    url: `https://github.com/${RELEASE_REPOSITORY}/releases/download/v${version}/${name}`,
+  }));
+}
diff --git a/packages/release-verify/src/verify.ts b/packages/release-verify/src/verify.ts
new file mode 100644
index 00000000..8bd92cb3
--- /dev/null
+++ b/packages/release-verify/src/verify.ts
@@ -0,0 +1,157 @@
+/**
+ * `verifyRelease` (RFC-0046 §2, §11a): the six steps of §2 in order, with no soft-fail anywhere.
+ *
+ *   1. sizes: the statement at most 4096 bytes, the signature exactly 64;
+ *   2. Ed25519 over the exact statement bytes with a trusted key for `signing.keyId`;
+ *   3. the statement is for the requested version and channel (invariant 7);
+ *   4. the statement's schema and invariants 1, 2, 3 and 6;
+ *   5. the manifest: size and SHA-256 equal the statement's, schema, invariants 4 and 5, the
+ *      signature form, `planVersion`, and every listing (paths, §7 limits);
+ *   6. the tarball, read once: size and SHA-256 equal the statement's.
+ *
+ * `entries()` then reads the tarball a second time and yields each entry only after its path is
+ * safe and its size and SHA-256 equal its listing; it throws on an unlisted entry, on anything
+ * outside §7's tar format and, at the end, on a listed entry that never came. A consumer may upload
+ * entries while it iterates; it must not activate a deployment until the iteration has finished.
+ */
+
+import { incrementalSha256, sha256Hex, verifyEd25519 } from "./crypto.ts";
+import {
+  checkManifestDoc,
+  checkRequested,
+  checkStatementDoc,
+  type ListedPart,
+  listedParts,
+  parseJson,
+  type Worker,
+} from "./documents.ts";
+import { refuse } from "./errors.ts";
+import type { ReleaseManifest } from "./generated/release-manifest.ts";
+import type { ReleaseStatement } from "./generated/release-statement.ts";
+import { PINNED_KEYS, type ReleaseKeys, trustedKey } from "./keys.ts";
+import { LIMITS } from "./limits.ts";
+import { checkPath } from "./paths.ts";
+import { type ByteSource, chunksOf, gunzip, readTar } from "./tar.ts";
+
+export interface VerifyReleaseInput {
+  /** What the consumer asked for; `channel` defaults to the one the version implies. */
+  readonly request: { readonly version: string; readonly channel?: "stable" | "next" };
+  /** The exact downloaded bytes; nothing is re-serialised. */
+  readonly statement: Uint8Array;
+  readonly signature: Uint8Array;
+  readonly manifest: Uint8Array;
+  /** Opens the tarball; called twice (the digest pass, then `entries()`). */
+  readonly tarball: () => ByteSource | Promise;
+  /** The highest `planVersion` the caller supports. */
+  readonly planVersion: number;
+  /** The trusted keys (default `PINNED_KEYS`); see keys.ts. */
+  readonly keys?: ReleaseKeys;
+  /** Key ids to distrust on top of `keys` (reduce-only). */
+  readonly revoked?: readonly string[];
+}
+
+export interface VerifiedEntry {
+  readonly worker: Worker | null;
+  readonly kind: "module" | "asset" | "starter";
+  /** The module path, the asset path (`/x`), or `starter/`. */
+  readonly path: string;
+  /** The tar path (`site/modules/x.mjs`). */
+  readonly tarPath: string;
+  /** A module's type from the manifest (`esm`, `cjs`, `wasm`, `text`, `data`). */
+  readonly type?: string;
+  readonly bytes: Uint8Array;
+}
+
+export interface VerifiedRelease {
+  readonly statement: ReleaseStatement;
+  readonly manifest: ReleaseManifest;
+  entries(): AsyncGenerator;
+}
+
+export async function verifyRelease(input: VerifyReleaseInput): Promise {
+  // 1.
+  if (input.statement.length > LIMITS.statementBytes)
+    refuse("statement-too-large", `the statement is above ${LIMITS.statementBytes} bytes`);
+  if (input.signature.length !== 64)
+    refuse("signature-size", `the signature is ${input.signature.length} bytes, not 64`);
+  // 2.
+  const doc = parseJson(input.statement, "statement-invalid");
+  const keyId = (doc as { signing?: { keyId?: unknown } } | null)?.signing?.keyId;
+  if (typeof keyId !== "string") refuse("statement-invalid", "the statement names no key");
+  const key = trustedKey(input.keys ?? PINNED_KEYS, input.revoked ?? [], keyId);
+  if (!(await verifyEd25519(key, input.statement, input.signature)))
+    refuse("signature-invalid", `the signature does not verify with ${keyId}`);
+  // 3. and 4.
+  checkRequested(doc, input.request.version, input.request.channel);
+  const statement = checkStatementDoc(doc, input.statement.length);
+  // 5.
+  if (
+    input.manifest.length !== statement.manifest.bytes ||
+    (await sha256Hex(input.manifest)) !== statement.manifest.sha256
+  )
+    refuse("manifest-mismatch", "manifest.json is not the file the statement pins");
+  const manifest = checkManifestDoc(
+    parseJson(input.manifest, "manifest-invalid"),
+    statement,
+    input.planVersion,
+  );
+  // 6.
+  await checkTarballDigest(await input.tarball(), statement.tarball);
+  const listed = listedParts(manifest);
+  const mtime = Date.parse(statement.createdAt) / 1000;
+  return {
+    statement,
+    manifest,
+    entries: () => verifiedEntries(input.tarball, listed, mtime),
+  };
+}
+
+async function checkTarballDigest(
+  source: ByteSource,
+  want: { readonly bytes: number; readonly sha256: string },
+): Promise {
+  const h = incrementalSha256();
+  let bytes = 0;
+  for await (const chunk of chunksOf(source)) {
+    bytes += chunk.length;
+    if (bytes > want.bytes) refuse("tarball-mismatch", "the tarball is larger than its statement");
+    await h.update(chunk);
+  }
+  if (bytes !== want.bytes || (await h.digest()) !== want.sha256)
+    refuse("tarball-mismatch", "the tarball is not the file the statement pins");
+}
+
+async function* verifiedEntries(
+  open: VerifyReleaseInput["tarball"],
+  listed: ReadonlyMap,
+  mtime: number,
+): AsyncGenerator {
+  const left = new Map(listed);
+  let previous = "";
+  const admit = (tarPath: string, size: number) => {
+    checkPath(tarPath, "entry", "tar entry");
+    if (tarPath <= previous)
+      refuse("tar-format", `${tarPath}: tar entries are not sorted and unique`);
+    previous = tarPath;
+    const part = left.get(tarPath);
+    if (!part) refuse("entry-unlisted", `${tarPath} is in the tarball but not in the manifest`);
+    if (size !== part.bytes)
+      refuse("entry-mismatch", `${tarPath} is not the size its listing says`);
+  };
+  for await (const e of readTar(gunzip(chunksOf(await open())), mtime, admit)) {
+    const part = left.get(e.path) as ListedPart;
+    if ((await sha256Hex(e.bytes)) !== part.sha256)
+      refuse("entry-mismatch", `${e.path} does not match the manifest's SHA-256`);
+    left.delete(e.path);
+    yield {
+      worker: part.worker,
+      kind: part.kind,
+      path: part.path,
+      tarPath: e.path,
+      ...(part.type !== undefined ? { type: part.type } : {}),
+      bytes: e.bytes,
+    };
+  }
+  const missing = left.keys().next();
+  if (!missing.done) refuse("entry-missing", `${missing.value} is listed but not in the tarball`);
+}
diff --git a/packages/release-verify/test/attestation.test.ts b/packages/release-verify/test/attestation.test.ts
new file mode 100644
index 00000000..f4aa69df
--- /dev/null
+++ b/packages/release-verify/test/attestation.test.ts
@@ -0,0 +1,174 @@
+/**
+ * The attestation subpath (RFC-0046 §11a) with a scripted `gh`: the probe (missing, too old,
+ * lacking a flag, signed out → unavailable), the exact verify arguments, `--bundle` when the
+ * Sigstore asset is there, and every way a verify call can fail.
+ */
+import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
+import os from "node:os";
+import path from "node:path";
+import { afterEach, describe, expect, it } from "vitest";
+import {
+  ATTESTATION_FLAGS,
+  ghVersionOf,
+  MIN_GH_VERSION,
+  type Runner,
+  type RunResult,
+  verifyAttestation,
+  versionAtLeast,
+} from "../src/attestation.ts";
+
+const COMMIT = "6bfe74a17f3a941e68130bb3e05a866fd9f19df5";
+const HELP = `Verify the integrity and provenance of an artifact\n${ATTESTATION_FLAGS.map((f) => `      ${f} string`).join("\n")}\n`;
+const okRun = (stdout = ""): RunResult => ({
+  code: 0,
+  signal: null,
+  timedOut: false,
+  stdout,
+  stderr: "",
+});
+
+/** A scripted gh: `answers` by the first two arguments; records every call. */
+function gh(answers: Record = {}) {
+  const calls: string[][] = [];
+  const run: Runner = async (cmd, args) => {
+    expect(cmd).toBe("gh");
+    calls.push([...args]);
+    const key = args.slice(0, 2).join(" ");
+    const a =
+      answers[key] ??
+      (key === "--version"
+        ? okRun(
+            "gh version 2.102.0 (2026-09-30)\nhttps://github.com/cli/cli/releases/tag/v2.102.0\n",
+          )
+        : key === "attestation verify" && args[2] === "--help"
+          ? okRun(HELP)
+          : okRun());
+    if (a instanceof Error) throw a;
+    return a;
+  };
+  return { run, calls };
+}
+
+const dirs: string[] = [];
+afterEach(() => {
+  for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
+});
+const dir = (withBundle: boolean) => {
+  const d = mkdtempSync(path.join(os.tmpdir(), "attest-"));
+  dirs.push(d);
+  if (withBundle) writeFileSync(path.join(d, "cloudhal-release-0.9.0.sigstore.json"), "{}");
+  return d;
+};
+
+describe("verifyAttestation", () => {
+  it("verifies all three files with the exact flags, and --bundle when the asset is there", async () => {
+    const d = dir(true);
+    const { run, calls } = gh();
+    expect(await verifyAttestation({ dir: d, version: "0.9.0", commit: COMMIT, run })).toEqual({
+      status: "verified",
+    });
+    const verifies = calls.filter((c) => c[0] === "attestation" && c[2] !== "--help");
+    expect(verifies.map((c) => path.basename(c[2] as string))).toEqual([
+      "cloudhal-release-0.9.0.tar.gz",
+      "manifest.json",
+      "release-statement.json",
+    ]);
+    expect(verifies[0]?.slice(3)).toEqual([
+      "--repo",
+      "cloudhal-dev/cloudhal",
+      "--signer-workflow",
+      "cloudhal-dev/cloudhal/.github/workflows/bundle-build.yml",
+      "--source-ref",
+      "refs/heads/main",
+      "--source-digest",
+      COMMIT,
+      "--deny-self-hosted-runners",
+      "--bundle",
+      path.join(d, "cloudhal-release-0.9.0.sigstore.json"),
+    ]);
+    const second = gh();
+    await verifyAttestation({ dir: dir(false), version: "0.9.0", commit: COMMIT, run: second.run });
+    expect(second.calls.some((c) => c.includes("--bundle") && c[2] !== "--help")).toBe(false);
+  });
+
+  it("is unavailable, naming the version it needs, when gh is missing, old, lacking or signed out", async () => {
+    const cases: Record[] = [
+      { "--version": new Error("spawn gh ENOENT") },
+      { "--version": okRun("gh version 2.45.0 (2024-03-04)\n") },
+      { "--version": okRun("gh version 2.67.9 (2025-02-01)\n") },
+      { "attestation verify": okRun("--repo --signer-workflow --bundle") },
+      {
+        "auth status": {
+          code: 1,
+          signal: null,
+          timedOut: false,
+          stdout: "",
+          stderr: "not logged in",
+        },
+      },
+    ];
+    for (const answers of cases) {
+      const { run, calls } = gh(answers);
+      const r = await verifyAttestation({ dir: dir(false), version: "0.9.0", commit: COMMIT, run });
+      expect(r.status, JSON.stringify(answers)).toBe("unavailable");
+      if (!("auth status" in answers))
+        expect((r as { reason: string }).reason).toContain(MIN_GH_VERSION);
+      expect(calls.some((c) => c[0] === "attestation" && c[2] !== "--help")).toBe(false);
+    }
+  });
+
+  it("fails on a non-zero exit, a signal or a timeout, naming the file", async () => {
+    const verify = (r: RunResult): Record => ({ "attestation verify": r });
+    const answers = [
+      { code: 1, signal: null, timedOut: false, stdout: "", stderr: "x\nno matching attestation" },
+      { code: null, signal: "SIGTERM", timedOut: false, stdout: "", stderr: "" },
+      { code: null, signal: "SIGKILL", timedOut: true, stdout: "", stderr: "" },
+    ];
+    const want = ["no matching attestation", "stopped by SIGTERM", "no answer within 60 s"];
+    for (const [i, a] of answers.entries()) {
+      // The probe's `attestation verify --help` must still pass: answer only the real calls.
+      const { run } = gh();
+      const failing: Runner = (cmd, args, t) =>
+        args[0] !== "attestation" || args[2] === "--help"
+          ? run(cmd, args, t)
+          : Promise.resolve(verify(a)["attestation verify"] as RunResult);
+      const r = await verifyAttestation({
+        dir: dir(false),
+        version: "0.9.0",
+        commit: COMMIT,
+        run: failing,
+      });
+      expect(r).toEqual({
+        status: "failed",
+        file: "cloudhal-release-0.9.0.tar.gz",
+        reason: want[i],
+      });
+    }
+  });
+
+  it("refuses a malformed version or commit before running anything", async () => {
+    const { run, calls } = gh();
+    await expect(
+      verifyAttestation({ dir: "/x", version: "../1.0.0", commit: COMMIT, run }),
+    ).rejects.toThrow(TypeError);
+    await expect(
+      verifyAttestation({ dir: "/x", version: "0.9.0", commit: "abc", run }),
+    ).rejects.toThrow(TypeError);
+    await expect(
+      verifyAttestation({ dir: "/x", version: "0.9.0", commit: COMMIT.toUpperCase(), run }),
+    ).rejects.toThrow(TypeError);
+    expect(calls).toEqual([]);
+  });
+});
+
+describe("gh version parsing", () => {
+  it("reads gh's first line and compares numerically", () => {
+    expect(ghVersionOf("gh version 2.102.0 (2026-09-30)\nhttps://…")).toBe("2.102.0");
+    expect(ghVersionOf("something else")).toBeUndefined();
+    expect(versionAtLeast("2.68.0", "2.68.0")).toBe(true);
+    expect(versionAtLeast("2.102.0", "2.68.0")).toBe(true);
+    expect(versionAtLeast("2.67.10", "2.68.0")).toBe(false);
+    expect(versionAtLeast("3.0.0", "2.68.0")).toBe(true);
+    expect(versionAtLeast("x.1.2", "2.68.0")).toBe(false);
+  });
+});
diff --git a/packages/release-verify/test/crypto.test.ts b/packages/release-verify/test/crypto.test.ts
new file mode 100644
index 00000000..9fa17000
--- /dev/null
+++ b/packages/release-verify/test/crypto.test.ts
@@ -0,0 +1,111 @@
+/**
+ * Both SHA-256 paths (RFC-0046 §11a) against the NIST vectors and `node:crypto`: @noble/hashes
+ * (Node has no `crypto.DigestStream`) and the DigestStream adapter, driven here by a stand-in
+ * with workerd's shape. Ed25519 through WebCrypto against node:crypto signatures.
+ */
+import { createHash, generateKeyPairSync, randomBytes, sign } from "node:crypto";
+import { describe, expect, it } from "vitest";
+import {
+  type DigestStreamConstructor,
+  incrementalSha256,
+  sha256Hex,
+  verifyEd25519,
+} from "../src/crypto.ts";
+
+const NIST: [string, string][] = [
+  ["", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"],
+  ["abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"],
+  [
+    "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
+    "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1",
+  ],
+  [
+    "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
+    "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1",
+  ],
+];
+const MILLION_A = "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0";
+
+/** workerd's DigestStream, stood in for by node:crypto: a WritableStream whose digest resolves on close. */
+const FakeDigestStream = class extends WritableStream {
+  readonly digest: Promise;
+  constructor(algorithm: string) {
+    if (algorithm !== "SHA-256") throw new Error(algorithm);
+    const h = createHash("sha256");
+    let done: (b: ArrayBuffer) => void = () => {};
+    const digest = new Promise((r) => {
+      done = r;
+    });
+    super({
+      write: (chunk) => {
+        h.update(chunk);
+      },
+      close: () => {
+        const b = h.digest();
+        done(b.buffer.slice(b.byteOffset, b.byteOffset + b.length));
+      },
+    });
+    this.digest = digest;
+  }
+} as unknown as DigestStreamConstructor;
+
+const paths = [
+  ["@noble/hashes", undefined],
+  ["DigestStream", FakeDigestStream],
+] as const;
+
+async function hashInChunks(
+  bytes: Uint8Array,
+  sizes: number[],
+  ds: DigestStreamConstructor | undefined,
+): Promise {
+  const h = incrementalSha256(ds);
+  let at = 0;
+  let i = 0;
+  while (at < bytes.length) {
+    const n = sizes[i++ % sizes.length] as number;
+    await h.update(bytes.subarray(at, at + n));
+    at += n;
+  }
+  return h.digest();
+}
+
+describe.each(paths)("incremental SHA-256 through %s", (_, ds) => {
+  it("matches the NIST vectors, whole and split", async () => {
+    for (const [msg, want] of NIST) {
+      const b = new TextEncoder().encode(msg);
+      expect(await hashInChunks(b, [b.length || 1], ds)).toBe(want);
+      expect(await hashInChunks(b, [1, 3, 7], ds)).toBe(want);
+    }
+    const million = new Uint8Array(1_000_000).fill(0x61);
+    expect(await hashInChunks(million, [65_536, 1, 4099], ds)).toBe(MILLION_A);
+  });
+
+  it("matches node:crypto on random inputs and splits", async () => {
+    for (let i = 0; i < 20; i++) {
+      const b = randomBytes(1 + Math.floor(Math.random() * 200_000));
+      const sizes = [1 + Math.floor(Math.random() * 5000), 1 + Math.floor(Math.random() * 70)];
+      expect(await hashInChunks(b, sizes, ds)).toBe(createHash("sha256").update(b).digest("hex"));
+    }
+  });
+});
+
+describe("one-shot SHA-256 and Ed25519", () => {
+  it("sha256Hex matches node:crypto", async () => {
+    const b = randomBytes(10_000);
+    expect(await sha256Hex(b)).toBe(createHash("sha256").update(b).digest("hex"));
+  });
+
+  it("verifies node:crypto Ed25519 signatures and refuses anything else", async () => {
+    const { privateKey, publicKey } = generateKeyPairSync("ed25519");
+    const raw = new Uint8Array(publicKey.export({ format: "der", type: "spki" }).subarray(12));
+    const msg = randomBytes(300);
+    const sig = new Uint8Array(sign(null, msg, privateKey));
+    expect(await verifyEd25519(raw, msg, sig)).toBe(true);
+    const bad = new Uint8Array(sig);
+    bad[0] = (bad[0] as number) ^ 1;
+    expect(await verifyEd25519(raw, msg, bad)).toBe(false);
+    expect(await verifyEd25519(raw, randomBytes(300), sig)).toBe(false);
+    expect(await verifyEd25519(new Uint8Array(31), msg, sig)).toBe(false);
+  });
+});
diff --git a/packages/release-verify/test/fixtures/v0.9.0/.gitattributes b/packages/release-verify/test/fixtures/v0.9.0/.gitattributes
new file mode 100644
index 00000000..c9c3256d
--- /dev/null
+++ b/packages/release-verify/test/fixtures/v0.9.0/.gitattributes
@@ -0,0 +1,2 @@
+# Signed release assets: byte-exact, never normalised (their SHA-256 and signature cover these bytes).
+* -text
diff --git a/packages/release-verify/test/fixtures/v0.9.0/manifest.json b/packages/release-verify/test/fixtures/v0.9.0/manifest.json
new file mode 100644
index 00000000..4c776b5d
--- /dev/null
+++ b/packages/release-verify/test/fixtures/v0.9.0/manifest.json
@@ -0,0 +1,5933 @@
+{
+  "bindingTemplates": {
+    "admin": {
+      "bindings": [
+        {
+          "name": "DB",
+          "required": true,
+          "type": "d1"
+        },
+        {
+          "name": "BLOBS",
+          "required": true,
+          "type": "r2_bucket"
+        },
+        {
+          "name": "SITE",
+          "required": true,
+          "type": "service"
+        },
+        {
+          "name": "ASSETS",
+          "required": true,
+          "type": "assets"
+        },
+        {
+          "name": "SCHEDULER_CLOCK",
+          "required": true,
+          "type": "durable_object_namespace"
+        },
+        {
+          "name": "BUILD_ROOMS",
+          "required": true,
+          "type": "durable_object_namespace"
+        },
+        {
+          "name": "ANALYTICS_SQL",
+          "required": false,
+          "type": "analytics_sql"
+        },
+        {
+          "name": "EMAIL",
+          "required": false,
+          "type": "send_email"
+        },
+        {
+          "name": "AI",
+          "required": false,
+          "type": "ai"
+        },
+        {
+          "name": "BROWSER",
+          "required": false,
+          "type": "browser"
+        }
+      ],
+      "cron": "* * * * *",
+      "secrets": [
+        "BETTER_AUTH_SECRET",
+        "MCP_STATE_KEY",
+        "CLOUDHAL_PREVIEW_SECRET",
+        "CLOUDHAL_PURGE_TOKEN",
+        "CLOUDHAL_DEPLOY_TOKEN",
+        "CLOUDHAL_SETUP_TOKEN",
+        "CLOUDHAL_COMMENTS_KEY"
+      ],
+      "vars": [
+        "CLOUDHAL_SCHEDULER",
+        "CLOUDHAL_TICK_BUDGET",
+        "CLOUDHAL_AUTOMATIONS",
+        "CLOUDHAL_COMMENTS_AVAILABLE",
+        "CLOUDHAL_SITE_ID"
+      ]
+    },
+    "site": {
+      "bindings": [
+        {
+          "name": "DB",
+          "required": true,
+          "type": "d1"
+        },
+        {
+          "name": "BLOBS",
+          "required": true,
+          "type": "r2_bucket"
+        },
+        {
+          "name": "ANALYTICS",
+          "required": false,
+          "type": "analytics_engine"
+        },
+        {
+          "name": "ANALYTICS_RL",
+          "required": false,
+          "type": "ratelimit"
+        },
+        {
+          "name": "COMMENTS_RL",
+          "required": false,
+          "type": "ratelimit"
+        }
+      ],
+      "cache": true,
+      "secrets": [
+        "CLOUDHAL_PREVIEW_SECRET",
+        "CLOUDHAL_COMMENTS_KEY",
+        "CLOUDHAL_PURGE_TOKEN"
+      ],
+      "vars": [
+        "CLOUDHAL_SITE_ID",
+        "CLOUDHAL_SITE_HOSTS"
+      ]
+    }
+  },
+  "channel": "stable",
+  "commit": "6bfe74a17f3a941e68130bb3e05a866fd9f19df5",
+  "contracts": {
+    "schemaVersion": 1
+  },
+  "createdAt": "2026-11-13T16:13:20Z",
+  "durableObjects": {
+    "bindings": [
+      {
+        "className": "SchedulerClock",
+        "migration": "v1",
+        "name": "SCHEDULER_CLOCK"
+      },
+      {
+        "className": "BuildRoom",
+        "migration": "v2",
+        "name": "BUILD_ROOMS"
+      }
+    ],
+    "lifecycleChange": false
+  },
+  "features": {
+    "adminSitesAcceptsSiteId": false,
+    "healthReportsVersion": false,
+    "mediaTransformVar": false
+  },
+  "format": "cloudhal.release-manifest@1",
+  "levels": {
+    "gates": 2,
+    "schedules": 1
+  },
+  "migrations": {
+    "compatibleWithPrevious": true,
+    "count": 33,
+    "first": "0001_init",
+    "last": "0033_entry_refs",
+    "names": [
+      "0001_init",
+      "0002_auth",
+      "0003_agent",
+      "0004_audit_actor_name",
+      "0005_auth_rate_limit",
+      "0006_approvals",
+      "0007_audit_mode",
+      "0008_policy_detail",
+      "0009_audit_elevation",
+      "0010_approval_result",
+      "0011_op_guards",
+      "0012_roles",
+      "0013_media",
+      "0014_analytics",
+      "0015_analytics_privacy_signal",
+      "0016_comments",
+      "0017_schedules",
+      "0018_query_indexes",
+      "0019_body_proposals",
+      "0020_ai",
+      "0021_search",
+      "0022_search_kinds",
+      "0023_search_usage",
+      "0024_search_quota",
+      "0025_translation_groups",
+      "0026_search_cjk",
+      "0027_multi_site",
+      "0028_site_members",
+      "0029_entry_list_values",
+      "0030_rate_limits",
+      "0031_oauth",
+      "0032_oauth_client_kind_index",
+      "0033_entry_refs"
+    ]
+  },
+  "optionalCapabilities": [
+    "analytics",
+    "domains"
+  ],
+  "optionalScopes": [
+    "account-analytics.read",
+    "workers-routes.write",
+    "dns.write"
+  ],
+  "planVersion": 1,
+  "requiredCapabilities": [
+    "account",
+    "workers",
+    "d1",
+    "r2"
+  ],
+  "requiredScopes": [
+    "account-settings.read",
+    "workers-scripts.edit",
+    "d1.write",
+    "workers-r2.write"
+  ],
+  "runtime": {
+    "compatibilityDate": "2026-10-01",
+    "compatibilityFlags": [
+      "nodejs_compat"
+    ]
+  },
+  "schemaVersion": 1,
+  "securityRelease": false,
+  "signing": {
+    "algorithm": "Ed25519",
+    "keyId": "cloudhal-release-2026a",
+    "signature": "release-statement.sig"
+  },
+  "skills": [],
+  "starter": {
+    "kind": "admin-api"
+  },
+  "theme": {
+    "blockTypes": [],
+    "designSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+    "name": "blog",
+    "settingsSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+    "version": "0.9.0"
+  },
+  "toolchain": {
+    "astro": "7.4.7",
+    "cli": "0.9.0",
+    "node": "24.22.2",
+    "producer": "cloudhal-release-bundle",
+    "starter": "blog",
+    "wrangler": "4.158.0"
+  },
+  "version": "0.9.0",
+  "workers": {
+    "admin": {
+      "assets": {
+        "bytes": 13278095,
+        "config": {
+          "not_found_handling": "none",
+          "run_worker_first": [
+            "/api/*",
+            "/mcp",
+            "/mcp/*",
+            "/.well-known/*"
+          ]
+        },
+        "count": 729,
+        "files": [
+          {
+            "assetHash": "5545e48f18ffa94cd7385af148b9c13e",
+            "bytes": 9696,
+            "contentType": "image/png",
+            "path": "/apple-touch-icon.png",
+            "sha256": "ab4e14602497589b55e935e19fb86f18718f25a4a4c17714d4226a52e44d32fd"
+          },
+          {
+            "assetHash": "3ce674aba417934db182b245d1c0c1cc",
+            "bytes": 19047,
+            "contentType": "text/javascript",
+            "path": "/assets/AccountPage-nAu_rO1o.js",
+            "sha256": "edc7ff0a8727d36e86c01f45c87419ee3d55441416c357542323deadc7192919"
+          },
+          {
+            "assetHash": "5ac581821489bc4940cb352e7bfb2164",
+            "bytes": 14330,
+            "contentType": "text/javascript",
+            "path": "/assets/ActivityPage-WMRfMUvK.js",
+            "sha256": "369bb77ebc30ffd8c570a292e42916a72581751533da26f7bf61bee04e0c70b9"
+          },
+          {
+            "assetHash": "0bb9b121339622aeeaa6dea6ec3cd58f",
+            "bytes": 10751,
+            "contentType": "text/javascript",
+            "path": "/assets/AgentModes-_sBUnXcz.js",
+            "sha256": "3b428f86ce02cae711c851bb0a6fe089ddd5958c98c85057cf05bf4e6b5c29cb"
+          },
+          {
+            "assetHash": "8ade051d50c396af2c2360425d225d84",
+            "bytes": 45270,
+            "contentType": "text/javascript",
+            "path": "/assets/AgentsPage-DLLmx-so.js",
+            "sha256": "cb67e8b17b89d898684c3b1ec03386cb8c32748295a46f4e1e297c64cd0cad14"
+          },
+          {
+            "assetHash": "ceca72b6eb9aaabd819d6637f4ca839a",
+            "bytes": 56771,
+            "contentType": "text/javascript",
+            "path": "/assets/AnalyticsPage-C-ex-HSB.js",
+            "sha256": "62907d8891ea3699c5140a78fbd403a5109f4cc8097f6df398e3a745c319465e"
+          },
+          {
+            "assetHash": "dbbbfeec290a09d01821a85aca90fca2",
+            "bytes": 1110,
+            "contentType": "text/javascript",
+            "path": "/assets/ApprovalDialog-BAugkVuS.js",
+            "sha256": "334e3b6bfea8775b6140a61b61256e947280a38cd7512be34f09f3bf67766b37"
+          },
+          {
+            "assetHash": "c61f37501b5cfc171c81ded94203d2a7",
+            "bytes": 36590,
+            "contentType": "text/javascript",
+            "path": "/assets/ApprovalsPage-s6dwVD03.js",
+            "sha256": "c8ebfe504f3db637eb593902309fb4a64df2776a5e76f3b57395f71df76b58e2"
+          },
+          {
+            "assetHash": "97bb4852b48b213dcab0b4d9cb3bbcc3",
+            "bytes": 1241,
+            "contentType": "text/javascript",
+            "path": "/assets/Archive.es-D8Bl_t0F.js",
+            "sha256": "4f9b7231412d3b4435e334929a467a7c43982afd44573c710f8f7a5569ea458f"
+          },
+          {
+            "assetHash": "43abf7d86034d3b425994e0a3c89847f",
+            "bytes": 1227,
+            "contentType": "text/javascript",
+            "path": "/assets/ArrowClockwise.es-CBDiTqBo.js",
+            "sha256": "29178b0b88694b6a2a86ab07f981368c0cd032fdafd75e341b3e0c321c28fe82"
+          },
+          {
+            "assetHash": "0548591bb91a55e69ad57e73e7eb5353",
+            "bytes": 1291,
+            "contentType": "text/javascript",
+            "path": "/assets/ArrowCounterClockwise.es-DTfZr_jW.js",
+            "sha256": "a7b1b1d5eca742c47ea7fadc53bf356c285a1e5259ae9b3d8e93cef929028637"
+          },
+          {
+            "assetHash": "2caefec186d31ffb25e45228a6c5a690",
+            "bytes": 1716,
+            "contentType": "text/javascript",
+            "path": "/assets/ArrowUp.es-PTLJggiP.js",
+            "sha256": "3701040c3ed49b5e69e3eaa528806b67780e5b2bc65afbcd127a5abb60258fb4"
+          },
+          {
+            "assetHash": "6a0b42e1e1bc24364796ce4fb47e05ea",
+            "bytes": 1233,
+            "contentType": "text/javascript",
+            "path": "/assets/ArrowsMerge.es-DvvBxeUr.js",
+            "sha256": "2d561a32a950393793bdb32ea043ad594b3e9125c0493ac8761c75d1202e8215"
+          },
+          {
+            "assetHash": "181c25bf50a4f50a6b36e8a4e853145e",
+            "bytes": 1380,
+            "contentType": "text/javascript",
+            "path": "/assets/Article.es-B1mhw0Yg.js",
+            "sha256": "68261a052d33ee32347039ee9e3c5b2e2f08a901c52349691db6c2c41e84aa18"
+          },
+          {
+            "assetHash": "da6eae0920e011c6c881f9f1df3647ed",
+            "bytes": 22716,
+            "contentType": "text/javascript",
+            "path": "/assets/ArticleEditorPage-T1evZ638.js",
+            "sha256": "4caeaa50deb449361e8a89f68090e3ee5fe0793339732ba395d81bbfd145658e"
+          },
+          {
+            "assetHash": "43f99c9d79f081840991f009fb4ab7d4",
+            "bytes": 18992,
+            "contentType": "text/javascript",
+            "path": "/assets/ArticlesPage-DMpZFnlE.js",
+            "sha256": "cc2212b969090453d4e6faa5fb12e18bb3e7ed272b9c6e116612ec12fd7b357f"
+          },
+          {
+            "assetHash": "11c1c8dad830e82722894b352d3fcef4",
+            "bytes": 28668,
+            "contentType": "text/javascript",
+            "path": "/assets/AssetPage-5LwLx1gR.js",
+            "sha256": "1398cdfdabb57ad429a2dd3b75c77ef3204ba918cf515989a1b92d698fe116ff"
+          },
+          {
+            "assetHash": "a7f219794c0ccef544e3b74f31f1ae26",
+            "bytes": 2765,
+            "contentType": "text/javascript",
+            "path": "/assets/Audit-DQffLTzE.js",
+            "sha256": "7f61ba16ae2f2563762c41923dadd90c94ddfd9e0647dcb88e8be36526f64723"
+          },
+          {
+            "assetHash": "17702de4998670d16069249d54994851",
+            "bytes": 1282,
+            "contentType": "text/javascript",
+            "path": "/assets/AuditBadges-CY1uAjI6.js",
+            "sha256": "42778233d63e4b0daaae714bfe54f37b5ec52b34e7fcc6b7da5be50909a6ee00"
+          },
+          {
+            "assetHash": "fcaff52275455092f5714b2db76d2922",
+            "bytes": 1528,
+            "contentType": "text/javascript",
+            "path": "/assets/AuthLayout-LZ12sEI9.js",
+            "sha256": "c7d5920aa2324dcad0a3c4ac247a1b1ac6fee274b794f65ac1809e5927a7dd66"
+          },
+          {
+            "assetHash": "b185a10ffeb9d100c4027a029e75f102",
+            "bytes": 1159,
+            "contentType": "text/javascript",
+            "path": "/assets/AutocompleteItem-drxau0wm3735nihq-CSomwNGo.js",
+            "sha256": "deabe9c97df5e94867f0f5f7f1da257d123f8669bb31d04eab87f629cf1e6063"
+          },
+          {
+            "assetHash": "3e271114c8966e75e7f3e5c100ae6dec",
+            "bytes": 29083,
+            "contentType": "text/javascript",
+            "path": "/assets/AutomationsPage-BDlQj-9A.js",
+            "sha256": "37e63826473f46d843a95ff987429c0ecc09709dee6bbafc068d9fb17ac213cf"
+          },
+          {
+            "assetHash": "1c8875420ece18d597317f7d9239f46c",
+            "bytes": 5398,
+            "contentType": "text/javascript",
+            "path": "/assets/BlockPropsForm--Eo9cpDv.js",
+            "sha256": "9d0a77126716b2126a6b725e4ec6c7a0fa6c1a0673f0cd63d0b5e3ba03065dab"
+          },
+          {
+            "assetHash": "b77c4955a1fa01fe18f82783f1c1b9a3",
+            "bytes": 2339,
+            "contentType": "text/javascript",
+            "path": "/assets/BlockSection-BYhmGeTx.js",
+            "sha256": "c0cd9740feb13fe71388ee4ccda3d7137eaf92d2acdd50bc68aada2a8cb36113"
+          },
+          {
+            "assetHash": "b631e8fcb3d252fd07662319ff5be2cf",
+            "bytes": 6706,
+            "contentType": "text/javascript",
+            "path": "/assets/BodyChanges-DIjm1bJU.js",
+            "sha256": "67997ae2bb11703fcd764ae1c07dcbd450872e9e3d93dfa0e8d455128d259d07"
+          },
+          {
+            "assetHash": "dee4545dc7870cece1ff466d8ba7714e",
+            "bytes": 14237,
+            "contentType": "text/javascript",
+            "path": "/assets/BodyEditor-DeqV1Lsh.js",
+            "sha256": "2a0993a940df99f4564f2558421979a65e5ada0e1c974614db8473ff6eb8bcd4"
+          },
+          {
+            "assetHash": "0b696ecb43cc69917fd7f5d61aada368",
+            "bytes": 2051,
+            "contentType": "text/javascript",
+            "path": "/assets/BookOpenText.es-CpOl9V_c.js",
+            "sha256": "d4a300e271e44156854d9e83e43a4176638c3472e3a3196c90325785c4b60dd9"
+          },
+          {
+            "assetHash": "802e4429bbbf198f4793df2ae8eb790d",
+            "bytes": 17694,
+            "contentType": "text/javascript",
+            "path": "/assets/BuildPage-CQjvu0rJ.js",
+            "sha256": "0802f6f0151d4c3195b75fed1e44f4b7da1355b88272f470b352929e6fd2ac9c"
+          },
+          {
+            "assetHash": "192345e00cfc86aae7234ac797cfc443",
+            "bytes": 310,
+            "contentType": "text/javascript",
+            "path": "/assets/CSPContext-ctyrgg64girv38ls-CFLKuzrY.js",
+            "sha256": "3b0b74b675197f0f0181f6b17eca74e15874acc733a63cd9645426df1018fe72"
+          },
+          {
+            "assetHash": "25d033d54413584e4b1356cff0809723",
+            "bytes": 1215,
+            "contentType": "text/javascript",
+            "path": "/assets/CalendarBlank.es-VpMruUO0.js",
+            "sha256": "9139f6106ef1e7e430e04a9185a575e760232749f0b04b377e9961948abd04a8"
+          },
+          {
+            "assetHash": "4da074258d1b95b9b9fc501c7a039214",
+            "bytes": 1589,
+            "contentType": "text/javascript",
+            "path": "/assets/CalendarCheck.es-B721IRvA.js",
+            "sha256": "9b94d9ea190b63d38f956483949f9f243b32462ef95570fd45fc411580c4d0ae"
+          },
+          {
+            "assetHash": "be25ba71207c1c6a1f626b1b4ab732fd",
+            "bytes": 1549,
+            "contentType": "text/javascript",
+            "path": "/assets/CalendarPlus.es-LVUym72A.js",
+            "sha256": "880c42e59025ed36f9aaea341d1adfa5928ec253d8366df44874f51aebfc32a7"
+          },
+          {
+            "assetHash": "aefe20b5829e30b2eb82b0805f5e25c6",
+            "bytes": 1759,
+            "contentType": "text/javascript",
+            "path": "/assets/CalendarX.es-Dog7ncNo.js",
+            "sha256": "5abc140728e373c4354f6be7ea9839f1b47428df4eba2fb80d5a3385a9d18150"
+          },
+          {
+            "assetHash": "fb12866504987e1bb5b75a88173dbec9",
+            "bytes": 831,
+            "contentType": "text/javascript",
+            "path": "/assets/CaretLeft.es-Xx2_Cq9g.js",
+            "sha256": "1f4773838c2ab1dfe154bceccd79aff846a0bf26558bc1412204a69df4e201a8"
+          },
+          {
+            "assetHash": "3db170f80e3eaabdf918e61c3c11b710",
+            "bytes": 886,
+            "contentType": "text/javascript",
+            "path": "/assets/CaretRight.es-BMaNDJ5e.js",
+            "sha256": "f9deeb8174ef2a4e0eaed8f07695874aad384fcf72be10be0a405d401f49ae07"
+          },
+          {
+            "assetHash": "25b18960f4a48580bd1723af3ab312b3",
+            "bytes": 919,
+            "contentType": "text/javascript",
+            "path": "/assets/CaretUp.es-CkQ436Qy.js",
+            "sha256": "4d1055bff3d9739ca09bbae6bdc9c199f1e0ca2be658c8b13d688ef945b4a9ae"
+          },
+          {
+            "assetHash": "5b7bfb0eefd12fd13f5959789ebe4674",
+            "bytes": 1200,
+            "contentType": "text/javascript",
+            "path": "/assets/CheckCircle.es-B-KQQvPf.js",
+            "sha256": "4c8d290e8967befbc2fd0e574aaab114f6fddae7e7f43972b08263e26929c455"
+          },
+          {
+            "assetHash": "fe3060618cd80fdd12bccb7aa9d78e88",
+            "bytes": 1522,
+            "contentType": "text/javascript",
+            "path": "/assets/ClockClockwise.es-CVI0mOoA.js",
+            "sha256": "70f34028cdeade43459d80ce4c96cf0b025d33ccf659e2a3ed3efb69567e3792"
+          },
+          {
+            "assetHash": "a2419e2f839605f7fd31b874c1316a88",
+            "bytes": 1514,
+            "contentType": "text/javascript",
+            "path": "/assets/ClockCountdown.es-CVi6XWmD.js",
+            "sha256": "0c662add21945c6fe51abfe021b940c6a928785ddaf4475c2f6a954de66bbe8e"
+          },
+          {
+            "assetHash": "0bbf96397e3bc185900a1da237f6c8dc",
+            "bytes": 7930,
+            "contentType": "text/javascript",
+            "path": "/assets/CodeHighlighted-CrjDUTMO.js",
+            "sha256": "6c16f5d2db05172c0b436a5c59ae2702578aa76992dc6229f56fdbefded51b40"
+          },
+          {
+            "assetHash": "c57783359da74428acd36a4bdca901aa",
+            "bytes": 20312,
+            "contentType": "text/javascript",
+            "path": "/assets/CollectionSettingsPage-DWscLife.js",
+            "sha256": "a9b66e4acdba1023b8c89a6067c88ec780b8f1fe613f71a37db484246f9129f2"
+          },
+          {
+            "assetHash": "84295e5598c5ae2bc7d9fc75481387b2",
+            "bytes": 7466,
+            "contentType": "text/javascript",
+            "path": "/assets/CollectionsPage-7s3YSr_B.js",
+            "sha256": "b2d3cb8671bcad247d38c1120267fe3bdf79cf6aa94535b9dab925260031d72d"
+          },
+          {
+            "assetHash": "092d54cb0925062fe498f989c2f009f2",
+            "bytes": 523,
+            "contentType": "text/javascript",
+            "path": "/assets/Columns-CwWLxZux.js",
+            "sha256": "41cae536668386039f6980c87c42ef3e384ed61be3839fb8b2bc11e54a03a741"
+          },
+          {
+            "assetHash": "11434b4e612dee27ac71cd11ff36b916",
+            "bytes": 30989,
+            "contentType": "text/javascript",
+            "path": "/assets/ComboboxEmpty-o0q20j2l2syar9kx-Br8JgcPC.js",
+            "sha256": "81c3d8d81c89e99c33e35ad2094f153cbbdfd78a49b89e1dc5523e02f2a09299"
+          },
+          {
+            "assetHash": "d6f72fe28ff87c74613eb696657c1728",
+            "bytes": 12007,
+            "contentType": "text/javascript",
+            "path": "/assets/CommandMenu-N_Ip-LPa.js",
+            "sha256": "454f140633ee73fdf3c24ddf27811e8d92db2c5d976a566c0a1f69a2f3ae1744"
+          },
+          {
+            "assetHash": "a1008ad33338a9144d92ef38883f8b2c",
+            "bytes": 3599,
+            "contentType": "text/javascript",
+            "path": "/assets/CommentThreadControls-CQyiHy0N.js",
+            "sha256": "a119a83804407a892a35c42163cd8276bcbcce0e798d09e73f8aceec0e2dcfe6"
+          },
+          {
+            "assetHash": "3581152392c9699cf2fc359ae15378f8",
+            "bytes": 24874,
+            "contentType": "text/javascript",
+            "path": "/assets/CommentThreadPage-DVSOcxOD.js",
+            "sha256": "6966ff1524cabbd5ad6979332b51337e2d0a80dcca2567a4336ebfb2232de95b"
+          },
+          {
+            "assetHash": "67240cccce120e1840852793924847b1",
+            "bytes": 16052,
+            "contentType": "text/javascript",
+            "path": "/assets/CommenterPage-CPXdSFdV.js",
+            "sha256": "98889ab7158aff0cc16e9a8b81fbf85570e584af0301e4cccaeecd3b43170a98"
+          },
+          {
+            "assetHash": "2d4c1d2ffb830a32292c0f36b0a0b87d",
+            "bytes": 19931,
+            "contentType": "text/javascript",
+            "path": "/assets/Comments-DJVsgCAa.js",
+            "sha256": "5f7b7bda1ee174105c2ef7027e9a1c715e7569b23771dd82ac6d3217df44d48e"
+          },
+          {
+            "assetHash": "23c499914cb9b501860a99922b493a49",
+            "bytes": 15882,
+            "contentType": "text/javascript",
+            "path": "/assets/CommentsPage-Cf_CseQi.js",
+            "sha256": "22404353d828ed31c664cf8d0ba8fcac272370a5544149778e927f3b7daf574c"
+          },
+          {
+            "assetHash": "f7fec88d2933bf4612fc615df2aeeab9",
+            "bytes": 492,
+            "contentType": "text/javascript",
+            "path": "/assets/CompositeItem-hzuk5x3e5zsfgnyj-DaKnXsNK.js",
+            "sha256": "31a19157e0c919476badbf20dc2a8f1ac1e4506604c7268bc381b02ad8be92a6"
+          },
+          {
+            "assetHash": "62b41167dc00d3861be206edd905c774",
+            "bytes": 4021,
+            "contentType": "text/javascript",
+            "path": "/assets/CompositeRoot-g72lchaerolf5dc9-BMCp-sQO.js",
+            "sha256": "120905b61eda8452e0c44c2e2214cc929de34831acc69548fefcea52c9f98881"
+          },
+          {
+            "assetHash": "32f06b592b92798171fe88a9f09fb5e9",
+            "bytes": 1190,
+            "contentType": "text/javascript",
+            "path": "/assets/ConfirmDialog-UY7UuQt8.js",
+            "sha256": "11f0d2e6ee72747dc02aeb535fb69ecf0a115f2ada0770b9b1352a8958cba7f2"
+          },
+          {
+            "assetHash": "4bb6cb2f7846685240866ed8312451d7",
+            "bytes": 6299,
+            "contentType": "text/javascript",
+            "path": "/assets/ContentListQuery-oMrol2As.js",
+            "sha256": "20354c39ba10641f8bfdd42daa96f3a731dc1d4bffef007aff9e2a6e4b206a9e"
+          },
+          {
+            "assetHash": "3f56c6c622abec7de00d4134dc919e3d",
+            "bytes": 1099,
+            "contentType": "text/javascript",
+            "path": "/assets/Copy.es-DUeQTRuS.js",
+            "sha256": "3bb0fb360c91929627244a754acfdcd212171e5333a55eda401bac72d0977acc"
+          },
+          {
+            "assetHash": "4412177aa59fd8b860115059e2f420c3",
+            "bytes": 4399,
+            "contentType": "text/javascript",
+            "path": "/assets/Crumbs-DEIiY7p1.js",
+            "sha256": "09a0511f5556195646b6d877153525454a6a15ce4a341f3ed9738d4216b7b665"
+          },
+          {
+            "assetHash": "a6236f33b25e084a71e3144d88b5653d",
+            "bytes": 1417,
+            "contentType": "text/javascript",
+            "path": "/assets/Cube.es-DJRc8J3h.js",
+            "sha256": "f1f5ef7915f9c5f5c08bf9e0c23bf4e855cb4dfaa948cd480f3c254439cfb788"
+          },
+          {
+            "assetHash": "22fa9b06b02106ae927c03adf74f7335",
+            "bytes": 3654,
+            "contentType": "text/javascript",
+            "path": "/assets/DateTimeField-DyVXd_hG.js",
+            "sha256": "4044d06fbf5acf78d3a173b4dd6a681d2601238fc47dcc62354efea21a92354a"
+          },
+          {
+            "assetHash": "f99647d4d857b8ec1fdf99fc0163754f",
+            "bytes": 4718,
+            "contentType": "text/javascript",
+            "path": "/assets/DeleteReferences-cijJ-S_1.js",
+            "sha256": "755d809a5bdd63f28bbd438f01644485af7381fe7db87952cd538392ffa864c2"
+          },
+          {
+            "assetHash": "a7aaa7d263a27df985ca67faedbbdf1e",
+            "bytes": 1180,
+            "contentType": "text/javascript",
+            "path": "/assets/DeviceMobile.es-prjdJoHR.js",
+            "sha256": "10e08325e698ba73e15258357bf91cb7183c91ad19b7600a15a8d5cbf553d023"
+          },
+          {
+            "assetHash": "edb11078988a62ead272cace57f4a665",
+            "bytes": 1188,
+            "contentType": "text/javascript",
+            "path": "/assets/DeviceTablet.es-BHaK_TWH.js",
+            "sha256": "9e5f671ffe26640d326a6a14d690f9208e0f4f434093377774ef4b141978d728"
+          },
+          {
+            "assetHash": "1d195ad91c0f16884e82b1862cc979fa",
+            "bytes": 2212,
+            "contentType": "text/javascript",
+            "path": "/assets/DialogRoot-hl3genvzl9h8ridm-BGNgsVHc.js",
+            "sha256": "21f3af5e2db8467f2b5ba88d9aaa46aad88fcc9c261e3d20eb1fe111def0ae5b"
+          },
+          {
+            "assetHash": "7aa40515b6f14806c97f281e4b71da8a",
+            "bytes": 2182,
+            "contentType": "text/javascript",
+            "path": "/assets/DialogTrigger-g44cuem7n4wez533-EhxANqX5.js",
+            "sha256": "3e2d9f6fbd9ee20ef315bb6d4b69820f716b4a34413a642f61a15efd33ac084c"
+          },
+          {
+            "assetHash": "7c7fcabf361621a6f0db07b32db9ef4f",
+            "bytes": 1454,
+            "contentType": "text/javascript",
+            "path": "/assets/DirectionalIcon-BitUORGN.js",
+            "sha256": "3a8cb2acc82fdd8a9a17a9f04afa3109d5421539eba4bc3e79b95eb745f76744"
+          },
+          {
+            "assetHash": "0883f29940e6bef08c58d7ba91c73fa0",
+            "bytes": 1056,
+            "contentType": "text/javascript",
+            "path": "/assets/DotsThree.es-D0trk56j.js",
+            "sha256": "01100ddcd8aeadd172b64a848fc150e2732c88cc8df7ea74fd95f7a82aef8456"
+          },
+          {
+            "assetHash": "1db07b611437897896543b278254a41a",
+            "bytes": 1190,
+            "contentType": "text/javascript",
+            "path": "/assets/DownloadSimple.es-DqABLE5v.js",
+            "sha256": "b1ba45f442b49e8998f86c3940fbc3cdd30c4f349537b11dcd81e29a471e83ba"
+          },
+          {
+            "assetHash": "34b32679b9f233fd7b41e049a3cd86f3",
+            "bytes": 3814,
+            "contentType": "text/javascript",
+            "path": "/assets/EditorLayout-HTyUZGbM.js",
+            "sha256": "b2ec564d9d7f0bf2b25a575445461a9fa83052e707a92ff133dde399333d4b0e"
+          },
+          {
+            "assetHash": "a42583f73e1af1616e456318408f5d33",
+            "bytes": 967,
+            "contentType": "text/javascript",
+            "path": "/assets/EditorPreview-C3-nmzAO.js",
+            "sha256": "027cde2ade0c2d8ee1a0776e6a53c4e00d4a9d79c721dffb2b24fe8097076ea5"
+          },
+          {
+            "assetHash": "a5cb76047e12b9e14ab930cb95a27828",
+            "bytes": 5096,
+            "contentType": "text/javascript",
+            "path": "/assets/Elevation-DmmvKhh3.js",
+            "sha256": "d9432030b84781e9649ad82ce59584ecb123ec5542332e5aa529088603effe1e"
+          },
+          {
+            "assetHash": "4e2a50b0f373c6c871e5688ff400646a",
+            "bytes": 4265,
+            "contentType": "text/javascript",
+            "path": "/assets/ElevationRequest-Cayyvqqw.js",
+            "sha256": "532479867f8b8478be36d61083c88885d5565755daa4b1ce793bd61ce518e133"
+          },
+          {
+            "assetHash": "2b76bf41864621cb8b463bac4c1dd619",
+            "bytes": 2803,
+            "contentType": "text/javascript",
+            "path": "/assets/EndedBadge-SSv5wA-H.js",
+            "sha256": "327732201047ce0a650f962db681395688c069ed1af7b8872b3fbec358ecb49d"
+          },
+          {
+            "assetHash": "9c1a2eab14f04acd38d4c2b1b23c60d9",
+            "bytes": 42147,
+            "contentType": "text/javascript",
+            "path": "/assets/EntryFieldsCard-aHg_PtwX.js",
+            "sha256": "3205e5a576469115e4d7d93eaeb21385956f4b6d28c3186287c970c7d13e02f5"
+          },
+          {
+            "assetHash": "2c74e615956fc7b5252f2ed8a2330ea8",
+            "bytes": 581,
+            "contentType": "text/javascript",
+            "path": "/assets/EntryPath-CCxKH9wf.js",
+            "sha256": "e836166e433caabe6c6e2807bd802f87c0967693dffd47a33bc14183d34ddb7c"
+          },
+          {
+            "assetHash": "00292a629453316c7e18541a774f3def",
+            "bytes": 1083,
+            "contentType": "text/javascript",
+            "path": "/assets/EnvelopeSimple.es-DN98o0aM.js",
+            "sha256": "cf7a9427c8f65ebcd0c04cd5f7b04f343a2e76997509272f1c9139fd2c9cbd21"
+          },
+          {
+            "assetHash": "a32ee3042e13d8c0dd03a2783f401a8c",
+            "bytes": 2124,
+            "contentType": "text/javascript",
+            "path": "/assets/Eye.es-D_s6AHKD.js",
+            "sha256": "d3c8c2b9aaeea94d1f5a1d66835d7c4d934b0f57092b995528d5b3788aed9cf7"
+          },
+          {
+            "assetHash": "de53cf6b5befba53cbdf635e0ab4624a",
+            "bytes": 2624,
+            "contentType": "text/javascript",
+            "path": "/assets/EyeSlash.es-C4_clqud.js",
+            "sha256": "9c0ddc3e260eb3d3e4ae64b3337a8d17e0cfa4b96c679e713f358f8987a5140a"
+          },
+          {
+            "assetHash": "099b066bc2f8700f6bd209e0f6174ea6",
+            "bytes": 47977,
+            "contentType": "text/javascript",
+            "path": "/assets/FieldsSection-Dapb2jAb.js",
+            "sha256": "11631d1b51b6649f674264c7df2a7cc01e2e2a8f288470de3b3556fd997fd6ee"
+          },
+          {
+            "assetHash": "4846679150142d3a7a4bac92cb5a4aa7",
+            "bytes": 1080,
+            "contentType": "text/javascript",
+            "path": "/assets/File.es-7rkjM_ge.js",
+            "sha256": "b085e8285a5a5bb42ef3f33c0ed0603ac31f78749af83ff0e99a51fc9e90e3f9"
+          },
+          {
+            "assetHash": "189b6e56fea403ea57a675ab6fd6feba",
+            "bytes": 1763,
+            "contentType": "text/javascript",
+            "path": "/assets/Files.es-BwAHmY88.js",
+            "sha256": "143040a5199f92fe3074864bf54bfe3fe847d42fe35aad77f43aa80186df6bc9"
+          },
+          {
+            "assetHash": "6eca779d2484ed93afdba9f36980df2b",
+            "bytes": 1621,
+            "contentType": "text/javascript",
+            "path": "/assets/Flag.es-JBpXrgwA.js",
+            "sha256": "ab19a8fa3e1dad66ff9d5e5ae66cdf47ccb3355fa8e3be23a92e9006d2aafef4"
+          },
+          {
+            "assetHash": "f6c3edc61567742634538580e8469d32",
+            "bytes": 1111,
+            "contentType": "text/javascript",
+            "path": "/assets/FolderSimple.es-4lPVR2-0.js",
+            "sha256": "511d6bc22206aba63b813734c41e21b9c639eed4344465357695c5790da330c3"
+          },
+          {
+            "assetHash": "bfa1550b6cf25ba8905aff719f422b67",
+            "bytes": 1456,
+            "contentType": "text/javascript",
+            "path": "/assets/FolderSimplePlus.es-DkhijKDr.js",
+            "sha256": "636e16e61c03ba953cdf3806f15a05969dedc3171f0bf7d808be0cad086d1add"
+          },
+          {
+            "assetHash": "b76723023309787d2a44bd86b70cd31b",
+            "bytes": 2787,
+            "contentType": "text/javascript",
+            "path": "/assets/ForgotPasswordPage-jJLhjZX7.js",
+            "sha256": "b940896debd8eb6b325fbf474a1203c455eec6dc122d24b05951769be6951acc"
+          },
+          {
+            "assetHash": "ff21c49cb34a7635c4db0f501d05cc8a",
+            "bytes": 1270,
+            "contentType": "text/javascript",
+            "path": "/assets/Funnel.es-D9Fm-7zi.js",
+            "sha256": "79fb02f2284167d71e4477b13e94ebaafc9b6f59d9acd65e88afd9fba67486e8"
+          },
+          {
+            "assetHash": "0f6d75de237ba19887cf3c01f9b5ef8d",
+            "bytes": 648,
+            "contentType": "text/javascript",
+            "path": "/assets/Gating-HCCmndPb.js",
+            "sha256": "c0d51fe6e74d3418dd0fd200fa405c4d85d9d7b9df8c72a3baa6e4cf74e6307b"
+          },
+          {
+            "assetHash": "7b8d979421e08e3a7c8abe8aeddd9dc6",
+            "bytes": 1896,
+            "contentType": "text/javascript",
+            "path": "/assets/Gauge.es-CKxSD-ha.js",
+            "sha256": "1d981ddba12c7b4aafcf872fbe0f8a132c6529188582b0c7de735ad88d3e7569"
+          },
+          {
+            "assetHash": "be213c3443399ddc061bb6d8104c6ebb",
+            "bytes": 2518,
+            "contentType": "text/javascript",
+            "path": "/assets/GitDiff.es-DuMS1_Po.js",
+            "sha256": "2bcabf0d570891cbb11e3f6acd3c7e5176c6492dc07bd200e96c5154a806a787"
+          },
+          {
+            "assetHash": "125f2d35dbb8e6c625bc2a16a184a8ef",
+            "bytes": 2827,
+            "contentType": "text/javascript",
+            "path": "/assets/GlobeHemisphereWest.es-BvwDtHCs.js",
+            "sha256": "0913fc5be6f0f4fd1720ad91996b294954c9200ebcae388560946987de43674e"
+          },
+          {
+            "assetHash": "614b8f1aab200a654bbaab9ab60f524f",
+            "bytes": 12279,
+            "contentType": "text/javascript",
+            "path": "/assets/GovernanceDialog-Cx3r0q-n.js",
+            "sha256": "58315e0be432d75df198e896a6dbe12e407cbe85c89733939db9b58ab7e079d7"
+          },
+          {
+            "assetHash": "4c25ee083f6e4ac3cc62386517fa941a",
+            "bytes": 2371,
+            "contentType": "text/javascript",
+            "path": "/assets/Heartbeat.es-CI1hyl1d.js",
+            "sha256": "39d45fd3fce1ef72ae502f70cb1009dd7544adacb80d32453c882cc7925a86f8"
+          },
+          {
+            "assetHash": "a489d6ca48346b8d6df015e976592443",
+            "bytes": 1429,
+            "contentType": "text/javascript",
+            "path": "/assets/Hourglass.es-Cq2_RgX0.js",
+            "sha256": "0e2a0f8975cb3353d0905c662f85a65b0d7206fe5e594cd7f99cc1f4ab435d73"
+          },
+          {
+            "assetHash": "c77d7cd4b9a6a5b2411142e9e370dc29",
+            "bytes": 692,
+            "contentType": "text/javascript",
+            "path": "/assets/IconBase.es-BXBadb8L.js",
+            "sha256": "6cc825cc45b12047868b61dd0eecb647d0eb6e831a5e45d063ce99cfe9294b11"
+          },
+          {
+            "assetHash": "bfdc06eaee86879afdc08ba880929227",
+            "bytes": 1440,
+            "contentType": "text/javascript",
+            "path": "/assets/Image.es-hbodihZN.js",
+            "sha256": "96fbf13052db9ac627dddb513027b86bb5c86c474ebfa1369de56c7a52069e0c"
+          },
+          {
+            "assetHash": "0324a49b784558ef6a368aa569b4cbf3",
+            "bytes": 590,
+            "contentType": "text/javascript",
+            "path": "/assets/InsetPanel-BCDyegfj.js",
+            "sha256": "3fde70f13b722cd71e912f4fe1faa44317fe158c968cb2329d5b6531079b4918"
+          },
+          {
+            "assetHash": "faa9fdb6cb14783fb29152b5a9a68d4c",
+            "bytes": 8720,
+            "contentType": "text/javascript",
+            "path": "/assets/InvitePage-BHGVYDQW.js",
+            "sha256": "0fcfda0f2861f52806751aad132d6a9a49ff7786b5643737ba50ad41a57049e9"
+          },
+          {
+            "assetHash": "19613f843dce6873148902f64e223d5a",
+            "bytes": 1939,
+            "contentType": "text/javascript",
+            "path": "/assets/Key.es-7JaWxcaF.js",
+            "sha256": "2a7600f855471782ca8650ad1c4ef126d57d127fd7366ba106a66c80093968fd"
+          },
+          {
+            "assetHash": "d5810596c973ff465f5ff71a2051b90b",
+            "bytes": 1296,
+            "contentType": "text/javascript",
+            "path": "/assets/Lightning.es-Bgp9MYVi.js",
+            "sha256": "356ff687697bc1db072ab9a6221106565f969386b1bfbbcbcdb88ab78a591b1d"
+          },
+          {
+            "assetHash": "99bb87094e6bea4ccf69cc6b047407c7",
+            "bytes": 2330,
+            "contentType": "text/javascript",
+            "path": "/assets/LinkBreak.es-DJPTzmWT.js",
+            "sha256": "d726182cb7e89ec285e868546796eecc17e50d5b0b95373b5c84ae7c4cd49129"
+          },
+          {
+            "assetHash": "d1e88e087cf9b41d673a4afa51fe8898",
+            "bytes": 1811,
+            "contentType": "text/javascript",
+            "path": "/assets/LinkSimple.es-Bmke3KUE.js",
+            "sha256": "4ca27e5413c06053ff74b47f487f1affe735752f8a4debdbee09a8528077605e"
+          },
+          {
+            "assetHash": "5163ad58b56679e8a000bcbe164be0ce",
+            "bytes": 1485,
+            "contentType": "text/javascript",
+            "path": "/assets/ListBullets.es-BRt_pQYZ.js",
+            "sha256": "ebb14b75a6a8062623aff4406c79a2c31a4e829860995adc614ab548a471bf62"
+          },
+          {
+            "assetHash": "601fe379910f89611067b1d1cc575a55",
+            "bytes": 4012,
+            "contentType": "text/javascript",
+            "path": "/assets/ListFieldColumns-BW5Ycf3p.js",
+            "sha256": "2ca76add490980f5c4f2a6f8c720a72b217dd85d3d3d8694db1535049d57d28f"
+          },
+          {
+            "assetHash": "86c642cdaa5ce952754544a67bd3f94b",
+            "bytes": 689,
+            "contentType": "text/javascript",
+            "path": "/assets/ListToolbar-AiTPzw9z.js",
+            "sha256": "d735bc30bddd11c5b4b2d681fc832d7becef271de1b2567a6b350236413f0c72"
+          },
+          {
+            "assetHash": "8c2b20c89915f8191d910e5317c8f445",
+            "bytes": 17046,
+            "contentType": "text/javascript",
+            "path": "/assets/ListingSections-Cifcu5P5.js",
+            "sha256": "9900f9820ac8086537ec8e5b472b9a7cf25164d8d527ded03d173a7b8aba98ab"
+          },
+          {
+            "assetHash": "61461da6f550420c9a31efa52ca0c4a6",
+            "bytes": 3781,
+            "contentType": "text/javascript",
+            "path": "/assets/LoginPage-C3-Zu_Ta.js",
+            "sha256": "d8440774cc6f89c468e869a184df49825ef4c359dc80c8fc7b80256b70a6fa77"
+          },
+          {
+            "assetHash": "6a8b548489f1f9f89f10abfd70202529",
+            "bytes": 1491,
+            "contentType": "text/javascript",
+            "path": "/assets/MapPin.es-CtoNnkl7.js",
+            "sha256": "8dfc5af1c79002f7a45ef5155a981cc09d459dc79eef19ee6713edf670687648"
+          },
+          {
+            "assetHash": "0c3a74b98100c423765b418e51d5c8a1",
+            "bytes": 8077,
+            "contentType": "text/javascript",
+            "path": "/assets/MediaPage-By0qbkgh.js",
+            "sha256": "19116d33e75944913abf5f344748fcf2d11e1ae5bdffbc7091fe30338470cd84"
+          },
+          {
+            "assetHash": "97ff0f588a1b2030b03fbe98bd53eea1",
+            "bytes": 8098,
+            "contentType": "text/javascript",
+            "path": "/assets/MediaPicker-CHinIGGd.js",
+            "sha256": "0c6c19299b153018a7c886733d13757ae91e4e3a302040f4573d3144a59fbe19"
+          },
+          {
+            "assetHash": "26ecec3692d6f7c6f0502ee7bd9e126a",
+            "bytes": 28110,
+            "contentType": "text/javascript",
+            "path": "/assets/MediaUpload-FEtMnYgM.js",
+            "sha256": "6e128736a54f626c05dcb3b4fec1ae88d611c51d60237cb13df498b713f2466e"
+          },
+          {
+            "assetHash": "4da34d032dbe7b9b792adbdb5721a704",
+            "bytes": 38783,
+            "contentType": "text/javascript",
+            "path": "/assets/MembersPage-DRQlIhLy.js",
+            "sha256": "955f79ce92cdf72eddb2aacf1389061fbdc8a32110bddb360c6f9edac767aacf"
+          },
+          {
+            "assetHash": "ca128f40a5874af15b91de8479a63404",
+            "bytes": 25426,
+            "contentType": "text/javascript",
+            "path": "/assets/Menus-DpTuIwZQ.js",
+            "sha256": "f6d45d3d609f4e70f8c6ec2364e33132495d1b98e1e6fcdfc41db9a364503a54"
+          },
+          {
+            "assetHash": "d5b334081882ac0938e7de8ab8ed2ce1",
+            "bytes": 1157,
+            "contentType": "text/javascript",
+            "path": "/assets/Monitor.es-Bjx-aBZN.js",
+            "sha256": "2fdb627e43428427d0a2d84313b5db0c1851812fd815a178831fb2cc63b80030"
+          },
+          {
+            "assetHash": "1180a616ea2de06771061b80fa481c27",
+            "bytes": 56792,
+            "contentType": "text/javascript",
+            "path": "/assets/NavigationPage-dmNWCcLa.js",
+            "sha256": "42f1aa5333eed63f65af13da4fb4080631b453042bc31532d769cb64cfd244e4"
+          },
+          {
+            "assetHash": "cf9e91624f1b2756f78f050638541869",
+            "bytes": 3823,
+            "contentType": "text/javascript",
+            "path": "/assets/NewSiteDialog-CXajABEy.js",
+            "sha256": "5a1b387fc471d8d2cc5eb0a3e8a24908ccb4fd54b0be599cc50d445affc323ec"
+          },
+          {
+            "assetHash": "6e27e67548c1a7d6e68c26d1127e298f",
+            "bytes": 8253,
+            "contentType": "text/javascript",
+            "path": "/assets/NotificationItems-Bql2PkI9.js",
+            "sha256": "a03541fb6a684b71097ef670ea6df727947fa41e31021567ead568a1250c7d9b"
+          },
+          {
+            "assetHash": "931fab8f316e9c706a2edc7ce4103219",
+            "bytes": 5465,
+            "contentType": "text/javascript",
+            "path": "/assets/OAuthConsentPage-B-DOabVo.js",
+            "sha256": "07e4db2766dc1b1c13a599cb847004c52f873032339bc10a02e27edfbb829eae"
+          },
+          {
+            "assetHash": "f59aa3feb5118f47232d87e078d6ca70",
+            "bytes": 1675,
+            "contentType": "text/javascript",
+            "path": "/assets/Package.es--NqoouWd.js",
+            "sha256": "ef7f3e8e8cb379e8e71b11c65a832d23ca99eadbd27479a60c14f67c71e5999a"
+          },
+          {
+            "assetHash": "c925852e13df5a3a29e0da975d18e047",
+            "bytes": 27322,
+            "contentType": "text/javascript",
+            "path": "/assets/PageEditorPage-CtOD2ZcK.js",
+            "sha256": "c50803c74aa31dc28dc354e17447c52ba5442c6baae35492bcd3fd8bc7afd10e"
+          },
+          {
+            "assetHash": "ffb6c3e901b7e21b4e3f36c034ce9d2c",
+            "bytes": 5872,
+            "contentType": "text/javascript",
+            "path": "/assets/PagesPage-DptpPusO.js",
+            "sha256": "b1da386bd7ae0cf1e96d67eda77d8df5bfa5e2277ab177aae1f4048ec432280f"
+          },
+          {
+            "assetHash": "8cd5828037390624d52536956d464ea7",
+            "bytes": 1068,
+            "contentType": "text/javascript",
+            "path": "/assets/PasswordField-Cio_z-vH.js",
+            "sha256": "ca6498205fa8f62e834d89d893331925c1ce63748fb798507a67e1cb4f6701c3"
+          },
+          {
+            "assetHash": "ebb4482caec14c6df135b9ef97784e11",
+            "bytes": 1091,
+            "contentType": "text/javascript",
+            "path": "/assets/PauseCircle.es-DqE1hsRF.js",
+            "sha256": "1091ed1b527899975da8b7181c5543c78d9d4a95bd68f2723de718cfefe01cd8"
+          },
+          {
+            "assetHash": "485f65e1c00253701f7f56bcd6a855f8",
+            "bytes": 1258,
+            "contentType": "text/javascript",
+            "path": "/assets/PencilSimple.es-Op-nnaqy.js",
+            "sha256": "bd45bcbeb0e351d9723a705b5e22f2b26ea5d73f3b9d3e01dece3743f03449cb"
+          },
+          {
+            "assetHash": "91203bdf6f81696e289c932e11c0ff9e",
+            "bytes": 3042,
+            "contentType": "text/javascript",
+            "path": "/assets/PlugsConnected.es-DctXvtCh.js",
+            "sha256": "a4218470bfd1c93a46fe739a297b384225aefc9135ddb24578d9269b24881e38"
+          },
+          {
+            "assetHash": "3004532dbb6ec0117e46ec59907f3095",
+            "bytes": 971,
+            "contentType": "text/javascript",
+            "path": "/assets/Plus.es-2_hsc1B3.js",
+            "sha256": "b801268dd86593f4ed70eec58318d2b41bcc01fb29a153030e52f7d4b0b3794e"
+          },
+          {
+            "assetHash": "6ced8c4038c4af71b503ec18e70e2bb5",
+            "bytes": 7127,
+            "contentType": "text/javascript",
+            "path": "/assets/PreviewPanel-DGRZbOUK.js",
+            "sha256": "3821f75cc232b24aa0708328862981466e7ebd1df64fc890366fcf5a4397219a"
+          },
+          {
+            "assetHash": "c7889d77866fab7f7f32ae48d9f10ce3",
+            "bytes": 1201,
+            "contentType": "text/javascript",
+            "path": "/assets/Prohibit.es-D_dPTZZy.js",
+            "sha256": "98013d6dfe7ca6d06d4b60d1d1b95c6fe5ecb437ad7f1d3c02c5832d288b9ca9"
+          },
+          {
+            "assetHash": "10821de2e2ef111894fe672682730d8a",
+            "bytes": 1051,
+            "contentType": "text/javascript",
+            "path": "/assets/ProhibitInset.es-CcjznQ17.js",
+            "sha256": "7f0135b31d47dcb148b1b0b7800a5493c0ba7006fe04428d2b748093af4084d1"
+          },
+          {
+            "assetHash": "6e6f706b879b03896dd175bba549957a",
+            "bytes": 5123,
+            "contentType": "text/javascript",
+            "path": "/assets/ProjectPage-B6bKsTrC.js",
+            "sha256": "1bf508578a1611216f654b3847ea2c8556827c1a69b19ae4e2fe7c0b9b399665"
+          },
+          {
+            "assetHash": "f88296e126c64515dbc4693c3b0037d2",
+            "bytes": 2082,
+            "contentType": "text/javascript",
+            "path": "/assets/PublishWarnings-CHJBC0rp.js",
+            "sha256": "a5a31716b1f29a50d6f090335f7026ebf7375877622095b1329e0a8e95b97b88"
+          },
+          {
+            "assetHash": "022d31d2309ca394bf0819394aa0d762",
+            "bytes": 1536,
+            "contentType": "text/javascript",
+            "path": "/assets/Question.es-trc8MUtE.js",
+            "sha256": "c0940bbd7b59f15a5bef4fa1bfd15c33c81e2d12c8f89e51f17a80025e5cad93"
+          },
+          {
+            "assetHash": "8e3e194715b6f87defbb3c75a009c648",
+            "bytes": 1425,
+            "contentType": "text/javascript",
+            "path": "/assets/Quotes.es-DyaHmUO_.js",
+            "sha256": "39333694126cfb6a1092d62f1ca0ab963d797617b597911f45dd23820b25a98f"
+          },
+          {
+            "assetHash": "51100874e604cd20236f65663ca6fbdf",
+            "bytes": 11602,
+            "contentType": "text/javascript",
+            "path": "/assets/Readers-Bvy9rxlu.js",
+            "sha256": "beab1aace35bd2507dd7a4303f8227258ade89e43db7f241a56695e4ba2183fe"
+          },
+          {
+            "assetHash": "b9550bd0b0e2116e829c1f2bdf352b03",
+            "bytes": 8101,
+            "contentType": "text/javascript",
+            "path": "/assets/RedirectDialog-Bi6gecDo.js",
+            "sha256": "010c17c5cd872568953ad5f74132ca9ad738ded4f0e8f489a83cebdfb2ba02ac"
+          },
+          {
+            "assetHash": "7800ab7a234ba460d3c43f3227997139",
+            "bytes": 8449,
+            "contentType": "text/javascript",
+            "path": "/assets/RedirectsPage-XukadT0D.js",
+            "sha256": "21eed3c2a77b7823ee729872496a7b28b68a10a98b799d6c80eaf8e452c320b7"
+          },
+          {
+            "assetHash": "539ea0b324e584cde9a0c49ac1589663",
+            "bytes": 604,
+            "contentType": "text/javascript",
+            "path": "/assets/RelativeTime-pU7izbH5.js",
+            "sha256": "b1b0972bc2b9810a97ac13d07f1039b81efe1a6aac58f7cdf610193745f2a343"
+          },
+          {
+            "assetHash": "1b0e3a6a5cc33ce2dbbb34554f757c75",
+            "bytes": 2962,
+            "contentType": "text/javascript",
+            "path": "/assets/ReleaseApproval-BJE5qtlq.js",
+            "sha256": "e71b5c962c81d9399c598ea51ed9195cdb0168f8d6fce94b8b83e7b765f2bdeb"
+          },
+          {
+            "assetHash": "6db3b64daa8fead34dedbac6d666cc07",
+            "bytes": 12021,
+            "contentType": "text/javascript",
+            "path": "/assets/ReleasePage-WSfD9gZ3.js",
+            "sha256": "4b85a7bf2370465777511a4367c82f56e4234f50e61b7176b45f94dd48d1886a"
+          },
+          {
+            "assetHash": "c2b1094cc374300d434f510db5718e3c",
+            "bytes": 7046,
+            "contentType": "text/javascript",
+            "path": "/assets/ReleaseParts-BtLl_1C4.js",
+            "sha256": "74fa7673c4fda63570e0ceac3d697a982ab15f6358baa791abcf136356deeb2d"
+          },
+          {
+            "assetHash": "180dbf573af98d28d644965390d8a89f",
+            "bytes": 3716,
+            "contentType": "text/javascript",
+            "path": "/assets/ResetPasswordPage-G4u5dPGv.js",
+            "sha256": "243a0347ef21f1aea387af6c5f7420dc1328a0738eb06ecfa73f1c01fce73fa4"
+          },
+          {
+            "assetHash": "c5979311e123d475ace7f3f3d980c768",
+            "bytes": 2638,
+            "contentType": "text/javascript",
+            "path": "/assets/RocketLaunch.es-BbmkW8GS.js",
+            "sha256": "86a047cf76a9b1076954a6a6503b1af6ced046ea1587fe5abb29d545c1317542"
+          },
+          {
+            "assetHash": "eb43feccd2e3cf12f69846a0cc6743f9",
+            "bytes": 1164,
+            "contentType": "text/javascript",
+            "path": "/assets/RowMenu-BDHWbqzj.js",
+            "sha256": "0720fc2e37a17d15f3036cad609876d66a7d271269d1d62c0542b74c3873224d"
+          },
+          {
+            "assetHash": "353fe60ab972c5011ca719e914acf1c3",
+            "bytes": 7798,
+            "contentType": "text/javascript",
+            "path": "/assets/ScheduleCard-CU87XC4_.js",
+            "sha256": "8cbfba513837ccd6caee5fab8025d92969d42cf3198c4f39655807801c504216"
+          },
+          {
+            "assetHash": "7e3c57f97305079cd8c874c5c6c83a84",
+            "bytes": 7827,
+            "contentType": "text/javascript",
+            "path": "/assets/ScheduleDialog-DZywcZBs.js",
+            "sha256": "70e4ffb7ad31902e231f444c9411a771d0aefb6f620227d9c5be51fe19ecfa04"
+          },
+          {
+            "assetHash": "e47dee92539ed2bd13c8f95c882c2ceb",
+            "bytes": 19373,
+            "contentType": "text/javascript",
+            "path": "/assets/ScheduleHistoryPage-BA8TT-E9.js",
+            "sha256": "62d0ac539eea39a06e6dd0b4185a1a7054068519b1177323c6dc26dee6351ac3"
+          },
+          {
+            "assetHash": "f04ddf0698a7f3d0d9d93738464cfa44",
+            "bytes": 9546,
+            "contentType": "text/javascript",
+            "path": "/assets/ScheduleStatusBadge-BvBYX3z6.js",
+            "sha256": "1e6e53655574f4013ee7d29f8a6ea98dc4939a3f31c13e00015faacc64ef6099"
+          },
+          {
+            "assetHash": "a5fe82b574a83b65ee368df3962bd219",
+            "bytes": 1124,
+            "contentType": "text/javascript",
+            "path": "/assets/SchedulerStalled-CysR10ca.js",
+            "sha256": "0d788071d82c4edd3eae91635396317c70748050b0c759f44bfcc4129b0b2510"
+          },
+          {
+            "assetHash": "efd1e620e46a8a49ded4ff0675a39026",
+            "bytes": 14260,
+            "contentType": "text/javascript",
+            "path": "/assets/Schedules-WQwT-Vor.js",
+            "sha256": "dd8c1e3c28eef32bc8482b3aa8dc8fdb56d4cbe3254549f7071c969ca719ae7d"
+          },
+          {
+            "assetHash": "ec0852c0268b7f76a8ab7922ebe76fdb",
+            "bytes": 46685,
+            "contentType": "text/javascript",
+            "path": "/assets/SchedulesPage-B-H3TV5a.js",
+            "sha256": "fda62a9e10c080a5343c34f1f2c9fa373a830d4a57306a145ef0a1e1ffc9369a"
+          },
+          {
+            "assetHash": "d00a05cfbd01b34853b9ab9d0474f8cb",
+            "bytes": 578,
+            "contentType": "text/javascript",
+            "path": "/assets/ScopeBadge-C1pL9R9H.js",
+            "sha256": "e1104305a6c937dbb4639b72b63ce409e0be047e02fe9d182e57cad7b452808a"
+          },
+          {
+            "assetHash": "b05205913e003a9b7cfac963875d6617",
+            "bytes": 1535,
+            "contentType": "text/javascript",
+            "path": "/assets/SectionCard-B2sCuWOB.js",
+            "sha256": "99a96d851c5bebc28356385b14f426b8a013ac43a0b5281c4f6b195e6092d1a2"
+          },
+          {
+            "assetHash": "a1ce2bdd2f7fec52d4c40e8b829b2b9d",
+            "bytes": 7944,
+            "contentType": "text/javascript",
+            "path": "/assets/Seo-BFG0drND.js",
+            "sha256": "3489f90029e7fadcae02a60eb5df10bc6f9f2e0c538191436e7587199a455af3"
+          },
+          {
+            "assetHash": "df17ee06aa122c07a2e5d94cca61dfcf",
+            "bytes": 6431,
+            "contentType": "text/javascript",
+            "path": "/assets/SeoPreview-BUgjhYVU.js",
+            "sha256": "7b0f042b1499bb230f20521e6950ad3d52248029430f92de75710a184caf518f"
+          },
+          {
+            "assetHash": "2df2ac343dd17b0eade0589eb8958ddb",
+            "bytes": 5482,
+            "contentType": "text/javascript",
+            "path": "/assets/Settings-b3xnPmhf.js",
+            "sha256": "d7b06383f80f2c67d7bfff52bbeac37108e94240ae190fcbfd85c5dda4836bf3"
+          },
+          {
+            "assetHash": "53b6827fc4f570bd6ec4b13053c120f7",
+            "bytes": 1734,
+            "contentType": "text/javascript",
+            "path": "/assets/ShieldCheck.es-BuroL7xK.js",
+            "sha256": "10b9feb7862342217e06ded85ee30a20f1d06fa65467f54fee35ff3a149a4b67"
+          },
+          {
+            "assetHash": "7faaf80cf358585ca7b7cca0c2867d5a",
+            "bytes": 1699,
+            "contentType": "text/javascript",
+            "path": "/assets/ShieldWarning.es-DxHvM6gR.js",
+            "sha256": "f52bd2fb7c1ff0c5853e98f5aca1420edd50faa97936b9a48d622d30a2dc753e"
+          },
+          {
+            "assetHash": "65e20b9643f16a4d6b3e82741df63cf2",
+            "bytes": 1223,
+            "contentType": "text/javascript",
+            "path": "/assets/SignIn.es-DR9QZRQ6.js",
+            "sha256": "6b8ac26243b12cf49cc2f4d33a94d83b6e1f7978544344bf3589d133a3217592"
+          },
+          {
+            "assetHash": "ead4167685f648fa550efff95e69df1e",
+            "bytes": 2006,
+            "contentType": "text/javascript",
+            "path": "/assets/SiteAccessField-BRjbll_n.js",
+            "sha256": "581f967149b9af3e64a3cbf625522bf925c8b278d05e525370bfe4456f3aa8f6"
+          },
+          {
+            "assetHash": "462a4c0eea17cc1fc48c7c947c9d0f9c",
+            "bytes": 14299,
+            "contentType": "text/javascript",
+            "path": "/assets/SiteOverviewPage-Ca_9NhG1.js",
+            "sha256": "31d714505bcada94325c97056c7d09a6a0072c89f75b9123e544db862de4560b"
+          },
+          {
+            "assetHash": "356d903ab0c00c923501668562873ff6",
+            "bytes": 95635,
+            "contentType": "text/javascript",
+            "path": "/assets/SiteSettingsPage-Ci9KyZ3V.js",
+            "sha256": "4e30bb4ef4da357f3327232486d35839ae9ec1acd44da98bfdcb2d6a723adc22"
+          },
+          {
+            "assetHash": "5fe77b69a6f46aba2098f5b622c4d590",
+            "bytes": 4210,
+            "contentType": "text/javascript",
+            "path": "/assets/SitesPage-DqXhJDMt.js",
+            "sha256": "865717da0cc69947665a013c56d5c90b982c2d22abb9a842eff84c0f72bf327c"
+          },
+          {
+            "assetHash": "256860776443c7f4eae412a3c895640e",
+            "bytes": 12159,
+            "contentType": "text/javascript",
+            "path": "/assets/SkillsPage-IgK0RwyD.js",
+            "sha256": "6ac47d5b4231b1a53f03de0a976e99da586beb862a7f474da45991a0d08b0967"
+          },
+          {
+            "assetHash": "0b1ce88d79461f6c8741cdf01e840c2a",
+            "bytes": 1335,
+            "contentType": "text/javascript",
+            "path": "/assets/SlidersHorizontal.es-DU9pP_oN.js",
+            "sha256": "0ea79cae7904a42d96e37f1b5f8c85bba2bf06e4c0ade7cbf177d18a4cfabbb9"
+          },
+          {
+            "assetHash": "e8dd3a473dc0320d7b863eccf81d6a83",
+            "bytes": 20548,
+            "contentType": "text/javascript",
+            "path": "/assets/SnapshotsPage-8W2qFiqI.js",
+            "sha256": "a4d255ac855afb92f1ccbde6a06edd3dfc18b1719e9e2f76f89a2c09a15def8b"
+          },
+          {
+            "assetHash": "9e452e71b429efcc6766cd6d2ff00db3",
+            "bytes": 307637,
+            "contentType": "text/javascript",
+            "path": "/assets/SourceEditor-W-oKqiEa.js",
+            "sha256": "ea58a0bf2f1526c25e866c384a7159c5ea9f1f7c384a2f94cccbd4fe6be18652"
+          },
+          {
+            "assetHash": "e5da68d7726e160fae3a75d38d96149f",
+            "bytes": 2132,
+            "contentType": "text/javascript",
+            "path": "/assets/Sparkle.es-CFKD1Frb.js",
+            "sha256": "5c4ef28b2b079688f07914b9a585112934a5f16acd4150192e6ad893936bf43e"
+          },
+          {
+            "assetHash": "690354b50b0955c116f9987595c34945",
+            "bytes": 2022,
+            "contentType": "text/javascript",
+            "path": "/assets/Star.es-CtMOP0Bt.js",
+            "sha256": "df52a7e7b60231f63c495d36182c66430f5a0490bb275567e19fa73d6198acab"
+          },
+          {
+            "assetHash": "2632966ba7134ab141d16d8a4aabeaad",
+            "bytes": 11898,
+            "contentType": "text/javascript",
+            "path": "/assets/StatTile-IFSjHMic.js",
+            "sha256": "3ec9fa2807a13e790fb5ae257c3d11aced4ac10944de5e37b06d66de621b7cc5"
+          },
+          {
+            "assetHash": "a46ed4c2c8b385a5268c04b8a566a2f6",
+            "bytes": 548,
+            "contentType": "text/javascript",
+            "path": "/assets/Status-Dd9WRBP1.js",
+            "sha256": "52b7acb5e816a06cfe6b10e244eb560a5db4facdad66219538f99f4ea26288ec"
+          },
+          {
+            "assetHash": "c8fd6c8e9fbb7434f1b1b8070eaee97f",
+            "bytes": 18153,
+            "contentType": "text/javascript",
+            "path": "/assets/StatusActions-B1rqPKp6.js",
+            "sha256": "846b0734a5318baa306000dce8c3fa9b903ed648f5a2cc2a8a026fe02ac2a536"
+          },
+          {
+            "assetHash": "48974df9dac213fecb53f675471d9f22",
+            "bytes": 1096,
+            "contentType": "text/javascript",
+            "path": "/assets/StopCircle.es-Ee8cClpc.js",
+            "sha256": "95c44a3d9721062e442988149f023c75d9b43bef93d1a0c4013c0f328ec10cbb"
+          },
+          {
+            "assetHash": "146cd33931388abf2e3e88aafe9a2a37",
+            "bytes": 1067,
+            "contentType": "text/javascript",
+            "path": "/assets/Table.es-CPWQPhl7.js",
+            "sha256": "7e00913187188ea7fa1daeea8ca763dd2f7a35afeb94252154f8d5c711afdae6"
+          },
+          {
+            "assetHash": "8f9530951b09712c5200c1c3e6c66ed3",
+            "bytes": 445,
+            "contentType": "text/javascript",
+            "path": "/assets/TableSkeleton-DqggMdG0.js",
+            "sha256": "7e281819e7178f86ff06f8fc6348fd38617cd545d57c7bf6ef14e84b89d130b0"
+          },
+          {
+            "assetHash": "1d95556b844366ad13245f361736c95f",
+            "bytes": 4879,
+            "contentType": "text/javascript",
+            "path": "/assets/TagsMigration-D6F-LQG_.js",
+            "sha256": "ae76f3ed2f6333ece43feacb073d55a6dcefc9d449d77055dacc16daa756d703"
+          },
+          {
+            "assetHash": "859e6c4cfc74826e1632856da4998978",
+            "bytes": 6754,
+            "contentType": "text/javascript",
+            "path": "/assets/Taxonomy-DyzU0u8B.js",
+            "sha256": "6f68735394ac219d04fcf7805028fc5ca8472bb53cdf0b75e27bd11d6c125d47"
+          },
+          {
+            "assetHash": "6184658c5e0b4084808f464a0e245199",
+            "bytes": 25455,
+            "contentType": "text/javascript",
+            "path": "/assets/TaxonomyPage-dXgi9R5w.js",
+            "sha256": "3a56947a841e6378b6637f3ed6f071f4605bad982e22bd8e40d87682a501b96f"
+          },
+          {
+            "assetHash": "46da9702256f176cde9c7522b7be7475",
+            "bytes": 16713,
+            "contentType": "text/javascript",
+            "path": "/assets/TaxonomySettingsDialog-HND1ryKG.js",
+            "sha256": "9fa8775b0add0f9bf8ba2e4ff90561e19c115ec712ed0ae771db38214d175e92"
+          },
+          {
+            "assetHash": "10f7c9ebcb2e5039dedba346d67b4bee",
+            "bytes": 16199,
+            "contentType": "text/javascript",
+            "path": "/assets/TermEditorPage-DXL3ULWI.js",
+            "sha256": "1337cdc159a8bce2647dfebbd19bfe8df602c2a471ba53156df2d464d7f34f16"
+          },
+          {
+            "assetHash": "438dca74930ca6a38d0753809ef53241",
+            "bytes": 10945,
+            "contentType": "text/javascript",
+            "path": "/assets/TermPickers-C8Bo9FgX.js",
+            "sha256": "c5445c169c9642bcac6c44c17da3221c0d58702a18196479e031d98d36d7e515"
+          },
+          {
+            "assetHash": "2a553d19089f19fba31170cb22510ac9",
+            "bytes": 1352,
+            "contentType": "text/javascript",
+            "path": "/assets/TerminalWindow.es-BhmVQkxb.js",
+            "sha256": "493aa46ae39d0cdf20968c095af817d75c6da388cd5e9b6afadc3bfbb2245901"
+          },
+          {
+            "assetHash": "0cd88dce879696f9c097c5c5cd784035",
+            "bytes": 2041,
+            "contentType": "text/javascript",
+            "path": "/assets/TextAa.es-DadodqoM.js",
+            "sha256": "939d9383cb70a6d414c04fc57b2893cc6c3bacce685f7d1fa73aff6927a76f25"
+          },
+          {
+            "assetHash": "6a2c132caf3aafd294e9de3db27d3597",
+            "bytes": 23187,
+            "contentType": "text/javascript",
+            "path": "/assets/Toast-BRFXozgt.js",
+            "sha256": "039150666a8db5d3eee526692412270ffba7534a87472d13757c824fca5335d9"
+          },
+          {
+            "assetHash": "10b6686e1424ec5b4c76b09a0a8086ec",
+            "bytes": 3005,
+            "contentType": "text/javascript",
+            "path": "/assets/ToolbarRootContext-eqk40r8uh4h6r211-BkyB9D6c.js",
+            "sha256": "abd52c850497e20771c23d15a3c68242d3f51144c80ec01adf54334a98093d81"
+          },
+          {
+            "assetHash": "c40e7e6b0edcdc7933b92e84477bc38d",
+            "bytes": 8927,
+            "contentType": "text/javascript",
+            "path": "/assets/TranslationList-BhzYkuAS.js",
+            "sha256": "3d95dca59ccd98a7211de0e7a09339001044f90807ff3ec04fb443b815dbfedf"
+          },
+          {
+            "assetHash": "76851be9b4906cbd0ad73a6c5f3f590b",
+            "bytes": 6098,
+            "contentType": "text/javascript",
+            "path": "/assets/TranslationsPage-6f2UMDgu.js",
+            "sha256": "df440ce7a8a55a87efb6d33f6eb07ef3fd56ca4f0402c8486fa5e97ae4bb4eb2"
+          },
+          {
+            "assetHash": "af701d50899be9b04386bfb37c2f1b6e",
+            "bytes": 1413,
+            "contentType": "text/javascript",
+            "path": "/assets/Trash.es-2JYykXsH.js",
+            "sha256": "f5a912f32dd1475702e02c202277027fa09f58d541cdfbbcb05c1a0bbcccea3d"
+          },
+          {
+            "assetHash": "dfb9551882a899828b55481b9bb0f623",
+            "bytes": 2258,
+            "contentType": "text/javascript",
+            "path": "/assets/UsedByCard-0ZXWZYKn.js",
+            "sha256": "162032f3cc2059b8977d56d8467dd424500ee43f5b0671478f277005132b7333"
+          },
+          {
+            "assetHash": "bcc46e8716d8fb61c9ec76e65de4df50",
+            "bytes": 1797,
+            "contentType": "text/javascript",
+            "path": "/assets/UsedByList-CqGtfhzh.js",
+            "sha256": "a52fe9f8f367db074b20f10111a2823824ee5da66f1c469977eb4a450d7a64f3"
+          },
+          {
+            "assetHash": "8c663c70acb54485e11765ec40961273",
+            "bytes": 1181,
+            "contentType": "text/javascript",
+            "path": "/assets/User.es-qItRdQDl.js",
+            "sha256": "7529739b623c188c979e1e0c1c9cd7288897b09527a19a926047f88bbd2678b8"
+          },
+          {
+            "assetHash": "0faf84adf2b5a68976b5e21f9e54e979",
+            "bytes": 1455,
+            "contentType": "text/javascript",
+            "path": "/assets/UserCircle.es-CRDG7dJK.js",
+            "sha256": "c9859567ea5cefb358489dd1b78eb5438be85cc57248f0846b7ea00ba5c41ec9"
+          },
+          {
+            "assetHash": "4f4345deb5ae1c702894f35bead841fb",
+            "bytes": 1564,
+            "contentType": "text/javascript",
+            "path": "/assets/UserPlus.es-Dh-6Qn_c.js",
+            "sha256": "4b5b191ab4ca4dc35a89c975f5e67614c14f49a7d3d8f7fb724e42d7dd826664"
+          },
+          {
+            "assetHash": "484893d1faba33c5f562ea53ad8a5d29",
+            "bytes": 1706,
+            "contentType": "text/javascript",
+            "path": "/assets/Warning.es-BiL19mbZ.js",
+            "sha256": "fcb2bc934e97097e76b003257aa35662fe8294b457e177f5f8e045f143c9d6f4"
+          },
+          {
+            "assetHash": "a174c76e15d73b95f4eee0ab4c495d2b",
+            "bytes": 1100,
+            "contentType": "text/javascript",
+            "path": "/assets/WarningCircle.es-Cy3I_TDN.js",
+            "sha256": "dd03b69efc6572e3de677614cc737fbece096ff5719fc2d61665d00b83992bc3"
+          },
+          {
+            "assetHash": "335c47afea55fce9e067de51cd5ae686",
+            "bytes": 1374,
+            "contentType": "text/javascript",
+            "path": "/assets/XCircle.es-85KvulLu.js",
+            "sha256": "c0668267e8b0506a43b8702c8537bca9aad879be095787db926b49c9171e2926"
+          },
+          {
+            "assetHash": "822c3823f363d141cbd2f269107030f3",
+            "bytes": 1816,
+            "contentType": "text/javascript",
+            "path": "/assets/account-BXWuYNDB.js",
+            "sha256": "73f413293063bc5cdcc9ccc9151b893437f539752c81dfee98a276e983d2d9bc"
+          },
+          {
+            "assetHash": "194e1d03fd5a6272ddc005dbd49c120b",
+            "bytes": 3131,
+            "contentType": "text/javascript",
+            "path": "/assets/activity-OO14X2Sc.js",
+            "sha256": "280d7e81e7352bf7267937363154ab00f6fbc100037e58a32c3ecd7c23c1dc93"
+          },
+          {
+            "assetHash": "80e2eed2795b727b0ca8b3b8ec2a7dd1",
+            "bytes": 3535,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.ar-BhZdR6qG.js",
+            "sha256": "d6cfde5bf4df32a07c8350931e896ab765ecc424c5b976983223e9caf88f3fa6"
+          },
+          {
+            "assetHash": "f04450542ebe0a625638ffdd5e7ef77d",
+            "bytes": 2726,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.de-CXaCGWio.js",
+            "sha256": "3f2768410666f3d03702a74d235a763c93b2494d77c97653332b64c86770477e"
+          },
+          {
+            "assetHash": "47d1cbf599532c81026386e30a9dedce",
+            "bytes": 2351,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.en-C8dj9xkg.js",
+            "sha256": "0d641ecd917458595f81874e1c8c46387763380aabc8230311bcb7b8a6c363ca"
+          },
+          {
+            "assetHash": "c7e1b3e6be810e8c167ba8c91ad118b3",
+            "bytes": 2672,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.es-avCBqU5-.js",
+            "sha256": "389819d4d79eac72a50f7fbc70cc52df964f72317c81f96aa78b3b485fa7cebd"
+          },
+          {
+            "assetHash": "d92fe060717e65615dcff0865d3b7e67",
+            "bytes": 4018,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.fa-CKFnDB0Q.js",
+            "sha256": "29d9c6d8883a3621df4150cfb19aab2dcc632eef15f3811d6b70d3cd5c4f59d6"
+          },
+          {
+            "assetHash": "f867fa56dd7621653974f363578f8271",
+            "bytes": 2863,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.fr-35_A52Su.js",
+            "sha256": "f618b78d367991602211d3d07dfb680295aa61ddf90b02e3f73f160eadb5ad69"
+          },
+          {
+            "assetHash": "ab43330215e6f22cfd418ecfc9d2bdd4",
+            "bytes": 3194,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.he-CbsdxPL1.js",
+            "sha256": "c9fac9f0ac2c08a3dfa33e3d2bb5fa034f1bc5ad843d5dc75fc7113a03284acb"
+          },
+          {
+            "assetHash": "2528a7ae2f64bdef5243bf7d1343fd04",
+            "bytes": 4805,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.hi-y4QpRZgC.js",
+            "sha256": "e99b2c7c5b237768bc72d5e2e61214b0249edfc9e59a8ff1922af1ea4b263ee3"
+          },
+          {
+            "assetHash": "dcc50564fd708d8d57f7ac9b63442617",
+            "bytes": 2643,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.id-CO4I-Nr1.js",
+            "sha256": "ae8ade4f419b3cc7b1d05c66d22c3d100b48baaeb8f782177dd2380437993f03"
+          },
+          {
+            "assetHash": "31da7a50296e3f3114d994cce0496628",
+            "bytes": 2635,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.it-B-OytWUY.js",
+            "sha256": "2fda9ed55ac84a495f8f2e340bcd5831c3e8dd8e002de771accbaf1597151a0c"
+          },
+          {
+            "assetHash": "78041b6dab0133e5ce369819e4bf692c",
+            "bytes": 3221,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.ja-ClLGRpLX.js",
+            "sha256": "99da43a8d3bdf4d964149f73b63f88d8b1d22479836f45885bea9615d87a5013"
+          },
+          {
+            "assetHash": "3187cdd4decb805efd9b88559f4115eb",
+            "bytes": 2911,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.ko-CedNf6wA.js",
+            "sha256": "ced5810c9fa0756dc7cfe0d655f5bcacac23f3c5c76e248e55ced9cad63aafe0"
+          },
+          {
+            "assetHash": "7c95ade2c7a0fbadff2bfbbc86b2dec4",
+            "bytes": 2629,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.nl-DuEVpMb_.js",
+            "sha256": "e32724fa7454291b10265bb7fbd499197bb980ff7e92d2901fc639b7e1f8a9fd"
+          },
+          {
+            "assetHash": "9b6b5da051dcd3e98683980602e777c6",
+            "bytes": 2583,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.pl-wXUoviqN.js",
+            "sha256": "d2f3e64ec5597b2a989458826caee1659c346372df51cf39b27fe68582215b94"
+          },
+          {
+            "assetHash": "784e4cb3e6134c678de033c032df64f8",
+            "bytes": 2643,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.pt-BR-Dm3HTskY.js",
+            "sha256": "c308a60cb3caab6ba6e7f6dd640aeaa9463f0349d67ed4519c0f5c61a355be19"
+          },
+          {
+            "assetHash": "3fdc8da77e38c0810c1e372a0727d7b7",
+            "bytes": 3961,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.ru-DOWh4XHX.js",
+            "sha256": "3c0e587f9cf5fd9faa3fa2c294f6cfa39e2d85aae376efab7d65ed279a5b9da0"
+          },
+          {
+            "assetHash": "93331bfb697fdbfc25bf02dcfc31b979",
+            "bytes": 2717,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.tr-xC2kyYK8.js",
+            "sha256": "55d994e58114101f0a3f6f8df35dc094d0f350babafd4d03b3d4e0ba45f449bf"
+          },
+          {
+            "assetHash": "23ed6ac0a89b2636f7151eb5d952a7ca",
+            "bytes": 3914,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.uk-DrE0ioQJ.js",
+            "sha256": "80b993d8ff79191fde0e97fc2eae64f306c9225a7ed3f95a4647c18852a007ff"
+          },
+          {
+            "assetHash": "05cf879f1f09284b5bddff13f7ee93b7",
+            "bytes": 2398,
+            "contentType": "text/javascript",
+            "path": "/assets/aiSettings.zh-Hans-CBZ7ePsr.js",
+            "sha256": "8146782c22ca4741d55ec85ffa4e35f2584e096d8d6dc15a20be6c292d282b7a"
+          },
+          {
+            "assetHash": "b036c01633b89e613fb4fa418ad235a0",
+            "bytes": 1208,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics-range-BqKm9TTo.js",
+            "sha256": "6028f01ed31ef095b7f07727d7bdd84ce9a2cef7990634b7bf612c00cabbdcbb"
+          },
+          {
+            "assetHash": "1ddb94692a79dd2088e8480526679263",
+            "bytes": 1207,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics-settings-DzxkQswL.js",
+            "sha256": "4f2e2aa1cb401b2b5663baf7aa8b01b69bbef78524926bc937fdde632fc5eeea"
+          },
+          {
+            "assetHash": "6f3cb2c3020b03806eaa9da2fa7cf6f2",
+            "bytes": 14647,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.ar-DFKPNZOf.js",
+            "sha256": "d83a3d1f63d8405a41cab5696f14dcb951ccc7621233284da34e9ecec4ce2d05"
+          },
+          {
+            "assetHash": "32e40df490bc93e57e99126837c86023",
+            "bytes": 11065,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.de-CE2_NGRB.js",
+            "sha256": "2ac95aeb978cf3a1dc0f928acc40e8bd6e479d464ea744fe250fe57c7e1eb22d"
+          },
+          {
+            "assetHash": "97250bed2685bc9d03579beebd5e6246",
+            "bytes": 9574,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.en-BjRe9VE9.js",
+            "sha256": "564beae03a01f0414e04cbbf0e672183586495a92e12e15c31530b07dd4cd6c8"
+          },
+          {
+            "assetHash": "3c7f64c0197f798c802c98a250f3b0b8",
+            "bytes": 11077,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.es-DmWAl1lg.js",
+            "sha256": "b4dc7e99f7659ab4b4213f3980cd35c667589249a51de329bc20d4f078eac914"
+          },
+          {
+            "assetHash": "953b9eff3671eb99b8cadf7f2ce2dc62",
+            "bytes": 15233,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.fa-YJ3Xk-rc.js",
+            "sha256": "02afe5cb42ae830870495f37feaf79a3653f3f738775d7522041df8d6ebbc2e7"
+          },
+          {
+            "assetHash": "8e0f759d9718b35820df7c659a18a7ec",
+            "bytes": 11526,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.fr-BDyCguOG.js",
+            "sha256": "ff5d54a0036da4ffdbbf364cd522280dd3c333a961f5436a4d7a76431d95a86a"
+          },
+          {
+            "assetHash": "fe77adaf993247a5d0d6a28295ae37a1",
+            "bytes": 13125,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.he-BJ5kfLvV.js",
+            "sha256": "7e2fc3b13d45a9a15c370334ca0a6b03afd6cd572245b2366813a5aed09ae2cd"
+          },
+          {
+            "assetHash": "f79b606adab001f366d0ec3b86bc8f2f",
+            "bytes": 18925,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.hi-Cc9ZxzNg.js",
+            "sha256": "65b05fa951bae318f809c3859786ba53adedb78364cf16b78f3f81f8fe0bad1a"
+          },
+          {
+            "assetHash": "16b8856351f1e68e5fa83698c66f6f43",
+            "bytes": 10680,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.id-CyUlDO82.js",
+            "sha256": "708b56a2f3d1d6218d86add30301f9fc4c1f84789fe069916af7b4916b30d844"
+          },
+          {
+            "assetHash": "05e74ab21b23a7a916e40bce94d43bee",
+            "bytes": 10900,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.it-NlLzAH3s.js",
+            "sha256": "468e7ac4189982aca29be45d1ff6755950b0963f44ddae43f14fe005a8e45a7a"
+          },
+          {
+            "assetHash": "c78b4ac0105583660a49fad8178f1811",
+            "bytes": 13151,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.ja-DWrGTLXw.js",
+            "sha256": "3dd0c52841f032e534126c014c969202d0dc3ad29e2f9d9e6a927a7befdf7bc2"
+          },
+          {
+            "assetHash": "8276cb84fd1f40eee589f6614b7ff7b8",
+            "bytes": 11449,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.ko-BtZwKyZk.js",
+            "sha256": "168afc319cac7fccf52e2880340d9ce6d62c3026ea923fda8c50b3e07a31c241"
+          },
+          {
+            "assetHash": "a135bcc868ea40d0d71e27dbff10ecae",
+            "bytes": 10405,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.nl-CAny5E3q.js",
+            "sha256": "15b143010c72394b2d7b724d3a0d541e0810180622fc289ceeb96899f59e72ed"
+          },
+          {
+            "assetHash": "4b2301051c21b531f1bedcd00c8700fe",
+            "bytes": 10948,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.pl-CpcS7l9b.js",
+            "sha256": "f96b0fa3e3f0bab25d9f6a6edeafb71738ef964323147ddfa70df6bfb1bd342f"
+          },
+          {
+            "assetHash": "9c811bfb33e2ed6f2a586770855c2fd9",
+            "bytes": 11042,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.pt-BR-BAK_LNqi.js",
+            "sha256": "0f1f14e76075607de363d7f76ad393314feede7e094cdbb65a83657d31dcf9ae"
+          },
+          {
+            "assetHash": "018778ca899b3e1b20ad9f8ce1bf6019",
+            "bytes": 16202,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.ru-BhvbWlC_.js",
+            "sha256": "dfef99f481fe200a72558c95ac0b2b046ab47fb09b70e06662121915ca1aaac9"
+          },
+          {
+            "assetHash": "e3252a31f0c3d13207a235bf0519c7ed",
+            "bytes": 10943,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.tr-aCP2FxF6.js",
+            "sha256": "859e476deee864b4d84008bb7ed5753b569a657fc670c7caee3df7b83bf40880"
+          },
+          {
+            "assetHash": "5eb2717ef9105d53618f288e12ad5a10",
+            "bytes": 16152,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.uk-CAGnu8AX.js",
+            "sha256": "fc9051e61e6197277c45984dede6dd1f84887846ee954d938c533a5c5d08c826"
+          },
+          {
+            "assetHash": "e8f7ff0e4d133878520ce21caab093af",
+            "bytes": 9659,
+            "contentType": "text/javascript",
+            "path": "/assets/analytics.zh-Hans-NF-Le9KQ.js",
+            "sha256": "854e3470b756801352bafa0f8a2b7755921cc903622a9927bd99c2e69e31156f"
+          },
+          {
+            "assetHash": "72c05e8587e9aafbb0f3cb9ac2a5c9c2",
+            "bytes": 9200,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.ar-C5r58XUn.js",
+            "sha256": "38770fcf954314be1dbc30806589f9ad4202747a5e6723ed0b081cccf9acaa87"
+          },
+          {
+            "assetHash": "9c7c5bb061cc0deab81830e2195ca68f",
+            "bytes": 7285,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.de-DVXtSeKl.js",
+            "sha256": "5a0c903c6c7a9232dddb13b064f0e25f0593e23baabb1cbb70235f31220fa52b"
+          },
+          {
+            "assetHash": "0946ead93682b1c8d2c86656227c39c1",
+            "bytes": 6030,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.en-Dn_QYKbo.js",
+            "sha256": "849015b4f45f796443cd84957fdf8ced29f3aa720664192c61fdcb13d5d7a2b1"
+          },
+          {
+            "assetHash": "6d546b6b025f27c66a9d670687c9e3b6",
+            "bytes": 7044,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.es-BdkgtxLM.js",
+            "sha256": "2d9ee6b8c4583aba6fbbbf7201289163fb372de0ddf0922d160698f343ed2352"
+          },
+          {
+            "assetHash": "89339ac66b0c8c11711e809a4f00370c",
+            "bytes": 10462,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.fa-CPuBleHO.js",
+            "sha256": "e25261b895eadc2dd306df6458b6bc40b6f0eae94401d216fcc6e2bf4cc17fe4"
+          },
+          {
+            "assetHash": "534b7c6a3a436bc9763079ecf7023a7a",
+            "bytes": 7785,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.fr-BSq7kZAb.js",
+            "sha256": "3eb7e53b4bd42cf78eb16fe87f82a53e66db44081b752858b29c792c9bb51356"
+          },
+          {
+            "assetHash": "e42215ac089ea460dd0dd55ba2feda10",
+            "bytes": 8532,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.he-Bt4A9H0D.js",
+            "sha256": "33a00c364557a38afc4fc2e1539b877b30feed2529d1462e1f6de11b799f2d21"
+          },
+          {
+            "assetHash": "ce856d6405b72d5a50215f84ce232d71",
+            "bytes": 13628,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.hi-ii2QSWPw.js",
+            "sha256": "61eb6382107887ada33b51f55fbb9ad4019247abbfdbfa7f8ec049fc9aeb2b65"
+          },
+          {
+            "assetHash": "364fb76d89e0b0fc5a525a87231bc0eb",
+            "bytes": 6643,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.id-CIZyEXYS.js",
+            "sha256": "47ceaf3f0811c0f706cc253a47a51037ea8823f6429c14e5c0a88d9e44d96760"
+          },
+          {
+            "assetHash": "42135dfa865b36e1aab36200f4cff06a",
+            "bytes": 7117,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.it-Sevpe-t4.js",
+            "sha256": "7476bea9dbdd0948492680ada552ea0cbd03541777f4df5fd1b5a536a767d1d8"
+          },
+          {
+            "assetHash": "7737b89c4a9220455a73b104de842aeb",
+            "bytes": 8732,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.ja-C_52hTsO.js",
+            "sha256": "82ba044f88f50618353c3565066c436ceb74f1df212e8e5cd42923b72fc2da9e"
+          },
+          {
+            "assetHash": "e0da5f7ca7fc22a54c272e4afa22b805",
+            "bytes": 7268,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.ko-Bpr2STd6.js",
+            "sha256": "f8a50f3ed9663dab140255a72e649646f5d2a886122de75c58146d757ff6485d"
+          },
+          {
+            "assetHash": "dee0093fc699be1a0aa20fa4ea4b3d27",
+            "bytes": 6758,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.nl-DYfP6Uld.js",
+            "sha256": "972d11c525ff02764c49d5ff4db0462e79fe6b27ef4cecc46b30e478461646e5"
+          },
+          {
+            "assetHash": "33d1fffda9ffb1c77099c4eb040115c4",
+            "bytes": 6919,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.pl-BuJZVVIi.js",
+            "sha256": "6e5f0496045b21ce2753f8451a0b04cb733e5cb84ac355139c8ef270ef5fc0bd"
+          },
+          {
+            "assetHash": "c65b75a8832add5154f6307f6c6a81c0",
+            "bytes": 6983,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.pt-BR-C4BNIRKw.js",
+            "sha256": "8969b669d1efce22d6ee053571f74efd4380ef6c077cf5c0bb4c3b7ffa8fc2af"
+          },
+          {
+            "assetHash": "f813313c66f991a9ef380a219cef80f1",
+            "bytes": 11334,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.ru-CiMbwjeg.js",
+            "sha256": "44a5f3f1b9d9829647662d1597cd9c85cc9316887c654f34a032003cef9f9229"
+          },
+          {
+            "assetHash": "0805eed4b6eca90c978a3bf24cd63f3c",
+            "bytes": 6752,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.tr-Csuqe5q9.js",
+            "sha256": "1059783dbcb3de67dfb6fa489a27be8e15f137db5039a8cfa10c1e9950688ea6"
+          },
+          {
+            "assetHash": "1e0f9ec0704e5c1b5b661008aca891e7",
+            "bytes": 10927,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.uk-DVRqhoqA.js",
+            "sha256": "811e7faf62870eaed40d5311953bb861d614006d1cb4f66ebbf0ea4ff071f138"
+          },
+          {
+            "assetHash": "6a774e1b597735283d5ad867ac45ebbf",
+            "bytes": 5894,
+            "contentType": "text/javascript",
+            "path": "/assets/analyticsSettings.zh-Hans-Y3Ei4xGe.js",
+            "sha256": "17a042fd18f474ba8985f1c5d91c3a9a473ae21a7c6c253922a30e9382fc1a84"
+          },
+          {
+            "assetHash": "de0cfc344e9d8d8109c2e40dab5ef98e",
+            "bytes": 18312,
+            "contentType": "text/javascript",
+            "path": "/assets/api-CBSTvRxX.js",
+            "sha256": "41d9b2f541e625f8f1ed8c89d53ba89066679cd89ae0336778c7b418713ca734"
+          },
+          {
+            "assetHash": "2e464a3b0126b64cf72b3a3ba167fa14",
+            "bytes": 578,
+            "contentType": "text/javascript",
+            "path": "/assets/api-errors-DLxbD7fm.js",
+            "sha256": "a7810ddf7a236dc6c9b57971dd5cd512acfe1aa314502430ad65257f755fb9cd"
+          },
+          {
+            "assetHash": "6113bad2e59dae10c546f0f7c40ea7c6",
+            "bytes": 2325,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.ar-CjHmVq1_.js",
+            "sha256": "ec971f4db9c2e25557011d72f9d19982512d5da663829821b3ce4cd4a2314467"
+          },
+          {
+            "assetHash": "d510dce8a8f12e703ed782739cbdff0c",
+            "bytes": 1926,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.de-RD44SQgd.js",
+            "sha256": "45b9bad048288d83ce4a36a90f8ec894d5a2044be9d61508aed65d469a9a66bf"
+          },
+          {
+            "assetHash": "5994cf36c6298d2445b75428ac71187e",
+            "bytes": 1663,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.en-mFJuOtIH.js",
+            "sha256": "73c4bffdf2b434c7a485dad4ac04a14d038b7b5248ba92e7ada910e38912b3e5"
+          },
+          {
+            "assetHash": "9ec11ba7d1cfb1b1289b100d5884a772",
+            "bytes": 1897,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.es-Dn1vcVTZ.js",
+            "sha256": "bbc2165bb775fe9ef4e433cb6c1838c552c30ab014d9840ee705de5d342a9b79"
+          },
+          {
+            "assetHash": "294f3c31bf26ba4f2a9e53040ca5c638",
+            "bytes": 2508,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.fa-DclMXXrQ.js",
+            "sha256": "00af801ef8e77755d0ade3dacc992621f946b3ce65f26a8b8a0ef4d2aafdded7"
+          },
+          {
+            "assetHash": "393fab603574b673a5c737ffedf4367b",
+            "bytes": 1986,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.fr-CRqH6-pt.js",
+            "sha256": "a12f7338b4d9b7b46cf111b7d0a111155d5d995392dd074ae5c41fe458ddd6a9"
+          },
+          {
+            "assetHash": "bafa7b99e7001350730c7fe9bf52bae4",
+            "bytes": 2313,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.he-COP1zzas.js",
+            "sha256": "9cc7e2236cfbc2510e3f2a66c690cb649fb53bc1d5e4a605fedc71205f3ef808"
+          },
+          {
+            "assetHash": "066085e1507ce3b28c7ff4dc3b535446",
+            "bytes": 3196,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.hi-CnnlhSEx.js",
+            "sha256": "758b61b78af7f840b3c5c43f134036fcabf807daab5bc9248c3af13e904763d0"
+          },
+          {
+            "assetHash": "637ca8742c260cd6b9bb750ff0c587ba",
+            "bytes": 1814,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.id-aSp7eOOW.js",
+            "sha256": "fad4085871eabaa19f802fae4378a18ff7ed806422af0018afb164a099597612"
+          },
+          {
+            "assetHash": "a5efa205f31a2cb598d0b85973f07cc3",
+            "bytes": 1906,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.it-D4AXypSF.js",
+            "sha256": "918be2fb134ff385529120270d3814a9d9ffa35225ea4015e2a9a51c38eb34ab"
+          },
+          {
+            "assetHash": "2a644db7a2f9b65110949096548b8c12",
+            "bytes": 2263,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.ja-BoMHIaWB.js",
+            "sha256": "e2beacc7b67f8416fa13de6b0982cc073614272d83d40ccd893b9ecb7297d596"
+          },
+          {
+            "assetHash": "cbab6b142d474e4a40462d2a98dc966b",
+            "bytes": 1993,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.ko-BqysekcN.js",
+            "sha256": "4088f56a25c6727659bafcc8c8e070b5ee7c0dafab883b131ecd2dd12214250b"
+          },
+          {
+            "assetHash": "58b74859d3513caa70e4db162fe17698",
+            "bytes": 1820,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.nl-DkQd7NMN.js",
+            "sha256": "ab451c9a0d9e2cb3fb7722df2ad3a9d253864afa49fbaf33010a5373f4a6e736"
+          },
+          {
+            "assetHash": "1a81886a0f6c2fe85d682b79c2009ca3",
+            "bytes": 1892,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.pl-YvYqfbPP.js",
+            "sha256": "f00d5acaaf2a7c1c63491f6e4f4b720c2bcb62d5f4d2fa374088775ae7c90ab5"
+          },
+          {
+            "assetHash": "7370f1648b9aaaa9474aea7494fbff5b",
+            "bytes": 1876,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.pt-BR-8R80D9mc.js",
+            "sha256": "e42363ad26a177e9ad36948b1103ad754b7b93ef5aab5b66de9af6c800d2508f"
+          },
+          {
+            "assetHash": "0efa521070f93ba5f5fe61a9b1c8491c",
+            "bytes": 2671,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.ru-CLiGYIyF.js",
+            "sha256": "7bc34e2e67cfcb56c5af7dd81f59ba7e05319ec73606ff883ad8aa9bf888c165"
+          },
+          {
+            "assetHash": "c6a9d0739766d52c88f52f90d51795dd",
+            "bytes": 1825,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.tr-T9TtsGiz.js",
+            "sha256": "f1ef45e80f1b49292c7b9dfb60009d7ed25acbdf939c1a4cb095b10d0891c99b"
+          },
+          {
+            "assetHash": "b7e0b844faa7a394dc1659aad451158c",
+            "bytes": 2628,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.uk-C-AHWC43.js",
+            "sha256": "909d636aee56cf24206d71ed21bc205829106ad3cfd576cb8e8ef2970b2382e8"
+          },
+          {
+            "assetHash": "fbc7dfc15db263a36ff904592797017d",
+            "bytes": 1732,
+            "contentType": "text/javascript",
+            "path": "/assets/appearance.zh-Hans-0HyzAidT.js",
+            "sha256": "b4dee21bc873c48a2e560e68b0854e2b940e55d27ba3d592231b90c6347ec7c5"
+          },
+          {
+            "assetHash": "099137162cb836836a1ea845c2a31656",
+            "bytes": 2377,
+            "contentType": "text/javascript",
+            "path": "/assets/approvals-BkvNejXF.js",
+            "sha256": "435fd19c894f47c1a7abd4a4441fd5b075bbe35beb2a3d8c0b16d77b0ce50902"
+          },
+          {
+            "assetHash": "44fe1326d0f65b9e02ab063bd2369d64",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/ar-CeWpkHnL.js",
+            "sha256": "bc77bc83711ba2a98463fa86b9804845a25dff7a63912fbfe4d480d820741e3e"
+          },
+          {
+            "assetHash": "b4d17695009c71ee24be4bf31aa464ad",
+            "bytes": 165293,
+            "contentType": "text/javascript",
+            "path": "/assets/ar-bjEc1xi8.js",
+            "sha256": "fa9213f455ec035ffe47072106513b904bec58f396a28ea77ff352ad1c26cc17"
+          },
+          {
+            "assetHash": "4812cfac4797a2a7f940f31a9cd831cd",
+            "bytes": 209,
+            "contentType": "text/javascript",
+            "path": "/assets/areArraysEqual-bcx201k1x9ziizym-DpJhDMgE.js",
+            "sha256": "c2f13fdcc4a35558e0706801e63fef2799262dc4301212083b3beb79c2a96cce"
+          },
+          {
+            "assetHash": "124399b70bcf57c876b6416471908fdd",
+            "bytes": 44509,
+            "contentType": "text/javascript",
+            "path": "/assets/auth-B7gQUldh.js",
+            "sha256": "ce7458b5513bbeb77a29de6860b8498027f0e5c38ae72efa9cd2db69c9570411"
+          },
+          {
+            "assetHash": "62e88ba4be53cf1044b3c7104ad996a8",
+            "bytes": 3501,
+            "contentType": "text/javascript",
+            "path": "/assets/badge-dq8bx752b9dp7dst-BSe3ItaV.js",
+            "sha256": "85b7f63c4070cebe6e843840b3f35f28770eeaa3b0c6a61b8cd783a00573b51c"
+          },
+          {
+            "assetHash": "2c11e90c4206b24c208ef1190ec4d022",
+            "bytes": 4917,
+            "contentType": "text/javascript",
+            "path": "/assets/banner-msnmqm7of2lck01x-FNnkpRbE.js",
+            "sha256": "9bb041bb0aa640e5cb0e136107a70a173c845413c7cc7d93dbba6cb6585a8d32"
+          },
+          {
+            "assetHash": "badcc389fce9fea89ad78560deba633c",
+            "bytes": 64,
+            "contentType": "text/javascript",
+            "path": "/assets/bash-jpzh6zbf5237424t-Sd52I7bZ.js",
+            "sha256": "b31daa972a59b1d11132500e46d16812e73b788a1b171b8add323e1f5ab2ac2c"
+          },
+          {
+            "assetHash": "9bd902462c52eb27de3fd51aa2f0af7b",
+            "bytes": 3606,
+            "contentType": "text/javascript",
+            "path": "/assets/blockIcon-DoQ1yAO_.js",
+            "sha256": "29f4f4178af1b5ab1166d36f9bb6b940a53ed06096a0f28459782f001acaa2a5"
+          },
+          {
+            "assetHash": "63e416a38b1f29d1f5fba74294880393",
+            "bytes": 16107,
+            "contentType": "text/javascript",
+            "path": "/assets/blocks-DaRWgHXb.js",
+            "sha256": "198e233d398e35c1f8fddd19912c281147c21cc501b384edcf140091b21bf213"
+          },
+          {
+            "assetHash": "e2793d49b0cffaeb24874225b7a4b27f",
+            "bytes": 5965,
+            "contentType": "text/javascript",
+            "path": "/assets/body-blocks-BvozTe2C.js",
+            "sha256": "d162c0c7c08752b12d3538d2bf96c6a2665fa32a2e3283a921cfc72a399b5e9f"
+          },
+          {
+            "assetHash": "1f8d70b32c9008dfdfbcb02983423710",
+            "bytes": 4096,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.ar-DIDA9Sn4.js",
+            "sha256": "c08977eb8bee5b469efddec6d96c924693fffa35c4b17e2af5838bd9fad5f775"
+          },
+          {
+            "assetHash": "cdc936407827476a43f54a627025ad0a",
+            "bytes": 3257,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.de-qfipY5HQ.js",
+            "sha256": "0d5bd4072a13d9b0e2feb0894a048f9006c1f922b0bf8982d8a7fe62230ea3f1"
+          },
+          {
+            "assetHash": "ea1080d7c5199e35be71f63e9409406e",
+            "bytes": 2745,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.en-B8FW4NF8.js",
+            "sha256": "38d360e2713f04ad1f1822a91394e7264d8d31a7e92b7d0af6265878dba5fd17"
+          },
+          {
+            "assetHash": "00d00b9adaaf62d83b52853e09738547",
+            "bytes": 3116,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.es-t2G9FCJv.js",
+            "sha256": "0bc372decd69029f1f59fd6ae161dcacb2d558cb3b14eebffa09afaaa85d42f4"
+          },
+          {
+            "assetHash": "a83cfb1748bef72a42915d4ad424e68b",
+            "bytes": 4405,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.fa-C4B5tIHm.js",
+            "sha256": "1b9df1d761772d0c7717916df9c826816475ec6060015d66e59611e1f33c34a2"
+          },
+          {
+            "assetHash": "1518be46cf48ac40d532cd8243744453",
+            "bytes": 3188,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.fr-B93u2T8B.js",
+            "sha256": "8d47a82bc02bc2c2f20c1b76c3c4250ac70df4ef117e525e3aa278ffde3fe091"
+          },
+          {
+            "assetHash": "69eed9e23729a60682922575f9285e2f",
+            "bytes": 3686,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.he-AjBYzDbH.js",
+            "sha256": "87f99c022a0029fc8348572ebfb5bc1c8186cfdef67b3fe9f01a720e58c498cf"
+          },
+          {
+            "assetHash": "8e718e5a5a1b8cb007b438a3943340c8",
+            "bytes": 5541,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.hi-BL4B8EIK.js",
+            "sha256": "2f8494affad252260b30847949d4713b57932feaade4ceadc63dbb46220cefbf"
+          },
+          {
+            "assetHash": "03bb6a429117629f5d308cf870b67992",
+            "bytes": 2925,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.id-DaiRImPk.js",
+            "sha256": "87b9f8cb6572ef00193e596811df568693e776b2ac4f5fe7d7d878204fbd922c"
+          },
+          {
+            "assetHash": "bb181d18a88d28cf6038fc4c2081b03a",
+            "bytes": 3081,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.it-COSRU27e.js",
+            "sha256": "7ee04de5f3f39cf12c487c1ebfd684116c2ca9b88f47d70f71620f99bc813861"
+          },
+          {
+            "assetHash": "e7993be3e3a84abf698276ba45f1884b",
+            "bytes": 3732,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.ja-4ELaS2Vi.js",
+            "sha256": "459e49a37484da8e72993507ce843612eb65bfe237008ac3b51b1eab18523490"
+          },
+          {
+            "assetHash": "57fdf13dc555b5d03b42202928459cbe",
+            "bytes": 3333,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.ko-DzXpHAYj.js",
+            "sha256": "354390f49403cca4ebc42a7b9df69a1cb884c309fddcc7b3c27e44f060685264"
+          },
+          {
+            "assetHash": "ec1fb832321926b7245933904696bbbe",
+            "bytes": 3053,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.nl-7a_XD53a.js",
+            "sha256": "319dec82defc20b10ac52fa5cbb1572dd307864b4929b0b102b9adcaaa086d33"
+          },
+          {
+            "assetHash": "76bf897f202d32cad200e7015036f503",
+            "bytes": 3004,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.pl-o2L5fJfH.js",
+            "sha256": "b6979b8367b8c4f02d7bffac7fcbe78284c209494e3bf2d5935d5523f314dcd2"
+          },
+          {
+            "assetHash": "66001659e5b4e53175f469e58561c0a4",
+            "bytes": 3107,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.pt-BR-CRnn6BFL.js",
+            "sha256": "73fa572d1940f35d85c3754a4b89d6c550d134d120d7528084eb44145866abbd"
+          },
+          {
+            "assetHash": "53c351ec6b9b38d287cfdbef4901e058",
+            "bytes": 4708,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.ru-BS2txhVI.js",
+            "sha256": "f6aee5fac06dc3c1bf06ac8ba16a296639572a4bd19f109b92b869935b5e001e"
+          },
+          {
+            "assetHash": "906217e26cc23d48cbd30bf50b515e61",
+            "bytes": 3048,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.tr-DXm3ACCU.js",
+            "sha256": "174a7ba653c35ffb1fa27dad74eb0b740d5ea984bd039a39d4a50ddf8787b013"
+          },
+          {
+            "assetHash": "34942324f9603780b51f38d732e30373",
+            "bytes": 4586,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.uk-D32F3Hv3.js",
+            "sha256": "a1a68159e1c41c3f18290d37611ea280fa1f480b3d3dadc32ecacacf35c68bd2"
+          },
+          {
+            "assetHash": "e2dcbdbdfd9c87385e8bd28b5374704b",
+            "bytes": 2716,
+            "contentType": "text/javascript",
+            "path": "/assets/bodyEditor.zh-Hans-DzWUdSMX.js",
+            "sha256": "1e05227568f98a1615679219bd97e2c9ebc32f1a1b277bef0ffc2cbcd87f6b4a"
+          },
+          {
+            "assetHash": "7d4b3407c760b2b565cc0652b080debd",
+            "bytes": 6095,
+            "contentType": "text/javascript",
+            "path": "/assets/build.ar-DICwrPz-.js",
+            "sha256": "1effea9bf3a76779025e05569ca84a20ee5f2f1bb9abf8c922044fb72e08c100"
+          },
+          {
+            "assetHash": "c3f35356fefe24ff8d6d49d452867936",
+            "bytes": 4500,
+            "contentType": "text/javascript",
+            "path": "/assets/build.de-Cx-KY7eK.js",
+            "sha256": "0ab3da2dd6359f829bc7e5989f1ec90e63c109294a410d9aed105d070c6518b6"
+          },
+          {
+            "assetHash": "29ea333fc714534eca707e20533d8a66",
+            "bytes": 3891,
+            "contentType": "text/javascript",
+            "path": "/assets/build.en-J3aIgw-E.js",
+            "sha256": "aec3973f8ffcc024c1c849cef722715f8b65392e7733419d0e48036bd50a00ad"
+          },
+          {
+            "assetHash": "5c3e5dee5387890c73970339c147b888",
+            "bytes": 4535,
+            "contentType": "text/javascript",
+            "path": "/assets/build.es-BsZGAdF_.js",
+            "sha256": "0bbb057d6155af661b05f977b68204e4054beb6b23a82b1dc6a0189fb40d0c00"
+          },
+          {
+            "assetHash": "a844e97e3659ac0fe1a54f512e8b8e3b",
+            "bytes": 6403,
+            "contentType": "text/javascript",
+            "path": "/assets/build.fa-mbCzQ7aF.js",
+            "sha256": "04161f63c407e37d294480c2d4ad1a29bd2f68729124228114fa7d1395a938f3"
+          },
+          {
+            "assetHash": "486824ae0239a03061aec40964e4a737",
+            "bytes": 4739,
+            "contentType": "text/javascript",
+            "path": "/assets/build.fr-10p2Wv2s.js",
+            "sha256": "047bd1913125911b0bfe4b4fde631c3c56147c182e171cc838007b4291e61680"
+          },
+          {
+            "assetHash": "da723410caf42e3dc4c2708bf7f5068f",
+            "bytes": 5233,
+            "contentType": "text/javascript",
+            "path": "/assets/build.he-kibnQ3R2.js",
+            "sha256": "f0c506586b1f9f5febc1d2ff60966bd03203d8c48bcd86d26262d8463a3a2710"
+          },
+          {
+            "assetHash": "a4b1dc096ee1cdad4227a93a8b4f4ef3",
+            "bytes": 8007,
+            "contentType": "text/javascript",
+            "path": "/assets/build.hi-ZGOwJb9r.js",
+            "sha256": "62c1362cac07896e56531a65602f260790dcfe7c9d09a28298ef394a5390422d"
+          },
+          {
+            "assetHash": "a8adc89bb28926aeb37cc0e087fb0e1b",
+            "bytes": 4135,
+            "contentType": "text/javascript",
+            "path": "/assets/build.id-BLo5VcE6.js",
+            "sha256": "a4da39add65bf4f8177e62c27f402c86dc58a554ca4d4421fcf046534a7b6274"
+          },
+          {
+            "assetHash": "d7c3d3b6441f992456cb190c682192bd",
+            "bytes": 4449,
+            "contentType": "text/javascript",
+            "path": "/assets/build.it-BOQcuIQd.js",
+            "sha256": "39c4a48f9f721a02ff6967663b9fe35413896d61e6799cace0d74c220d0b3aa2"
+          },
+          {
+            "assetHash": "d7c4c3bf8c1b07b34689311f59eb3b5c",
+            "bytes": 5258,
+            "contentType": "text/javascript",
+            "path": "/assets/build.ja-w2Qo2d5D.js",
+            "sha256": "8d707453ed939d45601853dafbf91181f7e751dea0a8bb848eec83eba5b972aa"
+          },
+          {
+            "assetHash": "dee5a7db5cfc74bb292a980284597895",
+            "bytes": 4693,
+            "contentType": "text/javascript",
+            "path": "/assets/build.ko-6DJGawGQ.js",
+            "sha256": "a43eeea2977006de9e8f87ce60d8af2d57d57869a598d6547ce67159eba3394f"
+          },
+          {
+            "assetHash": "e90a30d9efedff4940c95f07ab555157",
+            "bytes": 4288,
+            "contentType": "text/javascript",
+            "path": "/assets/build.nl-B7YO-A1Y.js",
+            "sha256": "bb547ea480012cb1ee520bc0681f8910b4d9ca6184dafbd2a94fbf423e5cb788"
+          },
+          {
+            "assetHash": "906eb84c82a77f25af7040deaa1a442d",
+            "bytes": 4368,
+            "contentType": "text/javascript",
+            "path": "/assets/build.pl-BhTAIv5C.js",
+            "sha256": "1174eb3f0221033232e54d36ab225fe50e431b51453e8afbe067f7bec9fd61a2"
+          },
+          {
+            "assetHash": "3104366261ccad0d3a4dded7d8abae8a",
+            "bytes": 4329,
+            "contentType": "text/javascript",
+            "path": "/assets/build.pt-BR-D8Wn1Dgy.js",
+            "sha256": "b7494b55577e6ed1eac2215ac986ad795b3107ea31b9170b87a4eef336dcc989"
+          },
+          {
+            "assetHash": "68157e62454f9eaced4754dd231aa0b1",
+            "bytes": 7087,
+            "contentType": "text/javascript",
+            "path": "/assets/build.ru-v--oehFN.js",
+            "sha256": "bc9dc036851dd101e6173cb479b72738aa643c70b79ce6ecf07e8227a5849dbf"
+          },
+          {
+            "assetHash": "4d5af5b08659f4b6e16060a07337907d",
+            "bytes": 4196,
+            "contentType": "text/javascript",
+            "path": "/assets/build.tr-B4gIb6oo.js",
+            "sha256": "b90d8e5f3edd9c1dfe866eb4a25c38721e8ee45ce907c5df4fea8604ccd384be"
+          },
+          {
+            "assetHash": "d2d34f9bd4b1d90c87b608759a29b931",
+            "bytes": 6746,
+            "contentType": "text/javascript",
+            "path": "/assets/build.uk-C2Yq3Kzn.js",
+            "sha256": "bbbf306348a75503693dc4459bdfedf13035fba2c0bb60eb6ab1d5d5757c0758"
+          },
+          {
+            "assetHash": "60035a6c3c89f9456efade6fcbe3a973",
+            "bytes": 3911,
+            "contentType": "text/javascript",
+            "path": "/assets/build.zh-Hans-9TOJDL2O.js",
+            "sha256": "b7832dcb9f1381e6254ee29e394250f85cf9ce562e55d7fea8b4c60638beba5e"
+          },
+          {
+            "assetHash": "9c838238124ed33ab25042197747aac4",
+            "bytes": 8310,
+            "contentType": "text/javascript",
+            "path": "/assets/button-pa1bq9b85hawg41a-CTLAwRb5.js",
+            "sha256": "d2fe2b849d82aee6d590f7f0c063473125250a6304971d31c136e821c0b53b03"
+          },
+          {
+            "assetHash": "b0b02e5e7be8c4ffe6cb29dc5f768860",
+            "bytes": 942,
+            "contentType": "text/javascript",
+            "path": "/assets/calendar-locale-BMT99H2y.js",
+            "sha256": "beb1cf6705e6e0d9e46368dbebce92f7d620fed320414556401443984208a48e"
+          },
+          {
+            "assetHash": "7c87281655f9fbe178759202dc3d2273",
+            "bytes": 702457,
+            "contentType": "text/javascript",
+            "path": "/assets/charts-Bp8JMtwp.js",
+            "sha256": "f3fe147c99c03c0e749ee14b67a134bbe4ba74a0973b6c97fa629c807687c3b7"
+          },
+          {
+            "assetHash": "d51f37e0b30635591eef2f0f746bf29c",
+            "bytes": 12541,
+            "contentType": "text/javascript",
+            "path": "/assets/checkbox-lbo436duy7krxcsk-CY7ZGWjv.js",
+            "sha256": "dcd7c06aa05fe447bca9681de3e2c11478a643db1733eccb2eb9344538b7bed1"
+          },
+          {
+            "assetHash": "528f3820e2931017927b3cea745dc714",
+            "bytes": 145,
+            "contentType": "text/javascript",
+            "path": "/assets/clamp-jcjhzx71s6wt1rnc-D0kAXd-u.js",
+            "sha256": "87000bcf318c2defe62c6cf22ad6f97e72735da4a105beff7c65ce3d28b0fbbc"
+          },
+          {
+            "assetHash": "c81f2fa90c3a09acd6a8e68b8b2a0353",
+            "bytes": 4674,
+            "contentType": "text/javascript",
+            "path": "/assets/clipboard-text-gd6s3n1bqse9zgt8-DCKKvcvF.js",
+            "sha256": "6d554d889ae9de01ac6299ac0043cfcf3c0bc074554b0ea952eff83fc7d017fc"
+          },
+          {
+            "assetHash": "fa4e8e069da747f67ba4dc77a066473b",
+            "bytes": 1077,
+            "contentType": "text/javascript",
+            "path": "/assets/code-eu8kzwqnlhoyin24-BlzpiHD7.js",
+            "sha256": "211d7d9bad927b65d9ebd49ce0f2fce0575c47ea4f5dab292649a6cc2daa22b1"
+          },
+          {
+            "assetHash": "a827b22b76c38cf29ca34bd1bd570eef",
+            "bytes": 8576,
+            "contentType": "text/javascript",
+            "path": "/assets/collapsible-l8g94on4w7qfosmn-D4VL6lMu.js",
+            "sha256": "58b4840f519670c8cce06c8e3fc620b720db16afe36de2500a9f2bd6ad08a584"
+          },
+          {
+            "assetHash": "b321668f24605e49b5256c41ab618e9b",
+            "bytes": 6485,
+            "contentType": "text/javascript",
+            "path": "/assets/collection-icons-KuQZKlox.js",
+            "sha256": "089ee04e7b6c9a291b56a7217c6d16a4104b3272d99fdf05762ee8006ad2ba76"
+          },
+          {
+            "assetHash": "91feba0e0db54fc7ffb48c48ec948a9d",
+            "bytes": 1482,
+            "contentType": "text/javascript",
+            "path": "/assets/collection-permalinks-C7ylEEj1.js",
+            "sha256": "e1832b87432fadde0cf49ac10b46b8c2d3b011fe0b3ae2cd10f2c7a14fb4af87"
+          },
+          {
+            "assetHash": "b026d46af4b51975d36ce93d272747cf",
+            "bytes": 321,
+            "contentType": "text/javascript",
+            "path": "/assets/collections-CjXIa-in.js",
+            "sha256": "10821fc650c84fa437792cbb0d9ca3ab38f5b208f62f9353f92a3a1dd027cb2b"
+          },
+          {
+            "assetHash": "c683eca215683d989539ac44d14a6274",
+            "bytes": 5670,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.ar-DiA_JpOG.js",
+            "sha256": "3afbf6df40504ec2699c66fc00238a2b22530361b78d0c9002fdf4daead2ff11"
+          },
+          {
+            "assetHash": "6a6a527515311a5e82dc3ba169510d38",
+            "bytes": 4377,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.de-BNlePM1M.js",
+            "sha256": "91e0c641f6dda33d729b925c39808bf903b1d934261f395a52f98db506e76f70"
+          },
+          {
+            "assetHash": "1b4c49709a54cdd20cfa2c2cb12e0572",
+            "bytes": 3825,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.en-Al6EpZqR.js",
+            "sha256": "c3a14cba2d8a668b3d3ec1a8bfaed9c0d9517f85f2275738e82810c45e50e5bf"
+          },
+          {
+            "assetHash": "1fc47da9e7c0b9ed7f7e17daf822b217",
+            "bytes": 4420,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.es-DWx8cORX.js",
+            "sha256": "14b48069947250b35d3c04310188451016297415accd7209f09bf4b1356684b3"
+          },
+          {
+            "assetHash": "72daab72db6076ac439a3ee5a692b15b",
+            "bytes": 6239,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.fa-D-lEKHxW.js",
+            "sha256": "ade2451d856d11224d3b2ddce2d2924a36ab070653580d77c915f2d01436476f"
+          },
+          {
+            "assetHash": "82f970a00aee6d228fd6c744a4a15041",
+            "bytes": 4703,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.fr-CVm7DnSq.js",
+            "sha256": "1694332cdad579b41210d905184649b9c8e5d9e23c0567f4079ff6f91a75098e"
+          },
+          {
+            "assetHash": "62ab1a863d19e5d6b78035d1fcf5221a",
+            "bytes": 5150,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.he-BCWU_Mw-.js",
+            "sha256": "066f08f7b079129fc775dd06a802bb5765bcbdd461d10be8219099f7d9602f25"
+          },
+          {
+            "assetHash": "271bd18e4d29c1442c4b940ab7c55bb5",
+            "bytes": 8026,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.hi-Du_t8YUb.js",
+            "sha256": "efc7bd4d8e9f5384be8db52e013cb852bbe69b1f02a6cf3e0f8b8bf81de04d47"
+          },
+          {
+            "assetHash": "2c22ff7306e25a642a3523fe768db5fe",
+            "bytes": 3902,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.id-DxYHV7Fx.js",
+            "sha256": "2b002cd2daa0db91ab018119e00069c5fa7b331f36ea20b81072e692bacef0d0"
+          },
+          {
+            "assetHash": "2ee799920e9847864e7634bb502ced93",
+            "bytes": 4243,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.it-ClP5yKOZ.js",
+            "sha256": "8c173e3a1c2bada46b76cbdbec452b6418fbf68db560b6b32de121216aadb50c"
+          },
+          {
+            "assetHash": "2491bd497183799bfd12cf3ee59d3d0d",
+            "bytes": 5346,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.ja-DKBBf6-5.js",
+            "sha256": "cbf111eb21eae281b968a05c6e36df5b82b5aafc8c7f37ac766c524e3742e411"
+          },
+          {
+            "assetHash": "1284db40ac1c2d5aec0bd763f3223207",
+            "bytes": 4496,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.ko-0VKlGhuV.js",
+            "sha256": "2f489111abe72b322da11dca61c45c4814826f2d3dba5b4acaabb5d80315034f"
+          },
+          {
+            "assetHash": "b4670f037cfd526f9dc067b71d0be31d",
+            "bytes": 4147,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.nl-vMMbQJHm.js",
+            "sha256": "95426c4e71ef4f22f63505c69d9c5daae114a733e3e78ce813e4410b4b28262c"
+          },
+          {
+            "assetHash": "78b4950918dd71a65190675275c524d3",
+            "bytes": 4222,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.pl-D8KTyHBU.js",
+            "sha256": "aa853a903fa091bb189c3e03954ff23352c6b043632073b1f843a0b2f76aae3a"
+          },
+          {
+            "assetHash": "3e06c7945dcb58e36912ff30c2ea5733",
+            "bytes": 4312,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.pt-BR--jUFS-5e.js",
+            "sha256": "532195f45ff1ec9a3888aef7ba44b8b997b7399c6c4669b5090f5545d827a7fc"
+          },
+          {
+            "assetHash": "fc85d9a6480abbacea4b163faebdcfeb",
+            "bytes": 6540,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.ru-5dQtadOM.js",
+            "sha256": "6a5ee434e6492eb3b4ef2e2317a0ccb676e7c4ff3ff952478dedd7a59ae237a9"
+          },
+          {
+            "assetHash": "80a442330759471cbb0d623939767907",
+            "bytes": 4293,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.tr-CYelcwm0.js",
+            "sha256": "553fadb8dc27cf20e751a144a029e5e7436d0b294fa5a47df98a2db7dcd7a652"
+          },
+          {
+            "assetHash": "17d6a0d53042664e2bc8130bc24274f4",
+            "bytes": 6320,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.uk-CZAcAtFw.js",
+            "sha256": "a898a85a0496c4e5c68db3f43b87640f5cdc6ae859e23055f0c7b04d2318ed4b"
+          },
+          {
+            "assetHash": "b318a17385ac1a70d18ca1afe4706447",
+            "bytes": 3877,
+            "contentType": "text/javascript",
+            "path": "/assets/collections.zh-Hans-BPoBzFUY.js",
+            "sha256": "f60d5b86cd65f6b9a617e4bf7ccd99b14dea6e83b2c4294f4ea540493845c9b3"
+          },
+          {
+            "assetHash": "108d0525fe3a104671d1fdd2c40afa38",
+            "bytes": 19777,
+            "contentType": "text/javascript",
+            "path": "/assets/combobox-hdxtsm2gqcxxgfju-BJ6B99nZ.js",
+            "sha256": "fc87fea142f639f0402d6c206f416ed344a2402adebc339975ea1b853351e3b8"
+          },
+          {
+            "assetHash": "a37d8bd3e3ef76a1ccb9a98d5f3eae40",
+            "bytes": 2537,
+            "contentType": "text/javascript",
+            "path": "/assets/comment-model-CArtcrfg.js",
+            "sha256": "1718700fa5b3841646bd7f1919dac3de00cbcb5e7e076888ffc2fa34567aa592"
+          },
+          {
+            "assetHash": "3b7b758da4ce0152064c3908ecbe5363",
+            "bytes": 2368,
+            "contentType": "text/javascript",
+            "path": "/assets/comment-settings-QV6Pl-DK.js",
+            "sha256": "9b5812494a01444590eb53069f91d05bd13f21019eb9781d79534cf25b40ca12"
+          },
+          {
+            "assetHash": "c85ea946c02ce547231b326012581de2",
+            "bytes": 3157,
+            "contentType": "text/javascript",
+            "path": "/assets/comments-B1PIozwq.js",
+            "sha256": "aa8f5e5e32b5abd11b0ee033cca27841daa71e019ed03181be0b3b37d607dda1"
+          },
+          {
+            "assetHash": "0aa547b6214ab43adb97873ddfeb7b29",
+            "bytes": 29101,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.ar-7NPLRbXL.js",
+            "sha256": "25faf4c58cfe9ccdead2029573d0114b4f057550069bbf6ed19a0e8e248d48cf"
+          },
+          {
+            "assetHash": "2f0695373d3c971af485f53a4c53ae8b",
+            "bytes": 21267,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.de-BML9h-8i.js",
+            "sha256": "86623ae63f994a48612dbe65ffe732301f52dd8848e63805d69a1e2253efca3c"
+          },
+          {
+            "assetHash": "d7d129d3627f4cdf7aff7338a821b88a",
+            "bytes": 18059,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.en-DXe1aP-J.js",
+            "sha256": "2df0a4a5edbc6224f961da80bdf80734dc2c3bc455808926b4d6297427ead768"
+          },
+          {
+            "assetHash": "e9423826df3dd17f20d16baee0f869f9",
+            "bytes": 21302,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.es-B9qnqc6H.js",
+            "sha256": "1d5da781a576a083c5186f8b868e2d5cae480563049725fc17a1d33b7148299c"
+          },
+          {
+            "assetHash": "86986649bc94e79ce7da65c942ac9845",
+            "bytes": 28993,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.fa-q5l6LTXH.js",
+            "sha256": "ca0fd67ed450bad9b28237565290caf8719248893048a35f1f7e6149e71ddfd9"
+          },
+          {
+            "assetHash": "afd3adec8b27b424035ee7e1977d80ef",
+            "bytes": 22274,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.fr-7tNmTLTd.js",
+            "sha256": "bf3f4402f2fdeba2b5c4dc089b2b3cf2b10b2ab5462018a450a69ce1fb010978"
+          },
+          {
+            "assetHash": "9d818671e6a424f7db035d426a814ae5",
+            "bytes": 24577,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.he-C6eZFBr_.js",
+            "sha256": "617efabe57d4393063961b450f5be2eb02c267fb969bbeeffe15a8d2ffd8780c"
+          },
+          {
+            "assetHash": "b20745a9318a7c66c3746568879f642a",
+            "bytes": 38412,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.hi-BBW7jI8Z.js",
+            "sha256": "e2b17b792f09e5a764d06c65e16200045016504f18e81f5eece92638c0e2f935"
+          },
+          {
+            "assetHash": "d57238d1283a4850e62d8c06894b902a",
+            "bytes": 19431,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.id-XxjsE0p0.js",
+            "sha256": "d0afe81d9a457ca6b8d9bbc3d72266b416b83ee3a6117c7062710e93d7c380ed"
+          },
+          {
+            "assetHash": "991ca95439a74b034957292c9766c130",
+            "bytes": 20744,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.it-3QvytToA.js",
+            "sha256": "d7e1c7834a1bc0e233c11c445e45484885d2f3c28463152f247f857613be92bb"
+          },
+          {
+            "assetHash": "847fd53cf80d5e79e616c6f42ccbbf7c",
+            "bytes": 23643,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.ja-SEFPl5By.js",
+            "sha256": "b46512db1fd916dbee9b4d31ca93f3c7f8d096e6c8c6c5ca7cab0b0b3006b370"
+          },
+          {
+            "assetHash": "3e43d88289ff33443c304862fceb848f",
+            "bytes": 20824,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.ko-B_laV8JW.js",
+            "sha256": "8e1f4502aff7be1a71219aea0aa2cc23973c999d83588171d9fd79ce14d85c4e"
+          },
+          {
+            "assetHash": "626982a53e302fe61e7cd15671d3ad4b",
+            "bytes": 19742,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.nl-sc5X58Ly.js",
+            "sha256": "d58436f8849ac07d119b5706ebab2f1b8a90a6b6dfd5c38b163e3797c88bce97"
+          },
+          {
+            "assetHash": "f750d03f580d91a543771743c04bf54d",
+            "bytes": 22204,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.pl-DF090zb0.js",
+            "sha256": "261a95189fdbd85b4f8c8c1cc393d0e09e93a26f82e20c9c0c6bdb156ca90c11"
+          },
+          {
+            "assetHash": "4d50b0b1b6689ca81a24c38c3ba0fe60",
+            "bytes": 20839,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.pt-BR-DItdiBpU.js",
+            "sha256": "53ef6db7c57e49aa526ba1e50f5518e848a3b06e8b0ce2e6f3659e40788cf5f3"
+          },
+          {
+            "assetHash": "0404e9f28b7e23f9040f9d29a77ffe93",
+            "bytes": 33306,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.ru-BQjNpLAH.js",
+            "sha256": "fa9b6e53783ebba35004026078e6e41716e9422b37e5059f4370b561be723b8c"
+          },
+          {
+            "assetHash": "725ae1fb93143d042451cfe01ea24fa5",
+            "bytes": 18749,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.tr-B22wAK59.js",
+            "sha256": "d7863102fd6f4538498422a866e6bc1c327ee35f1acb17d7ec2c027b96b977a8"
+          },
+          {
+            "assetHash": "f3465a1227a4c02570e9509a66b3a9e7",
+            "bytes": 31936,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.uk-D7MXty4-.js",
+            "sha256": "29b2876de25b4a221626f9e01950c7876e3a7afb7509242c7fe2ca7775b466b9"
+          },
+          {
+            "assetHash": "1e7bd665c723d367955ad314d1199958",
+            "bytes": 17835,
+            "contentType": "text/javascript",
+            "path": "/assets/comments.zh-Hans-Cuxj9eiV.js",
+            "sha256": "2bb7bb9c2a51edd7fc0cd727663ffa5af782a334d3c62db4a89245be1645bd6e"
+          },
+          {
+            "assetHash": "e43377d7cc3ddca5fbfa06501fe3cd4c",
+            "bytes": 10995,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.ar-CFEC5U3P.js",
+            "sha256": "d8a9ba8919b177317ccb911c91bbafe68299ae6c8df44fb693f68b9462bc2da4"
+          },
+          {
+            "assetHash": "e1541ca1e3a37ac6039d410a1659fd3c",
+            "bytes": 8186,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.de-B7jRspXp.js",
+            "sha256": "810ef0411e07181257667b8e539ef39ce896f252074ca8adf2f719340f754880"
+          },
+          {
+            "assetHash": "1e25b9903da3c49e0b03c3892a468998",
+            "bytes": 6881,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.en-CjQdkMZH.js",
+            "sha256": "b0c34e0c22ea10ca449264e7bec556209ff0bdd356b9139bfd6d70f4e582edbb"
+          },
+          {
+            "assetHash": "e77ab2bb41c06f44d69a3f3940bbaf89",
+            "bytes": 8226,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.es-hozx4ts0.js",
+            "sha256": "9e8c0fad4bd72936689564ddd64754d3828a1731d52de6d028eace5e857d2f00"
+          },
+          {
+            "assetHash": "41a0f08a1b01bce1cd83870d1aeafe5b",
+            "bytes": 12354,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.fa-Idlvxfyt.js",
+            "sha256": "66c95e2b89399a71c8321e483977d60cfef37473f36dd93cbc691715befa7ac0"
+          },
+          {
+            "assetHash": "7c00c0e380099cab3a80fa10d3a4a1fd",
+            "bytes": 8860,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.fr-CUYc20e7.js",
+            "sha256": "911908eb91546bb04380f2b2c48bff1dcb17b3d268da7eeb2f7a16d9b67acf56"
+          },
+          {
+            "assetHash": "2b63f20d0455f4ac87bc28b7b6af93c0",
+            "bytes": 9826,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.he-DXrfjejv.js",
+            "sha256": "3ce0b898c368243ae575dee315d85828f8390b6b707eebd5d230814af7d81462"
+          },
+          {
+            "assetHash": "3a7258bfcc46a2ab71b4951738469ca6",
+            "bytes": 16253,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.hi-Bc2QmPb_.js",
+            "sha256": "6a0da6c5d42960eafd83a87e0d9b00978151ab9f2e8b535cf95c826b881ffec7"
+          },
+          {
+            "assetHash": "cde0b84fcaf3beca3c42f0831301a8d9",
+            "bytes": 7805,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.id-BlqU87IM.js",
+            "sha256": "e4a934c4e2c67c24ae414c606e4199fed1c2e9d98713a62c89e3a468d1fd009a"
+          },
+          {
+            "assetHash": "403bef5016bd504a70c1866b35941105",
+            "bytes": 8069,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.it-BxURj-49.js",
+            "sha256": "8c417fba9a054540a10371c891c9b8e57308789309abdb5939f0cc4d3698c0d2"
+          },
+          {
+            "assetHash": "a51fe1cc8717cb01e1d531c61b6130f6",
+            "bytes": 9860,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.ja--0JkaZKL.js",
+            "sha256": "9f59974214a81258c2a41d68bc1cafab5e7074bc3af54365405d0077ae312e82"
+          },
+          {
+            "assetHash": "29166cdedf6b729407f9212b7d67d0db",
+            "bytes": 8457,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.ko-Bc51E0cW.js",
+            "sha256": "0986f63cfa517c7461c652f9647c1bba7271f6652a1e0412aec323f1405e3b73"
+          },
+          {
+            "assetHash": "ef663096e88dc6e9a096403132444333",
+            "bytes": 7729,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.nl-BAfV9gzf.js",
+            "sha256": "3efdb8f8952053d63ccbe39292bcbe8812dd304e8a47c92dd95dca772e32ecc2"
+          },
+          {
+            "assetHash": "9d065415c64a67583e2734c9b2540830",
+            "bytes": 8083,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.pl-CqCY2bHr.js",
+            "sha256": "77f464037bfc4b234efb7a4357b64dada8117efe4795884b357f903fb7a27c58"
+          },
+          {
+            "assetHash": "14287846fe02721488e02b1612c6de9b",
+            "bytes": 8060,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.pt-BR-C1W1xEAM.js",
+            "sha256": "ea98f303e9c63f86e749ec9ceb4ee094ad9329df6fbd1fa40c48466c05c37361"
+          },
+          {
+            "assetHash": "0c0fe044294783fff53c4ab3282e5df7",
+            "bytes": 13354,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.ru-BNldyy_U.js",
+            "sha256": "9692f1c5eba883e474f52102ed96b2583f4c73e8af927a2858d6a7cf2d4d9e7d"
+          },
+          {
+            "assetHash": "6201cf384de794e3b0b81a2c9295b21b",
+            "bytes": 7872,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.tr-DoM2JLr_.js",
+            "sha256": "85e40485e3b56fa48163a48142997293f6ec4cf91f96dfd278e30a71acaf6e69"
+          },
+          {
+            "assetHash": "7b6e41edc06c60124d1997fc992fc4ad",
+            "bytes": 12510,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.uk-B07IS9-u.js",
+            "sha256": "a4f4a965f4e1bd968f5c7f01b1894c57b0e3625254109c6a33229eb6113fa6fd"
+          },
+          {
+            "assetHash": "d3f308c8681ba1b152cd198f79be465b",
+            "bytes": 6872,
+            "contentType": "text/javascript",
+            "path": "/assets/commentsSettings.zh-Hans-iQA7XsFn.js",
+            "sha256": "15f50da81dcde3281f0987e7ddbfa74f5903169c8ebe5819375c56f8d7c7afb8"
+          },
+          {
+            "assetHash": "1f97a93822b4577df0dd50135412fe7b",
+            "bytes": 5458,
+            "contentType": "text/javascript",
+            "path": "/assets/composite-ab8dmdfoj6tmhcn1-BnZxF-Cq.js",
+            "sha256": "48ec43892ee9d8af2e7dbf377c108fed10c671423ed47ee01313cb15b2d31b5c"
+          },
+          {
+            "assetHash": "a48d85280c26c7a94d9c4b2715f093b6",
+            "bytes": 4137,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.ar-CZQv9UFR.js",
+            "sha256": "d40246fbe71d7012780311879d08119aaf2419e3cdf24fb19de763c1e13c15d6"
+          },
+          {
+            "assetHash": "0a67996e1fb4ef1bdc202e9d24c2bde9",
+            "bytes": 3443,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.de-D_HmlIbU.js",
+            "sha256": "bc2083233a89fa3fc819a6ef849eda59713d2793fca591a214175d257e381f8e"
+          },
+          {
+            "assetHash": "06fcf1f1d04c81c9fb3b4084c30bb4c6",
+            "bytes": 2955,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.en-vfOBiDoZ.js",
+            "sha256": "4eca172dde971cf169ee1330a6817a5a449eb08e6be5af0860136d87bfc36e45"
+          },
+          {
+            "assetHash": "3358776045c55ae17a1a779b1cc15abe",
+            "bytes": 3216,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.es-5gFnKU1U.js",
+            "sha256": "ae2b99c4b372aa226c6c4332acc02b2186379ce503fe5db9683588470ea88dc0"
+          },
+          {
+            "assetHash": "1c99c7ba41f57de64c08d28a0429fc77",
+            "bytes": 4722,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.fa-Cm7b_TEF.js",
+            "sha256": "337260adb4d2968679fc9cfb719805c8d29ac2b15e48b4fe46ab8d6221821895"
+          },
+          {
+            "assetHash": "c5c3c33cad48723321ac7f7f4e0d7fd0",
+            "bytes": 3464,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.fr-C94-crKu.js",
+            "sha256": "c3abe91c42e6b30ce7f10d23f5ac078e414bc30d7147025b5246689227023b6e"
+          },
+          {
+            "assetHash": "012ecdf6bf74a53d3220b0fce765bde2",
+            "bytes": 4017,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.he-Cx4r1A8t.js",
+            "sha256": "1d90eb67c0b1988521898506b3caa3582f43ed2624549825e0b441d17ea353ea"
+          },
+          {
+            "assetHash": "8d609d2520ec6f39b4f1d18926607f66",
+            "bytes": 6425,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.hi-UAA5Lvz_.js",
+            "sha256": "228e407189e9cd5b5124f689987378df4a4e2838b28ad2fa14b38fc23d140c41"
+          },
+          {
+            "assetHash": "c2bf85d2bf8efc8ca1479ce3f0364bdf",
+            "bytes": 3310,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.id-CAi-UmMq.js",
+            "sha256": "a6533074d6e440b59da6f8a993a1bb766638b2c0961990c9e512c8ab47664242"
+          },
+          {
+            "assetHash": "0ccdd063fe75abe92ed08115dafde592",
+            "bytes": 3166,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.it-2VcAqqQi.js",
+            "sha256": "27db4ab623aef617bea31a89337befee678c1efba528ebac36548a441f58f367"
+          },
+          {
+            "assetHash": "4e8d78c0e4fdee23345fcd5a08068741",
+            "bytes": 4094,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.ja-DGxOnIq-.js",
+            "sha256": "90a4a088c0b14e39fcf016ef52b12e5baf8b5bd53dc1a63931bc6c76f498708a"
+          },
+          {
+            "assetHash": "c0140ad060b227e5c713cde8e04dfc53",
+            "bytes": 3669,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.ko-CBa9BP_O.js",
+            "sha256": "2df23f42051fee91f6b3f74d738c56834b1bdbf4fef9fb4580d658634c49fbb0"
+          },
+          {
+            "assetHash": "f2b0aa8a4456358666d913439244a110",
+            "bytes": 3263,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.nl-Do4Njf-G.js",
+            "sha256": "fe2fb28c5ca1083ea6a38bbcfed597feb4055af0cd911058bc1849bef9243900"
+          },
+          {
+            "assetHash": "c545170ca2def86c1b03520a6a8752f7",
+            "bytes": 3341,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.pl-YmXng56S.js",
+            "sha256": "3bfc3f8411dfb2568337536b6010e0635b2e3ad0129e0888eb22378a20e9e40e"
+          },
+          {
+            "assetHash": "0e04243295ae76a6f3c21c192ad7ef9f",
+            "bytes": 3167,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.pt-BR-CY1IPfPH.js",
+            "sha256": "0de126d5aa4358dffe287ca126c1ed31d0728a80a4f6b082eb890f881a4cbaae"
+          },
+          {
+            "assetHash": "8b928a572ce8091cb06759c7701a424c",
+            "bytes": 5028,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.ru-CV1g4jxQ.js",
+            "sha256": "faaa020670588b7b0f9592a0fcf4ae2954f7a714bd01531f9b059b93224ceba2"
+          },
+          {
+            "assetHash": "bbb7e508d33225f55853e4b9d80d43df",
+            "bytes": 3354,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.tr-BpfzufK9.js",
+            "sha256": "53c57568d09f43ed0e84137380ea791f0493bf55f2ba7a60b73bff0caa52f850"
+          },
+          {
+            "assetHash": "ef7d96fce46643539a9ab0cdaeef243b",
+            "bytes": 4928,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.uk-BmJIBHLU.js",
+            "sha256": "47bb2c23fdea84049e618c66c4a647670693129ee200a7914546cf2ab4ae98f7"
+          },
+          {
+            "assetHash": "2fcaa3fbd19a824b7512cd365ebab8c2",
+            "bytes": 3185,
+            "contentType": "text/javascript",
+            "path": "/assets/connectedAgents.zh-Hans-eSKUpF66.js",
+            "sha256": "01cbe2c166b18fd881e4b2f1121f25289e4c44b7f1f025dd8d789f04342c1915"
+          },
+          {
+            "assetHash": "0040197637c8d33617763b183d5e6536",
+            "bytes": 114,
+            "contentType": "text/javascript",
+            "path": "/assets/constants-flxfd6q03phtu007-DkiaRZ18.js",
+            "sha256": "3d714d867bb986adb6bce35ad0b9c7c1ba357fd543f2aa113ba0e5110ae9bfc1"
+          },
+          {
+            "assetHash": "6b19d54b630b63cb286889a702842196",
+            "bytes": 103632,
+            "contentType": "text/javascript",
+            "path": "/assets/core-n0de50o00ej2ecfe-DHGY8swS.js",
+            "sha256": "23e9954928918bb45b47f0c008fdf48d3e9b400f8d292c4fc6bdaa01fd43feb5"
+          },
+          {
+            "assetHash": "1eb8211c8b50cc5b9f8e3b723ecb746f",
+            "bytes": 171028,
+            "contentType": "application/json",
+            "path": "/assets/countries-110m-CmKPZPcB.json",
+            "sha256": "8371042ba6dd6c72f0ee59dcd6ad4002daf851041c0f50ed6c00d7deff8f8502"
+          },
+          {
+            "assetHash": "4ef36987b24f83063047843ff5cc18f2",
+            "bytes": 17965,
+            "contentType": "text/javascript",
+            "path": "/assets/createToastManager-onmoqrmr8zo02rif-BKvFbhid.js",
+            "sha256": "503f34e3d32f12cdf7b20cff033603564b948d42650cb733fdf5429926309b55"
+          },
+          {
+            "assetHash": "09118ea5c1423039e0fc5a168314f381",
+            "bytes": 49092,
+            "contentType": "text/javascript",
+            "path": "/assets/css-j501gxq1am2gnikh-BsVw1vtW.js",
+            "sha256": "3dfe0e30e52b37d5e5e8869d6c48490442c1d46ccdb1eb5df3fc52b0f8304ca4"
+          },
+          {
+            "assetHash": "f419ca316795e904b68834d5613221d7",
+            "bytes": 67269,
+            "contentType": "text/javascript",
+            "path": "/assets/date-picker-cotkexrpqu5tjikv-C4eu_Fjq.js",
+            "sha256": "00e2729b64f63535719eef9ead5d3fc67aeed7c69b891c74f00de9c11f2f0340"
+          },
+          {
+            "assetHash": "15bc8dabfbcc1b27bab3f445509b706e",
+            "bytes": 72,
+            "contentType": "text/javascript",
+            "path": "/assets/date-picker-xpWVhP2g.js",
+            "sha256": "a0ebbaa2f32c0ba9077f7432330b815ffe314c1e20ffc92ce219140c673dfea7"
+          },
+          {
+            "assetHash": "58a3b7c54a66e03419213a13bc2e3062",
+            "bytes": 132285,
+            "contentType": "text/javascript",
+            "path": "/assets/de-9F-ZS53q.js",
+            "sha256": "dc736b2fa0b90da6195a1581acc4071a5d5fdf0be3107c4d738537a913ae9ff8"
+          },
+          {
+            "assetHash": "3179a3cb828385c5857c4248708e7a99",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/de-D_ihQIB0.js",
+            "sha256": "68f10cee68cca2d312652d336ac60e3c4c71abf0a45990cecffe0732b0ec5bca"
+          },
+          {
+            "assetHash": "4d5afea4d11aee000439c7b1bbae6257",
+            "bytes": 3889,
+            "contentType": "text/javascript",
+            "path": "/assets/deleteLabels-CXdCOUFd.js",
+            "sha256": "9e7aebbe91a02317c930898865cd1cc62addd3ce082672659aa847cbf77853c1"
+          },
+          {
+            "assetHash": "074c0181aea02f45c002bed1a57aa46a",
+            "bytes": 3289,
+            "contentType": "text/javascript",
+            "path": "/assets/dialog-glpnivujociqs9lh-RRpeKfY_.js",
+            "sha256": "0e76cc02196637bdb9530fbbe5ddcdc8d9c6b40dcb1644aaebd69b1a9664284b"
+          },
+          {
+            "assetHash": "d658264cb8872ee19f39d193e1a67da2",
+            "bytes": 2621,
+            "contentType": "text/javascript",
+            "path": "/assets/diff-g2re0kj6fh27rgg5-woXpYk--.js",
+            "sha256": "c15bef6040291817f0db52e56d740b031dbdaeabf6c730a422fc844d171674c2"
+          },
+          {
+            "assetHash": "e076107ab48a9663006701947170cb7c",
+            "bytes": 36629,
+            "contentType": "text/javascript",
+            "path": "/assets/dropdown-m53ji1d66mws89wh-Bg69fzkE.js",
+            "sha256": "d8f2c7f16ed72aebdc2a88d40681552d0641dae29eb73d127c01adcd438e8a0f"
+          },
+          {
+            "assetHash": "014e77b3e2178cccdcdd9607fb11adca",
+            "bytes": 627032,
+            "contentType": "text/javascript",
+            "path": "/assets/editor-BJhMMfuG.js",
+            "sha256": "b73509d5ebf01533936ea8824fc9e464ca735d4c9835999780ee516805134fed"
+          },
+          {
+            "assetHash": "af3d647501a043ef26aebeadf5d6c8eb",
+            "bytes": 6752,
+            "contentType": "text/javascript",
+            "path": "/assets/elevation-Cs1FecXf.js",
+            "sha256": "7dbd39e22710d9b456434dfaa30cfb4833a5bbb8c0c06c131e40786914360688"
+          },
+          {
+            "assetHash": "0fe1e89945680c7181df0a2ec8937163",
+            "bytes": 2307,
+            "contentType": "text/javascript",
+            "path": "/assets/empty-hkvg3is3uc6dj1at-DtMJCIY_.js",
+            "sha256": "82f6602f2cadc1769a5f1d8a7d80130a390132e10f1dcaa9c6e51820aaeddf4a"
+          },
+          {
+            "assetHash": "b8ed41d01dd4df73bf9bcb049f9416d6",
+            "bytes": 870,
+            "contentType": "text/javascript",
+            "path": "/assets/empty-items-D5iNfqK3.js",
+            "sha256": "d5391957cc51357c0ec805b3f362a4e8d639d42301054e21bb34842feb0b4b7d"
+          },
+          {
+            "assetHash": "91e200e5072e1e0eaa07ca61d327ef98",
+            "bytes": 57378,
+            "contentType": "text/javascript",
+            "path": "/assets/engine-javascript-e9xj8hhz9xfqhpnu-C99cuIw8.js",
+            "sha256": "31f77dd0de5449338376faee305ed59f3089ecf8444c22b86223a77d154c0ff1"
+          },
+          {
+            "assetHash": "a713b0f0904f55be68cae3bdc1529a71",
+            "bytes": 6658,
+            "contentType": "text/javascript",
+            "path": "/assets/engine-oniguruma-ias56ildu01gf939-BO8SA_1o.js",
+            "sha256": "45385209ec7f671c46b5cb2e64710e3c3c21d380e9ca4051bea0a1b53644e4ce"
+          },
+          {
+            "assetHash": "0ad27ed2aa65f1787ba2541d93e89e02",
+            "bytes": 308,
+            "contentType": "text/javascript",
+            "path": "/assets/entry-href-BvhAhTJo.js",
+            "sha256": "6bfefff4356fafcd2492c2b5e79affa0358d307e95b58cd76e7273801c07bed0"
+          },
+          {
+            "assetHash": "832b346312f78da626f82385029f3b52",
+            "bytes": 180,
+            "contentType": "text/javascript",
+            "path": "/assets/errors-svQ83Q62.js",
+            "sha256": "44145cb4c52c7999fc6fb7bbbac0af09c6d976477b2a8929f19f9f11886c213d"
+          },
+          {
+            "assetHash": "7a610bc3d598346ab81ce8a1fd0f39e8",
+            "bytes": 130066,
+            "contentType": "text/javascript",
+            "path": "/assets/es-CG1JH3PI.js",
+            "sha256": "8b1463a6f440b8b17f74d066140330232472aedacc52ff833e0e90fef69dfff9"
+          },
+          {
+            "assetHash": "3dee432bb20c3dca36fb6ef33ee5c975",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/es-DkYdcfpE.js",
+            "sha256": "478ca56d793559d19f6e8d2b28716f7882db5ea7ac642547a914933c1acebc75"
+          },
+          {
+            "assetHash": "9e9ccb3c740149e1ff110b752dea12fb",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/fa-BbjDnP5A.js",
+            "sha256": "a23b57f249938339a8b0848619e3b2183d0b5baa9234961f5ee78349df30e844"
+          },
+          {
+            "assetHash": "2108c39755d0f426d36981ae7c5b2719",
+            "bytes": 184758,
+            "contentType": "text/javascript",
+            "path": "/assets/fa-n7DtKTNq.js",
+            "sha256": "545953fe1c600973cf0dbf5eb54331df30c4c34414e0700987916109c17a68e1"
+          },
+          {
+            "assetHash": "bd2f88f3875c40e83c8ae21716773d81",
+            "bytes": 2052,
+            "contentType": "text/javascript",
+            "path": "/assets/field-references-Don41Ylb.js",
+            "sha256": "bcf0bf1a38d5c3aca3234a868861d4edd676c658fd7ed806b2db62fe01ad7307"
+          },
+          {
+            "assetHash": "dacb6d7e90a0bd05d738bed4cdc0c0c1",
+            "bytes": 1155,
+            "contentType": "text/javascript",
+            "path": "/assets/fields-Bq2_MzOk.js",
+            "sha256": "f6d714cbec8cdf00475481eb3584158ea8af9a63878778d289cbd45e62009075"
+          },
+          {
+            "assetHash": "0ca8ac8f5f757c6a6c123ca9eadcd14d",
+            "bytes": 20179,
+            "contentType": "text/javascript",
+            "path": "/assets/fields-vzbepTY0.js",
+            "sha256": "2b422efc91b4bbb613983d0dbde9730b10c003cc2db9c9e1ac550d771afd18b0"
+          },
+          {
+            "assetHash": "428fb1cc315e739e45d0fd179a6222b3",
+            "bytes": 22821,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.ar-CJ_7qFnJ.js",
+            "sha256": "a81188ee74e2e72d843115400a33edc41d0f5eadedb2933ce477df12efed8e8b"
+          },
+          {
+            "assetHash": "ed69e720423277208f3493337794a0e7",
+            "bytes": 16031,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.de-ChOjBVrh.js",
+            "sha256": "cb3e0436d3d10bc573da81b7429fcacb82ca170f9b118d524eee111e49a7e58f"
+          },
+          {
+            "assetHash": "0092c34f578840d979b023bd06766cf4",
+            "bytes": 13770,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.en-BtpciqAV.js",
+            "sha256": "32f816d04b79797c39712b6815762e79425f6fc80aab5ce786b3587daa77e71d"
+          },
+          {
+            "assetHash": "a3ec62593247f8951e4eb8d398917978",
+            "bytes": 16047,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.es-Cl5ZbYXy.js",
+            "sha256": "7d0d7a1ef03e6c1f461e30183558877229852edfdc986aa59262f1b46ea1fccf"
+          },
+          {
+            "assetHash": "2a0eb4c0a99b89d7c108e538a36c49a7",
+            "bytes": 21664,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.fa-Qvu0alyX.js",
+            "sha256": "ef6aef9dc6434448eb4da7bfa76b46b8c332b4f02952d41095071bfbe7cc2567"
+          },
+          {
+            "assetHash": "4827faa5dc7b41d3cfa317e9ec5a8200",
+            "bytes": 17169,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.fr-CNzJwZpG.js",
+            "sha256": "563b90334c8256a278c7e076d8a1ec2b9e548f13bc8e0002d1861256b36d705b"
+          },
+          {
+            "assetHash": "d4f15c5b2213a546247f9c91b6666262",
+            "bytes": 18838,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.he-C2XjAH74.js",
+            "sha256": "937a5d359f7cc06859fbfbabb9c65527eb9e00ae07fa2cf5f2300a61590c1622"
+          },
+          {
+            "assetHash": "51d39ae653285053ffade2a340a73d57",
+            "bytes": 27983,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.hi-HibfAa2P.js",
+            "sha256": "90dc9b81e0a87fa0b2cc9352d42d2159c40d937b98f3c2a9d994a4b4bdfaee60"
+          },
+          {
+            "assetHash": "34abc4a88674e7ec0484165ea42f9209",
+            "bytes": 14030,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.id-D5_l29P8.js",
+            "sha256": "938c503d3c37e5c898584733335c971820f9fa44fe7de02ce05de02f7f56cd05"
+          },
+          {
+            "assetHash": "5a31e5f594a0204129b3b31c5274c626",
+            "bytes": 16003,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.it-DWX-OtGa.js",
+            "sha256": "8c2bd4908adbe56f8dc2deeb075283160a6abf8c2927771cee31e3e41a43b2b7"
+          },
+          {
+            "assetHash": "7935489a25cfe87ee8eaf78f698732db",
+            "bytes": 17964,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.ja-B0x5wU2h.js",
+            "sha256": "6f29725f325456b5a956864f19b81c98adfd43e26e139dc4479ff7b1797d898b"
+          },
+          {
+            "assetHash": "57c26d1f8e693f0c0f8c99bd8788e273",
+            "bytes": 15608,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.ko-CtR1PXeC.js",
+            "sha256": "17ca8688241c9357321da71ec64d3dcb6cc2354367a2bad4d449e7116cd6795a"
+          },
+          {
+            "assetHash": "705a6263b717ef7622b191b82843338d",
+            "bytes": 15013,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.nl-DMn8gx5r.js",
+            "sha256": "366e2e723a4789add7f50a1131d29160206e710cfc680b21e4fad254bff8c95d"
+          },
+          {
+            "assetHash": "eec348e938b6253e2a8124567ceabed9",
+            "bytes": 16713,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.pl-CAnaRccr.js",
+            "sha256": "d6b1d13d16fd758f42c11ea18d78fff0ad7ade30375d6978e96cb3729ec388a9"
+          },
+          {
+            "assetHash": "da517a29c7ec89f19d902426bac92429",
+            "bytes": 16116,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.pt-BR-D8ng31US.js",
+            "sha256": "2c289017fe720a299264c9a4e7d5b99312bb9100fdb7bcf145f3836922a0856c"
+          },
+          {
+            "assetHash": "c6c7012c1ed03409914b83f0dc766b0b",
+            "bytes": 24504,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.ru-Cr7uNGzz.js",
+            "sha256": "3ed865ccfce5e1a446b6459c555af308de7936360da262333a2f6ea78fa76533"
+          },
+          {
+            "assetHash": "4cf65ccf395f76a6a95f368e3a8d8e55",
+            "bytes": 15148,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.tr-CDdfh68b.js",
+            "sha256": "8afac8390d97526116b2217ede54ff1b26d4b866b0b52e9415ad65f9ab971a0b"
+          },
+          {
+            "assetHash": "ceaffa097f7ce825060b2c0c188cb400",
+            "bytes": 24077,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.uk-BXL1o-xZ.js",
+            "sha256": "c828f7f509b5b60099ebe623ca116e5f02f1278f8cac95bd357e12a90e1c407a"
+          },
+          {
+            "assetHash": "58a824721839809317036783a49f4a6e",
+            "bytes": 13204,
+            "contentType": "text/javascript",
+            "path": "/assets/fields.zh-Hans-SRCio7HE.js",
+            "sha256": "104cb28b9b6702417b65b7456f56184ffa07100bd71066675139d7f937db49b2"
+          },
+          {
+            "assetHash": "6e10c8fa4666f38233cccd725c7216c6",
+            "bytes": 2609,
+            "contentType": "text/javascript",
+            "path": "/assets/filter-B_r-t96-.js",
+            "sha256": "2fd777ff2c2a85713b21a2c9a1b8f34160dc149ff465918740a8b76ab94edf0b"
+          },
+          {
+            "assetHash": "795ed102bf40c293ff33ad974fbe5ca3",
+            "bytes": 1374,
+            "contentType": "text/javascript",
+            "path": "/assets/format-BOWG2Mwk.js",
+            "sha256": "238d1b3a6a3210353d86bcb1d11db42c121aff6aaf2d1cbf01f5466befe0b6ae"
+          },
+          {
+            "assetHash": "8dcfe8a26e01a9fa5f17e127affca7e3",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/fr-C33vbfx6.js",
+            "sha256": "6f23d7d0117e7477aa03a86ab964814066f0fc71be3a05e2034493ab48245243"
+          },
+          {
+            "assetHash": "673a717ec968b9d080c9935dc58eabbe",
+            "bytes": 137983,
+            "contentType": "text/javascript",
+            "path": "/assets/fr-pVCeMw5i.js",
+            "sha256": "ad1699bcd4f79de2bd3eac53c3cddf3431679516b890cf27f31ead8c6fe68539"
+          },
+          {
+            "assetHash": "3600cf2d3eafcb016400e173b834e46c",
+            "bytes": 454,
+            "contentType": "text/javascript",
+            "path": "/assets/getElementTransform-cuiqmjglkrz4db6c-1fD5DQn4.js",
+            "sha256": "291a6988cf6fe40a4b0c1b0a69f19e123a9b2932b06e5bbb3eb4b62e78328f82"
+          },
+          {
+            "assetHash": "9ca7d3d22a5d853720bb2751bb129b73",
+            "bytes": 11248,
+            "contentType": "text/javascript",
+            "path": "/assets/github-light-hir7yctc38258pwt-EUqPIrTm.js",
+            "sha256": "50ac11a42921abac49e28bbadf1fff59ca02e45a7ff986f8b835eb8b6dc84438"
+          },
+          {
+            "assetHash": "3f7d9022cad9ddbe2851b933f0108416",
+            "bytes": 18129,
+            "contentType": "text/javascript",
+            "path": "/assets/graphql-n9vlzkvcp2j19p7a-qc8EicCw.js",
+            "sha256": "43b41d85e42b0ab056d66a88ea81b2e00ba7dac1a99ba35cb6c83ce63a4b043e"
+          },
+          {
+            "assetHash": "2207fc29ff7eb7ad87e28bf07b083ea8",
+            "bytes": 10104,
+            "contentType": "text/javascript",
+            "path": "/assets/hcl-ohpfnrffs96hzyj5-Dh228itO.js",
+            "sha256": "d01d4cfa15119dd1f16647f5967ea80d6dc77e0c048c22d7fab8864dabada726"
+          },
+          {
+            "assetHash": "3c36b780574175cf12f19bb07c1e6527",
+            "bytes": 154179,
+            "contentType": "text/javascript",
+            "path": "/assets/he-Ci-75oMY.js",
+            "sha256": "e362311219a6f75143786bee9e05741e703fb81597838bad22f79556456702d2"
+          },
+          {
+            "assetHash": "22cf0cbd73d8ea5069f8285ed8bfa493",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/he-Cyqj1vId.js",
+            "sha256": "9887486f3e9d7f0e77757c49f8938ef99796006d72ae2846f834718f28d95d63"
+          },
+          {
+            "assetHash": "4fe3f30facabee00a976da45ce7f19a7",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/hi-C6ZBlabj.js",
+            "sha256": "4e2ce45d04119ce62a35fc87f40bd53b235abae0f7d1a80c631d3221581ddc2b"
+          },
+          {
+            "assetHash": "da3b51156e80497e764a0fcd8b6127db",
+            "bytes": 241881,
+            "contentType": "text/javascript",
+            "path": "/assets/hi-vWvskHFx.js",
+            "sha256": "926c47d45e545b128e6da0be6e712ddc36847232e56489130ef04df685ac271a"
+          },
+          {
+            "assetHash": "8aa6eaa16b36fcbdf1bd0531e86b08f0",
+            "bytes": 57337,
+            "contentType": "text/javascript",
+            "path": "/assets/html-nimj6ljkkifnc9ni-_gDbiBIU.js",
+            "sha256": "2a11212beaeb6dea2f6a55674dc8f446e6b1f7b24b8cf7a825be168a0eebc449"
+          },
+          {
+            "assetHash": "b8438b46ef9bf2fa1a1a40d14327590c",
+            "bytes": 122147,
+            "contentType": "text/javascript",
+            "path": "/assets/i18n-Dpznm8tI.js",
+            "sha256": "9a10d536c55fcc4922bbb44943a013cb7f5db2ce620a8478d137101f884d571e"
+          },
+          {
+            "assetHash": "abe0075f06c490922ce7d13f416d84bf",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/id-1PERTIer.js",
+            "sha256": "d821c7c5f8c4a47f613c38ad49b5fe6fe075961b149dc66d263939f7e369226e"
+          },
+          {
+            "assetHash": "5a9931927f46894515352fd99f52e18e",
+            "bytes": 123238,
+            "contentType": "text/javascript",
+            "path": "/assets/id-D8-ABWdx.js",
+            "sha256": "7b20f25f68d441b265324a00a4e2eb3af189b33ba68a1c597485736a3de52928"
+          },
+          {
+            "assetHash": "0e47d42daa198e7c20a2a4a8439e4b55",
+            "bytes": 182798,
+            "contentType": "text/css",
+            "path": "/assets/index-IQPO-IzP.css",
+            "sha256": "29b20aa90cb17a7771372dc2a3b57858456587362d92c64b2fec0a44f9e57d08"
+          },
+          {
+            "assetHash": "38a0469aa408ac73f8eb865a42bc1958",
+            "bytes": 206503,
+            "contentType": "text/javascript",
+            "path": "/assets/index-jx_2-Qkj.js",
+            "sha256": "dd208da4b8d125de9b869edbf08a24e3535c04682be0f5513f2bd21dd544b78c"
+          },
+          {
+            "assetHash": "6e50566f6c42ae51b32ce4541df5075e",
+            "bytes": 1018,
+            "contentType": "text/javascript",
+            "path": "/assets/inertValue-e0o6qvuef83v78fk-D0BI_tKj.js",
+            "sha256": "c807fcd633bcb6c95afb9dfdb828cd881b85735c2f93d12657e386dadd8c9be6"
+          },
+          {
+            "assetHash": "ba1da2dadcbc02487d105ffd152c1b9a",
+            "bytes": 3209,
+            "contentType": "text/javascript",
+            "path": "/assets/inline-copy-text-gbd7jhwpor27l4rq-CM0BFxHH.js",
+            "sha256": "455e3ab8619ea2dc27b2f26be00f0ceb65a44cfc3c8230db7a4e9dd1a22c365b"
+          },
+          {
+            "assetHash": "ffad2cf37c2da3b7ffc5b31a9a7e7669",
+            "bytes": 7234,
+            "contentType": "text/javascript",
+            "path": "/assets/input-c8lv9ulv0kortliq-D4CKBAlm.js",
+            "sha256": "a9f487c8635fd17c62cae1ac8f65d5e72a0cb4ac4556d3b1d7f7bc3e913cfa20"
+          },
+          {
+            "assetHash": "573a8c7add22ec1b2df40fa521ee7053",
+            "bytes": 8832,
+            "contentType": "text/javascript",
+            "path": "/assets/input-group-fjr4r5ax0x78ppax-BqMPZ742.js",
+            "sha256": "7b69d3e6362fede46362682418e718773a9058c38f904bddad4dc045efa60501"
+          },
+          {
+            "assetHash": "baaf81cfb2685b9863b97c9689e46608",
+            "bytes": 3051,
+            "contentType": "text/javascript",
+            "path": "/assets/input-h5ox92gf13ge04hp-C3iQI8nS.js",
+            "sha256": "f03f668bd8b7fc2c1b6508738f4de47cecfa0b95583e5fa28a2f208da8354e3d"
+          },
+          {
+            "assetHash": "b451ea5da7d16ec4b936914b26d4f803",
+            "bytes": 782,
+            "contentType": "text/javascript",
+            "path": "/assets/intl-C22KAx_l.js",
+            "sha256": "ebd29bb37979ecf70571166623eb77027947b577ff7596fc68bb0dc01a683157"
+          },
+          {
+            "assetHash": "c52cb17aaeb2de793691a017cc33365b",
+            "bytes": 186,
+            "contentType": "text/javascript",
+            "path": "/assets/isElementDisabled-o0j7po84u0xjuwgg-KSuuBj8D.js",
+            "sha256": "ada419dae591ddb8bbd8ed77001d4998a6f03fbe2d42620ee60405bc826a02ed"
+          },
+          {
+            "assetHash": "71861febba8b5c90a2ebd95cb3085dc6",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/it-C6OglVw8.js",
+            "sha256": "a7bca113efad633602bc7e9929b11327aac7617e8e4c951f10b46d33c4310cea"
+          },
+          {
+            "assetHash": "6df95fea993fd08ff7c68712cf1cc753",
+            "bytes": 127790,
+            "contentType": "text/javascript",
+            "path": "/assets/it-Cz6UG7Sr.js",
+            "sha256": "8f7de3be2bd9c40aed9eb34179bbe0025bb3562e0b1e4f247049bdce2b5882ab"
+          },
+          {
+            "assetHash": "8c12a43f4d7f2d3c160446fe7f5e6c46",
+            "bytes": 542,
+            "contentType": "text/javascript",
+            "path": "/assets/item-url-Bn_COg0I.js",
+            "sha256": "972201ef5cb7e2c639612e5dcd592bd4fd67f5729467212103bd9e6b08b11bfe"
+          },
+          {
+            "assetHash": "33bd1f20396c4e5979bff548207ec8ac",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/ja-BIuEh8T_.js",
+            "sha256": "8c99bbf161844f937f70b258bfb162196f988355496084937eaa153247a73728"
+          },
+          {
+            "assetHash": "16da5e94da401f3d371d5be47e5cf815",
+            "bytes": 157529,
+            "contentType": "text/javascript",
+            "path": "/assets/ja-BVndBUot.js",
+            "sha256": "ca7bc37e60102ace2249855842cae5a0c6a27a79d94dd8a9314771fcf9fb79d0"
+          },
+          {
+            "assetHash": "9230def69b42938f6299f95b692a370d",
+            "bytes": 174885,
+            "contentType": "text/javascript",
+            "path": "/assets/javascript-b6fq9bl50fdj6813-BgS3c2Ky.js",
+            "sha256": "16a39fa3179d023f2bf1a9d64de4f8d0551ecdea5230d3b0fb5b7e892a0cd914"
+          },
+          {
+            "assetHash": "20eb936efdba02410c2b3689fe8d4f5d",
+            "bytes": 2876,
+            "contentType": "text/javascript",
+            "path": "/assets/json-h2g0z143t3cs13d7-qhed-kSA.js",
+            "sha256": "75e4aa89f03abaeeb670cd3adc1f31e1a32f05c43d88d008bb6001c5a796533b"
+          },
+          {
+            "assetHash": "0f0b2b42095dabc3f38cf8b07e210a5a",
+            "bytes": 3162,
+            "contentType": "text/javascript",
+            "path": "/assets/jsonc-jxlzvingck161eej-CYpm1nAK.js",
+            "sha256": "71b810e1e65a637b3ce6315998b75f758bb37caad6441015062c6390b6e650bb"
+          },
+          {
+            "assetHash": "30bb9b3aac12dd44791a462bb506150f",
+            "bytes": 177843,
+            "contentType": "text/javascript",
+            "path": "/assets/jsx-jp7a33937sef92sn-Bz0zcwM4.js",
+            "sha256": "8a2273f19fd0a88b4a1e983ce1ac09643c8e63be42e01e78097891c83ab161d9"
+          },
+          {
+            "assetHash": "d4cbeade340b46d219b6f06019ad8ded",
+            "bytes": 135414,
+            "contentType": "text/javascript",
+            "path": "/assets/ko-DrxtgLnV.js",
+            "sha256": "a74178e99cc94876b44e0096e00382acfd1e8725739d176eee157898df1b6341"
+          },
+          {
+            "assetHash": "7b9022f1e6b733d5a0dd664b8c9ffbf9",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/ko-Ek-hxxNl.js",
+            "sha256": "65e37a7bda9e1f7ba39bd63b4d125fdba45cfbe86c358d07f040c9ff3175e074"
+          },
+          {
+            "assetHash": "7dc9b6dfb31954071baa941ccdf15a1d",
+            "bytes": 561,
+            "contentType": "text/javascript",
+            "path": "/assets/languages-CbQ722uB.js",
+            "sha256": "f3ef995bed2a2ef5883b389676f2a94f9b168f3dc5985dfdfb669334364e5d34"
+          },
+          {
+            "assetHash": "4d06db981dc9a037210509c3441106bb",
+            "bytes": 1488,
+            "contentType": "text/javascript",
+            "path": "/assets/layer-card-idh3863vdgprgqai-ByBfeMqc.js",
+            "sha256": "e0054f6cbeddc4d33e50af68e694b74042f5704aa90d47938f73f36f9aa902c8"
+          },
+          {
+            "assetHash": "455943152824f299c6d0626fce0e33f4",
+            "bytes": 30265,
+            "contentType": "text/javascript",
+            "path": "/assets/layer-dialog-j5ywmzs46ccg6oqy-C7BM4aKu.js",
+            "sha256": "bc6c77aef750028efdb440038e910ab6fe6dc0859436d42ba1d155607c7888da"
+          },
+          {
+            "assetHash": "5367bd7b1edfbd2be66f991db09619ff",
+            "bytes": 2461,
+            "contentType": "text/javascript",
+            "path": "/assets/link-cqyfjezntvr53rh7-hX7iswfD.js",
+            "sha256": "5f97997302d9bd725b6e5ddefb4d77ff1f6f2dc7495b372996dde63b5c72366e"
+          },
+          {
+            "assetHash": "7def040f8c204c150f780e26947b6986",
+            "bytes": 11466,
+            "contentType": "text/javascript",
+            "path": "/assets/listings-B_899FYd.js",
+            "sha256": "362cb9e49b1bce960e325cdcc7b62f53924d62383a2141486fca744d8df1d237"
+          },
+          {
+            "assetHash": "f7ecabb41567728ae63406fa65f92873",
+            "bytes": 458,
+            "contentType": "text/javascript",
+            "path": "/assets/listings-Co7v75js.js",
+            "sha256": "f294f8e5225d5ecade1c3c1eb4a1fc3867b808469cc215845803c1c89578317a"
+          },
+          {
+            "assetHash": "6a81e7d5cc7b3f4b36955dd67b88e5ff",
+            "bytes": 9725,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.ar-M7PHTSIQ.js",
+            "sha256": "8f07cfa7ed9e89210b3ff9f9ad3d2e4c373fd7c5f832edcf941db2170ed73ee0"
+          },
+          {
+            "assetHash": "137c5cca9847dc770d48b6111ebfc292",
+            "bytes": 7897,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.de-C7rl6seo.js",
+            "sha256": "83980a9b695329b36aac20bb124810bebdb330f694bfeb0668e9eb390d5f5813"
+          },
+          {
+            "assetHash": "0f0c6b9254e02d9a463d303e5db91f66",
+            "bytes": 6730,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.en-BokvFDHp.js",
+            "sha256": "d50c69b024f84ef1f12e3ab6a790239af05d1d95340c2832b0f201df5a4718cb"
+          },
+          {
+            "assetHash": "92b093bef276e39ea16e84afe5a4ed28",
+            "bytes": 7808,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.es-BjuKqdzh.js",
+            "sha256": "6ddd368b48cfe4dda6a397288beab29bdfdc023cf8258899a15735f08d99d90f"
+          },
+          {
+            "assetHash": "86bebc3993111161d21817b45cedc101",
+            "bytes": 10752,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.fa-BnnJbvsw.js",
+            "sha256": "2a9cb8042685c4c95181309a84bc1242be5c6f229a1da204ad057044b8cec433"
+          },
+          {
+            "assetHash": "af10ddde38cd07f08e7691fdce5c9efc",
+            "bytes": 8277,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.fr-aeta0QZO.js",
+            "sha256": "d36d1c55ba372f6f61e8f5a5c948cbc468a9b447a5427526843234d001f8dd91"
+          },
+          {
+            "assetHash": "42a0cd9fd7fd16b678dd69f4094838d2",
+            "bytes": 9062,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.he-ChZA73x4.js",
+            "sha256": "8cbd1cf01eb222fee901beb674c746b9ed85c45a9dd8a624247f1adb0502539b"
+          },
+          {
+            "assetHash": "bd6a293c0248ddbb0e86a43aa72702d0",
+            "bytes": 14637,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.hi-DvnctLXs.js",
+            "sha256": "82bedcdecb53c544530a63064d4711a183f590bc9843439af50cbaf5dca58770"
+          },
+          {
+            "assetHash": "fec581b3633d900b25fed12f89919dba",
+            "bytes": 7347,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.id-BgB5YXwS.js",
+            "sha256": "9c33bb1dbce88d24e0092717b385299eef9dacec7d7400be8424c297df826e4b"
+          },
+          {
+            "assetHash": "7f24657c3f7b42f6baaadf62083fc7d5",
+            "bytes": 7720,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.it-B7w3hljt.js",
+            "sha256": "03c52ecc11d783d0c4b907ab84de2af5ea2dc466fae9d787302256247b3922e0"
+          },
+          {
+            "assetHash": "83bae850338d5686ede0d49e733c58b2",
+            "bytes": 9373,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.ja-PobWk81a.js",
+            "sha256": "9396aab95b7f0ac223c471b682fe9f3b0a349e792c2e653bbf8d18507c32d404"
+          },
+          {
+            "assetHash": "fb8a5332724b86aec02866a212c68144",
+            "bytes": 7845,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.ko-qpyZKtTx.js",
+            "sha256": "11b4a80e3dc450e7fe9c030f706f2c00ceb041dbdf87bbf1109d64b6f5f5a5cc"
+          },
+          {
+            "assetHash": "3997265eacf8bfa533b988993bfd63e8",
+            "bytes": 7445,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.nl-CxdrzNqn.js",
+            "sha256": "9b90075588d1365ac111c5b73cafcec4b9dda9c4f004cfd2acdcc2e9cfa939ab"
+          },
+          {
+            "assetHash": "535dc0d0d37ded5151181d443a99f43f",
+            "bytes": 7601,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.pl-Dk98JZqK.js",
+            "sha256": "1c058df2c3887571a7f3047da179c00d94f8c6fb9fbeaec95f49aab0ac431519"
+          },
+          {
+            "assetHash": "4e1ce0b191b3ebd23fb351126c88de08",
+            "bytes": 7695,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.pt-BR-Tw2u72DP.js",
+            "sha256": "dcf9548c3d07e8ea65562ffbd9cb0f3666931677193ec6fc4b2f768751b3aecd"
+          },
+          {
+            "assetHash": "245692b333b84adfd6ebf429e7ca09d7",
+            "bytes": 11666,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.ru-CfB8nrXj.js",
+            "sha256": "023a9ab6a869e703d0ab8dc4edc2cd4501b0f5c63fca581a3ba0a028569bccc1"
+          },
+          {
+            "assetHash": "6f5e1aaf889388477408df43afc541d8",
+            "bytes": 7718,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.tr-CEX7fIpw.js",
+            "sha256": "170737974e8154c26bff5935d2d4e57eb5cf18e49b0d765be9049cc3dbbbb4c0"
+          },
+          {
+            "assetHash": "4d4a78c0467d3bc16ffdc6694cac95db",
+            "bytes": 11500,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.uk-D_5gi8P6.js",
+            "sha256": "59f9cd6fb708c5189ff0b10ce2a6b22cccfec225b0244c8b8bf9422e54f4ffa0"
+          },
+          {
+            "assetHash": "089c87e878cf80270cfe4e240076af29",
+            "bytes": 6758,
+            "contentType": "text/javascript",
+            "path": "/assets/listings.zh-Hans-B5HM6I_k.js",
+            "sha256": "3ca5b6f9c15af4a3c8fc7de43ecb985a72e7c875ebc18d474fa2a3ea7e3f5902"
+          },
+          {
+            "assetHash": "5aab16ec71a4ce68229da0c45e14f156",
+            "bytes": 1384,
+            "contentType": "text/javascript",
+            "path": "/assets/loader-j2v6fab8iucuxz1z-Cc_2xU85.js",
+            "sha256": "ab9d29a1e9df6b9dcc3c99e00cc9ec90a8847c28a845bab0b1fe30efe473e84e"
+          },
+          {
+            "assetHash": "d2464e50d57e659f3ae12bf4d18b5cb4",
+            "bytes": 59391,
+            "contentType": "text/javascript",
+            "path": "/assets/markdown-eybeqjr58vfygtz1-BYOwaDjH.js",
+            "sha256": "39c1e7b4e289004615d30a5fa45f0ce1442a83296e8c1e4a27ad1043d93fedfc"
+          },
+          {
+            "assetHash": "6e8163110c830eacd6e3d3770d0ebe7e",
+            "bytes": 45549,
+            "contentType": "text/javascript",
+            "path": "/assets/marked.esm-CO-4B_gx.js",
+            "sha256": "c9ae029e9767da2943b85d824b52a2d85e8cfdfa8d88ba42e3d7a5c418ac52d6"
+          },
+          {
+            "assetHash": "066277793ed7718a9b0c20c70bce973c",
+            "bytes": 19109,
+            "contentType": "text/javascript",
+            "path": "/assets/media-Dcljui17.js",
+            "sha256": "0241e6c63d5ae8b359d8bd0c2571f0f5bf5badd0e136ba3279980f6da9903e61"
+          },
+          {
+            "assetHash": "e67b413a657266c042157a638f2e0d20",
+            "bytes": 16671,
+            "contentType": "text/javascript",
+            "path": "/assets/media.ar-Dx9z7Ceu.js",
+            "sha256": "9a3d4cdc55eaa1be025d16e1f6b98d0dbf4a168e71e490536ab57f956a6ffbd7"
+          },
+          {
+            "assetHash": "e0401585ec892aa0c60e559d75a94bee",
+            "bytes": 13348,
+            "contentType": "text/javascript",
+            "path": "/assets/media.de-D6vlydhW.js",
+            "sha256": "36af4600185e37cec1be22c43c556453dae1396fe377a1a4feccaa1d7d8ee61f"
+          },
+          {
+            "assetHash": "3773b46365b7f236bc3bf086b6a9224f",
+            "bytes": 11336,
+            "contentType": "text/javascript",
+            "path": "/assets/media.en-BT47PRGz.js",
+            "sha256": "c845b74ede6cd2e6f86cff6e280d6711982891b18f07ae477d5f290011619dd3"
+          },
+          {
+            "assetHash": "b233868518c0900554d008f08d3ff398",
+            "bytes": 13109,
+            "contentType": "text/javascript",
+            "path": "/assets/media.es-CDRleryS.js",
+            "sha256": "5aad50664d4bd0321132a941d91602d1cb117be40b136a11620664ca3bd06de6"
+          },
+          {
+            "assetHash": "e99e49787a1fd9a2abbb680c0ef0722c",
+            "bytes": 18878,
+            "contentType": "text/javascript",
+            "path": "/assets/media.fa-DXam9tY_.js",
+            "sha256": "488e74209223123cdfe4a6b73686c8c24de04d879ae301e77c071672e4c7435b"
+          },
+          {
+            "assetHash": "8ffbedbd3177a23053ed6f12e3d732da",
+            "bytes": 14085,
+            "contentType": "text/javascript",
+            "path": "/assets/media.fr-DKfkWH0D.js",
+            "sha256": "3b8786fe787436abaa373301ef8e92821fdec18eb7562d6f22df232725abb475"
+          },
+          {
+            "assetHash": "42d84cf2cb5272538b9a72d3acd3b079",
+            "bytes": 16205,
+            "contentType": "text/javascript",
+            "path": "/assets/media.he-COj2GX3h.js",
+            "sha256": "d688a55aa7246e4c11f034fa6bdaac44555c389bebdcd7098de9dd8658e94701"
+          },
+          {
+            "assetHash": "8884377cf32fee7d3e5b9571312855da",
+            "bytes": 24729,
+            "contentType": "text/javascript",
+            "path": "/assets/media.hi-1a0-Fk6b.js",
+            "sha256": "beb6c3e6225e68214b66decef0a275a95ae8e2dc3b9a689753981dac51e3a2d7"
+          },
+          {
+            "assetHash": "60ea75c5bdfd5e43fae1520b27c74e69",
+            "bytes": 12136,
+            "contentType": "text/javascript",
+            "path": "/assets/media.id-CAgR7je9.js",
+            "sha256": "2d86500e2178102b0ab6feedefa633182ebd6e236822c1be69e2e1eb1bd6f31e"
+          },
+          {
+            "assetHash": "dd76c3d4e1fd38776e6d0b7768f16ffe",
+            "bytes": 12798,
+            "contentType": "text/javascript",
+            "path": "/assets/media.it-huMn4qfV.js",
+            "sha256": "fb115147904f3408cbf1531c725f39b626364f8c10c5542ce27d2bfe80746e67"
+          },
+          {
+            "assetHash": "6bee316f6a4fe2fec470b5402dd7305a",
+            "bytes": 16455,
+            "contentType": "text/javascript",
+            "path": "/assets/media.ja-DIiDhxEt.js",
+            "sha256": "9d13f35f05cb0e0f8f1592f80dca7b437f6498c7825f33987ff20efdd5239c49"
+          },
+          {
+            "assetHash": "41c3b7e5af66677164945133a1b7fcff",
+            "bytes": 14042,
+            "contentType": "text/javascript",
+            "path": "/assets/media.ko-CEzTT9w2.js",
+            "sha256": "f1b0479e986c5ebfaa5a51b65455474749b94792b24d4cae1ef2df521ffda882"
+          },
+          {
+            "assetHash": "abeb24d88e54a5a5f854af9c9b5d3854",
+            "bytes": 13065,
+            "contentType": "text/javascript",
+            "path": "/assets/media.nl-DAcAkgwj.js",
+            "sha256": "952551773817d1ee5ae781f416142bcff7ec1fd051e1526f8d00e2014b5757f0"
+          },
+          {
+            "assetHash": "548aaa55440d0d627bce997dd51055d5",
+            "bytes": 12809,
+            "contentType": "text/javascript",
+            "path": "/assets/media.pl-BIZYXHQa.js",
+            "sha256": "b02317f359c3e8bae7548e45101abc5c00a48ced1c9dbe8f6df82443b9fa7f4d"
+          },
+          {
+            "assetHash": "3aeca96bbaa6a8d20ba554de685f8267",
+            "bytes": 12789,
+            "contentType": "text/javascript",
+            "path": "/assets/media.pt-BR-C1Xda0__.js",
+            "sha256": "fca391b137e246c3aadac187c7078cbee91055f1270e94eb96823d27dda8c73f"
+          },
+          {
+            "assetHash": "c8e3bfac09ab15a6d8d002e8799ed827",
+            "bytes": 20340,
+            "contentType": "text/javascript",
+            "path": "/assets/media.ru-DQzNvBXO.js",
+            "sha256": "0b4322ce6878e07ed77dffc842e02f9cd0f2a3885d4cfbe3a541780a2214173e"
+          },
+          {
+            "assetHash": "12a2033c3f519242981b4d9e42283492",
+            "bytes": 12506,
+            "contentType": "text/javascript",
+            "path": "/assets/media.tr-BaqtqqOw.js",
+            "sha256": "d376f282eb70109d0fcd4e2c241f50d8f3a614e898ca9051341300f659c67562"
+          },
+          {
+            "assetHash": "de7ad6a01a22ff33a0af0c3073e6f483",
+            "bytes": 19695,
+            "contentType": "text/javascript",
+            "path": "/assets/media.uk-DGk-oYWc.js",
+            "sha256": "b14cdf7dc56bf3df096a64a4fd1871da588a9b2a071717bb667b920fbbb9ec4e"
+          },
+          {
+            "assetHash": "304c6108c86c74aeed8455e9ec5d6809",
+            "bytes": 11296,
+            "contentType": "text/javascript",
+            "path": "/assets/media.zh-Hans-CrLAn2xd.js",
+            "sha256": "f91f96f3b441cf4522f16c9382518d1b165366eee7509db26bddb6cfb0d89906"
+          },
+          {
+            "assetHash": "bb27e1152ee2121f8052581e6623d9c6",
+            "bytes": 96,
+            "contentType": "text/javascript",
+            "path": "/assets/members-XiS2ssJq.js",
+            "sha256": "ad726af6b5452ee32d5012d94129924708d507a8dadd2bba549b183bbc44f959"
+          },
+          {
+            "assetHash": "8427ed2834ac834da1520f0edf95db34",
+            "bytes": 25284,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.ar-BABqU-We.js",
+            "sha256": "8c9c2362c01052eea4bac82a1613b7143b7468f19855380767eb21a0a21dae28"
+          },
+          {
+            "assetHash": "9303593e525096f21db2dc92b2557059",
+            "bytes": 19594,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.de-Ba8EWf-g.js",
+            "sha256": "76006e71964d452978aa4433702f7c592dff7eb0fa94d8c52fdc146951a493a5"
+          },
+          {
+            "assetHash": "80a0848efd87588cb1f2ca51c7aa3a3f",
+            "bytes": 16257,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.en-CJ5d7ms0.js",
+            "sha256": "48b4c31397c878f0be77e0ee534d1fe594c4b11726fb82c9d51edc65471a0cdd"
+          },
+          {
+            "assetHash": "07c43ec1253dbf084ecf02e697a6999d",
+            "bytes": 19125,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.es-CA6YLxuk.js",
+            "sha256": "5ada0b74cda0c2b287d75408cce20216221e5b367736e3afa1133109b18f5b8c"
+          },
+          {
+            "assetHash": "dae723da22a7afe190333fc409d782de",
+            "bytes": 26450,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.fa-C7O8Jr5Z.js",
+            "sha256": "9643e6dfeb95b84ecbe1f59de18b4d6d892d1cce3aed162422484ae7f9755bcc"
+          },
+          {
+            "assetHash": "4838bed84a2a69d956d939ac9f6a25e9",
+            "bytes": 20303,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.fr-Bq5ukcBM.js",
+            "sha256": "fec51e78f0eb6671f027e3b89544fc82d47288e640670b619dea1866e0945886"
+          },
+          {
+            "assetHash": "fd32ef6e26adbf252d7b26270ba615b6",
+            "bytes": 23631,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.he-BiYUZo7X.js",
+            "sha256": "1e47ddd9ba8af1bc39eb9436d45a8d82ed88b000f061c83b46c94cf250ea83d9"
+          },
+          {
+            "assetHash": "e51c2b011ad96c8f7cd68aadd36a682a",
+            "bytes": 34338,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.hi-CPYsx5YL.js",
+            "sha256": "2d8aee9c9f44c5a358266bd902dd2973de342217a22cda2b34f6590482835c8f"
+          },
+          {
+            "assetHash": "b518ec60308948161b0dfa4a01311bdc",
+            "bytes": 18001,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.id-B5JAh3Kk.js",
+            "sha256": "14795477942449fdd1826e29524af4abd51b608a0da35035385ce52221d9410d"
+          },
+          {
+            "assetHash": "504c05d81dae9f6e1bee73bf5ac42fba",
+            "bytes": 18782,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.it-DpdTqaL4.js",
+            "sha256": "61f8bbff0d3a9e10b15669010a7abcdd6eecc54d812536c6092fa35eab56b38c"
+          },
+          {
+            "assetHash": "988f7e6cb3ed0698531a21749c672531",
+            "bytes": 21938,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.ja-NH9k2gC3.js",
+            "sha256": "266550a109d0a2edaea8cca564a071c6c8cd24564852174845e5c254b31319ee"
+          },
+          {
+            "assetHash": "a5f90de2805124cc1b69ecbccf619597",
+            "bytes": 19848,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.ko-C_MCO4Mp.js",
+            "sha256": "92f63dba8106b6e43d4879cc56958bef67ba33f2d93a1766759a89e835b6a1af"
+          },
+          {
+            "assetHash": "d40448217a5cd00ccdfa915dfa4eaa30",
+            "bytes": 17927,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.nl-CLflbcbY.js",
+            "sha256": "ba5d4ab1d3e378309c5a3e2cadb946d3728c545b6bd9340405731741519872f0"
+          },
+          {
+            "assetHash": "ecbeed91aeb0fb83769011e1a03d57a0",
+            "bytes": 19065,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.pl-DLXN0-US.js",
+            "sha256": "0af69564fbd03615920bc15b1f67122b88701f0742974bb4da8f14de9d693157"
+          },
+          {
+            "assetHash": "d9a03dbd3553cfdca4f03eb82da66718",
+            "bytes": 18259,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.pt-BR-Djh61S7r.js",
+            "sha256": "560d10004d083654220c5f2bdd05223e2113f7691aa021f743ed99595ed90328"
+          },
+          {
+            "assetHash": "ec5fdb9669ef7044a7ea986aa813142b",
+            "bytes": 29997,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.ru-Dn7rmQ8H.js",
+            "sha256": "75879db619eb84036bb55a81182d148335b903664c66952e40897d62b5e2c4ab"
+          },
+          {
+            "assetHash": "ccc613a33772c33785268b548ef5c9c0",
+            "bytes": 18188,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.tr-BOeLMbHv.js",
+            "sha256": "9427a548001ff331ffa158526386a07515fd0f17db7dae55a057ee6239d97d25"
+          },
+          {
+            "assetHash": "64282a66a4a7c61822dec2a14c00b5e4",
+            "bytes": 28420,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.uk-BVwLEGId.js",
+            "sha256": "290872cc34200a705e91ecab39fea4200dd1f97d43f9a50ed13629460ec42c5e"
+          },
+          {
+            "assetHash": "980b1e32d4583b5d5d970c7d259ed712",
+            "bytes": 16464,
+            "contentType": "text/javascript",
+            "path": "/assets/menus.zh-Hans-X7QVgirl.js",
+            "sha256": "fe9e41665cabb21427a86d1329ec985c89f5c306dca745d42ba50d2d4be95edc"
+          },
+          {
+            "assetHash": "8980b4035009dbb67153ad3d4131f390",
+            "bytes": 3333,
+            "contentType": "text/javascript",
+            "path": "/assets/meter-majprsslwmpbmkzg-BnIeb3wC.js",
+            "sha256": "9dc2329149f6f780be1d234cba2c6ccf964da6c79b5e974e75a3d5b38e4ca7d9"
+          },
+          {
+            "assetHash": "ea4a003b8a03fe417e58ff2dda950afa",
+            "bytes": 2960,
+            "contentType": "text/javascript",
+            "path": "/assets/names-DtgvAZkI.js",
+            "sha256": "b9e4c402aba4e2bb5eb8169b8761338bad1c7ad570a73500ac87d60b7c26e6d0"
+          },
+          {
+            "assetHash": "671cb1474524fd6b175530b157c201e8",
+            "bytes": 124485,
+            "contentType": "text/javascript",
+            "path": "/assets/nl-CdoE0jC0.js",
+            "sha256": "c0283c1442728adb5306fdda1af85b222ee1cbd49134ef1c7cb0cb1faf2ae04a"
+          },
+          {
+            "assetHash": "b22f9e118d767f924b6ab164f58c7191",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/nl-aexOpcky.js",
+            "sha256": "0c723219b90c38c2c9a12e5020c989cf3366eee885a3dd442f8693d75f705a28"
+          },
+          {
+            "assetHash": "eff14a29ea2a918a9ac057a14930bea6",
+            "bytes": 2788,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.ar-BOgz8_Xe.js",
+            "sha256": "f877036886791eecf13923760e5e9ea789b788fa8f16ef4c45ae7bdaa1f3ec2d"
+          },
+          {
+            "assetHash": "a3291510d73e4e226fdb178853ea617b",
+            "bytes": 2209,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.de-tHUfyhvC.js",
+            "sha256": "72158c4b45bc88fa74bfc64c112f6d09d25fa9e0bcb854555d64b07bbeebd9b9"
+          },
+          {
+            "assetHash": "c77fa4583ff5ffcc03864ce0b31bde9d",
+            "bytes": 1951,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.en-BBrrjHxr.js",
+            "sha256": "d145c7bdd7e80a861683336ae1bc6c122f658bfbfd25832ca162cc701e699817"
+          },
+          {
+            "assetHash": "556072592d1d413c6aca7184f098389e",
+            "bytes": 2071,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.es-hEpAvrGt.js",
+            "sha256": "b239779d85c8ce8d685081339e7c695d97edc76256eb92fec24d5975cc50f9f5"
+          },
+          {
+            "assetHash": "aa3e2f44565771d9501345570a755450",
+            "bytes": 3171,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.fa-WWemlc-u.js",
+            "sha256": "7442f03d6fc21897e7626024c42905fd3a8a0f26312d741d02c16498c551f14f"
+          },
+          {
+            "assetHash": "5d5c4ce329ae733548f644741ac3c3fc",
+            "bytes": 2378,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.fr-Cg4Ndu6l.js",
+            "sha256": "99a71e7e3e300c11694b2fca2132ce0f08f572ca423dcaf2a06631000137f8e3"
+          },
+          {
+            "assetHash": "7088bdf8eb834a230aceab1d0ec18693",
+            "bytes": 2610,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.he-BhgAtszl.js",
+            "sha256": "61b4792d81006d4b2a6a88946e09d92d5c76450d354c9124143ba5261cdf3d89"
+          },
+          {
+            "assetHash": "c4420009f8e0abec01955286ff949e36",
+            "bytes": 4416,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.hi-C0trPpTB.js",
+            "sha256": "4889c148c72064d191b6e5d1fbfb411c189ba66078940a5811010dac10cb41d5"
+          },
+          {
+            "assetHash": "24536e2d7c6439c163dff28d75d0dd82",
+            "bytes": 2089,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.id-If8qqF0O.js",
+            "sha256": "6b51008fcfc9ea0dbda02a1c61310c37b5078a44c22e7d835b8234cbe2573d51"
+          },
+          {
+            "assetHash": "a7a8173e2aa37b54dec6c6727f566d60",
+            "bytes": 2097,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.it-DT05Q_UO.js",
+            "sha256": "f445e73a0fefe5a2e1a69c40500f8f1d571fb08df65bc4058ce43b9ffb436b67"
+          },
+          {
+            "assetHash": "0ec85cac5042ef9142467a1286e67fc3",
+            "bytes": 2552,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.ja-C0OEXpsi.js",
+            "sha256": "1a215e076f376fe5ea0c2277fd959563b2ef3bc9d14c85966e259fb5f240deef"
+          },
+          {
+            "assetHash": "b3398328c003b497eb86a9012f1c05b9",
+            "bytes": 2288,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.ko-DVXhQG-f.js",
+            "sha256": "c48f23981eb87044b5c8d53c9013c8c0137b74191800a856a181627a49111321"
+          },
+          {
+            "assetHash": "285c457b71fb6c36ba33676c58b867a1",
+            "bytes": 2069,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.nl-BqHhHqT-.js",
+            "sha256": "83a648a9f510d7f53be8fb31f3c4ea094d6c5c9b4e284958c8392e7d8526e255"
+          },
+          {
+            "assetHash": "a5f1990cdf75675dd56144b6e97086ad",
+            "bytes": 2309,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.pl-C4aKW_Rd.js",
+            "sha256": "c5ea664f8f83444e2e7b09a913a14ba6a69f5464efd77cbad8b0b38b8839b88a"
+          },
+          {
+            "assetHash": "d3a7c0be77f617d10b7021d67d422b7c",
+            "bytes": 2188,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.pt-BR-C0KUQdnc.js",
+            "sha256": "918cf95d6ae06e40428837ec67d77c578707539434018be9b0ca56023631e90e"
+          },
+          {
+            "assetHash": "98bca51c6ff0d7c10f4b65b7aad3730f",
+            "bytes": 3481,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.ru-BxhELsho.js",
+            "sha256": "57c0c7648b3c543edbc87ac28cf672b54e470e2de241684d095d3fa72d603832"
+          },
+          {
+            "assetHash": "3be22b2d190c820c72bf89a9e1d38306",
+            "bytes": 2122,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.tr-DI3N2ZB6.js",
+            "sha256": "cacd9ed94810a0001d2a1608b75cce313bafc8e7761ead7637abf090fc7db62f"
+          },
+          {
+            "assetHash": "7e20696ee90139b003a5eb097f5ba353",
+            "bytes": 3382,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.uk-DwTpFu0e.js",
+            "sha256": "f846f7eaf80d4e4b633e88c085ab354d482d30929cd1374b29928f717b448d80"
+          },
+          {
+            "assetHash": "c55c0bcdb94804f51c9c80ef8d93797d",
+            "bytes": 1923,
+            "contentType": "text/javascript",
+            "path": "/assets/oauthConsent.zh-Hans-pTndpOv9.js",
+            "sha256": "54d287827ed5bb24fa2470cc05ee0ff3130fe00d64f37a39cc5ca4fb449c387c"
+          },
+          {
+            "assetHash": "e19ef94de44594261a9774f25ce867f3",
+            "bytes": 6992,
+            "contentType": "text/javascript",
+            "path": "/assets/pagination-oaenm9t7ou3lmcgw-DRBc3OEq.js",
+            "sha256": "d7168f4d954955c62a86a5d243dc9daf54d6ad9c32a1940521e6c5731533129b"
+          },
+          {
+            "assetHash": "bd0cfb17b5ca7a9c7140ade889082ce2",
+            "bytes": 334,
+            "contentType": "text/javascript",
+            "path": "/assets/permalinks-XTIdpYm2.js",
+            "sha256": "78f276d096f138b0762e1e1f250da7587d7234f8754daec79e0e5f2ba1472ac0"
+          },
+          {
+            "assetHash": "8ca4556a5e8fe6ca0fe29bfd1bde3903",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/pl-BA1Q42vo.js",
+            "sha256": "f196d82f6060c5598ff7baf287ecf7cc092f91e3407c709e99e0c9a8e8877b4e"
+          },
+          {
+            "assetHash": "d9ddf6a5b60d0b866994039b0021db14",
+            "bytes": 127750,
+            "contentType": "text/javascript",
+            "path": "/assets/pl-CTtqV9EF.js",
+            "sha256": "1de73dc1faf89b4c1fb656398f9bf101daac0a3cd0c6573ed483acdc0a03e2ab"
+          },
+          {
+            "assetHash": "355f42d18f6dca1ea1ed2c555d38f317",
+            "bytes": 1519,
+            "contentType": "text/javascript",
+            "path": "/assets/preload-helper-BLdHbfdN.js",
+            "sha256": "8aec9ff75ffa86a03d51268b63126abadb3754aa5a6b6c5bb16e4e2446a63dee"
+          },
+          {
+            "assetHash": "a2f1adf23a9939ed0739d0d043169758",
+            "bytes": 3915,
+            "contentType": "text/javascript",
+            "path": "/assets/props-QjM3zCAy.js",
+            "sha256": "8bb53b72f753417dfc03ccea511983960d920d46245d95891e12566c376bd1ab"
+          },
+          {
+            "assetHash": "9e1e4e9bda057fd9e5ecf9e37a286e71",
+            "bytes": 1664,
+            "contentType": "text/javascript",
+            "path": "/assets/pt-BR-CghnOnD0.js",
+            "sha256": "ae6a07b81ed168d4b1f5901ee6d0f7b290966c0df86a96690090e184aa4e4d7b"
+          },
+          {
+            "assetHash": "e5c38e6391b7c223e71298453d14a22b",
+            "bytes": 125855,
+            "contentType": "text/javascript",
+            "path": "/assets/pt-BR-DbRXVE6S.js",
+            "sha256": "9eda0a98d4a272b38075b7df63ecc85b3da1ed92312ba7b2ee363382e41b22a6"
+          },
+          {
+            "assetHash": "35eb2713723424f3d3f90d2bea4629e1",
+            "bytes": 70006,
+            "contentType": "text/javascript",
+            "path": "/assets/python-nrpnmxq4r6yp6fcd-gzcpVVnB.js",
+            "sha256": "1d1955cde7dd32057cbf5de18b1e89830cd12f75f6c7b864bb8018b166fbe3cd"
+          },
+          {
+            "assetHash": "0b5117872a105c41febb0706c8d9c4ea",
+            "bytes": 9890,
+            "contentType": "text/javascript",
+            "path": "/assets/radio-35dx7y6rjrezbjsp-Bx0uo6l5.js",
+            "sha256": "f3d1e09f73ddc609e61787fde75c1209d03a9c784f4628f8524516010c24c645"
+          },
+          {
+            "assetHash": "cefbf6fcdb74b4f01ab097ecd0f1220d",
+            "bytes": 218797,
+            "contentType": "text/javascript",
+            "path": "/assets/react-iRJfyluG.js",
+            "sha256": "28c7251e28abac45fefc198927781628bae2b2eac4eaa94e7fcd45e50c127114"
+          },
+          {
+            "assetHash": "e072c2f94e2b6cc9aee7e9e0f4772c41",
+            "bytes": 2884,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.ar-YaMfL_Lj.js",
+            "sha256": "91fd8bfe19b7a131cd296a59194876be0de6ce4fad498b011283f1d37c51e2ea"
+          },
+          {
+            "assetHash": "df72e30ea03f9c7e8728515917077637",
+            "bytes": 2164,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.de-wAkjNmXo.js",
+            "sha256": "47c6fd2a892ebb8e41d941f8d23285042ba7fc5a158817b6b3e82759a2f6a193"
+          },
+          {
+            "assetHash": "a69cac7f08119ce928fca106f13b1118",
+            "bytes": 1889,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.en-CbmwKSip.js",
+            "sha256": "b02f48c2f9a3cdd4c5aced2f3152c93ca82ea69d064bfa7d4df2355773d716e1"
+          },
+          {
+            "assetHash": "b84ac81e591181f1808ac0ebd5b9a0cb",
+            "bytes": 2208,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.es-Clx8q_tO.js",
+            "sha256": "83fa8e6c4ac68b25e524dde68e73fb16cfebdc51ddab9ab0601dbe80a5cba320"
+          },
+          {
+            "assetHash": "9ec9365e504ac16677ac5bad822e7711",
+            "bytes": 3023,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.fa-8cssg4YZ.js",
+            "sha256": "b405cf3df5c99c22152ab1da578cb943b74df155103ca06297287b757eb4e56e"
+          },
+          {
+            "assetHash": "52f530b8b0c0c1c1383ac2f98903dcd8",
+            "bytes": 2284,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.fr-JJ3OFBav.js",
+            "sha256": "029b85145b9292f6a0e82a6c753c5a44cc3377a4a88feca551e10103dc2213cf"
+          },
+          {
+            "assetHash": "f8630671c715c851528331a9ec56d162",
+            "bytes": 2747,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.he-B0yqdtyq.js",
+            "sha256": "fca45d4afbc210f1dccd48d6374da402957eece43c5027fd80ced60d938f832f"
+          },
+          {
+            "assetHash": "b9a01b15e1448c81081f14ce38ebaf73",
+            "bytes": 3738,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.hi-i7cAeleO.js",
+            "sha256": "6177e8a5c417b131a78366385aa62b2d24acf0d2b12d205f7347dd2b561013b3"
+          },
+          {
+            "assetHash": "89fe69cc9d8a1a947757e787003adca3",
+            "bytes": 2088,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.id-BdaxiKFf.js",
+            "sha256": "e129846c8c9f55b299b8c97d8c31c4730b22e6dbcfe2fb75c372ccb0d8f6bf36"
+          },
+          {
+            "assetHash": "2290c56d7a414eef4d6214aa74be3e23",
+            "bytes": 2149,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.it-DXf3Um9S.js",
+            "sha256": "8b0b96214df9c0608faa32b8d237d9bf5002bc82c4d6f7b81710b002229cc2e5"
+          },
+          {
+            "assetHash": "a12cbc84eeacd267fe73ab75c87e914b",
+            "bytes": 2357,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.ja-B2eb6ZUO.js",
+            "sha256": "8afdd1396fc6253b038673d11d7f1f8d8850cb7570ddcd309695a503bffcad44"
+          },
+          {
+            "assetHash": "bc1982de69cab8e08e528b46389e4984",
+            "bytes": 2194,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.ko-BX4g0Q7l.js",
+            "sha256": "45af97aa6f02e8d7eb9891477ac8c4723a97428678a827c7752e90f34308c555"
+          },
+          {
+            "assetHash": "befee12953baa148aa28d7266a2d3949",
+            "bytes": 2100,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.nl-cWSL2Ylr.js",
+            "sha256": "71544c5cdfaeca4915219905c8d6ffbfcfb793405e68aa5c24fb2f71d43ff58b"
+          },
+          {
+            "assetHash": "ba5fb5bc981223e7dd5a027eaa61d464",
+            "bytes": 2186,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.pl-C4WdmkML.js",
+            "sha256": "889397580b31ed02e70046731e17b699d21e6ba0c05c6a8d823bbd4fdcc83e77"
+          },
+          {
+            "assetHash": "4ec8b9253ac33ce0b305dd21853bcab8",
+            "bytes": 2207,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.pt-BR-x4dMCgC9.js",
+            "sha256": "6437d914f42f3dc3354dd014979423e769616c477368bc7248a36d119b04b10a"
+          },
+          {
+            "assetHash": "70f3673e9c6482320fc31b6b446ad64b",
+            "bytes": 3281,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.ru-BukUgT1q.js",
+            "sha256": "4f1987c1772f09443d5ed82fdc03427bd414a40a36c6aa77bbdab776853e13f1"
+          },
+          {
+            "assetHash": "4e254e8ed5df9d56a2667723721a24bb",
+            "bytes": 2053,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.tr-nYS49xYR.js",
+            "sha256": "f505f9343bad331ff591e3ee9f3c47fcd568f23d834855221961c256d67a2c66"
+          },
+          {
+            "assetHash": "1641cac9259a62ae353d14a237466dd3",
+            "bytes": 3161,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.uk-DbcRoxdR.js",
+            "sha256": "b6a0827c20403f4aa3a5b1f863dcf818dea05dff59f49852008b6c56f7cccc23"
+          },
+          {
+            "assetHash": "e873699be6dd3c3122ea98980f358bc8",
+            "bytes": 1847,
+            "contentType": "text/javascript",
+            "path": "/assets/readers.zh-Hans-DcGU8fSg.js",
+            "sha256": "393939b0f32ca276efbb770068ef40ae0cd614e0a9cebd463b6c19a6fca5efba"
+          },
+          {
+            "assetHash": "1bd9b0e9d39cbb69a27c7e8adea9b56e",
+            "bytes": 160,
+            "contentType": "text/javascript",
+            "path": "/assets/reading-keys-CBjq-Jc-.js",
+            "sha256": "b469cf62e77af4270d429a910ff20d7ed4181beab506f42d470ba3d7638e94e0"
+          },
+          {
+            "assetHash": "f7756cca8f9c22e61ca631b1ce866b86",
+            "bytes": 1331,
+            "contentType": "text/javascript",
+            "path": "/assets/resolveAriaLabelledBy-nc8mdie5shy87nsb-BodbanlU.js",
+            "sha256": "e8fe67c9c92e1233609a50767b0b9962204820d37994f315109dcf5ad5eb3f9b"
+          },
+          {
+            "assetHash": "3c31f19e363b34a17717ffe7cd84cead",
+            "bytes": 2405,
+            "contentType": "text/javascript",
+            "path": "/assets/resolveValueLabel-hpmsash3ektitvnb-CCmVsV0t.js",
+            "sha256": "a9da27b726c190fb16605c56da297c53db02a7a9ecd8bd8f91ee456eae6f2dd4"
+          },
+          {
+            "assetHash": "b076d441ac7dbc2930d36e18229a4283",
+            "bytes": 6026,
+            "contentType": "text/javascript",
+            "path": "/assets/roles-UXx4Dl3q.js",
+            "sha256": "34265ba0e544da433ef3e7f7e319607cc595eefb5efcb88c5397da8dbb71b613"
+          },
+          {
+            "assetHash": "8e4342273d4df497f66e0fb65144b167",
+            "bytes": 726,
+            "contentType": "text/javascript",
+            "path": "/assets/rolldown-runtime-XKyaHtl9.js",
+            "sha256": "548d1dbf321e7d2ae804fcc158b588706a7c52f8ca175de1490c6ccb4dbe55b7"
+          },
+          {
+            "assetHash": "16e6cf28fe74e4a9170ae7a8ee117106",
+            "bytes": 229,
+            "contentType": "text/javascript",
+            "path": "/assets/rolldown-runtime-hi7ti5xiuxsegr96-DK3Fl9T5.js",
+            "sha256": "9aec7c3af08dc58b4b803fa96f635a9902250012e75f1c6f45ea4cb3ee83bf42"
+          },
+          {
+            "assetHash": "901a8c69e64986ac3e5cdf3b30de03df",
+            "bytes": 200945,
+            "contentType": "text/javascript",
+            "path": "/assets/ru-DI-puNd9.js",
+            "sha256": "d8f73aefed3c0e12ec6372177cb7733c0580627b81e9ce0b4211ce7424569c0c"
+          },
+          {
+            "assetHash": "cbedb1f36ad2341fdc1ad5465a0a0eb8",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/ru-Uyhbg1QS.js",
+            "sha256": "09580c836ee0d21f1552eb5eb0fb30434b9c0db552d233d79356e33a396bfc26"
+          },
+          {
+            "assetHash": "f88109a6b59e1fe8364cadca0735a718",
+            "bytes": 3080,
+            "contentType": "text/javascript",
+            "path": "/assets/schedule-notices-CUIcpbXB.js",
+            "sha256": "7d9979398224727a7312bb288cead5289895ef5b6d1faf394316869e2ecca345"
+          },
+          {
+            "assetHash": "3572197d34aec06404faa5311cdb0b65",
+            "bytes": 3205,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.ar-DjCN-gPq.js",
+            "sha256": "a543729092b9c9cd6f68aa2caa815789fe1895fd54546259c5f9dd08c11de98f"
+          },
+          {
+            "assetHash": "cc462ab8a950de61c74a6665e90af9cf",
+            "bytes": 2619,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.de-CkLy189g.js",
+            "sha256": "163665839659aeefc1b97fbce3f91b76deedb1b5c329eef5ef0470e97e427f26"
+          },
+          {
+            "assetHash": "f50f4975f04c7e65b73c40567eb9b262",
+            "bytes": 2188,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.en-Ke9GgFqw.js",
+            "sha256": "4c32ee90ecae4a617d9d1f83ff62f202ef7b446f2ae97fc68d6f818783762acf"
+          },
+          {
+            "assetHash": "5dc544f966ddf3f2f1b52f183c36409b",
+            "bytes": 2628,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.es-DMlXyS4t.js",
+            "sha256": "b0c70afa1d28df82a781a0f49885a7c4619f186575b978ea22cdba4854379e19"
+          },
+          {
+            "assetHash": "be14a270d19e8b710c1666da24e97a0e",
+            "bytes": 4000,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.fa-B8pDw0e4.js",
+            "sha256": "dccd70f8ca21597be9f152cc8bee55d967bc322e21cc726d756de3ace8d77265"
+          },
+          {
+            "assetHash": "bb55c36c33c9ec6a1ab6e6ff2561c1e0",
+            "bytes": 2772,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.fr-D93CiYDm.js",
+            "sha256": "fedcc36f0c16ee520b669508e8efc57612c8afc39e83c43d5582e5bda1390113"
+          },
+          {
+            "assetHash": "c8a6aec2e138584f364b45dc0495f9b0",
+            "bytes": 3013,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.he-Ce8vr2xp.js",
+            "sha256": "80c54a2fe05dab4417ee8c87cc4e22f73cc3350cc0528702ca978f1dd4261aff"
+          },
+          {
+            "assetHash": "9296f0c0076826f6c3b6bbced2cfd04f",
+            "bytes": 4706,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.hi-ClQTAEIx.js",
+            "sha256": "931114267c6173ba0ace7b55495342798016bfa49af5829771ed3b036de9a625"
+          },
+          {
+            "assetHash": "c1b1b71ec5e76275eebbad032aa8657a",
+            "bytes": 2411,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.id-TZ0m_tpl.js",
+            "sha256": "4158eb5b24d521d4a80a9b6b038f45f6ff5bfae2044cc4bc2660d1e5a3dc74ed"
+          },
+          {
+            "assetHash": "07a18a434b85235629545ad322388f74",
+            "bytes": 2558,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.it-v63UBHwg.js",
+            "sha256": "4e8a926fd6e9e67943c8dafb5fe888faec7a8f433d68285303423726f7369d42"
+          },
+          {
+            "assetHash": "cc2fd747295266108b8b99ded4179fc8",
+            "bytes": 3116,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.ja-DjNw2QpF.js",
+            "sha256": "74edd662f162eb2bf105d0c636c3d248fd2aeac59f01dc418bc35ec648b5b946"
+          },
+          {
+            "assetHash": "1695ceba9ffc972b1d28c0abe144bbd0",
+            "bytes": 2636,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.ko-D-tIgkq1.js",
+            "sha256": "47a5d8bf6c1a91e8ef170c2d886d6a2ca3f3c9440563f400a3bb40211d39ca3d"
+          },
+          {
+            "assetHash": "9abc1aaa82e2f60d0b68d9b78c78acd4",
+            "bytes": 2498,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.nl-CQM8NRDu.js",
+            "sha256": "9014cfea05aeaa14cf07bd901a910e4d75d6414153be00df2fc532c868c1bb1a"
+          },
+          {
+            "assetHash": "b54dc41b921c7d2a6e565f23c91c2e2d",
+            "bytes": 2636,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.pl-CUY8YZhH.js",
+            "sha256": "eb4e30b659beb92cf9486e728369f2e506333913d202d95c320db9b1e72aebeb"
+          },
+          {
+            "assetHash": "d642023d8603ee096beb62e1f8070f47",
+            "bytes": 2500,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.pt-BR-2YkyZTua.js",
+            "sha256": "90e9e35b700919557276ccbcf46d7e5e3d3511d27fff13f460e7a3b297cface8"
+          },
+          {
+            "assetHash": "2daa6af4652b6cb7e250c6f103af0fe7",
+            "bytes": 4074,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.ru-D-XI8CtL.js",
+            "sha256": "b535c08dc06d4660e6a647fe33ddbc4924c453c27b550eaeb925b2cfdaa76c77"
+          },
+          {
+            "assetHash": "56e6ce0ad20064e6e43442525330baa8",
+            "bytes": 2633,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.tr-ChWn742p.js",
+            "sha256": "ca7b5444df146c014daf9dd8375aec24a103928f74e94f52bc55159ea51af3e3"
+          },
+          {
+            "assetHash": "7d77e33346c99637f69d93c3e743695a",
+            "bytes": 3877,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.uk-Czj_lfWV.js",
+            "sha256": "df80b0520ce8050e6291b739bacc83975ccf9a2b979e280987fb0772e231e508"
+          },
+          {
+            "assetHash": "e216f01fe934523f9bde04df97ff1666",
+            "bytes": 2146,
+            "contentType": "text/javascript",
+            "path": "/assets/scheduleSettings.zh-Hans-B1hytO83.js",
+            "sha256": "d116408c9e7b1af89254e2f619f2e7de7a7ace0a084dfcab2a99b6273051a24c"
+          },
+          {
+            "assetHash": "f72b2d0367981a760cf0819eb5fefcb9",
+            "bytes": 15779,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules-CEWibmQf.js",
+            "sha256": "a610e1b76f16f7598f811477eee1382d0070841b155422ee3dd9d849118a5129"
+          },
+          {
+            "assetHash": "7b5285b6eb5ff38da6d0453a35d0a1f3",
+            "bytes": 2044,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules-api-5jW4xCFS.js",
+            "sha256": "61651300bba9982a2d3f574f8527b4c7940aa79658527c0787b85f0ccb3602e7"
+          },
+          {
+            "assetHash": "29b1ca8cf08ceb1ec1784e6eff2752eb",
+            "bytes": 570,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules-search-B-7SpriY.js",
+            "sha256": "de8865a0c762649250aa61ca4b93c068a5015252797572b8d1e51e7c86fd9830"
+          },
+          {
+            "assetHash": "738db81cc8b488bae75b6b406e1664d8",
+            "bytes": 42526,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.ar-fkzR2RAY.js",
+            "sha256": "a1aa23fe47a2bb0a26b38a4deab5dba8e39dc477e2e32f8f5dc20bef1ce46d46"
+          },
+          {
+            "assetHash": "932fb2b90908946e050a63d2e4545fcc",
+            "bytes": 32732,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.de-DbgcJ6cl.js",
+            "sha256": "8cee0ee8242e7fc329b3439bcaa056acd95db48756709b9e249752b807359645"
+          },
+          {
+            "assetHash": "1d71a587f49eabf6c06c28ae588ae347",
+            "bytes": 27941,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.en-D7O8u4iy.js",
+            "sha256": "e4c85949fef265b557ab1f8aa99f3ebb523d31ea526fec1e8138ff72d480c811"
+          },
+          {
+            "assetHash": "ba83544db033efdb02622b32fdb98216",
+            "bytes": 32771,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.es-C3AvgW6g.js",
+            "sha256": "2bd48822b1c8c0f238aa2575397a6683801aca22e4aef9e4e8073ace31a5067e"
+          },
+          {
+            "assetHash": "0eb4dd020360a09ba358737ee10bbb5d",
+            "bytes": 44331,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.fa-1UfcjmAY.js",
+            "sha256": "736d9629503380909f5bbadb010b5517791768c580f1d4e634bdf0451838399d"
+          },
+          {
+            "assetHash": "6cf4f8866cc1e19eb2daff225a1a97d9",
+            "bytes": 34813,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.fr-KDBHjGVO.js",
+            "sha256": "4d8aa2260d38f606620c801327cf56827d78ff49a6fb585903364ca382ba528a"
+          },
+          {
+            "assetHash": "05fa4fe604b71665b2b94b773aafd748",
+            "bytes": 37775,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.he-D8Pds1dL.js",
+            "sha256": "8e2783c5eda7d5650ce05a2b46c3782f02d005c12134260f6ee8f47e475d9be1"
+          },
+          {
+            "assetHash": "93cf7925bc06ce46f9bcffe91624c370",
+            "bytes": 58341,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.hi-DNpu7GlX.js",
+            "sha256": "1254dad2aafe56143b611c63617d95f0933d127fb7897cde1c8a7c9272b47fd3"
+          },
+          {
+            "assetHash": "33d82b1990fbbf7e4f904ba3db646986",
+            "bytes": 30201,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.id-FewyiLfv.js",
+            "sha256": "5cefc3ff6910c9985f53988546af06a9c922043f324eacde24a9a5701ee5c98b"
+          },
+          {
+            "assetHash": "3f01039bace79fb2cf7396a6b1407c5d",
+            "bytes": 32698,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.it-CY96E2q3.js",
+            "sha256": "b1b3bc59bd98bd3ae6a7230c09bf3881881801722318b0afd370e35c033d2774"
+          },
+          {
+            "assetHash": "9128cc9a7a29d09bb55567c3cf4c7946",
+            "bytes": 37319,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.ja-CN3N8MH6.js",
+            "sha256": "d633b80bab78f7cf245b0fc983b7a7d379cce1d8af9152357511c579c41ca986"
+          },
+          {
+            "assetHash": "daaf30d9e0e333b8bbe344e76426b8cc",
+            "bytes": 32504,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.ko-B9ah4ZVM.js",
+            "sha256": "e7957d8a43817bc4e3b850c0793e8037ad7f533e071a3fb1ddc52ef9517de61b"
+          },
+          {
+            "assetHash": "9b976ef9b7810b865a47609821924dfd",
+            "bytes": 31497,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.nl-u3tZ7Ckd.js",
+            "sha256": "eda4088b4e0d32f8d7f6f87665a565f4e48b9e07f87cf1cdc2a537a1d475b295"
+          },
+          {
+            "assetHash": "34f5864520c7d5b64a33faf645fc3ac6",
+            "bytes": 33420,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.pl-Bofx1Ubo.js",
+            "sha256": "52633a4dc552a86e6d95a9d9d9f8a107cd3d8c4d44d003431e0e6751251eb26f"
+          },
+          {
+            "assetHash": "ed41c2f19538d192a95891bd2d785b33",
+            "bytes": 31594,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.pt-BR-BFxLBiyp.js",
+            "sha256": "0875d7cc5ff3b7640ad0549e0cfd0605bf7a0153dfe56720359d3df3f0eac0be"
+          },
+          {
+            "assetHash": "daaa0f0895902bb2985bb60a08fb7483",
+            "bytes": 50235,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.ru-C5ZnKmGt.js",
+            "sha256": "79b7697135ebfe4bed2ba4fe3defe67e7601d626b34bd56c939f2949e816913d"
+          },
+          {
+            "assetHash": "dad4799d870d63b8fab491c6a0eac250",
+            "bytes": 31335,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.tr-Bfi96rHj.js",
+            "sha256": "657ffce2ad4ea13b5e14999a22ab2b47ff094cf919112cd56872bf2f1797fe50"
+          },
+          {
+            "assetHash": "9b0cf2075cbe7755da81e8226c0caa9e",
+            "bytes": 48817,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.uk-BU4Kj6Az.js",
+            "sha256": "a0518426730d24b4efa537dd0b7c291543006f7ebf52b5f7b0c9483a10d1b21f"
+          },
+          {
+            "assetHash": "bb74f432df1bee73a65cea150dcb8d91",
+            "bytes": 28094,
+            "contentType": "text/javascript",
+            "path": "/assets/schedules.zh-Hans-DDsu_SeS.js",
+            "sha256": "e478e0926a9c48307c0b92997f3180061674e697e92ed3c93d48fc5e48df1214"
+          },
+          {
+            "assetHash": "82921b72e229e5721ead2d7974f6271e",
+            "bytes": 717,
+            "contentType": "text/javascript",
+            "path": "/assets/scrollable-b6m9ze1u7ps0q1qn-CsARZWmE.js",
+            "sha256": "72fba668b9d5acf5ee688c19a923b6460775eeebb42539385b2e03518c77c952"
+          },
+          {
+            "assetHash": "1ecb1db6d3de89e4781e368cab240ab7",
+            "bytes": 3764,
+            "contentType": "text/javascript",
+            "path": "/assets/sections-BbHZVt8p.js",
+            "sha256": "d51d81c6ac9a1f07efd1235109a188fcebc2acc7be33137b5b629b8396e4ad38"
+          },
+          {
+            "assetHash": "8db0263842b12da4f8114a56f3859c31",
+            "bytes": 146,
+            "contentType": "text/javascript",
+            "path": "/assets/sections-w0IGfJHx.js",
+            "sha256": "bb56c9041c9233bdb640643c0c8ffe57e7ec03ebc686d6b3807ad13ffdc8da77"
+          },
+          {
+            "assetHash": "cfb144526a8aea58151d2a28026c68d0",
+            "bytes": 30946,
+            "contentType": "text/javascript",
+            "path": "/assets/select-futox5zqr3r80y9a-DpDFF2Ws.js",
+            "sha256": "b0c1b805c06070f21c8ccd26025f04998ea821bbec5b89d1f9e5c3979e1c2994"
+          },
+          {
+            "assetHash": "3d2980d8c57a358b19b1cfb032e7fb68",
+            "bytes": 2865,
+            "contentType": "text/javascript",
+            "path": "/assets/seo-C-YSZ4mD.js",
+            "sha256": "cb6d6f4c5d48df86f8c59f5249bb9c284ab45247f47b25ffcb6ffb6f8fc3f349"
+          },
+          {
+            "assetHash": "20b2a353bedd0633a9e331c342c6ffc5",
+            "bytes": 202,
+            "contentType": "text/javascript",
+            "path": "/assets/serializeValue-ipcakiw32jb7kany-B_EucO5O.js",
+            "sha256": "9e347f92da11e55d3738c5eed10cd8ea541eca84f01b893e352293f94c89c93c"
+          },
+          {
+            "assetHash": "4aef86dda12799edfe874943c086ce67",
+            "bytes": 41538,
+            "contentType": "text/javascript",
+            "path": "/assets/shellscript-i4m7lo8wi0f5bl8b-CLZ0U2zV.js",
+            "sha256": "90505b3e7074b0a26125b628aef00451064821c8c0385c64d97db3ab65a0a619"
+          },
+          {
+            "assetHash": "59f49979423164ba23f4dec9d6356b00",
+            "bytes": 1374,
+            "contentType": "text/javascript",
+            "path": "/assets/site-settings-Cmq0DkaT.js",
+            "sha256": "da44224acff0d2e7fb1a182249de1e2c84420c54fadbd1b5c7d4f61dabfae700"
+          },
+          {
+            "assetHash": "78ec7a8c93095302934b1e53dedb605a",
+            "bytes": 12981,
+            "contentType": "text/javascript",
+            "path": "/assets/skeleton-line-m9dyh126t9k07jvg-Cif2vjvo.js",
+            "sha256": "9ed902fc86dc69adaaaf6254c982ecd3d1d2eff7f41ceba483b27d704ead02a9"
+          },
+          {
+            "assetHash": "7bad97df926d28f8e8a70a243eb1b930",
+            "bytes": 23479,
+            "contentType": "text/javascript",
+            "path": "/assets/sql-hqp2eekl7qhmxe92-BsFa4tDR.js",
+            "sha256": "a9852da8d8c15f70a9e1f5dea5322668a6f2f7c97c2f125c358cd9b7a4b86071"
+          },
+          {
+            "assetHash": "208123beeaaddbb707d3eaceed553146",
+            "bytes": 169,
+            "contentType": "text/javascript",
+            "path": "/assets/stringifyLocale-bxsgxdnzaef2vcar-CkHjdQTJ.js",
+            "sha256": "682d3224032d7c4598672193a4c36e3d47d023dfe332ee65bcab191c5d219700"
+          },
+          {
+            "assetHash": "32835c54fc6dae2e2ce420617310fe3d",
+            "bytes": 603,
+            "contentType": "text/javascript",
+            "path": "/assets/styles-kl02oyrlnld7arja-BOYSmemT.js",
+            "sha256": "be81d165f3e130604d270e6d9766226ed92189a32c262d78a27bc4d7e874593d"
+          },
+          {
+            "assetHash": "149735201905da30ba208d50cd9ed6dd",
+            "bytes": 45766,
+            "contentType": "text/javascript",
+            "path": "/assets/surfaces-CML_VFHE.js",
+            "sha256": "77f0cb4ecdf0612a2191d63a6fa55702dafd4789221a9caf57722b73203b714d"
+          },
+          {
+            "assetHash": "573b5139977daf0db8e260fe279b2c84",
+            "bytes": 7747,
+            "contentType": "text/javascript",
+            "path": "/assets/switch-cqsvolm7h69z7vgg-JsWPiKhH.js",
+            "sha256": "fa61764c36badea379cb10e9dbb750d3f74f8b53d45e1756fd9f1336c8a27f60"
+          },
+          {
+            "assetHash": "25aee6be4994270bb3336eff2ce6bb72",
+            "bytes": 15846,
+            "contentType": "text/javascript",
+            "path": "/assets/tabs-gf57qqf1wcclaejq-DZt0FNBy.js",
+            "sha256": "7cca332684fd215b9fb19b7728e9d4c2b45d1faee7aea7893bffeacd0868285b"
+          },
+          {
+            "assetHash": "87215426a648d88282f1c39493de9cf5",
+            "bytes": 2936,
+            "contentType": "text/javascript",
+            "path": "/assets/tag-input-ohfybx5dbxy3houy-CsTz-ays.js",
+            "sha256": "cadb0c80855b1232f64f61c385f51b6c71a0a3e68d8c9fa4c632bf30ea1d3bcf"
+          },
+          {
+            "assetHash": "f4881a3861c66c2ff023cf61efb8b6c9",
+            "bytes": 114512,
+            "contentType": "text/javascript",
+            "path": "/assets/tanstack-8GMTaj0M.js",
+            "sha256": "2ec1c2d2d0ace8e2965312a29e341217ee394ee1600e1cf702d9607874d3c4de"
+          },
+          {
+            "assetHash": "e9c2fa9b8607b5705563d07c3bccaaa9",
+            "bytes": 8352,
+            "contentType": "text/javascript",
+            "path": "/assets/taxonomy-BMZx2qa1.js",
+            "sha256": "82cb15e7624b029958ac8b0afa526b227720741f64a8e9fbf5a167c5fd7da548"
+          },
+          {
+            "assetHash": "56845cab6ee85c656eac0924423a8d28",
+            "bytes": 466,
+            "contentType": "text/javascript",
+            "path": "/assets/taxonomy-keys-QrO3vULe.js",
+            "sha256": "39792111ffd4a9955fbde7c1e23ac24a929e7800433043275306cd501329c611"
+          },
+          {
+            "assetHash": "9eafdacdec455b596e35bcc83b00a160",
+            "bytes": 2397,
+            "contentType": "text/javascript",
+            "path": "/assets/text-f1wxz183ckq7qtl7-CzhbVjnY.js",
+            "sha256": "f9182d9de4a4bde345a55a33cee71f6cdd85f9d9582056feb2a1bd1a6daca4cd"
+          },
+          {
+            "assetHash": "c1a8c29ce89306eaedd1c5cce07c4d9c",
+            "bytes": 6478,
+            "contentType": "text/javascript",
+            "path": "/assets/toml-oyqrgosuct25x413-CcmNWLt0.js",
+            "sha256": "079f193b884ca8b58621036e1822042f02e098a6ea0dd15ff67a0a217fc0858d"
+          },
+          {
+            "assetHash": "9c57d319d65b84909286757f6da69c31",
+            "bytes": 5667,
+            "contentType": "text/javascript",
+            "path": "/assets/toolbar-h213d8e22yjh5cwq-CxGgBLh5.js",
+            "sha256": "aece212068219500e70949a3515ab33b869423e47575d29aceab2019f662a50c"
+          },
+          {
+            "assetHash": "a89d99bacfbd2bacd55afee5254f7c14",
+            "bytes": 36917,
+            "contentType": "text/javascript",
+            "path": "/assets/tooltip-o4natsv01lpq8oq2-BXAIiJ4Y.js",
+            "sha256": "67a9b3a04dab0f8f407689a5a9c9d8369d8dcf3fddcc512853632a51d68b3e31"
+          },
+          {
+            "assetHash": "d38a1c7cebe2e73f554278b0ae36a686",
+            "bytes": 126137,
+            "contentType": "text/javascript",
+            "path": "/assets/tr-C3SvNhO5.js",
+            "sha256": "9f94a886ad9359968387eaeb35fc7d91ada85905b5f091ab51ddff264fcc237f"
+          },
+          {
+            "assetHash": "4f86bdc5176081622cc9a785581b51bd",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/tr-CgrBmoo-.js",
+            "sha256": "b8364840d04ca2133975106652611c658f262a29ff37c19617465192ecde2b6c"
+          },
+          {
+            "assetHash": "3f11f1152926af7e4c1bfe8bc1ee643f",
+            "bytes": 175587,
+            "contentType": "text/javascript",
+            "path": "/assets/tsx-pem8x69upvmibv6l-udAQXfEw.js",
+            "sha256": "12445a7060554d1223665234920d17abaa90d6e1345ac318409d16c7eca6981e"
+          },
+          {
+            "assetHash": "9b9256899ab0c364455e5ae0704e641a",
+            "bytes": 181138,
+            "contentType": "text/javascript",
+            "path": "/assets/typescript-pe5y016phdwbgws7-bsJCZSQ-.js",
+            "sha256": "75dbddd9f3ca83e92efc34d68ae2f927261c6a4477f7ebcc4d84e6c5425ec983"
+          },
+          {
+            "assetHash": "b11f6043f626b33e4e6d5441e80480e3",
+            "bytes": 194168,
+            "contentType": "text/javascript",
+            "path": "/assets/uk-6TCxkuuG.js",
+            "sha256": "edda3318f34adefa8492fffd1b14d737e2db3d6cea4a5fa10fb34d044587a5c0"
+          },
+          {
+            "assetHash": "285ee3fea6716f76fbced14eee1aa439",
+            "bytes": 1553,
+            "contentType": "text/javascript",
+            "path": "/assets/uk-RHbEgcUV.js",
+            "sha256": "af270268c16f37f6ffa050dcab859bc946b5e8511c646452b2cccdb05deefaf5"
+          },
+          {
+            "assetHash": "5366761ab59fd2645441b75afaeac4e2",
+            "bytes": 1810,
+            "contentType": "text/javascript",
+            "path": "/assets/useAnalyticsCard-ZctErZYl.js",
+            "sha256": "1943ee45be95b2b9b9f7e161fb5ff9ac38ad7122f7ba1bcbbabb4295da740591"
+          },
+          {
+            "assetHash": "f6027483c019fda25d9062886fe17f34",
+            "bytes": 1358,
+            "contentType": "text/javascript",
+            "path": "/assets/useAriaLabelledBy-ox8dum59shmgypq1-CPHKR85v.js",
+            "sha256": "27a8020ee3d2300193d39a9ad985d80f35c241b4cd024eaed9ac8494e482dd8d"
+          },
+          {
+            "assetHash": "ecf3240f89c7df8231d78a531eaa620e",
+            "bytes": 62495,
+            "contentType": "text/javascript",
+            "path": "/assets/useBaseUiId-lg1teubxudgin0w2-DE9psqVX.js",
+            "sha256": "3ea2dd3215084e0df3a96f8b16da5c4d040cb3dd71aab2567f31e6ad23ee149c"
+          },
+          {
+            "assetHash": "0ecdd8413a786bba37d815912d75e050",
+            "bytes": 7133,
+            "contentType": "text/javascript",
+            "path": "/assets/useButton-h7s9aq1sfw6arawe-CDg3MSn7.js",
+            "sha256": "3ccb1b89492da845b55cd4daf5fa65d31a60e802e9bacef1298a78386110f96c"
+          },
+          {
+            "assetHash": "ee84252c8e9af57f4d6bc38ca0a9d7b9",
+            "bytes": 69721,
+            "contentType": "text/javascript",
+            "path": "/assets/useChartPalette-mC9J5doA.js",
+            "sha256": "c5aa5f17a52b011aeda6c6afa61af70e9e4bf3f3c736b0d29e3e4159d52c64aa"
+          },
+          {
+            "assetHash": "99d9b08e9a964a80d1675d88e08462c7",
+            "bytes": 15070,
+            "contentType": "text/javascript",
+            "path": "/assets/useClick-l0p0endoxrlp0ae5-dDySeZPV.js",
+            "sha256": "b16becad633bd461483f53f0fb37ace2750d2f218d30ad6c3bef7c599dd6d384"
+          },
+          {
+            "assetHash": "023e7c2932b8f6224d35849257c32d46",
+            "bytes": 11430,
+            "contentType": "text/javascript",
+            "path": "/assets/useCollectionView-DIAOpQTO.js",
+            "sha256": "c1a3ad1650709876ef8562f4e985f781fb5cc0ca5c5c629d8f74ea4ab72a8c31"
+          },
+          {
+            "assetHash": "e705135ad53f0d59a40d7ca9879a338c",
+            "bytes": 732,
+            "contentType": "text/javascript",
+            "path": "/assets/useCompositeItem-p7090ga00dpqcg5j-DtFwg5rU.js",
+            "sha256": "3559a5a50f44d40631fa55804995cf9dbb4d013f1f4042400a1f14140f00eb66"
+          },
+          {
+            "assetHash": "82c86b07b839dc165742b73ba5e89eae",
+            "bytes": 306,
+            "contentType": "text/javascript",
+            "path": "/assets/useDebounced-BCEnaK6H.js",
+            "sha256": "a4d11abb1360fbabe38d8aaddf815e79487722a3061ecc170fd0d3cf1a3cab73"
+          },
+          {
+            "assetHash": "af1b72431a087dd85ca08a55c17537d4",
+            "bytes": 6398,
+            "contentType": "text/javascript",
+            "path": "/assets/useListNavigation-3nyjs362qcau34in-BDg8RPai.js",
+            "sha256": "e118662ced9fb71d2562606afca44b0bf296014aee7a77d8d6353bd110140f42"
+          },
+          {
+            "assetHash": "8e85ea1c3af7443145de4f7b8ba0860e",
+            "bytes": 2246,
+            "contentType": "text/javascript",
+            "path": "/assets/useLiveSite-CBUdATzu.js",
+            "sha256": "2f4eebba84dc59c45476b4015d46cb0f01992ec93856c6c7fb797d5fe7b8d59d"
+          },
+          {
+            "assetHash": "4cc50f32dde8dc044ca4bc737bfd74f0",
+            "bytes": 1821,
+            "contentType": "text/javascript",
+            "path": "/assets/useMe-BOoLavQL.js",
+            "sha256": "8c5cc0971ab8e5cc16cb8752ab9d57f8c93f34b786c1714cdf0837825438c281"
+          },
+          {
+            "assetHash": "cb7ad356717bb08b0d841283b43a6bc4",
+            "bytes": 554,
+            "contentType": "text/javascript",
+            "path": "/assets/usePaging-B-9GGI_m.js",
+            "sha256": "7a07a0a0257e8e9052622eb7676aca040d1e386a0218bb7990035e55773cec47"
+          },
+          {
+            "assetHash": "7485909d56b9d9c348234492d3681287",
+            "bytes": 907,
+            "contentType": "text/javascript",
+            "path": "/assets/usePendingApprovals-BSExTPEB.js",
+            "sha256": "c41fe80f6df6ac41c78133d2325403c6d836f3dbf80f46e5a01abe3e3e6722af"
+          },
+          {
+            "assetHash": "cd2f2c5b58579e51b88e0f6eac34adba",
+            "bytes": 190,
+            "contentType": "text/javascript",
+            "path": "/assets/useRender-onwjigxa00uch2ip-BMxnvLE_.js",
+            "sha256": "3da4aa47b3215219ad0e082083419de0ee98175f89c4d45584ea448e98771e2d"
+          },
+          {
+            "assetHash": "460776e57d167a04b2ae2305d695b024",
+            "bytes": 4894,
+            "contentType": "text/javascript",
+            "path": "/assets/useRenderDialogRoot-lddunc01twftravv-YHj8p0Db.js",
+            "sha256": "5784343b08933b337041896538e651fed98f01f0043834fa343b0a352954f117"
+          },
+          {
+            "assetHash": "d6ad7c5c3adc98e04015cfea08a80f49",
+            "bytes": 308,
+            "contentType": "text/javascript",
+            "path": "/assets/useSettled-BYoV3Xxs.js",
+            "sha256": "1cfd9f76f9ada955cff8e872beeed706cae1c51c99d2eb2292cd90162ce472ab"
+          },
+          {
+            "assetHash": "d3941c4a4381132bbc97dcb2db6ed9a3",
+            "bytes": 9628,
+            "contentType": "text/javascript",
+            "path": "/assets/useSwipeDismiss-m1s13x9t9ey9ax3p-D7CLht-d.js",
+            "sha256": "4b70a5e3970d3030ca6f538007f9968a74431d6aa97d7662e337a80903202592"
+          },
+          {
+            "assetHash": "624274ce09596c72ff688cee9c22bbd7",
+            "bytes": 863,
+            "contentType": "text/javascript",
+            "path": "/assets/useTriggerFocusGuards-lz7wt484xltizro4-5lIVnRXF.js",
+            "sha256": "5d922cce05cd82fb61e436af444d4f6e4798237aa7740df70deb64e4628a1ea7"
+          },
+          {
+            "assetHash": "a91623eea654825ee026873da0985602",
+            "bytes": 221,
+            "contentType": "text/javascript",
+            "path": "/assets/util-B4GkKYcU.js",
+            "sha256": "5699a49fb7bc31f5400055b2847aee8e84a29c2c9f2cd6d95dd13258c6af1b1f"
+          },
+          {
+            "assetHash": "435ba9fab6dd6afdeaa5b9c4a12c0948",
+            "bytes": 29681,
+            "contentType": "text/javascript",
+            "path": "/assets/vendor-styling-da1xibnxfkjmtglr-f72JA6U4.js",
+            "sha256": "29e5dc00471813c26ad896a5689098f671a100ccfa35795d5baaa4d9ae5a0426"
+          },
+          {
+            "assetHash": "b224f619a70b49bfaf66edf3a4cfce1a",
+            "bytes": 12791,
+            "contentType": "text/javascript",
+            "path": "/assets/vesper-g839e590k3dxnwh3-DdrHHSXu.js",
+            "sha256": "37f0c747a28683e99f8dff233317af54cdad32e0a2d33c0a21f78fedff97d072"
+          },
+          {
+            "assetHash": "2147bf8232b0b6220cb54a4270339e64",
+            "bytes": 1536,
+            "contentType": "text/javascript",
+            "path": "/assets/w3c-keyname-BOAvb0qz.js",
+            "sha256": "d1276e3afadaac8486e5f2810f26705535a59c02b5fdc6c6640862587fe5a00e"
+          },
+          {
+            "assetHash": "6bda714ea70a9e6cc2108caa8c1a1e09",
+            "bytes": 622384,
+            "contentType": "text/javascript",
+            "path": "/assets/wasm-cpasy0o9lsjrm39j-BnjxR4X6.js",
+            "sha256": "ee8898ffda1ff5d0c94206d09d49a41ce041208c1c4c9ae115835ab2f81a6559"
+          },
+          {
+            "assetHash": "c11a35d5a43d32c6c1c49d2bded87889",
+            "bytes": 369,
+            "contentType": "text/javascript",
+            "path": "/assets/workflow-BEVJ1EYC.js",
+            "sha256": "5a915ced693baac8b66434b7bf39d148a7e03f6fe4fc01bb338f72e67f0d598f"
+          },
+          {
+            "assetHash": "b7533be89da544a299ecb9f436a6a9c5",
+            "bytes": 10558,
+            "contentType": "text/javascript",
+            "path": "/assets/yaml-kzk5ddyekbyha04v-rwi0_p6S.js",
+            "sha256": "b4174ba016fb6235d95343fe3162b1c759b58ef54570631a5aee6c3ca73a174e"
+          },
+          {
+            "assetHash": "b2ed77ddbeb0c1c9c35e8cef9db01cf5",
+            "bytes": 1738,
+            "contentType": "text/javascript",
+            "path": "/assets/zh-Hans-D8-V9DSC.js",
+            "sha256": "fc705491371406f619261c8002f16452d39565851bc5ebed260b48ed3d79c3fe"
+          },
+          {
+            "assetHash": "72280161ecb34e45dc63a26b81714acb",
+            "bytes": 113515,
+            "contentType": "text/javascript",
+            "path": "/assets/zh-Hans-DR1iugTe.js",
+            "sha256": "1d820e39d8647fc495872e9e23bf841937b0561e2a7957ef86c3d13b2f6b505c"
+          },
+          {
+            "assetHash": "af4346ace98bf138a2a9437e43a59094",
+            "bytes": 4348,
+            "contentType": "image/x-icon",
+            "path": "/favicon.ico",
+            "sha256": "0c2d4eebd8a64e22f699ffbb0ed45a26bb06b8d6279067a16caf1fddb8a0c60f"
+          },
+          {
+            "assetHash": "28d64f886a8ddebd71b921406c5e4c87",
+            "bytes": 371,
+            "contentType": "image/svg+xml",
+            "path": "/favicon.svg",
+            "sha256": "09d7ba76cb7a88a3de89f46e4c806224f1cee862fde87db6930b355f73a0a5f4"
+          },
+          {
+            "assetHash": "0d642d95ab9f54e27d094124f23a25e7",
+            "bytes": 8666,
+            "contentType": "text/html",
+            "path": "/index.html",
+            "sha256": "0cbdd88a05e2a0ce45af42c6c8e257891caef61eab2d60ab283c231fed75b60e"
+          }
+        ],
+        "headers": "# Workers static assets (https://developers.cloudflare.com/workers/static-assets/headers/).\n# Vite names build files by content hash: cache them for good. index.html keeps the default\n# (revalidated), so a deploy is picked up on the next load.\n/assets/*\n  Cache-Control: public, max-age=31536000, immutable\n\n# Studio is the deployment's most privileged origin: never framed, no plugins, no  swaps,\n# no MIME sniffing. The admin Worker sends the same on the shell it serves for client routes.\n/*\n  Content-Security-Policy: frame-ancestors 'none'; object-src 'none'; base-uri 'none'\n  X-Frame-Options: DENY\n  X-Content-Type-Options: nosniff\n  Referrer-Policy: strict-origin-when-cross-origin\n  Strict-Transport-Security: max-age=31536000\n"
+      },
+      "bindingTemplate": "admin",
+      "mainModule": "worker.js",
+      "modules": [
+        {
+          "bytes": 10094524,
+          "path": "worker.js",
+          "sha256": "e137fec76a3905211b7241e4203bc3367f38ae281fd475877958f534eb89b9cb",
+          "type": "esm"
+        }
+      ]
+    },
+    "site": {
+      "assets": {
+        "bytes": 868586,
+        "config": {
+          "not_found_handling": "none"
+        },
+        "count": 51,
+        "files": [
+          {
+            "assetHash": "35922b3ca4e706efcccd0921377152c9",
+            "bytes": 147701,
+            "contentType": "text/css",
+            "path": "/_astro/_..JrUhA3sr.css",
+            "sha256": "77cbbc2d80f7a7bb4f9772502e65c4c9373ac255eaad609ecd1ab6948d3b21de"
+          },
+          {
+            "assetHash": "1c6b627a0496c784f983ac10f64e2872",
+            "bytes": 3966,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/ambient.1.js",
+            "sha256": "710768e8ec6661e1e66daffedd2b50f69cf72b1f41b38681ba27435c2b3cc9ac"
+          },
+          {
+            "assetHash": "415c8af39443a142e8b09acdeeab1c5b",
+            "bytes": 5024,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/c.93f188f9.js",
+            "sha256": "93f188f9208c9a466d93c6e0c22de7c7f10357a18eb8fd974677db1aa6557d4c"
+          },
+          {
+            "assetHash": "66f75c9e7524fa667a0bca7a344e18c3",
+            "bytes": 3921,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/carousel.1.js",
+            "sha256": "3d22a6c5655f2ee4b567a7ef987e9fcf42f236c71aa48c4572e856532117af0a"
+          },
+          {
+            "assetHash": "66f75c9e7524fa667a0bca7a344e18c3",
+            "bytes": 3921,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/carousel.2.js",
+            "sha256": "3d22a6c5655f2ee4b567a7ef987e9fcf42f236c71aa48c4572e856532117af0a"
+          },
+          {
+            "assetHash": "66f75c9e7524fa667a0bca7a344e18c3",
+            "bytes": 3921,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/carousel.3.js",
+            "sha256": "3d22a6c5655f2ee4b567a7ef987e9fcf42f236c71aa48c4572e856532117af0a"
+          },
+          {
+            "assetHash": "f880a77d7f8269164129e9f72755bb1c",
+            "bytes": 7488,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/chart.1.js",
+            "sha256": "702ef34d2ad35b4723f7b38780047db615921a82d1aef4ea4db5f28b4dabdaa4"
+          },
+          {
+            "assetHash": "0e5a066a8c221ce6b1ab61820191e731",
+            "bytes": 3581,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/chrome.1.js",
+            "sha256": "9e579ae46547755954cfd31d0d097affb04692384a0d8f5868f96035c0be4d1b"
+          },
+          {
+            "assetHash": "0e5a066a8c221ce6b1ab61820191e731",
+            "bytes": 3581,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/chrome.2.js",
+            "sha256": "9e579ae46547755954cfd31d0d097affb04692384a0d8f5868f96035c0be4d1b"
+          },
+          {
+            "assetHash": "00d720e89f75be75ad520e0a981650cd",
+            "bytes": 1354,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/cw.febec347.js",
+            "sha256": "febec347b48f5c5ef475bb9af5c09c34133235d3e49786e4403486ce46abfd30"
+          },
+          {
+            "assetHash": "f79725ff869548545de8a478f827cb23",
+            "bytes": 844,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/embed.1.js",
+            "sha256": "4e16f1f898ef20d622100e458afae576d88a9d5b2f55fab4b4257aff92a7c878"
+          },
+          {
+            "assetHash": "e2c7fab426617fabed38a938e03bb572",
+            "bytes": 6596,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/motion.1.js",
+            "sha256": "c387768ba4452b3cc41242ef47163e591f580e198d536483296c2fa972212ed7"
+          },
+          {
+            "assetHash": "e2c7fab426617fabed38a938e03bb572",
+            "bytes": 6596,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/motion.2.js",
+            "sha256": "c387768ba4452b3cc41242ef47163e591f580e198d536483296c2fa972212ed7"
+          },
+          {
+            "assetHash": "e2c7fab426617fabed38a938e03bb572",
+            "bytes": 6596,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/motion.3.js",
+            "sha256": "c387768ba4452b3cc41242ef47163e591f580e198d536483296c2fa972212ed7"
+          },
+          {
+            "assetHash": "e2c7fab426617fabed38a938e03bb572",
+            "bytes": 6596,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/motion.4.js",
+            "sha256": "c387768ba4452b3cc41242ef47163e591f580e198d536483296c2fa972212ed7"
+          },
+          {
+            "assetHash": "d03ceacbeb8461b9be8ed5346747e828",
+            "bytes": 3776,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/overlay.1.js",
+            "sha256": "b93e67159b4cbf7f4c2e6ec7d8af30a938d704a4eae748469ce84bc60bcf2681"
+          },
+          {
+            "assetHash": "a7dac89a5233128027ce8e273e261219",
+            "bytes": 1418,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/p.b638080c.js",
+            "sha256": "b638080c3480f7ae1ad1aaa1feed2e75e82cca36769e8373eef2e0eef49c0028"
+          },
+          {
+            "assetHash": "c784ee61d1d03f4fb91ec5992148f675",
+            "bytes": 389,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/prefs.1.js",
+            "sha256": "b33e24cf5e81c73e123102769ff4eef9eeecf0a9fc89d668d5992da351ef4468"
+          },
+          {
+            "assetHash": "7f157ed0167664239a66b2948649db49",
+            "bytes": 7167,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/search.1.js",
+            "sha256": "4629ab435f22ff3bd8eb48da1df99d1d165492ebb72480fec7db4eee7b7d8b4a"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.1.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.10.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.11.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.2.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.3.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.4.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.5.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.6.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.7.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.8.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "4dc09b767047a89c755dc11737fe4a84",
+            "bytes": 11325,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/surface.9.js",
+            "sha256": "c304c0f46e4e3f7c4096b8f55363e89a565905bce7e2a79ce0240558529caa1f"
+          },
+          {
+            "assetHash": "a5e536bc8acb7c5c23cc519379eff11e",
+            "bytes": 9877,
+            "contentType": "text/javascript",
+            "path": "/_cloudhal/v.b3b662dd.js",
+            "sha256": "b3b662dda0fe94bc85362ece876a875c7080a4078d4ba973037d1033e8e15cd0"
+          },
+          {
+            "assetHash": "d28b23e33d7573ab3a67f5688b14be10",
+            "bytes": 459,
+            "contentType": "image/x-icon",
+            "path": "/favicon.ico",
+            "sha256": "ebb794d2e4a887509d230cdca8361aa223af4d8c6921a9b1543aeaaeafe1576f"
+          },
+          {
+            "assetHash": "d8079c18c31e4c795d5e24bcc4e2b536",
+            "bytes": 241,
+            "contentType": "image/svg+xml",
+            "path": "/favicon.svg",
+            "sha256": "ef6e19f301c16a9e2629dce350b62dae9aa69c459a68a821add14984e06ea3da"
+          },
+          {
+            "assetHash": "2dc24d483a8f4019a179140d87c09fb5",
+            "bytes": 4504,
+            "contentType": "text/plain",
+            "path": "/fonts/v1/fraunces-OFL.txt",
+            "sha256": "cd3384cafac6f2bddc3955273958a2e029f97027c8037ac539ef5744a77b579e"
+          },
+          {
+            "assetHash": "7c4a29929dd26ecfb3d6047ab5166d91",
+            "bytes": 33584,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/fraunces-latin-ext-wght-normal.woff2",
+            "sha256": "a21ecfbf41fbc393e24ef9b7e38532a27e8da5e0a074aa7d66802d1b5ccec2f0"
+          },
+          {
+            "assetHash": "8f0317b7d998f625cfb11541fe242f48",
+            "bytes": 36620,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/fraunces-latin-wght-normal.woff2",
+            "sha256": "7f9d191d999336d3b9790afa72e1358e50a13b06d4f289341e92a311967a80f9"
+          },
+          {
+            "assetHash": "0db767dabd4d845aa49f638b842910fe",
+            "bytes": 4477,
+            "contentType": "text/plain",
+            "path": "/fonts/v1/inter-OFL.txt",
+            "sha256": "3b0a5fca3d17942cde889069889dedbbbd075e9b599968c82a95f4d944e9b345"
+          },
+          {
+            "assetHash": "262dd8407326f37493424e4aa1f7cd1e",
+            "bytes": 85068,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/inter-latin-ext-wght-normal.woff2",
+            "sha256": "34b9c504cab7a73e37b746343a449132e56cf7b5481af2cb81dc74dcff25c956"
+          },
+          {
+            "assetHash": "c04d4b1f9c6d5398681a2ff1d86918f8",
+            "bytes": 48256,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/inter-latin-wght-normal.woff2",
+            "sha256": "3100e775e8616cd2611beecfa23a4263d7037586789b43f035236a2e6fbd4c62"
+          },
+          {
+            "assetHash": "934cba7872cfb7f56631a00e9525fad3",
+            "bytes": 4524,
+            "contentType": "text/plain",
+            "path": "/fonts/v1/jetbrains-mono-OFL.txt",
+            "sha256": "403581b69dac5cff4079205e01c6b467e56af449ecbd7247693ddb1baafa005b"
+          },
+          {
+            "assetHash": "4961d5f28810e5771b24ce41f6c45c2a",
+            "bytes": 15196,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/jetbrains-mono-latin-ext-wght-normal.woff2",
+            "sha256": "79bfdab9ba467e26eea4122e6f2567e188dd8a09a8c730d501fc487c4ab99c6e"
+          },
+          {
+            "assetHash": "44c171e3a36f252d46d924ab774ea53b",
+            "bytes": 40404,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/jetbrains-mono-latin-wght-normal.woff2",
+            "sha256": "18be452724bfdc236c074ca94a249a7f41a86752c7d04ab258ce9ed5651f6a7e"
+          },
+          {
+            "assetHash": "531c97e617c3ef4e8363040d54545280",
+            "bytes": 4314,
+            "contentType": "text/plain",
+            "path": "/fonts/v1/source-sans-3-OFL.txt",
+            "sha256": "18aabf190848725e2576eefb5c29ba06aac1029d02132252a7f312eac2e50cf3"
+          },
+          {
+            "assetHash": "e7397d7a897b721ded3500c33ffafe5d",
+            "bytes": 60088,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/source-sans-3-latin-ext-wght-normal.woff2",
+            "sha256": "a85a7459bdb3cdc1136751e151a506bae653fc29ada3ca86237477df6f1b59e6"
+          },
+          {
+            "assetHash": "04d15311ec81c8d4585fbf7c37c6648b",
+            "bytes": 28872,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/source-sans-3-latin-wght-normal.woff2",
+            "sha256": "19143dca075972bc84e3fd9eab7416dd40cc565dccb2435a044bdfabe060c7d5"
+          },
+          {
+            "assetHash": "531c97e617c3ef4e8363040d54545280",
+            "bytes": 4314,
+            "contentType": "text/plain",
+            "path": "/fonts/v1/source-serif-4-OFL.txt",
+            "sha256": "18aabf190848725e2576eefb5c29ba06aac1029d02132252a7f312eac2e50cf3"
+          },
+          {
+            "assetHash": "a7fe437c7ef1a0d2e865f7421371e063",
+            "bytes": 42212,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/source-serif-4-latin-ext-wght-normal.woff2",
+            "sha256": "d10ccbbd0dc4ebae2938958dc1f622a53460fe297e9c958564742dd684309c3f"
+          },
+          {
+            "assetHash": "92703c2bf87a9f5b5107545cc3fa9aa3",
+            "bytes": 50936,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/source-serif-4-latin-wght-normal.woff2",
+            "sha256": "382475f38242c52d42a3e5ff8dc69b5d73f10d0d21e4550b5fe232984688779a"
+          },
+          {
+            "assetHash": "bb969a5d9bce3aef3ce7d25c30f467f5",
+            "bytes": 4401,
+            "contentType": "text/plain",
+            "path": "/fonts/v1/space-grotesk-OFL.txt",
+            "sha256": "18a4de52385f6b988782639d5d0cc1326e5a8c2de9a7f01d7b20d9aedcc60943"
+          },
+          {
+            "assetHash": "6673f1ae72351b1ec02dfd4aa8570e5b",
+            "bytes": 18940,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/space-grotesk-latin-ext-wght-normal.woff2",
+            "sha256": "952dddb45d2f96f71cbf3b7f510b24379afc3c89ea02fcf89d377b45d62c0166"
+          },
+          {
+            "assetHash": "3f6e2f8bb5b88d9247f7fa6a25f1131a",
+            "bytes": 22288,
+            "contentType": "font/woff2",
+            "path": "/fonts/v1/space-grotesk-latin-wght-normal.woff2",
+            "sha256": "0640890476fc1198ab4de571fb658de443c4d85b66466ec09534a8737ab1ce9d"
+          }
+        ],
+        "headers": "/_astro/*\n  Cache-Control: public, max-age=31536000, immutable\n\n# Font files never change under a path: a new font set gets a new directory (/fonts/v2/…).\n# Cached for a year, the browser uses them on every reload without asking the server, so the\n# text is drawn in its font from the first paint instead of swapping in later.\n/fonts/v1/*\n  Cache-Control: public, max-age=31536000, immutable\n\n/_cloudhal/*\n  Cache-Control: public, max-age=31536000, immutable\n"
+      },
+      "bindingTemplate": "site",
+      "mainModule": "entry.mjs",
+      "modules": [
+        {
+          "bytes": 151494,
+          "path": "chunks/_.._AqZiijNM.mjs",
+          "sha256": "3e09a8342ac2fb6b89f26e6633d5b0d6bd6310ffb5723afdb875b42c464f2f45",
+          "type": "esm"
+        },
+        {
+          "bytes": 37670,
+          "path": "chunks/_astro_assets_DTtcWcsT.mjs",
+          "sha256": "f872d55a4d97acc69c74439a3e5ed9816b90d51b2188b4d566cb9cc417d4f486",
+          "type": "esm"
+        },
+        {
+          "bytes": 255,
+          "path": "chunks/_virtual_astro_server-island-manifest_CddHi3Kr.mjs",
+          "sha256": "2aa5fdf76de53cf24a41936b3b51820be1570b19f0f29e9f2369937b7620b2fd",
+          "type": "esm"
+        },
+        {
+          "bytes": 203,
+          "path": "chunks/_virtual_astro_session-driver_CPZhEqd2.mjs",
+          "sha256": "363f6e5d25f7478bbc09b8bd4b94749a9f439f948c396ee34eb93762ccbbcebe",
+          "type": "esm"
+        },
+        {
+          "bytes": 941,
+          "path": "chunks/cache-tags_zTClyPCB.mjs",
+          "sha256": "f452c8140ec5d1c4c5d7f9118914460a7c09543800fb68bde5624ba839ba9993",
+          "type": "esm"
+        },
+        {
+          "bytes": 3516702,
+          "path": "chunks/collection-feeds_CI8LUUaj.mjs",
+          "sha256": "500da5de1db80dd70b1384f3019b66926de556816e49b71b77e584cd6c312408",
+          "type": "esm"
+        },
+        {
+          "bytes": 2415,
+          "path": "chunks/compiler_C5IEbvUZ.mjs",
+          "sha256": "0f7b30f305904a3a3175356e951b5f9d9dd831761854ef9ec160d5e9ba4cda93",
+          "type": "esm"
+        },
+        {
+          "bytes": 1668,
+          "path": "chunks/csp_CUqL-I_2.mjs",
+          "sha256": "b653ab17c8dfb59f6311cc95bff73d76c349ef3861ca56cddc9fd80164e22957",
+          "type": "esm"
+        },
+        {
+          "bytes": 6928,
+          "path": "chunks/feeds_DybGbkeL.mjs",
+          "sha256": "eb1e9e8cd0ea1a8b0e6e148925278ae1f5b67b3bbc5cd90dd377176f52ba3c27",
+          "type": "esm"
+        },
+        {
+          "bytes": 5663,
+          "path": "chunks/image-passthrough-endpoint_BczhD94p.mjs",
+          "sha256": "17dcfbd28b36b254b310b550fb4247e2838eb9a1b30f34e561974434893162b9",
+          "type": "esm"
+        },
+        {
+          "bytes": 1462,
+          "path": "chunks/llms_BmxM8twD.mjs",
+          "sha256": "ac0aaaf3f5db23740cc538d2ce96a21e9a3e546f305b83dd80c3eea4b4e65992",
+          "type": "esm"
+        },
+        {
+          "bytes": 2155,
+          "path": "chunks/llms_C8jWfnhC.mjs",
+          "sha256": "36d15d74fb8163b9239d05c6ea61d31f2d181eea5b13338ec0f37ef88053f436",
+          "type": "esm"
+        },
+        {
+          "bytes": 5550,
+          "path": "chunks/locale-feeds_CBhk1gQO.mjs",
+          "sha256": "bb21ae1638f01475150914d234f93cdf576ddd26f3020722089ce6c2642bcbf5",
+          "type": "esm"
+        },
+        {
+          "bytes": 137,
+          "path": "chunks/noop-entrypoint_Cx5UkKLo.mjs",
+          "sha256": "51769c2183a5fb196e19b13759d66ff7a7b9affaa449a4ef5301b4989facaf5b",
+          "type": "esm"
+        },
+        {
+          "bytes": 1133,
+          "path": "chunks/not-found__9to7-I3.mjs",
+          "sha256": "7129946ad45fe0ac6d4aca5a4788ce13a22721887b211ce2d1864d2ee94ee664",
+          "type": "esm"
+        },
+        {
+          "bytes": 977,
+          "path": "chunks/robots_C3e6BwPo.mjs",
+          "sha256": "560f46f85b2ddd507f7384afcf396d1ee4bc329b37cd6b2b3c267db7fe8e0a34",
+          "type": "esm"
+        },
+        {
+          "bytes": 1496,
+          "path": "chunks/rolldown-runtime_Z978xZBF.mjs",
+          "sha256": "1201bca69887bea4f78f68d951b6ce2d0884961b129b2723f4050c15c43ac972",
+          "type": "esm"
+        },
+        {
+          "bytes": 7700,
+          "path": "chunks/routing_BbPAdXN5.mjs",
+          "sha256": "ab5c426396c17f8a1d80e6e92ac3ce9c09e286e8f6383487573f6651225526d2",
+          "type": "esm"
+        },
+        {
+          "bytes": 4165,
+          "path": "chunks/rss_B40riUbb.mjs",
+          "sha256": "1b839d51b1a863807f41961ddd25199a4371a4e2970c04389a5850773bf340bf",
+          "type": "esm"
+        },
+        {
+          "bytes": 1563,
+          "path": "chunks/rss_DP1Xatmy.mjs",
+          "sha256": "22886d2a1f70dc77fa4d8c82ec3386bcc60ccee0d8327265582fa58573a237c9",
+          "type": "esm"
+        },
+        {
+          "bytes": 182646,
+          "path": "chunks/runtime-workerd_39eDMSgz.mjs",
+          "sha256": "bf66ef66b62572582e462059aad4f6fa7aa6801856898cfba4efe8bc1d157c70",
+          "type": "esm"
+        },
+        {
+          "bytes": 1359,
+          "path": "chunks/search_BCiqasdX.mjs",
+          "sha256": "f68a4c8c3c0fe28613141c754b6f576d27354b6eef2f3c6efe4f42017c4bce68",
+          "type": "esm"
+        },
+        {
+          "bytes": 3922,
+          "path": "chunks/search_DTwhWZQu.mjs",
+          "sha256": "ec600de3e4ae1263a8b14a2ed9d58bdfef6405696f970cdb49dd75a5e1540635",
+          "type": "esm"
+        },
+        {
+          "bytes": 1517,
+          "path": "chunks/search_DXbPhi2E.mjs",
+          "sha256": "992b62084bffb8dcb87e6bf731fb51900dde72f855a111e80ed5920996e38c9a",
+          "type": "esm"
+        },
+        {
+          "bytes": 26192,
+          "path": "chunks/seo_w0LpE-FM.mjs",
+          "sha256": "9cf0641d10a2cc8c4b1b80530c637812654daf133aaa16d6b59b22c6dfd38168",
+          "type": "esm"
+        },
+        {
+          "bytes": 309937,
+          "path": "chunks/server_tG0HgBaw.mjs",
+          "sha256": "32e7368efecd3071f8617ab571c17ea72c8c2346835df274413a10c21ee682df",
+          "type": "esm"
+        },
+        {
+          "bytes": 1579,
+          "path": "chunks/sitemap_CAW9t612.mjs",
+          "sha256": "82c4d53064e321aceb7bf411f76e6dee520e25933a1d4beeb27190aa4a8a223e",
+          "type": "esm"
+        },
+        {
+          "bytes": 34082,
+          "path": "chunks/status-page_D9UHBrWD.mjs",
+          "sha256": "cd23e1466072a05f7c892abf97047939aa238cbed92495cdbc6a09f7c383f0cd",
+          "type": "esm"
+        },
+        {
+          "bytes": 20871,
+          "path": "chunks/theme_Bb2j_6Fp.mjs",
+          "sha256": "89282ef79eb1fac3f1eb1d9e3f0b0c8d55675ae08ba721552dfcf2c8e726c8a0",
+          "type": "esm"
+        },
+        {
+          "bytes": 299438,
+          "path": "entry.mjs",
+          "sha256": "9836a9b246c023d3fb69f6cfb59c5bcaafc98543edb8110e4625a296670646ef",
+          "type": "esm"
+        },
+        {
+          "bytes": 5036,
+          "path": "virtual_astro_middleware.mjs",
+          "sha256": "763e2c9835f048c8e0ca2fd3d74565d9b5704a8c5ba4d986441e6061d732ab32",
+          "type": "esm"
+        }
+      ]
+    }
+  }
+}
diff --git a/packages/release-verify/test/fixtures/v0.9.0/release-statement.json b/packages/release-verify/test/fixtures/v0.9.0/release-statement.json
new file mode 100644
index 00000000..5d3db81c
--- /dev/null
+++ b/packages/release-verify/test/fixtures/v0.9.0/release-statement.json
@@ -0,0 +1,24 @@
+{
+  "channel": "stable",
+  "commit": "6bfe74a17f3a941e68130bb3e05a866fd9f19df5",
+  "createdAt": "2026-11-13T16:13:20Z",
+  "format": "cloudhal.release-statement@1",
+  "manifest": {
+    "bytes": 252366,
+    "name": "manifest.json",
+    "sha256": "4e77e60d281f112cf2079b3b61d69c12f1b880b93c40b06bfc84253efb577e87"
+  },
+  "schemaVersion": 1,
+  "securityRelease": false,
+  "signing": {
+    "algorithm": "Ed25519",
+    "keyId": "cloudhal-release-2026a",
+    "signature": "release-statement.sig"
+  },
+  "tarball": {
+    "bytes": 7226429,
+    "name": "cloudhal-release-0.9.0.tar.gz",
+    "sha256": "ab17dad5cf75b83d5f1434ae1e64818d8234b4d47210e06f93efb2422c1ca59b"
+  },
+  "version": "0.9.0"
+}
diff --git a/packages/release-verify/test/fixtures/v0.9.0/release-statement.sig b/packages/release-verify/test/fixtures/v0.9.0/release-statement.sig
new file mode 100644
index 00000000..1c714280
--- /dev/null
+++ b/packages/release-verify/test/fixtures/v0.9.0/release-statement.sig
@@ -0,0 +1 @@
+NF&ܟQWF(R9-ã¡g9öƒ{óªö-LìL3›M‹`!ý¬%†®êbB°ÊVŠ’ùUIWL$7¢�×2
\ No newline at end of file
diff --git a/packages/release-verify/test/helpers.ts b/packages/release-verify/test/helpers.ts
new file mode 100644
index 00000000..1a876d30
--- /dev/null
+++ b/packages/release-verify/test/helpers.ts
@@ -0,0 +1,170 @@
+/**
+ * Test bundles: the real v0.9.0 statement, signature and manifest (test/fixtures/v0.9.0, byte
+ * exact), and small synthetic bundles shaped like it, written by the producer's own tar and gzip
+ * code and signed with a throwaway key that tests pass as `keys`.
+ */
+import { createHash, generateKeyPairSync, sign } from "node:crypto";
+import { readFileSync } from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { canonicalJson } from "../../../scripts/release-bundle/manifest.ts";
+import { gzipBytes, tarArchive } from "../../../scripts/release-bundle/tar.ts";
+import type { ReleaseKeys, VerifyReleaseInput } from "../src/index.ts";
+
+const here = path.dirname(fileURLToPath(import.meta.url));
+const FIXTURES = path.join(here, "fixtures/v0.9.0");
+export const fixture = (name: string): Uint8Array => readFileSync(path.join(FIXTURES, name));
+
+/** The real v0.9.0 assets as `verifyRelease` input (no tarball: it is not committed). */
+export function realInput(over: Partial = {}): VerifyReleaseInput {
+  return {
+    request: { version: "0.9.0" },
+    statement: fixture("release-statement.json"),
+    signature: fixture("release-statement.sig"),
+    manifest: fixture("manifest.json"),
+    tarball: () => new Uint8Array(0),
+    planVersion: 1,
+    ...over,
+  };
+}
+
+/** A copy of `bytes` with one byte flipped at `at` (default: the middle). */
+export function flip(bytes: Uint8Array, at = Math.floor(bytes.length / 2)): Uint8Array {
+  const out = new Uint8Array(bytes);
+  out[at] = (out[at] as number) ^ 0x01;
+  return out;
+}
+
+const sha = (b: Uint8Array) => createHash("sha256").update(b).digest("hex");
+const utf8 = (s: string) => new TextEncoder().encode(s);
+
+export const TEST_KEY_ID = "test-release-key";
+
+type Doc = Record & {
+  workers: Record<"site" | "admin", Record>;
+};
+
+export interface SyntheticOptions {
+  /** Changes the manifest document before it is serialised (and pinned by the statement). */
+  readonly manifest?: (m: Doc) => void;
+  /** Changes the statement document before it is serialised and signed. */
+  readonly statement?: (s: Record) => void;
+  /** Tar entries: change, drop or add before the tar is written. */
+  readonly entries?: (e: { path: string; bytes: Uint8Array }[]) => void;
+  /** The mtime written into the tar (default: createdAt). */
+  readonly mtime?: number;
+  /** Compresses the tar (default: the producer's fixed-header gzip). */
+  readonly compress?: (tar: Uint8Array) => Promise;
+  /** Raw tar bytes instead of the producer's (after `entries`). */
+  readonly tar?: (tar: Uint8Array) => Uint8Array;
+}
+
+export interface Synthetic {
+  readonly input: VerifyReleaseInput;
+  readonly tarball: Uint8Array;
+  readonly keys: ReleaseKeys;
+  readonly listed: number;
+}
+
+const PARTS = {
+  "site/modules/entry.mjs": "export default { fetch() { return new Response('site'); } };\n",
+  "site/modules/chunks/page.mjs": "export const page = 'a page';\n",
+  "site/assets/index.html": "site\n",
+  "site/assets/fonts/v1/a.woff2": "not really a font\n",
+  "admin/modules/worker.js": "export default { fetch() { return new Response('admin'); } };\n",
+  "admin/assets/app.js": "console.log('studio');\n",
+} as const;
+
+const file = (p: string, bytes: Uint8Array) => ({
+  path: p,
+  bytes: bytes.length,
+  sha256: sha(bytes),
+  assetHash: sha(bytes).slice(0, 32),
+  contentType: "application/octet-stream",
+});
+
+/** A signed synthetic bundle of six parts, `opts` applied. */
+export async function synthetic(opts: SyntheticOptions = {}): Promise {
+  const m = JSON.parse(new TextDecoder().decode(fixture("manifest.json"))) as Doc;
+  const bytesOf = (p: keyof typeof PARTS) => utf8(PARTS[p]);
+  for (const role of ["site", "admin"] as const) {
+    const modules = Object.keys(PARTS)
+      .filter((p) => p.startsWith(`${role}/modules/`))
+      .map((p) => {
+        const b = bytesOf(p as keyof typeof PARTS);
+        return {
+          path: p.slice(`${role}/modules/`.length),
+          type: "esm",
+          bytes: b.length,
+          sha256: sha(b),
+        };
+      });
+    const files = Object.keys(PARTS)
+      .filter((p) => p.startsWith(`${role}/assets/`))
+      .map((p) => file(p.slice(`${role}/assets`.length), bytesOf(p as keyof typeof PARTS)));
+    const w = m.workers[role];
+    w.mainModule = (modules.find((x) => !x.path.startsWith("chunks/")) ?? modules[0])?.path;
+    w.modules = modules;
+    const assets = w.assets as Record;
+    assets.files = files;
+    assets.count = files.length;
+    assets.bytes = files.reduce((s, f) => s + f.bytes, 0);
+  }
+  (m.signing as Record).keyId = TEST_KEY_ID;
+  const commit = m.commit;
+  opts.manifest?.(m);
+  const manifestBytes = utf8(canonicalJson(m));
+
+  const entries = Object.entries(PARTS).map(([p, text]) => ({ path: p, bytes: utf8(text) }));
+  opts.entries?.(entries);
+  entries.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
+  const createdAt = "2026-11-13T16:13:20Z";
+  let tar = tarArchive(entries, opts.mtime ?? Date.parse(createdAt) / 1000);
+  if (opts.tar) tar = opts.tar(tar);
+  const tarball = await (opts.compress ?? gzipBytes)(tar);
+
+  const s: Record = {
+    schemaVersion: 1,
+    format: "cloudhal.release-statement@1",
+    version: "0.9.0",
+    channel: "stable",
+    securityRelease: false,
+    commit,
+    createdAt,
+    signing: { keyId: TEST_KEY_ID, algorithm: "Ed25519", signature: "release-statement.sig" },
+    manifest: { name: "manifest.json", sha256: sha(manifestBytes), bytes: manifestBytes.length },
+    tarball: { name: "cloudhal-release-0.9.0.tar.gz", sha256: sha(tarball), bytes: tarball.length },
+  };
+  opts.statement?.(s);
+  const statement = utf8(canonicalJson(s));
+  const { privateKey, publicKey } = generateKeyPairSync("ed25519");
+  const der = publicKey.export({ format: "der", type: "spki" });
+  const raw = Buffer.from(der.subarray(12)).toString("base64");
+  const keys: ReleaseKeys = { [TEST_KEY_ID]: { publicKey: raw, status: "active" } };
+  return {
+    input: {
+      request: { version: "0.9.0" },
+      statement,
+      signature: new Uint8Array(sign(null, statement, privateKey)),
+      manifest: manifestBytes,
+      tarball: () => tarball,
+      planVersion: 1,
+      keys,
+    },
+    tarball,
+    keys,
+    listed: Object.keys(PARTS).length,
+  };
+}
+
+/** `bytes` as a ReadableStream of `size`-byte chunks (exercises every buffer boundary). */
+export function chunked(bytes: Uint8Array, size: number): ReadableStream {
+  let at = 0;
+  return new ReadableStream({
+    pull(c) {
+      if (at >= bytes.length) return c.close();
+      c.enqueue(bytes.slice(at, at + size));
+      at += size;
+    },
+  });
+}
diff --git a/packages/release-verify/test/package.test.ts b/packages/release-verify/test/package.test.ts
new file mode 100644
index 00000000..380b2813
--- /dev/null
+++ b/packages/release-verify/test/package.test.ts
@@ -0,0 +1,154 @@
+/**
+ * What keeps the package honest (RFC-0046 §11a): the pinned keys equal the key doc, the limits
+ * equal the producer's, the generated files are current and import only the three runtime modules,
+ * the root entry is Worker-safe, and the helpers follow §1 and §3.
+ */
+import { readdirSync, readFileSync } from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import { describe, expect, it } from "vitest";
+import { LIMITS as PRODUCER_LIMITS } from "../../../scripts/release-bundle/manifest.ts";
+import { releaseAssetNames as producerAssetNames } from "../../../scripts/release-bundle/statement.ts";
+import { generate, OUT_DIR } from "../scripts/gen-validators.ts";
+import {
+  ERROR_CODES,
+  LIMITS,
+  missingScopes,
+  PINNED_KEYS,
+  releaseAssetNames,
+  releaseAssetUrls,
+  unsafePathReason,
+} from "../src/index.ts";
+import { fixture } from "./helpers.ts";
+
+const here = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.resolve(here, "../../..");
+const SRC = path.resolve(here, "../src");
+
+describe("the package and its sources of truth", () => {
+  it("PINNED_KEYS is the key doc's table, row for row", () => {
+    const doc = readFileSync(path.join(ROOT, "docs/security/release-signing-keys.md"), "utf8");
+    const rows = doc
+      .split("\n")
+      .filter((l) => l.startsWith("| `"))
+      .map((l) => l.split("|").map((c) => c.trim()));
+    const fromDoc = Object.fromEntries(
+      rows.map((r) => [
+        (r[1] as string).slice(1, -1),
+        { publicKey: (r[3] as string).slice(1, -1), status: r[4] },
+      ]),
+    );
+    expect(fromDoc).toEqual(PINNED_KEYS);
+    expect(Object.isFrozen(PINNED_KEYS)).toBe(true);
+  });
+
+  it("LIMITS and the asset names equal the producer's", () => {
+    expect(LIMITS).toEqual(PRODUCER_LIMITS);
+    expect(releaseAssetNames("0.9.0")).toEqual(producerAssetNames("0.9.0"));
+  });
+
+  it("the committed generated files are what the generator writes now", () => {
+    const files = generate();
+    expect(readdirSync(OUT_DIR).sort()).toEqual([...files.keys()].sort());
+    for (const [name, text] of files)
+      expect(readFileSync(path.join(OUT_DIR, name), "utf8"), `src/generated/${name}`).toBe(text);
+  });
+
+  it("the validators import only ajv's runtime helpers and ajv-formats' formats", () => {
+    const js = readFileSync(path.join(OUT_DIR, "validators.js"), "utf8");
+    const imports = [...js.matchAll(/^import [^;]* from "([^"]+)";$/gm)].map((m) => m[1]);
+    expect(imports.sort()).toEqual([
+      "ajv-formats/dist/formats.js",
+      "ajv/dist/runtime/equal.js",
+      "ajv/dist/runtime/ucs2length.js",
+    ]);
+    expect(js).not.toMatch(/new Function|\beval\(/);
+  });
+
+  it("the root entry reaches no node: module (only ./attestation may)", () => {
+    const seen = new Set();
+    const visit = (file: string) => {
+      if (seen.has(file)) return;
+      seen.add(file);
+      const text = readFileSync(file, "utf8");
+      for (const m of text.matchAll(/^(?:import|export)[^;]*? from "([^"]+)";/gms)) {
+        const spec = m[1] as string;
+        expect(spec.startsWith("node:"), `${path.relative(SRC, file)} imports ${spec}`).toBe(false);
+        if (spec.startsWith(".")) visit(path.resolve(path.dirname(file), spec));
+      }
+    };
+    visit(path.join(SRC, "index.ts"));
+    expect(seen.has(path.join(SRC, "attestation.ts"))).toBe(false);
+    expect(seen.size).toBeGreaterThan(8);
+  });
+
+  it("error codes are unique", () => {
+    expect(new Set(ERROR_CODES).size).toBe(ERROR_CODES.length);
+  });
+});
+
+describe("missingScopes (§3)", () => {
+  const v090 = JSON.parse(new TextDecoder().decode(fixture("manifest.json")));
+  const next = { requiredScopes: ["account-settings.read", "workers-scripts.admin", "d1.write"] };
+
+  it("lets Workers Admin cover the Workers Editor of v0.9.0's manifest", () => {
+    const admin = [
+      "account-settings.read",
+      "workers-scripts.admin",
+      "d1.write",
+      "workers-r2.write",
+    ];
+    expect(v090.requiredScopes).toContain("workers-scripts.edit");
+    expect(missingScopes(v090, admin)).toEqual([]);
+    expect(missingScopes(next, admin)).toEqual([]);
+  });
+
+  it("compares every other id exactly: Editor never covers Admin", () => {
+    const editor = ["account-settings.read", "workers-scripts.edit", "d1.write"];
+    expect(missingScopes(next, editor)).toEqual(["workers-scripts.admin"]);
+    expect(missingScopes(v090, ["workers-scripts.admin"])).toEqual([
+      "account-settings.read",
+      "d1.write",
+      "workers-r2.write",
+    ]);
+    expect(missingScopes(next, ["d1.read", "account-settings.write"])).toEqual(next.requiredScopes);
+  });
+});
+
+describe("releaseAssetUrls and paths", () => {
+  it("names the five assets of §1 on the release's tag", () => {
+    expect(releaseAssetUrls("0.10.0-next.1").map((a) => a.url)).toEqual([
+      "https://github.com/cloudhal-dev/cloudhal/releases/download/v0.10.0-next.1/cloudhal-release-0.10.0-next.1.tar.gz",
+      "https://github.com/cloudhal-dev/cloudhal/releases/download/v0.10.0-next.1/manifest.json",
+      "https://github.com/cloudhal-dev/cloudhal/releases/download/v0.10.0-next.1/release-statement.json",
+      "https://github.com/cloudhal-dev/cloudhal/releases/download/v0.10.0-next.1/release-statement.sig",
+      "https://github.com/cloudhal-dev/cloudhal/releases/download/v0.10.0-next.1/cloudhal-release-0.10.0-next.1.sigstore.json",
+    ]);
+  });
+
+  it("refuses a version that could leave the release's path", () => {
+    for (const v of ["../0.9.0", "0.9.0/../x", "0.9", "v0.9.0", "0.9.0-", "01.2.3", ""])
+      expect(() => releaseAssetUrls(v), v).toThrow(TypeError);
+  });
+
+  it("unsafePathReason: each rule, and linear on a 100k-character path", () => {
+    expect(unsafePathReason("chunks/a.mjs", "module")).toBeUndefined();
+    expect(unsafePathReason("chunks/_.._x.mjs", "module")).toBeUndefined();
+    expect(unsafePathReason("/fonts/v1/a.woff2", "asset")).toBeUndefined();
+    expect(unsafePathReason("a/../b", "module")).toBe("a .. segment");
+    expect(unsafePathReason("a/./b", "module")).toBe("a . segment");
+    expect(unsafePathReason("a//b", "module")).toBe("an empty segment");
+    expect(unsafePathReason("a/", "module")).toBe("an empty segment");
+    expect(unsafePathReason("/a", "module")).toBe("a leading /");
+    expect(unsafePathReason("a", "asset")).toBe("an asset path that does not start with /");
+    expect(unsafePathReason("a\\b", "entry")).toBe("a backslash");
+    expect(unsafePathReason("a\u007fb", "entry")).toBe("a control character");
+    expect(unsafePathReason("a\nb", "entry")).toBe("a control character");
+    expect(unsafePathReason(`${"é".repeat(257)}`, "entry")).toBe("longer than 512 bytes");
+    const long = `${"a/".repeat(50_000)}..`;
+    const t = performance.now();
+    expect(unsafePathReason(long, "entry")).toBe("longer than 512 bytes");
+    expect(unsafePathReason(long.slice(-400), "entry")).toBe("a .. segment");
+    expect(performance.now() - t).toBeLessThan(200);
+  });
+});
diff --git a/packages/release-verify/test/real-tarball.test.ts b/packages/release-verify/test/real-tarball.test.ts
new file mode 100644
index 00000000..2b89bf6c
--- /dev/null
+++ b/packages/release-verify/test/real-tarball.test.ts
@@ -0,0 +1,44 @@
+/**
+ * Opt-in: the real v0.9.0 tarball, end to end. It runs only when CLOUDHAL_RELEASE_ASSETS names a
+ * directory holding the downloaded assets (`gh release download v0.9.0 --repo
+ * cloudhal-dev/cloudhal --dir `); the `release-verify-v0-9-0` CI job does that. Ordinary
+ * runs skip it and never touch the network.
+ */
+import { createReadStream, readFileSync } from "node:fs";
+import path from "node:path";
+import { Readable } from "node:stream";
+import { describe, expect, it } from "vitest";
+import { verifyRelease } from "../src/index.ts";
+
+const DIR = process.env.CLOUDHAL_RELEASE_ASSETS;
+
+describe.skipIf(!DIR)("the real v0.9.0 tarball", () => {
+  const file = (name: string) => path.join(DIR as string, name);
+
+  it("verifies with the pinned key and yields every listed part", async () => {
+    const release = await verifyRelease({
+      request: { version: "0.9.0" },
+      statement: readFileSync(file("release-statement.json")),
+      signature: readFileSync(file("release-statement.sig")),
+      manifest: readFileSync(file("manifest.json")),
+      tarball: () =>
+        Readable.toWeb(
+          createReadStream(file("cloudhal-release-0.9.0.tar.gz")),
+        ) as unknown as ReadableStream,
+      planVersion: 1,
+    });
+    const m = release.manifest;
+    const listed =
+      m.workers.site.modules.length +
+      m.workers.site.assets.files.length +
+      m.workers.admin.modules.length +
+      m.workers.admin.assets.files.length +
+      (m.starter.kind === "bundle" ? 1 : 0);
+    let n = 0;
+    for await (const e of release.entries()) {
+      n++;
+      if (e.kind === "module") expect(e.type).toBeDefined();
+    }
+    expect(n).toBe(listed);
+  }, 120_000);
+});
diff --git a/packages/release-verify/test/tar.test.ts b/packages/release-verify/test/tar.test.ts
new file mode 100644
index 00000000..618d6e7e
--- /dev/null
+++ b/packages/release-verify/test/tar.test.ts
@@ -0,0 +1,90 @@
+/**
+ * The streaming tar and gzip readers (RFC-0046 §7) below `verifyRelease`: limits a signed bundle
+ * cannot reach through the manifest (its size limit comes first), byte sources of every form, and
+ * a linear run on an adversarial header.
+ */
+import { describe, expect, it } from "vitest";
+import { gzipBytes, tarArchive } from "../../../scripts/release-bundle/tar.ts";
+import { chunksOf, gunzip, readTar } from "../src/tar.ts";
+import { chunked } from "./helpers.ts";
+
+const MTIME = 1_700_000_000;
+const drain = async (it: AsyncIterable) => {
+  let n = 0;
+  for await (const _ of it) n++;
+  return n;
+};
+const codeOf = (p: Promise) =>
+  p.then(
+    () => "ok",
+    (e: { code?: string }) => e.code,
+  );
+
+describe("readTar and gunzip", () => {
+  it("refuses more than 30,000 entries", async () => {
+    const entries = Array.from({ length: 30_001 }, (_, i) => ({
+      path: `p/${String(i).padStart(6, "0")}`,
+      bytes: new Uint8Array(0),
+    }));
+    const tar = tarArchive(entries, MTIME);
+    expect(await codeOf(drain(readTar(chunksOf(tar), MTIME, () => {})))).toBe("limit-exceeded");
+  });
+
+  it("refuses a tarball that decompresses beyond the cap", async () => {
+    const tar = tarArchive([{ path: "a", bytes: new Uint8Array(200_000) }], MTIME);
+    const gz = await gzipBytes(tar);
+    expect(await codeOf(drain(gunzip(chunksOf(gz), 100_000)))).toBe("limit-exceeded");
+    expect(await drain(gunzip(chunksOf(gz)))).toBeGreaterThan(0);
+  });
+
+  it("refuses gzip that is corrupt after a valid header, and a truncated one", async () => {
+    const gz = await gzipBytes(tarArchive([{ path: "a", bytes: new Uint8Array(5000) }], MTIME));
+    const corrupt = new Uint8Array(gz);
+    for (let i = 12; i < corrupt.length - 8; i++) corrupt[i] = 0xff;
+    expect(await codeOf(drain(gunzip(chunksOf(corrupt))))).toBe("tar-format");
+    expect(await codeOf(drain(gunzip(chunksOf(gz.subarray(0, 5)))))).toBe("tar-format");
+  });
+
+  it("reads Uint8Array, ReadableStream and async iterable sources alike", async () => {
+    const tar = tarArchive(
+      [
+        { path: "a", bytes: new Uint8Array(700).fill(1) },
+        { path: `${"d/".repeat(60)}long-name-needs-pax`, bytes: new Uint8Array(3) },
+      ],
+      MTIME,
+    );
+    async function* iter() {
+      for (let i = 0; i < tar.length; i += 333) yield tar.subarray(i, i + 333);
+    }
+    for (const source of [tar, chunked(tar, 1), chunked(tar, 513), iter()]) {
+      const got: string[] = [];
+      for await (const e of readTar(chunksOf(source), MTIME, () => {}))
+        got.push(`${e.path}:${e.bytes.length}`);
+      expect(got).toEqual(["a:700", `${"d/".repeat(60)}long-name-needs-pax:3`]);
+    }
+  });
+
+  it("calls admit before buffering an entry's bytes", async () => {
+    const tar = tarArchive([{ path: "big", bytes: new Uint8Array(1_000_000) }], MTIME);
+    let pulled = 0;
+    const source = new ReadableStream({
+      pull(c) {
+        if (pulled >= tar.length) return c.close();
+        c.enqueue(tar.subarray(pulled, pulled + 4096));
+        pulled += 4096;
+      },
+    });
+    const admit = () => {
+      throw Object.assign(new Error("no"), { code: "entry-unlisted" });
+    };
+    expect(await codeOf(drain(readTar(chunksOf(source), MTIME, admit)))).toBe("entry-unlisted");
+    expect(pulled).toBeLessThan(100_000);
+  });
+
+  it("stays linear on a 100k-byte header-like run of digits", async () => {
+    const junk = new Uint8Array(100_000).fill(0x37); // "7" everywhere: no header parses
+    const t = performance.now();
+    expect(await codeOf(drain(readTar(chunksOf(junk), MTIME, () => {})))).toBe("tar-format");
+    expect(performance.now() - t).toBeLessThan(500);
+  });
+});
diff --git a/packages/release-verify/test/verify-real.test.ts b/packages/release-verify/test/verify-real.test.ts
new file mode 100644
index 00000000..78407274
--- /dev/null
+++ b/packages/release-verify/test/verify-real.test.ts
@@ -0,0 +1,94 @@
+/**
+ * RFC-0046 §11a on the real v0.9.0 statement, signature and manifest (committed byte for byte).
+ * The 7.2 MB tarball is not committed, so a passing run of steps 1 to 5 shows as the step-6
+ * refusal `tarball-mismatch`; test/real-tarball.test.ts checks the real tarball where CI has it.
+ */
+import { describe, expect, it } from "vitest";
+import {
+  type ReleaseKeys,
+  type ReleaseVerifyErrorCode,
+  type VerifyReleaseInput,
+  verifyRelease,
+} from "../src/index.ts";
+import { fixture, flip, realInput } from "./helpers.ts";
+
+const KEY_ID = "cloudhal-release-2026a";
+
+async function codeOf(input: VerifyReleaseInput): Promise {
+  try {
+    await verifyRelease(input);
+    return "ok";
+  } catch (e) {
+    return (e as { code: ReleaseVerifyErrorCode }).code;
+  }
+}
+
+describe("the real v0.9.0 release", () => {
+  it("passes steps 1 to 5 with the pinned key: only the missing tarball is refused", async () => {
+    await expect(verifyRelease(realInput())).rejects.toMatchObject({
+      code: "tarball-mismatch",
+      name: "ReleaseVerifyError",
+    });
+  });
+
+  it("refuses a flipped byte in each file", async () => {
+    const statement = fixture("release-statement.json");
+    // Inside the commit hash: still valid JSON, so the signature is what fails.
+    const at = new TextDecoder().decode(statement).indexOf("6bfe74a1");
+    expect(await codeOf(realInput({ statement: flip(statement, at + 2) }))).toBe(
+      "signature-invalid",
+    );
+    expect(await codeOf(realInput({ signature: flip(fixture("release-statement.sig")) }))).toBe(
+      "signature-invalid",
+    );
+    expect(await codeOf(realInput({ manifest: flip(fixture("manifest.json")) }))).toBe(
+      "manifest-mismatch",
+    );
+  });
+
+  it("refuses a wrong key, an unknown key id and a revoked key", async () => {
+    const other: ReleaseKeys = {
+      [KEY_ID]: { publicKey: "7az3pPc4oT0AOfRcdfOZZQkM6XAGHo2jXEdDZ1qEVR0=", status: "active" },
+    };
+    expect(await codeOf(realInput({ keys: other }))).toBe("signature-invalid");
+    expect(await codeOf(realInput({ keys: {} }))).toBe("key-unknown");
+    expect(
+      await codeOf(
+        realInput({
+          keys: { [KEY_ID]: { publicKey: "c2hvcnQ=", status: "active" } },
+        }),
+      ),
+    ).toBe("key-unknown");
+    const revoked: ReleaseKeys = {
+      [KEY_ID]: { publicKey: "7az3pPc4oT0AOfRcdfOZZQkM6XAGHo2jXEdDZ1qEVR8=", status: "revoked" },
+    };
+    expect(await codeOf(realInput({ keys: revoked }))).toBe("key-revoked");
+    expect(await codeOf(realInput({ revoked: [KEY_ID] }))).toBe("key-revoked");
+  });
+
+  it("refuses a request for another version or channel (invariant 7)", async () => {
+    expect(await codeOf(realInput({ request: { version: "0.9.1" } }))).toBe("wrong-version");
+    expect(await codeOf(realInput({ request: { version: "0.8.0" } }))).toBe("wrong-version");
+    expect(await codeOf(realInput({ request: { version: "0.9.0", channel: "next" } }))).toBe(
+      "wrong-channel",
+    );
+  });
+
+  it("refuses a planVersion above what the caller supports", async () => {
+    expect(await codeOf(realInput({ planVersion: 0 }))).toBe("plan-version-unsupported");
+  });
+
+  it("checks sizes first: a large statement, a short signature", async () => {
+    const big = new Uint8Array(4097).fill(0x20);
+    expect(await codeOf(realInput({ statement: big }))).toBe("statement-too-large");
+    const sig = fixture("release-statement.sig").subarray(0, 63);
+    expect(await codeOf(realInput({ signature: sig }))).toBe("signature-size");
+  });
+
+  it("names nothing but its code and message: no key material, no file contents", async () => {
+    const e = await verifyRelease(realInput({ manifest: flip(fixture("manifest.json")) })).catch(
+      (x: Error) => x,
+    );
+    expect(String((e as Error).message)).toBe("manifest.json is not the file the statement pins");
+  });
+});
diff --git a/packages/release-verify/test/verify-synthetic.test.ts b/packages/release-verify/test/verify-synthetic.test.ts
new file mode 100644
index 00000000..04749225
--- /dev/null
+++ b/packages/release-verify/test/verify-synthetic.test.ts
@@ -0,0 +1,240 @@
+/**
+ * RFC-0046 §11a on small synthetic bundles signed with a throwaway key: every refusal that needs a
+ * tampered but validly signed bundle (editing the real manifest only ever reaches
+ * `manifest-mismatch`), the two-pass tarball reads, and entry-path safety.
+ */
+import { gzipSync } from "node:zlib";
+import { describe, expect, it } from "vitest";
+import { tarHeader } from "../../../scripts/release-bundle/tar.ts";
+import { type ReleaseVerifyErrorCode, type VerifiedEntry, verifyRelease } from "../src/index.ts";
+import { chunked, flip, type Synthetic, type SyntheticOptions, synthetic } from "./helpers.ts";
+
+/** The refusal code of verifying and then reading every entry, or "ok". */
+async function codeOf(b: Synthetic | Promise): Promise {
+  try {
+    const r = await verifyRelease((await b).input);
+    for await (const _ of r.entries()) {
+      // drain
+    }
+    return "ok";
+  } catch (e) {
+    const code = (e as { code?: ReleaseVerifyErrorCode }).code;
+    if (!code) throw e;
+    return code;
+  }
+}
+const make = (o: SyntheticOptions) => synthetic(o);
+
+describe("a valid synthetic bundle", () => {
+  it("verifies and yields every listed part once, with its manifest facts", async () => {
+    const b = await synthetic();
+    const r = await verifyRelease(b.input);
+    const got: VerifiedEntry[] = [];
+    for await (const e of r.entries()) got.push(e);
+    expect(got).toHaveLength(b.listed);
+    expect(got.map((e) => e.tarPath)).toEqual([
+      "admin/assets/app.js",
+      "admin/modules/worker.js",
+      "site/assets/fonts/v1/a.woff2",
+      "site/assets/index.html",
+      "site/modules/chunks/page.mjs",
+      "site/modules/entry.mjs",
+    ]);
+    const entry = got.find((e) => e.tarPath === "site/modules/entry.mjs");
+    expect(entry).toMatchObject({ worker: "site", kind: "module", path: "entry.mjs", type: "esm" });
+    expect(new TextDecoder().decode(entry?.bytes)).toContain("site");
+    expect(got.find((e) => e.kind === "asset" && e.worker === "site")?.path).toBe(
+      "/fonts/v1/a.woff2",
+    );
+  });
+
+  it("reads the tarball from a stream in odd-sized chunks, twice", async () => {
+    const b = await synthetic();
+    let opened = 0;
+    const r = await verifyRelease({
+      ...b.input,
+      tarball: () => {
+        opened++;
+        return chunked(b.tarball, 7);
+      },
+    });
+    let n = 0;
+    for await (const _ of r.entries()) n++;
+    expect([opened, n]).toEqual([2, b.listed]);
+  });
+
+  it("lets a consumer stop early without hanging", async () => {
+    const b = await synthetic();
+    const r = await verifyRelease({ ...b.input, tarball: () => chunked(b.tarball, 64) });
+    for await (const _ of r.entries()) break;
+  });
+});
+
+describe("refusals that need a validly signed bundle", () => {
+  it("a manifest whose version is not the statement's (invariant 5)", async () => {
+    expect(await codeOf(make({ manifest: (m) => (m.version = "0.9.1") }))).toBe(
+      "statement-manifest-mismatch",
+    );
+    expect(await codeOf(make({ manifest: (m) => (m.commit = "f".repeat(40)) }))).toBe(
+      "statement-manifest-mismatch",
+    );
+  });
+
+  it("a manifest that names the legacy manifest.sig, or is not valid", async () => {
+    const legacy = (m: Record) => {
+      (m.signing as Record).signature = "manifest.sig";
+    };
+    expect(await codeOf(make({ manifest: legacy }))).toBe("manifest-invalid");
+    expect(await codeOf(make({ manifest: (m) => (m.extra = true) }))).toBe("manifest-invalid");
+  });
+
+  it("statement invariants the schema cannot express (1 and 3)", async () => {
+    expect(
+      await codeOf(
+        make({ statement: (s) => ((s.manifest as Record).name = "other.json") }),
+      ),
+    ).toBe("statement-invalid");
+    const b = await make({ statement: (s) => (s.channel = "next") });
+    const r = await verifyRelease({
+      ...b.input,
+      request: { version: "0.9.0", channel: "next" },
+    }).catch((e: { code: string }) => e.code);
+    expect(r).toBe("statement-invalid");
+  });
+
+  it("a manifest above 1,900,000 bytes (invariant 2)", async () => {
+    const s = (st: Record) =>
+      ((st.manifest as Record).bytes = 2_000_000);
+    expect(await codeOf(make({ statement: s }))).toBe("statement-invalid");
+  });
+
+  it("a tarball that is not the one the statement pins", async () => {
+    const b = await synthetic();
+    const input = { ...b.input, tarball: () => flip(b.tarball) };
+    expect(await codeOf({ ...b, input })).toBe("tarball-mismatch");
+    const longer = new Uint8Array(b.tarball.length + 1);
+    longer.set(b.tarball);
+    expect(await codeOf({ ...b, input: { ...b.input, tarball: () => longer } })).toBe(
+      "tarball-mismatch",
+    );
+  });
+
+  it("an unlisted, a missing and a changed entry", async () => {
+    const extra = (e: { path: string; bytes: Uint8Array }[]) => {
+      e.push({ path: "site/modules/extra.mjs", bytes: new Uint8Array([1]) });
+    };
+    expect(await codeOf(make({ entries: extra }))).toBe("entry-unlisted");
+    const drop = (e: { path: string; bytes: Uint8Array }[]) => {
+      e.splice(
+        e.findIndex((x) => x.path === "admin/assets/app.js"),
+        1,
+      );
+    };
+    expect(await codeOf(make({ entries: drop }))).toBe("entry-missing");
+    const sameSize = (e: { path: string; bytes: Uint8Array }[]) => {
+      const x = e.find((y) => y.path === "site/assets/index.html");
+      if (x) x.bytes = flip(x.bytes);
+    };
+    expect(await codeOf(make({ entries: sameSize }))).toBe("entry-mismatch");
+    const longer = (e: { path: string; bytes: Uint8Array }[]) => {
+      const x = e.find((y) => y.path === "site/assets/index.html");
+      if (x) x.bytes = new Uint8Array([...x.bytes, 0x0a]);
+    };
+    expect(await codeOf(make({ entries: longer }))).toBe("entry-mismatch");
+  });
+
+  it("planVersion above what the caller supports", async () => {
+    const b = await synthetic({ manifest: (m) => (m.planVersion = 2) });
+    expect(await codeOf({ ...b, input: { ...b.input, planVersion: 1 } })).toBe(
+      "plan-version-unsupported",
+    );
+  });
+});
+
+describe("entry-path", () => {
+  const module =
+    (p: string) => (m: { workers: Record<"site" | "admin", Record> }) => {
+      const mods = m.workers.site.modules as { path: string }[];
+      (mods[0] as { path: string }).path = p;
+    };
+
+  it("refuses .. and . segments in a listing before the tarball is read", async () => {
+    let read = 0;
+    for (const p of ["../evil.mjs", "chunks/../../evil.mjs", "./entry.mjs", "chunks/./x.mjs"]) {
+      const b = await synthetic({ manifest: module(p) });
+      const code = await verifyRelease({
+        ...b.input,
+        tarball: () => {
+          read++;
+          return b.tarball;
+        },
+      }).catch((e: { code: string }) => e.code);
+      expect(code, p).toBe("entry-path");
+    }
+    expect(read).toBe(0);
+  });
+
+  it("refuses unsafe asset paths and paths that fold to the same name", async () => {
+    // files[0] is /index.html, files[1] /fonts/v1/a.woff2 (helpers.ts).
+    const asset =
+      (p: string) => (m: { workers: Record<"site" | "admin", Record> }) => {
+        const files = (m.workers.site.assets as { files: { path: string }[] }).files;
+        (files[1] as { path: string }).path = p;
+      };
+    for (const p of ["/fonts/../../etc/passwd", "/./a", "//a", "/a\\b", "/a\u0001b", "/"])
+      expect(await codeOf(make({ manifest: asset(p) })), p).toBe("entry-path");
+    expect(await codeOf(make({ manifest: asset("/Index.html") }))).toBe("entry-path");
+    expect(await codeOf(make({ manifest: asset("/INDEX.HTML") }))).toBe("entry-path");
+    // modules[0] is entry.mjs, modules[1] chunks/page.mjs.
+    expect(await codeOf(make({ manifest: module("Chunks/Page.mjs") }))).toBe("entry-path");
+  });
+
+  it("refuses unsafe tar entry names before anything else about them", async () => {
+    for (const p of ["site/modules/../x.mjs", "site/modules/a\\b.mjs", "site//modules/x.mjs"]) {
+      const add = (e: { path: string; bytes: Uint8Array }[]) => {
+        e.push({ path: p, bytes: new Uint8Array([1]) });
+      };
+      expect(await codeOf(make({ entries: add })), p).toBe("entry-path");
+    }
+  });
+});
+
+describe("tar format (§7)", () => {
+  it("refuses a gzip header that is not the fixed one", async () => {
+    const zlib = async (tar: Uint8Array) => new Uint8Array(gzipSync(tar));
+    expect(await codeOf(make({ compress: zlib }))).toBe("tar-format");
+  });
+
+  it("refuses an entry whose mtime is not createdAt", async () => {
+    expect(await codeOf(make({ mtime: 1 }))).toBe("tar-format");
+  });
+
+  it("refuses data after the end blocks and a missing second end block", async () => {
+    const junk = (tar: Uint8Array) => new Uint8Array([...tar, 1, ...new Uint8Array(511)]);
+    expect(await codeOf(make({ tar: junk }))).toBe("tar-format");
+    const short = (tar: Uint8Array) => tar.subarray(0, tar.length - 512);
+    expect(await codeOf(make({ tar: short }))).toBe("tar-format");
+  });
+
+  it("refuses a directory entry and an entry above the part limit", async () => {
+    const dir = (tar: Uint8Array) => {
+      const h = tarHeader("site/modules/dir", 0, "0", Date.parse("2026-11-13T16:13:20Z") / 1000);
+      h[156] = "5".charCodeAt(0);
+      let sum = 0;
+      for (let i = 0; i < 512; i++) sum += i >= 148 && i < 156 ? 0x20 : (h[i] as number);
+      h.set(new TextEncoder().encode(`${sum.toString(8).padStart(6, "0")}\0 `), 148);
+      return new Uint8Array([...h, ...tar]);
+    };
+    expect(await codeOf(make({ tar: dir }))).toBe("tar-format");
+    const huge = (tar: Uint8Array) => {
+      const h = tarHeader(
+        "site/modules/huge",
+        64 * 1024 * 1024 + 1,
+        "0",
+        Date.parse("2026-11-13T16:13:20Z") / 1000,
+      );
+      return new Uint8Array([...h, ...tar]);
+    };
+    expect(await codeOf(make({ tar: huge }))).toBe("limit-exceeded");
+  });
+});
diff --git a/packages/release-verify/tsconfig.json b/packages/release-verify/tsconfig.json
new file mode 100644
index 00000000..636a70fb
--- /dev/null
+++ b/packages/release-verify/tsconfig.json
@@ -0,0 +1,9 @@
+{
+  "extends": "../../tsconfig.base.json",
+  "compilerOptions": {
+    "rootDir": "../..",
+    "noEmit": true,
+    "types": ["node", "@cloudflare/workers-types"]
+  },
+  "include": ["src", "test", "scripts"]
+}
diff --git a/packages/release-verify/vitest.config.ts b/packages/release-verify/vitest.config.ts
new file mode 100644
index 00000000..a2cd792a
--- /dev/null
+++ b/packages/release-verify/vitest.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+  test: {
+    // One temp directory per run, removed at the end (scripts/vitest-tmpdir.ts).
+    globalSetup: ["../../scripts/vitest-tmpdir.ts"],
+    include: ["test/**/*.test.ts"],
+    testTimeout: 30_000,
+    hookTimeout: 60_000,
+  },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b1aa232e..086f1db9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -230,6 +230,9 @@ catalogs:
     '@modelcontextprotocol/server':
       specifier: 2.3.1
       version: 2.3.1
+    '@noble/hashes':
+      specifier: 2.4.0
+      version: 2.4.0
     '@phosphor-icons/core':
       specifier: 2.1.1
       version: 2.1.1
@@ -1023,6 +1026,34 @@ importers:
         specifier: 'catalog:'
         version: 5.1.0(@types/node@26.6.5)(happy-dom@21.0.4)(vite@8.3.5(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
 
+  packages/release-verify:
+    dependencies:
+      '@noble/hashes':
+        specifier: 'catalog:'
+        version: 2.4.0
+      ajv:
+        specifier: 'catalog:'
+        version: 8.20.0
+      ajv-formats:
+        specifier: 'catalog:'
+        version: 3.0.1(ajv@8.20.0)
+    devDependencies:
+      '@cloudflare/workers-types':
+        specifier: 'catalog:'
+        version: 5.20261007.1
+      '@cloudhal/core-contracts':
+        specifier: workspace:*
+        version: link:../core-contracts
+      '@types/node':
+        specifier: 'catalog:'
+        version: 26.6.5
+      typescript:
+        specifier: 'catalog:'
+        version: 7.0.2
+      vitest:
+        specifier: 'catalog:'
+        version: 5.1.0(@types/node@26.6.5)(happy-dom@21.0.4)(vite@8.3.5(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))
+
   packages/schedules:
     dependencies:
       '@cloudhal/core-contracts':
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index bbaea4c8..8b2f095c 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -15,6 +15,7 @@ catalog:
   "@types/node": ^26.6.5
   ajv: 8.20.0
   ajv-formats: 3.0.1
+  "@noble/hashes": 2.4.0
   json-schema-to-typescript: 16.0.0
   tsx: ^4.23.15
   turbo: 2.11.10
diff --git a/scripts/build-packages.ts b/scripts/build-packages.ts
index eda0347e..5a20671e 100644
--- a/scripts/build-packages.ts
+++ b/scripts/build-packages.ts
@@ -30,6 +30,7 @@ const require = createRequire(path.join(ROOT, "packages/core-ts/package.json"));
 /** Publishable packages, dependencies first. */
 export const PACKAGES = [
   "core-contracts",
+  "release-verify",
   "storage",
   "analytics",
   "core-ts",
@@ -50,7 +51,10 @@ const TEST_FILE = /\.test\.tsx?$/;
 /** A package's test-support entry; it ships only where its exports name it (./testing). */
 const TESTING_ENTRY = /(^|[\\/])testing\.ts$/;
 /** Development-only generators: they run in the monorepo (on dev dependencies) and never ship. */
-const DEV_ONLY = new Set(["core-ts/scripts/gen-icons.ts"]);
+const DEV_ONLY = new Set([
+  "core-ts/scripts/gen-icons.ts",
+  "release-verify/scripts/gen-validators.ts",
+]);
 
 type Json = Record;
 
diff --git a/scripts/code-size/measure.ts b/scripts/code-size/measure.ts
index bb652b36..2a7f3aea 100644
--- a/scripts/code-size/measure.ts
+++ b/scripts/code-size/measure.ts
@@ -25,6 +25,7 @@ const IGNORED = [
   /^packages\/core-contracts\/src\/generated\//,
   /^crates\/cloudhal-schema\/src\/generated\//,
   /^packages\/core-ts\/src\/generated\//,
+  /^packages\/release-verify\/src\/generated\//,
   // Studio's message parameter types, written by `pnpm gen` from the catalogs (RFC-0032 §3).
   /^apps\/studio\/src\/i18n\/generated\//,
   /^packages\/ui\/src\/blocks\/kumo\//,
diff --git a/scripts/release-bundle/verify.ts b/scripts/release-bundle/verify.ts
index 7590254c..93c8b181 100644
--- a/scripts/release-bundle/verify.ts
+++ b/scripts/release-bundle/verify.ts
@@ -4,10 +4,13 @@
  * the requested version and channel), §7's limits and tar format, every listed part present with
  * its size and SHA-256, no unlisted entry, and, when asked, the Ed25519 signature with a pinned key.
  * The builder runs it over its own unsigned output; `release-bundle.ts verify` runs it on demand.
+ * A signed bundle then also passes `@cloudhal/release-verify`, the one consumer verifier
+ * (RFC-0046 §11a), entry by entry.
  */
 import { existsSync, readFileSync } from "node:fs";
 import path from "node:path";
 import { assertValid, type ReleaseManifest } from "../../packages/core-contracts/src/index.ts";
+import { verifyRelease } from "../../packages/release-verify/src/index.ts";
 import { canonicalJson, LIMITS, manifestParts, sha256 } from "./manifest.ts";
 import { verifySignature } from "./signature.ts";
 import {
@@ -110,5 +113,41 @@ export async function verifyBundle(dir: string, o: VerifyOptions): Promise {
+  const keys = Object.fromEntries(
+    Object.entries(o.publicKeys ?? {}).map(([id, publicKey]) => [
+      id,
+      { publicKey, status: "active" as const },
+    ]),
+  );
+  const release = await verifyRelease({
+    request: {
+      version: o.version,
+      ...(o.channel === "stable" || o.channel === "next" ? { channel: o.channel } : {}),
+    },
+    statement: b.statementBytes,
+    signature: read(b.dir, SIGNATURE_FILE, 64),
+    manifest: b.manifestBytes,
+    tarball: () => b.tarball,
+    planVersion: b.manifest.planVersion,
+    keys,
+  });
+  for await (const _ of release.entries()) {
+    // every entry verified
+  }
+}
diff --git a/tsconfig.build.json b/tsconfig.build.json
index 082e6bf4..1ce47355 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -24,6 +24,7 @@
   "include": [
     "packages/core-contracts/src",
     "packages/core-contracts/scripts",
+    "packages/release-verify/src",
     "packages/storage/src",
     "packages/storage/migrations",
     "packages/analytics/src",
diff --git a/turbo.json b/turbo.json
index 5b28d1ee..78689b18 100644
--- a/turbo.json
+++ b/turbo.json
@@ -4,6 +4,7 @@
   "agentGuidance": true,
   "tasks": {
     "gen:ts": {
+      "dependsOn": ["^gen:ts"],
       "inputs": ["schemas/**", "scripts/**"],
       "outputs": ["src/generated/**", "schemas/.bundled/**"]
     },

From aaf9a12cb727c754e0d6ead0f3d636acd1ded775 Mon Sep 17 00:00:00 2001
From: Madraka <8898640+Madraka@users.noreply.github.com>
Date: Sat, 14 Nov 2026 15:42:28 +0300
Subject: [PATCH 2/2] fix(release-verify): PR #232 review round 1

- One byte rule for tar names. The ustar name field is the longest
  suffix of at most 100 UTF-8 bytes, cut on a code-point boundary
  (ustarName). The PAX record is written exactly when needsPax says so.
  The producer imports both from the package, and the verifier compares
  raw bytes. Long non-ASCII names cut on and between character
  boundaries now verify.
- Release planner: a group package that npm has never released (nothing,
  or only the placeholder) and whose changelog has no "## "
  entry is left out of the release and logged. main stays green after
  this merge and nothing publishes release-verify 0.9.0, dry runs
  included. A missing CHANGELOG.md reads as no entry.
- The producer runs the consumer's rules. The unsigned self-check runs
  checkManifestDoc and the second-pass entry reader, and publish-ci runs
  the full verifyRelease before it creates or uploads anything (loaded
  lazily: the plan job has no install).
- The second pass closes its stream chain on every exit: the end, a
  refusal, or a consumer that stops early. Errors from the source pass
  through unchanged.
- Low items:
  - missingScopes uses Object.hasOwn;
  - attestation prints "exit N" and passes gh absolute paths;
  - foldPath is NFC, lower, upper, lower, NFC;
  - the starter part limit and mainModule (a safe path and a listed
    module) are checked;
  - new tests for invariants 1 and 4 and one-sided fact changes;
  - the key doc states what each call covers;
  - test file references are fixed.

Refs #227

Co-Authored-By: Claude Opus 5.5 
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
---
 docs/security/release-signing-keys.md         |   7 +-
 .../cli/test/release-bundle-publish.test.ts   |  11 +
 .../cli/test/release-bundle-verify.test.ts    |  52 +++++
 packages/cli/test/release-plan.test.ts        |  71 ++++++
 packages/release-verify/src/attestation.ts    |   8 +-
 packages/release-verify/src/documents.ts      |  59 ++---
 packages/release-verify/src/keys.ts           |   2 +-
 packages/release-verify/src/limits.ts         |   2 +-
 packages/release-verify/src/paths.ts          |   8 +-
 packages/release-verify/src/scopes.ts         |   3 +-
 packages/release-verify/src/tar.ts            | 208 ++++++++++++++----
 packages/release-verify/src/verify.ts         |   8 +-
 .../release-verify/test/attestation.test.ts   |  22 ++
 packages/release-verify/test/helpers.ts       |  18 +-
 packages/release-verify/test/package.test.ts  |  15 ++
 packages/release-verify/test/tar.test.ts      |   8 +-
 .../test/verify-synthetic.test.ts             | 140 +++++++++++-
 scripts/publish-ci.ts                         |  89 ++++++--
 scripts/release-bundle/tar-read.ts            |   3 +-
 scripts/release-bundle/tar.ts                 |  15 +-
 scripts/release-bundle/verify.ts              |  21 ++
 scripts/release-plan.ts                       |  48 ++++
 22 files changed, 689 insertions(+), 129 deletions(-)

diff --git a/docs/security/release-signing-keys.md b/docs/security/release-signing-keys.md
index be7eee58..931fda66 100644
--- a/docs/security/release-signing-keys.md
+++ b/docs/security/release-signing-keys.md
@@ -51,7 +51,12 @@ Releases are immutable and can never gain an asset, so a release signed by a rev
 
 ## Verifying a release
 
-[`@cloudhal/release-verify`](../../packages/release-verify/README.md) does all of the steps below in one call, on Node, Workers or a browser. Its `PINNED_KEYS` is kept equal to the table above, and its `./attestation` subpath runs step 3. To check by hand instead, follow these steps.
+[`@cloudhal/release-verify`](../../packages/release-verify/README.md) can do two of the steps below for you:
+
+- **Step 1:** `verifyRelease` checks the statement, its signature and every digest, including each entry of the tarball, on Node, Workers or in a browser. Its `PINNED_KEYS` is kept equal to the table above.
+- **Step 3:** the attestation is a separate call. On Node, `verifyAttestation` (in `@cloudhal/release-verify/attestation`) runs `gh attestation verify` for you.
+
+Step 2, `gh release verify-asset`, stays manual. To check by hand instead, follow these steps.
 
 ## Verifying a release by hand
 
diff --git a/packages/cli/test/release-bundle-publish.test.ts b/packages/cli/test/release-bundle-publish.test.ts
index b60c2860..66d1ed80 100644
--- a/packages/cli/test/release-bundle-publish.test.ts
+++ b/packages/cli/test/release-bundle-publish.test.ts
@@ -7,6 +7,7 @@ import {
   loadBundle,
   publicKeysFor,
   RELEASE_KEY,
+  verifyAsConsumer,
   waitForDraft,
 } from "../../../scripts/publish-ci.ts";
 import { buildReleaseBundle, writeReleaseBundle } from "../../../scripts/release-bundle/build.ts";
@@ -46,6 +47,16 @@ describe("publish-ci loadBundle", () => {
     return dir;
   }
 
+  it("runs the consumer's verifier over the signed bundle before publishing (§11a)", async () => {
+    const k = throwawayKey();
+    const dir = await signedDir(k.privateKey);
+    await expect(verifyAsConsumer(dir, "0.8.0", { [KEY_ID]: k.raw })).resolves.toBeUndefined();
+    await expect(
+      verifyAsConsumer(dir, "0.8.0", { [KEY_ID]: throwawayKey().raw }),
+    ).rejects.toMatchObject({ code: "signature-invalid" });
+    await expect(verifyAsConsumer(dir, "0.8.1", { [KEY_ID]: k.raw })).rejects.toThrow();
+  });
+
   it("reads the five assets in upload order with their digests, and the statement", async () => {
     const k = throwawayKey();
     const dir = await signedDir(k.privateKey);
diff --git a/packages/cli/test/release-bundle-verify.test.ts b/packages/cli/test/release-bundle-verify.test.ts
index cfb89531..b1930a0c 100644
--- a/packages/cli/test/release-bundle-verify.test.ts
+++ b/packages/cli/test/release-bundle-verify.test.ts
@@ -147,6 +147,58 @@ describe("verifyBundle", () => {
   });
 });
 
+describe("the producer's self-check runs the consumer's rules (RFC-0046 §11a, #232)", () => {
+  /** An unsigned bundle of the fixture build with `edit` applied to its build output first. */
+  async function built(edit: (out: ReturnType) => void): Promise {
+    const fx = fixtureProject();
+    roots.push(fx.root);
+    const out = readBuildOutput({ project: fx.project, studio: fx.studio });
+    edit(out);
+    const secrets = readDeploySecrets(path.join(fx.project, ".cloudhal/deploy/secrets.json"));
+    const b = await buildReleaseBundle(out, facts({ version: "0.8.0" }), secrets);
+    const dir = mkdtempSync(path.join(os.tmpdir(), "release-bundle-out-"));
+    roots.push(dir);
+    writeReleaseBundle(b, dir);
+    return dir;
+  }
+  const addAsset = (p: string) => (out: ReturnType) => {
+    (out.site.assets as { path: string; bytes: Uint8Array }[]).push({
+      path: p,
+      bytes: enc.encode("x"),
+    });
+  };
+
+  it("refuses assets that collide after case folding, unsigned", async () => {
+    // Built in memory: a case-insensitive file system could not hold both files.
+    const dir = await built(addAsset("/FAVICON.svg"));
+    await expect(verifyBundle(dir, { version: "0.8.0" })).rejects.toMatchObject({
+      code: "entry-path",
+      message: expect.stringMatching(/collide/),
+    });
+  });
+
+  it("refuses a path over 512 bytes and a .. segment, unsigned", async () => {
+    const long = await built(
+      addAsset(`/${"a".repeat(200)}/${"b".repeat(200)}/${"c".repeat(200)}.svg`),
+    );
+    await expect(verifyBundle(long, { version: "0.8.0" })).rejects.toMatchObject({
+      code: "entry-path",
+    });
+    const dots = await built(addAsset("/x/../y.svg"));
+    await expect(verifyBundle(dots, { version: "0.8.0" })).rejects.toMatchObject({
+      code: "entry-path",
+    });
+  });
+
+  it("writes and reads long non-ASCII names with one byte rule (PAX + ustar name)", async () => {
+    const names = [`/${"界".repeat(30)}.txt`, `/x${"é".repeat(50)}`, `/${"😀".repeat(30)}`];
+    const dir = await built((out) => {
+      for (const n of names) addAsset(n)(out);
+    });
+    await expect(verifyBundle(dir, { version: "0.8.0" })).resolves.toMatchObject({});
+  });
+});
+
 describe("tar and gzip format checks", () => {
   const file = { path: "site/modules/a.mjs", bytes: enc.encode("a") };
 
diff --git a/packages/cli/test/release-plan.test.ts b/packages/cli/test/release-plan.test.ts
index f15e09d5..3fecdb71 100644
--- a/packages/cli/test/release-plan.test.ts
+++ b/packages/cli/test/release-plan.test.ts
@@ -5,10 +5,12 @@ import {
   isSecurityRelease,
   type Pkg,
   readPackages,
+  releaseGroup,
   releaseNotes,
   sharedVersion,
   unpublished,
 } from "../../../scripts/release-plan.ts";
+import { planPublish } from "../../../scripts/release-publish-plan.ts";
 
 const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
 const pkg = (name: string, version: string): Pkg => ({ dir: name, name, version });
@@ -104,3 +106,72 @@ describe("release plan", () => {
     expect(isSecurityRelease([], "0.8.1")).toBe(false);
   });
 });
+
+describe("a package that joined the fixed group before its first version PR (#232)", () => {
+  // main right after @cloudhal/release-verify merged: every package at 0.9.0, all released and
+  // with a "## 0.9.0" changelog entry, except release-verify (no CHANGELOG.md, nothing on npm).
+  const NEW = "@cloudhal/release-verify";
+  const at = (version: string) => readPackages(ROOT).map((p) => ({ ...p, version }));
+  const changelogs =
+    (version: string, newEntry: boolean) =>
+    (p: Pkg): string | undefined =>
+      p.name !== NEW || newEntry ? `# ${p.name}\n\n## ${version}\n\n- a change\n` : undefined;
+  const npm =
+    (forNew: string[], released: string) =>
+    async (name: string): Promise =>
+      name === NEW ? forNew : ["0.8.0", released];
+
+  it("is left out of the release: nothing publishes it at the group's current version", async () => {
+    for (const onNpmNew of [[], ["0.0.0-placeholder.0"]]) {
+      const pkgs = at("0.9.0");
+      expect(pkgs.map((p) => p.name)).toContain(NEW);
+      const r = await releaseGroup(
+        pkgs,
+        "0.9.0",
+        changelogs("0.9.0", false),
+        npm(onNpmNew, "0.9.0"),
+      );
+      expect(r.notYetReleased.map((p) => p.name)).toEqual([NEW]);
+      expect(r.group).toHaveLength(pkgs.length - 1);
+      // `--plan` on main: npm has everything else, so publish=false (main stays green).
+      const missing = await unpublished(r.group, async () => true);
+      expect(missing).toEqual([]);
+      // A workflow_dispatch dry run publishes the group with --dry-run: never the new package.
+      const dry = planPublish({
+        version: "0.9.0",
+        head: "a".repeat(40),
+        dryRun: true,
+        runNumber: "7",
+        bundle: null,
+        tagCommit: null,
+        releases: [],
+        packages: r.group.map((p) => p.name),
+        npmMissing: missing.map((p) => p.name),
+        tags: ["v0.9.0"],
+      });
+      const published = dry.flatMap((s) => (s.kind === "npm-publish" ? s.packages : []));
+      expect(published.map((p) => p.name)).not.toContain(NEW);
+      expect(published).toHaveLength(pkgs.length - 1);
+    }
+  });
+
+  it("is released by its first version PR, after only the placeholder", async () => {
+    const pkgs = at("0.9.1");
+    const r = await releaseGroup(
+      pkgs,
+      "0.9.1",
+      changelogs("0.9.1", true),
+      npm(["0.0.0-placeholder.0"], "0.9.0"),
+    );
+    expect(r.notYetReleased).toEqual([]);
+    const missing = await unpublished(r.group, async () => false);
+    expect(missing.map((p) => p.name)).toContain(NEW);
+  });
+
+  it("never leaves out a package npm has released, even without a changelog entry", async () => {
+    const pkgs = at("0.9.1");
+    const r = await releaseGroup(pkgs, "0.9.1", () => undefined, npm(["0.9.0"], "0.9.0"));
+    expect(r.notYetReleased).toEqual([]);
+    expect(r.group).toHaveLength(pkgs.length);
+  });
+});
diff --git a/packages/release-verify/src/attestation.ts b/packages/release-verify/src/attestation.ts
index d8949883..a0a26669 100644
--- a/packages/release-verify/src/attestation.ts
+++ b/packages/release-verify/src/attestation.ts
@@ -125,10 +125,12 @@ export async function verifyAttestation(o: AttestationOptions): Promise ({
         code: null,
@@ -143,7 +145,7 @@ export async function verifyAttestation(o: AttestationOptions): Promise {
   return out;
 }
 
+/** One Worker's listings: module and asset paths, `mainModule`, folded names and §7's limits. */
+function checkWorker(m: ReleaseManifest, worker: Worker): number {
+  const w = m.workers[worker];
+  for (const mod of w.modules) {
+    checkPath(mod.path, "module", `${worker} module`);
+    if (mod.bytes > LIMITS.partBytes)
+      refuse("limit-exceeded", `${worker} module ${mod.path} is above ${LIMITS.partBytes} bytes`);
+  }
+  checkNoFoldedDuplicates(
+    w.modules.map((x) => x.path),
+    `${worker} modules`,
+  );
+  checkPath(w.mainModule, "module", `${worker} mainModule`);
+  if (!w.modules.some((x) => x.path === w.mainModule))
+    refuse("manifest-invalid", `${worker} mainModule ${w.mainModule} is not a listed module`);
+  const files = w.assets.files;
+  if (files.length > LIMITS.assetsPerWorker)
+    refuse("limit-exceeded", `${worker}: more than ${LIMITS.assetsPerWorker} assets`);
+  for (const f of files) {
+    checkPath(f.path, "asset", `${worker} asset`);
+    if (f.bytes > LIMITS.assetBytes)
+      refuse("limit-exceeded", `${worker} asset ${f.path} is above ${LIMITS.assetBytes} bytes`);
+  }
+  checkNoFoldedDuplicates(
+    files.map((x) => x.path),
+    `${worker} assets`,
+  );
+  return w.modules.length + files.length;
+}
+
 /** Paths (§11a) and §7's limits over every listing, before the tarball is read. */
 function checkListings(m: ReleaseManifest): void {
-  let parts = 0;
-  for (const worker of ["site", "admin"] as const) {
-    const w = m.workers[worker];
-    for (const mod of w.modules) {
-      checkPath(mod.path, "module", `${worker} module`);
-      if (mod.bytes > LIMITS.partBytes)
-        refuse("limit-exceeded", `${worker} module ${mod.path} is above ${LIMITS.partBytes} bytes`);
-    }
-    checkNoFoldedDuplicates(
-      w.modules.map((x) => x.path),
-      `${worker} modules`,
-    );
-    const files = w.assets.files;
-    if (files.length > LIMITS.assetsPerWorker)
-      refuse("limit-exceeded", `${worker}: more than ${LIMITS.assetsPerWorker} assets`);
-    for (const f of files) {
-      checkPath(f.path, "asset", `${worker} asset`);
-      if (f.bytes > LIMITS.assetBytes)
-        refuse("limit-exceeded", `${worker} asset ${f.path} is above ${LIMITS.assetBytes} bytes`);
-    }
-    checkNoFoldedDuplicates(
-      files.map((x) => x.path),
-      `${worker} assets`,
-    );
-    parts += w.modules.length + files.length;
-  }
+  let parts = checkWorker(m, "site") + checkWorker(m, "admin");
   if (m.starter.kind === "bundle") {
     checkPath(m.starter.path, "entry", "starter");
+    if (m.starter.bytes > LIMITS.partBytes)
+      refuse("limit-exceeded", `the starter is above ${LIMITS.partBytes} bytes`);
     parts++;
   }
   if (parts > LIMITS.tarEntries)
diff --git a/packages/release-verify/src/keys.ts b/packages/release-verify/src/keys.ts
index b3f0a09b..d655398f 100644
--- a/packages/release-verify/src/keys.ts
+++ b/packages/release-verify/src/keys.ts
@@ -1,6 +1,6 @@
 /**
  * The release keys this package trusts (RFC-0046 §9, §11a): the table of
- * docs/security/release-signing-keys.md, kept equal to it by test/keys.test.ts.
+ * docs/security/release-signing-keys.md, kept equal to it by test/package.test.ts.
  *
  * `PINNED_KEYS` is frozen into each published version and cannot learn about a revocation by
  * itself. A caller may only reduce trust: `revoked` withdraws key ids. A caller that trusts a key
diff --git a/packages/release-verify/src/limits.ts b/packages/release-verify/src/limits.ts
index 54de1a2d..e06402a2 100644
--- a/packages/release-verify/src/limits.ts
+++ b/packages/release-verify/src/limits.ts
@@ -1,6 +1,6 @@
 /**
  * RFC-0046 §1 asset names and §7's normative limits. `scripts/release-bundle/manifest.ts` (the
- * producer) holds the same numbers; test/limits.test.ts keeps the two equal.
+ * producer) holds the same numbers; test/package.test.ts keeps the two equal.
  */
 export const LIMITS = {
   manifestBytes: 1_900_000,
diff --git a/packages/release-verify/src/paths.ts b/packages/release-verify/src/paths.ts
index eae13402..9a5aa44f 100644
--- a/packages/release-verify/src/paths.ts
+++ b/packages/release-verify/src/paths.ts
@@ -39,10 +39,12 @@ export function checkPath(path: string, kind: PathKind, where: string): void {
 }
 
 /**
- * Case folding as case-insensitive file systems apply it, approximately: NFC, then upper and
- * lower case (so "ß" and "SS" meet). Two listed paths that fold equal would collide on disk.
+ * Case folding as case-insensitive file systems apply it, approximately: NFC, lower, upper, lower
+ * again, NFC (so "ß", "ẞ" and "SS" meet, and so do "ΐ" and "Ϊ́"). Two listed paths that fold
+ * equal would collide on disk.
  */
-export const foldPath = (path: string): string => path.normalize("NFC").toUpperCase().toLowerCase();
+export const foldPath = (path: string): string =>
+  path.normalize("NFC").toLowerCase().toUpperCase().toLowerCase().normalize("NFC");
 
 /** Refuses two `paths` that are equal after case folding. */
 export function checkNoFoldedDuplicates(paths: Iterable, where: string): void {
diff --git a/packages/release-verify/src/scopes.ts b/packages/release-verify/src/scopes.ts
index b08ce3fc..2b7a6697 100644
--- a/packages/release-verify/src/scopes.ts
+++ b/packages/release-verify/src/scopes.ts
@@ -15,7 +15,8 @@ export function missingScopes(
   const held = new Set();
   for (const s of granted) {
     held.add(s);
-    for (const implied of IMPLIED_SCOPES[s] ?? []) held.add(implied);
+    const implied = Object.hasOwn(IMPLIED_SCOPES, s) ? IMPLIED_SCOPES[s] : undefined;
+    for (const i of implied ?? []) held.add(i);
   }
   return manifest.requiredScopes.filter((s) => !held.has(s));
 }
diff --git a/packages/release-verify/src/tar.ts b/packages/release-verify/src/tar.ts
index aeefbfd9..cd290b62 100644
--- a/packages/release-verify/src/tar.ts
+++ b/packages/release-verify/src/tar.ts
@@ -44,32 +44,87 @@ export function checkGzipHeader(head: Uint8Array): void {
   if (head[9] !== 255) refuse("tar-format", "the tarball's gzip OS byte is not 255");
 }
 
-/** The decompressed bytes of a gzip `source` whose header is the fixed one. */
+/**
+ * `source` as a ReadableStream we own: errors from the source itself are remembered (so they
+ * reach the caller unchanged, never as a tamper code), and `release()` cancels the caller's
+ * stream or ends its iterator, whatever state the pipe is in.
+ */
+function sourceStream(source: ByteSource) {
+  let failure: { error: unknown } | undefined;
+  let release: () => Promise;
+  let pull: () => Promise>;
+  if (typeof (source as ReadableStream).getReader === "function") {
+    const reader = (source as ReadableStream).getReader();
+    pull = async () => {
+      const r = await reader.read();
+      return r.done ? { done: true, value: undefined } : { done: false, value: r.value };
+    };
+    release = () => reader.cancel().catch(() => {});
+  } else {
+    const it = (
+      source instanceof Uint8Array
+        ? (async function* () {
+            yield source;
+          })()
+        : (source as AsyncIterable)
+    )[Symbol.asyncIterator]();
+    pull = () => it.next();
+    release = async () => {
+      await it.return?.()?.catch?.(() => {});
+    };
+  }
+  let released = false;
+  const once = async () => {
+    if (released) return;
+    released = true;
+    await release();
+  };
+  const stream = new ReadableStream({
+    async pull(c) {
+      try {
+        const next = await pull();
+        if (next.done) c.close();
+        else c.enqueue(next.value);
+      } catch (error) {
+        failure = { error };
+        throw error;
+      }
+    },
+    cancel: once,
+  });
+  return { stream, release: once, failure: () => failure };
+}
+
+/** Errors the stream unless its first ten bytes are the fixed gzip header (§7). */
+function gzipHeaderCheck(): TransformStream {
+  const head: number[] = [];
+  return new TransformStream({
+    transform(chunk, c) {
+      if (head.length < 10) {
+        for (const b of chunk.subarray(0, 10 - head.length)) head.push(b);
+        if (head.length === 10) checkGzipHeader(Uint8Array.from(head));
+      }
+      c.enqueue(chunk);
+    },
+    flush() {
+      if (head.length < 10) checkGzipHeader(Uint8Array.from(head));
+    },
+  });
+}
+
+/**
+ * The decompressed bytes of a gzip `source` whose header is the fixed one. Every exit (the end, a
+ * refusal, a consumer that stops early) cancels the chain and releases the caller's stream.
+ */
 export async function* gunzip(
-  source: AsyncIterable,
+  source: ByteSource,
   max: number = MAX_TAR_BYTES,
 ): AsyncGenerator {
-  const ds = new DecompressionStream("gzip");
-  const writer = ds.writable.getWriter();
-  const pump = (async () => {
-    const head: number[] = [];
-    try {
-      for await (const chunk of source) {
-        if (head.length < 10) {
-          for (const b of chunk.subarray(0, 10 - head.length)) head.push(b);
-          if (head.length === 10) checkGzipHeader(Uint8Array.from(head));
-        }
-        await writer.write(chunk);
-      }
-      if (head.length < 10) checkGzipHeader(Uint8Array.from(head));
-      await writer.close();
-    } catch (e) {
-      await writer.abort(e).catch(() => {});
-      throw e;
-    }
-  })();
-  pump.catch(() => {});
-  const reader = ds.readable.getReader();
+  const src = sourceStream(source);
+  const reader = src.stream
+    .pipeThrough(gzipHeaderCheck())
+    .pipeThrough(new DecompressionStream("gzip"))
+    .getReader();
   let total = 0;
   try {
     for (;;) {
@@ -77,21 +132,20 @@ export async function* gunzip(
       try {
         next = await reader.read();
       } catch (e) {
-        await pump; // a header refusal from the pump wins over the decompressor's error
-        throw e;
+        const failed = src.failure();
+        if (failed) throw failed.error; // the source's own error, unchanged
+        if (e instanceof ReleaseVerifyError) throw e;
+        return refuse("tar-format", `the tarball is not valid gzip (${(e as Error).message})`);
       }
-      if (next.done) break;
+      if (next.done) return;
       total += next.value.length;
       if (total > max)
         refuse("limit-exceeded", `the tarball decompresses to more than ${max} bytes`);
       yield next.value;
     }
-    await pump;
-  } catch (e) {
-    if (e instanceof ReleaseVerifyError) throw e;
-    refuse("tar-format", `the tarball is not valid gzip (${(e as Error).message})`);
   } finally {
     await reader.cancel().catch(() => {});
+    await src.release();
   }
 }
 
@@ -150,7 +204,8 @@ function checksumOk(h: Uint8Array): boolean {
 }
 
 interface Header {
-  readonly name: string;
+  /** The ustar name field up to its first NUL, as raw bytes. */
+  readonly name: Uint8Array;
   readonly size: number;
   readonly flag: "0" | "x";
 }
@@ -168,7 +223,9 @@ function checkHeader(h: Uint8Array, mtime: number): Header {
     refuse("tar-format", "a tar entry's mtime is not the release's createdAt");
   if (str(h, 157, 100) || str(h, 265, 32) || str(h, 297, 32) || str(h, 345, 155))
     refuse("tar-format", "a tar entry has a link name, an owner name or a prefix");
-  return { name: str(h, 0, 100), size: num(h, 124, 12, "size"), flag };
+  const field = h.subarray(0, 100);
+  const end = field.indexOf(0);
+  return { name: end >= 0 ? field.subarray(0, end) : field, size: num(h, 124, 12, "size"), flag };
 }
 
 /** The `path` of a PAX header holding exactly one `path` record. */
@@ -201,6 +258,10 @@ class TarReader {
   constructor(source: AsyncIterable) {
     this.it = source[Symbol.asyncIterator]();
   }
+  /** Ends the source early (a refusal or a consumer that stopped): its own cleanup runs. */
+  async close(): Promise {
+    await this.it.return?.();
+  }
   /** Buffers at least `n` bytes; false when the stream ends first. */
   async fill(n: number): Promise {
     while (this.q.length < n) {
@@ -227,6 +288,59 @@ class TarReader {
   }
 }
 
+const utf8 = new TextEncoder();
+
+/**
+ * Whether `path` needs a PAX `path` record: longer than the 100-byte ustar name field in UTF-8,
+ * or not printable ASCII. The producer (scripts/release-bundle/tar.ts) imports this rule.
+ */
+export function needsPax(path: string): boolean {
+  if (utf8.encode(path).length > 100) return true;
+  for (let i = 0; i < path.length; i++) {
+    const c = path.charCodeAt(i);
+    if (c < 0x20 || c > 0x7e) return true;
+  }
+  return false;
+}
+
+/**
+ * The ustar name field of `path`: its longest suffix whose UTF-8 is at most 100 bytes, cut on a
+ * code-point boundary (so it is valid UTF-8). For a path of at most 100 ASCII bytes that is the
+ * path itself. One byte-based rule for the producer, which imports it, and the verifier.
+ */
+export function ustarName(path: string): string {
+  const points = Array.from(path);
+  let bytes = 0;
+  let i = points.length;
+  while (i > 0) {
+    const n = utf8.encode(points[i - 1] as string).length;
+    if (bytes + n > 100) break;
+    bytes += n;
+    i--;
+  }
+  return points.slice(i).join("");
+}
+
+const sameBytes = (a: Uint8Array, b: Uint8Array) =>
+  a.length === b.length && a.every((x, i) => x === b[i]);
+
+/** The path of a file header: the PAX path when one came first, else its own ASCII name. */
+function entryPath(name: Uint8Array, pax: string | null): string {
+  if (pax !== null) {
+    if (!needsPax(pax)) refuse("tar-format", `${pax}: a PAX record for a name that needs none`);
+    if (!sameBytes(name, utf8.encode(ustarName(pax))))
+      refuse("tar-format", `${pax}: the ustar name is not the end of the PAX path`);
+    return pax;
+  }
+  let path = "";
+  for (const c of name) {
+    if (c < 0x20 || c > 0x7e)
+      refuse("tar-format", "a tar name that is not ASCII has no PAX record");
+    path += String.fromCharCode(c);
+  }
+  return path;
+}
+
 /** The next file header with its PAX path applied, or null at the first zero block. */
 async function nextHeader(
   r: TarReader,
@@ -242,12 +356,10 @@ async function nextHeader(
     const { name, size, flag } = checkHeader(h, mtime);
     if (size > LIMITS.partBytes)
       refuse("limit-exceeded", `a tar entry is above ${LIMITS.partBytes} bytes`);
-    if (flag === "0") {
-      if (pax !== null && pax.slice(-100) !== name)
-        refuse("tar-format", `${pax}: the ustar name is not the end of the PAX path`);
-      return { path: pax ?? name, size };
-    }
+    if (flag === "0") return { path: entryPath(name, pax), size };
     if (pax !== null) refuse("tar-format", "two PAX headers in a row");
+    if (!sameBytes(name, utf8.encode("././@PaxHeader")))
+      refuse("tar-format", "a PAX header is not named ././@PaxHeader");
     if (size > 4096) refuse("tar-format", "a PAX header is too large");
     pax = paxPath((await r.take(padded(size))).subarray(0, size));
   }
@@ -265,14 +377,18 @@ export async function* readTar(
 ): AsyncGenerator {
   const r = new TarReader(source);
   let count = 0;
-  for (;;) {
-    const header = await nextHeader(r, mtime);
-    if (!header) break;
-    if (++count > LIMITS.tarEntries)
-      refuse("limit-exceeded", `the tarball has more than ${LIMITS.tarEntries} entries`);
-    admit(header.path, header.size);
-    const data = await r.take(padded(header.size));
-    yield { path: header.path, bytes: data.subarray(0, header.size) };
+  try {
+    for (;;) {
+      const header = await nextHeader(r, mtime);
+      if (!header) break;
+      if (++count > LIMITS.tarEntries)
+        refuse("limit-exceeded", `the tarball has more than ${LIMITS.tarEntries} entries`);
+      admit(header.path, header.size);
+      const data = await r.take(padded(header.size));
+      yield { path: header.path, bytes: data.subarray(0, header.size) };
+    }
+    await r.checkEnd(512);
+  } finally {
+    await r.close();
   }
-  await r.checkEnd(512);
 }
diff --git a/packages/release-verify/src/verify.ts b/packages/release-verify/src/verify.ts
index 8bd92cb3..acccc209 100644
--- a/packages/release-verify/src/verify.ts
+++ b/packages/release-verify/src/verify.ts
@@ -121,7 +121,11 @@ async function checkTarballDigest(
     refuse("tarball-mismatch", "the tarball is not the file the statement pins");
 }
 
-async function* verifiedEntries(
+/**
+ * The second pass (step 6's entries), exported for the producer's self-check
+ * (scripts/release-bundle/verify.ts); not part of the package's public exports.
+ */
+export async function* verifiedEntries(
   open: VerifyReleaseInput["tarball"],
   listed: ReadonlyMap,
   mtime: number,
@@ -138,7 +142,7 @@ async function* verifiedEntries(
     if (size !== part.bytes)
       refuse("entry-mismatch", `${tarPath} is not the size its listing says`);
   };
-  for await (const e of readTar(gunzip(chunksOf(await open())), mtime, admit)) {
+  for await (const e of readTar(gunzip(await open()), mtime, admit)) {
     const part = left.get(e.path) as ListedPart;
     if ((await sha256Hex(e.bytes)) !== part.sha256)
       refuse("entry-mismatch", `${e.path} does not match the manifest's SHA-256`);
diff --git a/packages/release-verify/test/attestation.test.ts b/packages/release-verify/test/attestation.test.ts
index f4aa69df..c42e65ce 100644
--- a/packages/release-verify/test/attestation.test.ts
+++ b/packages/release-verify/test/attestation.test.ts
@@ -146,6 +146,28 @@ describe("verifyAttestation", () => {
     }
   });
 
+  it("says exit N when gh prints nothing, and passes gh absolute paths only", async () => {
+    const { run, calls } = gh();
+    const quiet: Runner = (cmd, args, t) =>
+      args[0] !== "attestation" || args[2] === "--help"
+        ? run(cmd, args, t)
+        : Promise.resolve({ code: 3, signal: null, timedOut: false, stdout: "", stderr: "  \n" });
+    const rel = path.relative(process.cwd(), dir(false));
+    const r = await verifyAttestation({ dir: rel, version: "0.9.0", commit: COMMIT, run: quiet });
+    expect(r).toEqual({
+      status: "failed",
+      file: "cloudhal-release-0.9.0.tar.gz",
+      reason: "exit 3",
+    });
+    const ok = gh();
+    await verifyAttestation({ dir: rel, version: "0.9.0", commit: COMMIT, run: ok.run });
+    const files = ok.calls
+      .filter((c) => c[0] === "attestation" && c[2] !== "--help")
+      .map((c) => c[2]);
+    expect(files.every((f) => path.isAbsolute(f as string))).toBe(true);
+    expect(calls.length).toBeGreaterThan(0);
+  });
+
   it("refuses a malformed version or commit before running anything", async () => {
     const { run, calls } = gh();
     await expect(
diff --git a/packages/release-verify/test/helpers.ts b/packages/release-verify/test/helpers.ts
index 1a876d30..a330e59a 100644
--- a/packages/release-verify/test/helpers.ts
+++ b/packages/release-verify/test/helpers.ts
@@ -57,6 +57,8 @@ export interface SyntheticOptions {
   readonly compress?: (tar: Uint8Array) => Promise;
   /** Raw tar bytes instead of the producer's (after `entries`). */
   readonly tar?: (tar: Uint8Array) => Uint8Array;
+  /** More parts, listed and in the tar, by tar path (`site/assets/x`, `site/modules/y.mjs`). */
+  readonly parts?: Readonly>;
 }
 
 export interface Synthetic {
@@ -83,15 +85,15 @@ const file = (p: string, bytes: Uint8Array) => ({
   contentType: "application/octet-stream",
 });
 
-/** A signed synthetic bundle of six parts, `opts` applied. */
+/** A signed synthetic bundle of six parts (plus `opts.parts`), `opts` applied. */
 export async function synthetic(opts: SyntheticOptions = {}): Promise {
   const m = JSON.parse(new TextDecoder().decode(fixture("manifest.json"))) as Doc;
-  const bytesOf = (p: keyof typeof PARTS) => utf8(PARTS[p]);
+  const parts: Record = { ...PARTS, ...opts.parts };
   for (const role of ["site", "admin"] as const) {
-    const modules = Object.keys(PARTS)
+    const modules = Object.keys(parts)
       .filter((p) => p.startsWith(`${role}/modules/`))
       .map((p) => {
-        const b = bytesOf(p as keyof typeof PARTS);
+        const b = utf8(parts[p] as string);
         return {
           path: p.slice(`${role}/modules/`.length),
           type: "esm",
@@ -99,9 +101,9 @@ export async function synthetic(opts: SyntheticOptions = {}): Promise
           sha256: sha(b),
         };
       });
-    const files = Object.keys(PARTS)
+    const files = Object.keys(parts)
       .filter((p) => p.startsWith(`${role}/assets/`))
-      .map((p) => file(p.slice(`${role}/assets`.length), bytesOf(p as keyof typeof PARTS)));
+      .map((p) => file(p.slice(`${role}/assets`.length), utf8(parts[p] as string)));
     const w = m.workers[role];
     w.mainModule = (modules.find((x) => !x.path.startsWith("chunks/")) ?? modules[0])?.path;
     w.modules = modules;
@@ -115,7 +117,7 @@ export async function synthetic(opts: SyntheticOptions = {}): Promise
   opts.manifest?.(m);
   const manifestBytes = utf8(canonicalJson(m));
 
-  const entries = Object.entries(PARTS).map(([p, text]) => ({ path: p, bytes: utf8(text) }));
+  const entries = Object.entries(parts).map(([p, text]) => ({ path: p, bytes: utf8(text) }));
   opts.entries?.(entries);
   entries.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
   const createdAt = "2026-11-13T16:13:20Z";
@@ -153,7 +155,7 @@ export async function synthetic(opts: SyntheticOptions = {}): Promise
     },
     tarball,
     keys,
-    listed: Object.keys(PARTS).length,
+    listed: Object.keys(parts).length,
   };
 }
 
diff --git a/packages/release-verify/test/package.test.ts b/packages/release-verify/test/package.test.ts
index 380b2813..b4b742bc 100644
--- a/packages/release-verify/test/package.test.ts
+++ b/packages/release-verify/test/package.test.ts
@@ -12,6 +12,7 @@ import { releaseAssetNames as producerAssetNames } from "../../../scripts/releas
 import { generate, OUT_DIR } from "../scripts/gen-validators.ts";
 import {
   ERROR_CODES,
+  foldPath,
   LIMITS,
   missingScopes,
   PINNED_KEYS,
@@ -103,6 +104,12 @@ describe("missingScopes (§3)", () => {
     expect(missingScopes(next, admin)).toEqual([]);
   });
 
+  it("is not fooled by Object.prototype names", () => {
+    expect(missingScopes(next, ["constructor", "__proto__", "toString"])).toEqual(
+      next.requiredScopes,
+    );
+  });
+
   it("compares every other id exactly: Editor never covers Admin", () => {
     const editor = ["account-settings.read", "workers-scripts.edit", "d1.write"];
     expect(missingScopes(next, editor)).toEqual(["workers-scripts.admin"]);
@@ -131,6 +138,14 @@ describe("releaseAssetUrls and paths", () => {
       expect(() => releaseAssetUrls(v), v).toThrow(TypeError);
   });
 
+  it("foldPath meets the case forms file systems treat as one name", () => {
+    expect(foldPath("ẞ")).toBe(foldPath("ß"));
+    expect(foldPath("ß")).toBe(foldPath("SS"));
+    expect(foldPath("ΐ")).toBe(foldPath("Ϊ́"));
+    expect(foldPath("e\u0301")).toBe(foldPath("É"));
+    expect(foldPath("a")).not.toBe(foldPath("b"));
+  });
+
   it("unsafePathReason: each rule, and linear on a 100k-character path", () => {
     expect(unsafePathReason("chunks/a.mjs", "module")).toBeUndefined();
     expect(unsafePathReason("chunks/_.._x.mjs", "module")).toBeUndefined();
diff --git a/packages/release-verify/test/tar.test.ts b/packages/release-verify/test/tar.test.ts
index 618d6e7e..7e611d87 100644
--- a/packages/release-verify/test/tar.test.ts
+++ b/packages/release-verify/test/tar.test.ts
@@ -33,16 +33,16 @@ describe("readTar and gunzip", () => {
   it("refuses a tarball that decompresses beyond the cap", async () => {
     const tar = tarArchive([{ path: "a", bytes: new Uint8Array(200_000) }], MTIME);
     const gz = await gzipBytes(tar);
-    expect(await codeOf(drain(gunzip(chunksOf(gz), 100_000)))).toBe("limit-exceeded");
-    expect(await drain(gunzip(chunksOf(gz)))).toBeGreaterThan(0);
+    expect(await codeOf(drain(gunzip(gz, 100_000)))).toBe("limit-exceeded");
+    expect(await drain(gunzip(gz))).toBeGreaterThan(0);
   });
 
   it("refuses gzip that is corrupt after a valid header, and a truncated one", async () => {
     const gz = await gzipBytes(tarArchive([{ path: "a", bytes: new Uint8Array(5000) }], MTIME));
     const corrupt = new Uint8Array(gz);
     for (let i = 12; i < corrupt.length - 8; i++) corrupt[i] = 0xff;
-    expect(await codeOf(drain(gunzip(chunksOf(corrupt))))).toBe("tar-format");
-    expect(await codeOf(drain(gunzip(chunksOf(gz.subarray(0, 5)))))).toBe("tar-format");
+    expect(await codeOf(drain(gunzip(corrupt)))).toBe("tar-format");
+    expect(await codeOf(drain(gunzip(gz.subarray(0, 5))))).toBe("tar-format");
   });
 
   it("reads Uint8Array, ReadableStream and async iterable sources alike", async () => {
diff --git a/packages/release-verify/test/verify-synthetic.test.ts b/packages/release-verify/test/verify-synthetic.test.ts
index 04749225..0fcab96b 100644
--- a/packages/release-verify/test/verify-synthetic.test.ts
+++ b/packages/release-verify/test/verify-synthetic.test.ts
@@ -63,10 +63,106 @@ describe("a valid synthetic bundle", () => {
     expect([opened, n]).toEqual([2, b.listed]);
   });
 
-  it("lets a consumer stop early without hanging", async () => {
+  /**
+   * `bytes` in 64-byte chunks from a stream that then never ends (as a slow network might), so
+   * only a cancel by the reader can release it, however far ahead the pipe buffers.
+   */
+  const watched = (bytes: Uint8Array) => {
+    const state = { cancelled: false };
+    let at = 0;
+    const stream = new ReadableStream({
+      pull(c) {
+        if (at >= bytes.length) return new Promise(() => {});
+        c.enqueue(bytes.slice(at, at + 64));
+        at += 64;
+      },
+      cancel() {
+        state.cancelled = true;
+      },
+    });
+    return { stream, state };
+  };
+
+  it("cancels the source when a consumer stops early", async () => {
     const b = await synthetic();
-    const r = await verifyRelease({ ...b.input, tarball: () => chunked(b.tarball, 64) });
+    let second: ReturnType | undefined;
+    let calls = 0;
+    const r = await verifyRelease({
+      ...b.input,
+      tarball: () => {
+        if (calls++ === 0) return b.tarball;
+        second = watched(b.tarball);
+        return second.stream;
+      },
+    });
     for await (const _ of r.entries()) break;
+    expect(second?.state.cancelled).toBe(true);
+  });
+
+  it("cancels the source when an entry is refused mid-stream", async () => {
+    const changed = (e: { path: string; bytes: Uint8Array }[]) => {
+      const x = e.find((y) => y.path === "admin/modules/worker.js");
+      if (x) x.bytes = flip(x.bytes);
+    };
+    const b = await synthetic({ entries: changed });
+    let second: ReturnType | undefined;
+    let calls = 0;
+    const r = await verifyRelease({
+      ...b.input,
+      tarball: () => {
+        if (calls++ === 0) return b.tarball;
+        second = watched(b.tarball);
+        return second.stream;
+      },
+    });
+    const err = await (async () => {
+      for await (const _ of r.entries()) {
+        // drain
+      }
+    })().catch((e: { code?: string }) => e.code);
+    expect(err).toBe("entry-mismatch");
+    expect(second?.state.cancelled).toBe(true);
+  });
+
+  it("passes a source I/O error in the second pass through unchanged", async () => {
+    const b = await synthetic();
+    const disk = new Error("disk went away");
+    let calls = 0;
+    const r = await verifyRelease({
+      ...b.input,
+      tarball: () => {
+        if (calls++ === 0) return b.tarball;
+        let sent = false;
+        return new ReadableStream({
+          pull(c) {
+            if (sent) return c.error(disk);
+            sent = true;
+            c.enqueue(b.tarball.subarray(0, 40));
+          },
+        });
+      },
+    });
+    const got = await (async () => {
+      for await (const _ of r.entries()) {
+        // drain
+      }
+    })().catch((e: unknown) => e);
+    expect(got).toBe(disk);
+  });
+
+  it("reads long non-ASCII names, cut on a character boundary and in the middle of one", async () => {
+    const names = [
+      `site/assets/${"界".repeat(30)}.txt`, // 3-byte characters: the 100-byte window cuts one
+      `site/assets/x${"é".repeat(50)}`, // 2-byte characters: the window ends on a boundary
+      `site/assets/${"😀".repeat(30)}`, // 4-byte characters (surrogate pairs in UTF-16)
+      `site/assets/${"é".repeat(44)}`, // exactly 100 bytes: not ASCII, so a PAX record anyway
+    ];
+    const b = await synthetic({ parts: Object.fromEntries(names.map((n) => [n, n])) });
+    const r = await verifyRelease(b.input);
+    const got: string[] = [];
+    for await (const e of r.entries()) got.push(e.tarPath);
+    expect(got).toEqual(expect.arrayContaining(names));
+    expect(got).toHaveLength(b.listed);
   });
 });
 
@@ -80,6 +176,46 @@ describe("refusals that need a validly signed bundle", () => {
     );
   });
 
+  it("one-sided changes to every shared fact, and to the key id (invariants 4 and 5)", async () => {
+    const changes: ((m: Record) => void)[] = [
+      (m) => (m.channel = "next"),
+      (m) => (m.securityRelease = true),
+      (m) => (m.createdAt = "2026-11-13T16:13:21Z"),
+      (m) => ((m.signing as Record).keyId = "another-key"),
+    ];
+    for (const change of changes)
+      expect(await codeOf(make({ manifest: change }))).toBe("statement-manifest-mismatch");
+  });
+
+  it("a statement whose tarball is not named for its version (invariant 1)", async () => {
+    const rename = (st: Record) => {
+      (st.tarball as Record).name = "cloudhal-release-0.9.1.tar.gz";
+    };
+    expect(await codeOf(make({ statement: rename }))).toBe("statement-invalid");
+  });
+
+  it("a mainModule that is not a listed module, or not a safe path", async () => {
+    const main =
+      (p: string) => (m: { workers: Record<"site" | "admin", Record> }) => {
+        m.workers.admin.mainModule = p;
+      };
+    expect(await codeOf(make({ manifest: main("other.js") }))).toBe("manifest-invalid");
+    expect(await codeOf(make({ manifest: main("../worker.js") }))).toBe("entry-path");
+  });
+
+  it("a starter part above the part limit", async () => {
+    const starter = (m: Record) => {
+      m.starter = {
+        kind: "bundle",
+        path: "starter/blog.cloudhal",
+        bytes: 64 * 1024 * 1024 + 1,
+        sha256: "0".repeat(64),
+        siteId: "site_starter",
+      };
+    };
+    expect(await codeOf(make({ manifest: starter }))).toBe("limit-exceeded");
+  });
+
   it("a manifest that names the legacy manifest.sig, or is not valid", async () => {
     const legacy = (m: Record) => {
       (m.signing as Record).signature = "manifest.sig";
diff --git a/scripts/publish-ci.ts b/scripts/publish-ci.ts
index b2d9469d..6bbde3bd 100644
--- a/scripts/publish-ci.ts
+++ b/scripts/publish-ci.ts
@@ -24,7 +24,14 @@
  */
 import { execFileSync } from "node:child_process";
 import { createHash } from "node:crypto";
-import { appendFileSync, mkdtempSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
+import {
+  appendFileSync,
+  existsSync,
+  mkdtempSync,
+  readdirSync,
+  readFileSync,
+  writeFileSync,
+} from "node:fs";
 import os from "node:os";
 import path from "node:path";
 import { fileURLToPath } from "node:url";
@@ -32,9 +39,11 @@ import { parseArgs } from "node:util";
 import { verifySignature } from "./release-bundle/signature.ts";
 import {
   isSecurityRelease,
+  npmVersions,
   onNpm,
   type Pkg,
   readPackages,
+  releaseGroup,
   releaseNotes,
   sharedVersion,
   unpublished,
@@ -142,6 +151,40 @@ function remoteTagCommit(tag: string): string | null {
   return (peeled ?? lines[0])?.[0] ?? null;
 }
 
+/**
+ * RFC-0046 §11a: `@cloudhal/release-verify`, the verifier every consumer runs, over the signed
+ * bundle in `dir`, every tarball entry included, so a release no consumer would install is never
+ * published. Imported here only: the `plan` job runs this script without a pnpm install.
+ */
+export async function verifyAsConsumer(
+  dir: string,
+  version: string,
+  publicKeys: Readonly>,
+): Promise {
+  const { verifyRelease } = await import("../packages/release-verify/src/index.ts");
+  const read = (name: string) => readFileSync(path.join(dir, name));
+  const release = await verifyRelease({
+    request: { version },
+    statement: read("release-statement.json"),
+    signature: read("release-statement.sig"),
+    manifest: read("manifest.json"),
+    tarball: () => read(`cloudhal-release-${version}.tar.gz`),
+    planVersion: Number.MAX_SAFE_INTEGER,
+    keys: Object.fromEntries(
+      Object.entries(publicKeys).map(([id, publicKey]) => [id, { publicKey, status: "active" }]),
+    ),
+  });
+  for await (const _ of release.entries()) {
+    // every entry verified
+  }
+}
+
+/** A package's CHANGELOG.md, or undefined when it has none yet (a package before its first release). */
+function changelogOf(p: Pkg): string | undefined {
+  const file = path.join(ROOT, "packages", p.dir, "CHANGELOG.md");
+  return existsSync(file) ? readFileSync(file, "utf8") : undefined;
+}
+
 async function npmPublish(pkgs: readonly Pkg[], publishes: readonly NpmPublish[], dryRun: boolean) {
   const tarballs = mkdtempSync(path.join(os.tmpdir(), "cloudhal-release-"));
   // Builds, packs and installs every package from scratch with npm; fails the release on error.
@@ -218,9 +261,7 @@ function githubPort(pkgs: readonly Pkg[], version: string, dryRun: boolean): Pub
     },
     releases: releasesOf,
     createDraft(tag, o) {
-      const changelogs = pkgs.map((p) =>
-        readFileSync(path.join(ROOT, "packages", p.dir, "CHANGELOG.md"), "utf8"),
-      );
+      const changelogs = pkgs.map((p) => changelogOf(p) ?? "");
       const notes = path.join(os.tmpdir(), `cloudhal-${tag}-notes.md`);
       writeFileSync(notes, releaseNotes(changelogs, version));
       const title = dryRun
@@ -269,13 +310,21 @@ export async function main(argv: readonly string[]): Promise {
     },
   });
   const dryRun = v["dry-run"] === true || process.env.RELEASE_DRY_RUN === "true";
-  const pkgs = readPackages(ROOT);
-  const version = sharedVersion(pkgs);
+  const all = readPackages(ROOT);
+  const version = sharedVersion(all);
+  const { group: pkgs, notYetReleased } = await releaseGroup(
+    all,
+    version,
+    changelogOf,
+    npmVersions,
+  );
+  for (const p of notYetReleased)
+    console.log(
+      `publish-ci: leaving out ${p.name}: never released and no "## ${version}" in its changelog (its first version PR has not landed)`,
+    );
   const missing = await unpublished(pkgs, onNpm);
   if (v.plan) {
-    const changelogs = pkgs.map((p) =>
-      readFileSync(path.join(ROOT, "packages", p.dir, "CHANGELOG.md"), "utf8"),
-    );
+    const changelogs = pkgs.map((p) => changelogOf(p) ?? "");
     const security = isSecurityRelease(changelogs, version);
     const out = `publish=${dryRun || missing.length > 0}\nversion=${version}\ndry_run=${dryRun}\nsecurity_release=${security}\n`;
     if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, out);
@@ -285,22 +334,22 @@ export async function main(argv: readonly string[]): Promise {
   const runNumber = process.env.GITHUB_RUN_NUMBER;
   const tag = tagFor({ version, dryRun, ...(runNumber ? { runNumber } : {}) });
   const port = githubPort(pkgs, version, dryRun);
+  const bundleDir = v["bundle-dir"];
+  const publicKeys = bundleDir
+    ? publicKeysFor({
+        dryRun,
+        runId: process.env.GITHUB_RUN_ID,
+        dryRunPublicKey: process.env.RELEASE_DRYRUN_PUBLIC_KEY,
+      })
+    : {};
+  // Before anything is created or uploaded: every consumer's own verifier accepts this bundle.
+  if (bundleDir) await verifyAsConsumer(bundleDir, version, publicKeys);
   const steps = await runPublish(port, {
     version,
     head: sh("git", ["rev-parse", "HEAD"], true).trim(),
     dryRun,
     ...(runNumber ? { runNumber } : {}),
-    bundle: v["bundle-dir"]
-      ? loadBundle(
-          v["bundle-dir"],
-          version,
-          publicKeysFor({
-            dryRun,
-            runId: process.env.GITHUB_RUN_ID,
-            dryRunPublicKey: process.env.RELEASE_DRYRUN_PUBLIC_KEY,
-          }),
-        )
-      : null,
+    bundle: bundleDir ? loadBundle(bundleDir, version, publicKeys) : null,
     tagCommit: dryRun ? null : remoteTagCommit(tag),
     releases: port.releases(tag),
     packages: pkgs.map((p) => p.name),
diff --git a/scripts/release-bundle/tar-read.ts b/scripts/release-bundle/tar-read.ts
index ec242fe7..04d4c373 100644
--- a/scripts/release-bundle/tar-read.ts
+++ b/scripts/release-bundle/tar-read.ts
@@ -6,6 +6,7 @@
  * `path` record for the entry that follows). Links, directories and every other type are refused.
  */
 import { gunzipCapped } from "../../packages/cli/src/archive/gzip.ts";
+import { ustarName } from "../../packages/release-verify/src/tar.ts";
 import { LIMITS } from "./manifest.ts";
 import type { SourceFile } from "./read-build.ts";
 
@@ -96,7 +97,7 @@ export function readTar(tar: Uint8Array, mtime: number): SourceFile[] {
       continue;
     }
     const path = pending ?? name;
-    if (pending !== null && pending.slice(-100) !== name)
+    if (pending !== null && ustarName(pending) !== name)
       throw new Error(`${path}: the ustar name is not the end of the PAX path`);
     pending = null;
     out.push({ path, bytes: data });
diff --git a/scripts/release-bundle/tar.ts b/scripts/release-bundle/tar.ts
index e4c1d1f9..24c902ac 100644
--- a/scripts/release-bundle/tar.ts
+++ b/scripts/release-bundle/tar.ts
@@ -6,6 +6,7 @@
  * (`gzipFixed`: mtime 0, no name, OS 255). The same parts give the same bytes on every run.
  */
 import { gzipFixed } from "../../packages/cli/src/archive/gzip.ts";
+import { needsPax, ustarName } from "../../packages/release-verify/src/tar.ts";
 import type { SourceFile } from "./read-build.ts";
 
 const enc = new TextEncoder();
@@ -48,15 +49,9 @@ export function paxRecord(p: string): Uint8Array {
 
 const pad = (n: number) => new Uint8Array((512 - (n % 512)) % 512);
 
-/** Whether `p` needs a PAX `path` record (longer than the 100-byte name field, or not ASCII). */
-export function needsPax(p: string): boolean {
-  if (enc.encode(p).length > 100) return true;
-  for (let i = 0; i < p.length; i++) {
-    const c = p.charCodeAt(i);
-    if (c < 0x20 || c > 0x7e) return true;
-  }
-  return false;
-}
+// One naming rule with the verifier (RFC-0046 §7): the PAX record when a path needs one, and the
+// ustar name field as the longest suffix of at most 100 UTF-8 bytes on a code-point boundary.
+export { needsPax, ustarName };
 
 /** The ustar archive of `entries` in the order given (callers sort them), two zero end blocks. */
 export function tarArchive(entries: readonly SourceFile[], mtime: number): Uint8Array {
@@ -68,7 +63,7 @@ export function tarArchive(entries: readonly SourceFile[], mtime: number): Uint8
       const rec = paxRecord(e.path);
       out.push(tarHeader("././@PaxHeader", rec.length, "x", mtime), rec, pad(rec.length));
     }
-    out.push(tarHeader(e.path.slice(-100), e.bytes.length, "0", mtime), e.bytes);
+    out.push(tarHeader(ustarName(e.path), e.bytes.length, "0", mtime), e.bytes);
     out.push(pad(e.bytes.length));
   }
   out.push(new Uint8Array(1024));
diff --git a/scripts/release-bundle/verify.ts b/scripts/release-bundle/verify.ts
index 93c8b181..82149fed 100644
--- a/scripts/release-bundle/verify.ts
+++ b/scripts/release-bundle/verify.ts
@@ -10,7 +10,9 @@
 import { existsSync, readFileSync } from "node:fs";
 import path from "node:path";
 import { assertValid, type ReleaseManifest } from "../../packages/core-contracts/src/index.ts";
+import { checkManifestDoc, listedParts } from "../../packages/release-verify/src/documents.ts";
 import { verifyRelease } from "../../packages/release-verify/src/index.ts";
+import { verifiedEntries } from "../../packages/release-verify/src/verify.ts";
 import { canonicalJson, LIMITS, manifestParts, sha256 } from "./manifest.ts";
 import { verifySignature } from "./signature.ts";
 import {
@@ -113,11 +115,30 @@ export async function verifyBundle(dir: string, o: VerifyOptions): Promise {
+  const consumer = statement as unknown as Parameters[1];
+  const m = checkManifestDoc(manifest, consumer, manifest.planVersion);
+  const mtime = Date.parse(statement.createdAt) / 1000;
+  for await (const _ of verifiedEntries(() => tarball, listedParts(m), mtime)) {
+    // every entry passes the consumer's tar and path rules
+  }
+}
+
 /** The shared consumer verifier (§11a) over the same bytes, with the given keys as trusted. */
 async function verifyAsConsumer(
   o: VerifyOptions,
diff --git a/scripts/release-plan.ts b/scripts/release-plan.ts
index bfdba618..5aebde6d 100644
--- a/scripts/release-plan.ts
+++ b/scripts/release-plan.ts
@@ -51,6 +51,54 @@ export async function unpublished(
   return out;
 }
 
+/** Every version of `name` npm has ever published ([] when npm has never seen the package). */
+export async function npmVersions(name: string): Promise {
+  const res = await fetch(`https://registry.npmjs.org/${name.replaceAll("/", "%2F")}`);
+  if (res.status === 404) return [];
+  if (!res.ok) throw new Error(`npm registry: ${name}: HTTP ${res.status}`);
+  return Object.keys(((await res.json()) as { versions?: Record }).versions ?? {});
+}
+
+/**
+ * The deprecated placeholder an owner publishes by hand so that npm trusted publishing can be
+ * set up for a new package (RFC-0046 §11a). It is never a release of the package.
+ */
+export const isPlaceholderVersion = (v: string): boolean => v.startsWith("0.0.0-placeholder.");
+
+/** Whether a changelog has a `## ` section. */
+export const hasChangelogEntry = (text: string | undefined, version: string): boolean =>
+  text?.includes(`\n## ${version}\n`) ?? false;
+
+export interface ReleaseGroup {
+  /** The packages this release covers, in publish order. */
+  readonly group: Pkg[];
+  /** Packages left out: never released, and no changelog entry for this version. */
+  readonly notYetReleased: Pkg[];
+}
+
+/**
+ * The fixed group minus the packages that are not part of this release yet. A package joins the
+ * group in one PR and gets its first version in the next version PR. In between, `main` holds it
+ * at the group's current version with no changelog entry for it. That version was never released
+ * for this package and must not be published from `main` (#232). Such a package is one npm holds
+ * no real version of (nothing, or only the placeholder) and whose changelog has no `## `.
+ * Every package already released keeps being published as before.
+ */
+export async function releaseGroup(
+  pkgs: readonly Pkg[],
+  version: string,
+  changelogOf: (p: Pkg) => string | undefined,
+  versionsOf: (name: string) => Promise,
+): Promise {
+  const notYetReleased: Pkg[] = [];
+  for (const p of pkgs) {
+    if (hasChangelogEntry(changelogOf(p), version)) continue;
+    const real = (await versionsOf(p.name)).filter((v) => !isPlaceholderVersion(v));
+    if (real.length === 0) notYetReleased.push(p);
+  }
+  return { group: pkgs.filter((p) => !notYetReleased.includes(p)), notYetReleased };
+}
+
 /** Lines the fixed group writes into every changelog: dependency bumps, not changes. */
 const DEPENDENCY_ONLY = /^- (?:Updated dependencies|(?:@cloudhal\/[a-z-]+|create-cloudhal)@\d)/;