diff --git a/.github/lockfile-change-policy.json b/.github/lockfile-change-policy.json index ece397d4f..88046b42a 100644 --- a/.github/lockfile-change-policy.json +++ b/.github/lockfile-change-policy.json @@ -1,20 +1,273 @@ { - "baseSha": "6bc8ed016dc07f95d4e041a3b79ac00c4086b182", + "baseSha": "e26d771470a4ece873c367b40b3cd6cb03ac7de3", "bulkChange": null, - "justification": "Remediate GHSA-2v37-7h3g-55p8 by advancing the single transitive nanoid package-lock node from 3.3.17 to the patched 3.3.18 release. Preserve all top-level lock metadata, PostCSS dependency declarations, and unrelated package nodes.", + "justification": "Replace the Wrangler/Miniflare/Sharp transitive development path with direct pinned workerd@1.20260625.1 and esbuild@0.28.1 dependencies for Noema Worker development and deployment tooling. The reviewed lockfile transition removes the Wrangler-owned Miniflare/Sharp/Libvips package set, preserves unchanged package objects and top-level lockfile metadata, and binds the exact protected-main base and regenerated head bytes.", "packageDigests": { - "node_modules/nanoid": { - "afterSha256": "d05f52cccf4bb2b3faa241c82560bdff38872191f8c2fc9e0fe11d1863c6689c", - "beforeSha256": "eb31926c2b062d6831f465580d52d350ebd0ec8cb0ae8c9b36a92e1bec871af4" + "": { + "afterSha256": "bc4820765f3986a162070a7c499943d4976663ce9dbf4bc0d039bc8111d14c87", + "beforeSha256": "bc4df75e5f7a57a7b5cbb8fca21fe3aada716dcd26e4bad5b889d93c5251e20c" + }, + "node_modules/@cloudflare/kv-asset-handler": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "baf9a6828aa48335b6b1ddc90c064891668bf48ed319cb98bad1aae065e5b110" + }, + "node_modules/@cloudflare/unenv-preset": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "3975dc435686ec2387ff6065520031589c8608c4040c1bffcfcf169693670bc6" + }, + "node_modules/@cspotcode/source-map-support": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "be3b4d0e114620b28f168efe57e2f082751ec98c255e5ff44642903ca8c8abc1" + }, + "node_modules/@img/colour": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "0ec9a3855c0d275ee3ddf26fc218c24bcd73c70ae1be64bc783adcee728fabd3" + }, + "node_modules/@img/sharp-darwin-arm64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "33753afdce1a4ef04bdbe3955ee21f6f7ec2d0e24950d2d48853ac21d06e0207" + }, + "node_modules/@img/sharp-darwin-x64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "92c5e9c8a824389e0d714e015b25bbfe4304b1968f9fc4551c31aeaa84953d7e" + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "e93d997495809b38e24ee02aae3570fd5388f6f29aca13ecc5790df62c4bf2ee" + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "5cbb579d4f882d736f41709f4ab8df92b444cc24a04ddf4568b6248903988dea" + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "52cc3b0d34d5f51e30fc3018eea0cf640c5963e6b662cacf9f6c377cc35b6dda" + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "e5de57abbf3750ebae77db880bdee4dd9bd5823468fe4d6a54b6f0076508c120" + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "b86ba40d539fb3254d0a045e330fa90141a3907deefadaf264ce4831512035a4" + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "666eb414e63b3f4a6f2338f14d6f59127c6f73562659425004d9258a499160a8" + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "c3e3d0a53cec8bb22b1319219dfcd6fc5d059cd2b133827668fac6e301f77c53" + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "6273b939a75e550fe2088ac52d90f1bb9918f93330d6b83a7df7db46b7b41efd" + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "21453f05d9d156d477d80b5f6726993033c6e7cc1ffba71d93042fa51648acb4" + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "84a3b95e19257d67c939f31d82dc6549cad376ead8dc7a9e451e6cfb1d1b3b3e" + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "4c5fa48ebba69feada47c1b31653a099b461543c104807afdcecf437a1f05f3e" + }, + "node_modules/@img/sharp-linux-arm": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "a65b10a97d8310bcb4d5e97be980320e91267d69b2b8fcd80aaa8134609e282c" + }, + "node_modules/@img/sharp-linux-arm64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "07b70ea8a68735233da5aabe69863f359f77bf152addd717311907255038bd5c" + }, + "node_modules/@img/sharp-linux-ppc64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "5a3f8bb47ada74df86462a8eb4c283cf2f5fc072974c81db4d98a5bd2774bfc6" + }, + "node_modules/@img/sharp-linux-riscv64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "807f16be2b136919b9089a0df5fc506b3f66f2708205659eb7e11945a578e070" + }, + "node_modules/@img/sharp-linux-s390x": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "dea04183a47348ebf4455ffc9f7b56d750a388bd59900937a3501a5f886fb0b5" + }, + "node_modules/@img/sharp-linux-x64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "e8b884ec932accbb31472f9532d30d9dea8cf69e3665a02a47ff8a278f4a5d26" + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "5d81370f988ddf9cfd71f818640fb1ddba3c61f34936ccd16f9318abb45e070a" + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "dcb1c509e3d9a5a917cfe6b3868acfe372bd4045a4bdeedb3c265d1c9ab2c1d8" + }, + "node_modules/@img/sharp-wasm32": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "d372231a4a3a965acefef6e4082f35d7faafee7c0ac332d333267eed0230f73f" + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "3950aee6b7b49d472361e907dd0a38966a4362a9dcd8e3a94cb91187965051da" + }, + "node_modules/@img/sharp-win32-arm64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "d7b719d77aad3ce761066678008a2b59ee708da1eac1edca5a52d595d064623c" + }, + "node_modules/@img/sharp-win32-ia32": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "6ac2a1af086a1bd93c725d3379a2d63abb1ccb96fa22d11c320fab8ee9323c0e" + }, + "node_modules/@img/sharp-win32-x64": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "83788206d3b5d601a59383a2e647e679ae3499f41a4c7c609f5d414fc876edf8" + }, + "node_modules/@jridgewell/trace-mapping": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "946e048fd4f5f06fd3a2558cecdd7a7e1a179d1c60f88c1a10deff92904cefb6" + }, + "node_modules/@poppinss/colors": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "9f6d9e5e656687bf9365aad30b1cd57e2005670d483825ff6a9041eb264c0a8c" + }, + "node_modules/@poppinss/dumper": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "4636d1e8ce5d92e9e6a74b8e331a1d0599151c423620605b7f0d391a6a324f45" + }, + "node_modules/@poppinss/exception": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "511ab6d7dda3a25412e2d6459b7458c52d35e8d5a38ccea9e8a1c767c2c2b6a3" + }, + "node_modules/@sindresorhus/is": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "9cf703780184209ca125688afa81e6cf6a49ca4cee7a6442648003875ffa7ede" + }, + "node_modules/@speed-highlight/core": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "b0d9aede1a43525b35c83c66cfe23301fefa32d437344a723f156bcb3bde75c6" + }, + "node_modules/blake3-wasm": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "2edd7b9afb0a3edfde7bf65df2176834db86926fb79bcb81757f823ece33e0e8" + }, + "node_modules/cookie": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "c83cc50b9edf74fff002ee1696f718a7893a2790be1c87668878d4259a9ed661" + }, + "node_modules/error-stack-parser-es": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "07d175e9a9ce5da0ce6a91827c6c941d31f51684281f0060b3dc3df25db6cc02" + }, + "node_modules/kleur": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "03d1698c44fce7057c0b68d8cba4bfad5ca7382a948e162d49d806f81ee4859c" + }, + "node_modules/miniflare": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "3e4f60462e1a727ae18971cc7805b70cee7a5e1ba6265490550d3cd545ce7c2e" + }, + "node_modules/path-to-regexp": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "206d20489bfee22f1bd8a9ef8b31f0c7bdf9544b7272decd73314db823528dd1" + }, + "node_modules/sharp": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "185d39448de75db02f7418462440e6b8755e9f1e94fd88b66712835a9f22153a" + }, + "node_modules/supports-color": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "1a548a2b86a1d2addc0f2fd3dc4a3da1f2a81cd8e94fe1f0d431de96989d234c" + }, + "node_modules/undici": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "ab97f8ae955e187ed30dae56574c6f24277da4c4068383998f42dd18990d6c9b" + }, + "node_modules/unenv": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "cd1ef9a2d07200fe1d861ae9a4f81c1b1990312c1c6d88ecf844246efb33f6fe" + }, + "node_modules/wrangler": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "b52ea831e8e92ebe06b3ed1776cc1f781f7de77ece30a4237a95ff477df65455" + }, + "node_modules/ws": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "8312a6b5d3e17eda63344fe09189e016ad35b526bbbef54af5468d22eb9902a6" + }, + "node_modules/youch": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "faaf7ca34f95ab4401519c3222a9b37ef594158220cdb37ea4f3c483817e81d4" + }, + "node_modules/youch-core": { + "afterSha256": "398b676e47d03a29016ee92fe378b8b4f1b3e965390c4c64ce78d27f79df74d1", + "beforeSha256": "054aee49bedca6747ec8256719b1a9d6c5e834903f6606daa12ef8497dc70043" } }, "schemaVersion": 3, "sources": [ - "https://github.com/advisories/GHSA-2v37-7h3g-55p8", - "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz" + "https://registry.npmjs.org/wrangler/-/wrangler-4.105.0.tgz", + "https://registry.npmjs.org/miniflare/-/miniflare-4.20260625.0.tgz", + "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "https://registry.npmjs.org/workerd/-/workerd-1.20260625.1.tgz", + "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz" ], "targetPackages": [ - "node_modules/nanoid" + "", + "node_modules/@cloudflare/kv-asset-handler", + "node_modules/@cloudflare/unenv-preset", + "node_modules/@cspotcode/source-map-support", + "node_modules/@img/colour", + "node_modules/@img/sharp-darwin-arm64", + "node_modules/@img/sharp-darwin-x64", + "node_modules/@img/sharp-freebsd-wasm32", + "node_modules/@img/sharp-libvips-darwin-arm64", + "node_modules/@img/sharp-libvips-darwin-x64", + "node_modules/@img/sharp-libvips-linux-arm", + "node_modules/@img/sharp-libvips-linux-arm64", + "node_modules/@img/sharp-libvips-linux-ppc64", + "node_modules/@img/sharp-libvips-linux-riscv64", + "node_modules/@img/sharp-libvips-linux-s390x", + "node_modules/@img/sharp-libvips-linux-x64", + "node_modules/@img/sharp-libvips-linuxmusl-arm64", + "node_modules/@img/sharp-libvips-linuxmusl-x64", + "node_modules/@img/sharp-linux-arm", + "node_modules/@img/sharp-linux-arm64", + "node_modules/@img/sharp-linux-ppc64", + "node_modules/@img/sharp-linux-riscv64", + "node_modules/@img/sharp-linux-s390x", + "node_modules/@img/sharp-linux-x64", + "node_modules/@img/sharp-linuxmusl-arm64", + "node_modules/@img/sharp-linuxmusl-x64", + "node_modules/@img/sharp-wasm32", + "node_modules/@img/sharp-webcontainers-wasm32", + "node_modules/@img/sharp-win32-arm64", + "node_modules/@img/sharp-win32-ia32", + "node_modules/@img/sharp-win32-x64", + "node_modules/@jridgewell/trace-mapping", + "node_modules/@poppinss/colors", + "node_modules/@poppinss/dumper", + "node_modules/@poppinss/exception", + "node_modules/@sindresorhus/is", + "node_modules/@speed-highlight/core", + "node_modules/blake3-wasm", + "node_modules/cookie", + "node_modules/error-stack-parser-es", + "node_modules/kleur", + "node_modules/miniflare", + "node_modules/path-to-regexp", + "node_modules/sharp", + "node_modules/supports-color", + "node_modules/undici", + "node_modules/unenv", + "node_modules/wrangler", + "node_modules/ws", + "node_modules/youch", + "node_modules/youch-core" ], "topLevelMetadataDigests": { "afterSha256": "354c77096d1795b6f33b903ac8b54c3922a045279413f3e8681c78c1fe5278b1", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d83efcc04..62bbd85a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -146,6 +146,52 @@ jobs: console.log(`Lockfile change control passed for ${result.changedPackages.length} changed package node(s).`); NODE + - name: regenerate canonical lockfile in disposable workspace + id: regenerate_lockfile + shell: bash + run: | + set -euo pipefail + regeneration_root="$RUNNER_TEMP/noema-lockfile-regeneration" + rm -rf "$regeneration_root" + mkdir -p "$regeneration_root" + cp package.json package-lock.json .npmrc "$regeneration_root/" + ( + cd "$regeneration_root" + npm install \ + --package-lock-only \ + --ignore-scripts \ + --no-audit \ + --no-fund \ + --legacy-peer-deps=false \ + --install-links=false + ) + cp "$regeneration_root/package-lock.json" "$RUNNER_TEMP/noema-package-lock-regenerated.json" + if cmp --silent package-lock.json "$RUNNER_TEMP/noema-package-lock-regenerated.json"; then + printf 'match=true\n' >> "$GITHUB_OUTPUT" + else + printf 'match=false\n' >> "$GITHUB_OUTPUT" + diff -u package-lock.json "$RUNNER_TEMP/noema-package-lock-regenerated.json" \ + > "$RUNNER_TEMP/noema-package-lock-regeneration.diff" || true + fi + + - name: upload regenerated lockfile evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: noema-lockfile-regeneration-${{ github.event.pull_request.head.sha || github.sha }} + path: | + ${{ runner.temp }}/noema-package-lock-regenerated.json + ${{ runner.temp }}/noema-package-lock-regeneration.diff + if-no-files-found: error + retention-days: 1 + + - name: require committed lockfile reproducibility + if: steps.regenerate_lockfile.outputs.match != 'true' + shell: bash + run: | + printf '::error::package-lock.json is not the canonical output of the pinned Node/npm toolchain.\n' + exit 1 + - name: install run: npm ci --legacy-peer-deps=false --install-links=false diff --git a/.github/workflows/patch-validator-image.yml b/.github/workflows/patch-validator-image.yml index 89ed4139b..cdb241a55 100644 --- a/.github/workflows/patch-validator-image.yml +++ b/.github/workflows/patch-validator-image.yml @@ -147,7 +147,11 @@ jobs: npm_config_os=wasip1-threads \ npm_config_cpu=wasm32 \ npm ci --include=optional --ignore-scripts --no-audit --no-fund - npm pkg delete devDependencies.@cloudflare/workers-types devDependencies.wrangler + npm pkg delete \ + devDependencies.@cloudflare/workers-types \ + devDependencies.wrangler \ + devDependencies.workerd \ + devDependencies.esbuild timeout --signal=TERM --kill-after=30s 5m env \ npm_config_os=wasip1-threads \ npm_config_cpu=wasm32 \ @@ -160,6 +164,8 @@ jobs: test ! -e node_modules/@cloudflare/workers-types test ! -e node_modules/wrangler test ! -e node_modules/workerd + test ! -e node_modules/esbuild + test ! -e node_modules/@esbuild test ! -e node_modules/miniflare ) diff --git a/.gitignore b/.gitignore index 910e84693..659de7229 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ .wrangler/ +.noema-dev/ coverage/ dist/ *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 437fbcb39..865b22a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## Unreleased +- Wrangler/Miniflare/Sharp/Libvips 제거 후 정확히 Node.js 24.19.0/npm 11.17.0으로 `package-lock.json`을 재생성해 남아 있던 GPL-family 의존성 경로(`node_modules/wrangler`, `node_modules/miniflare`, `node_modules/sharp`, `@img/sharp-libvips-*`, LGPL-3.0)를 제거한다. Node 24 supply-chain 계약 테스트가 새 `lockfile-reproducibility` 워크플로를 검토된 `actions/upload-artifact` 인벤토리에 포함하고, patch-validator 이미지 워크플로의 `npm pkg delete`가 `workerd`/`esbuild`까지 devDependencies에서 제거하는 다중 라인 형태를 검증하도록 갱신한다. Worker 설정 파서 회귀 테스트의 "root field silently omitted" 픽스처가 TOML 섹션 스코프상 실제로 `[vars]` 섹션에 귀속되던 위치 오류를 수정해, 인식되지 않은 root-level 키가 여전히 root 섹션에서 거부되는지를 올바르게 검증한다. - Noema reviewer의 strict changed-file evidence를 historical 12-file prefix에서 canonical 80-file CodeGraph scope와 일치시켰다. 13–80 file PR은 선택된 모든 current-head file context를 유지하고 81개 이상은 기존처럼 실패-폐쇄하며, local CodeGraph fallback의 `HOME`·`TEMP`·`TMP`·`TMPDIR`은 ambient host path를 상속하지 않고 실행마다 새 private temporary directory로 격리한다. - Workflow / Task Execution은 untrusted DAG를 execution/plan identity에 결합한 detached immutable snapshot으로 승인하고, validated array bounds 안에서만 task/dependency/state evidence를 읽는다. runnable 선택은 cross-execution·foreign·duplicate·non-canonical evidence, admitted concurrency를 초과한 running state, 성공하지 않은 prerequisite 뒤에 존재하는 causally impossible executed state를 실패-폐쇄하며, 선택 결과는 reservation이나 side-effect authority가 아닌 후보임을 명시한다. Agent Runtime lifecycle·State & Checkpoint·Workflow admission은 null·throwing accessor·revoked proxy 같은 malformed runtime input의 임의 JavaScript 예외를 각 bounded-context domain error로 정규화한다. - State & Checkpoint admission은 accepted/replay 결과와 내부 checkpoint를 모두 caller-owned alias에서 분리한 frozen snapshot으로 반환한다. TypeScript `readonly`만으로는 막을 수 없는 JavaScript 런타임 alias mutation이 승인된 checkpoint authority나 `accepted`/`replay` 분류를 사후 변경하지 못하도록 실패-폐쇄한다. diff --git a/package-lock.json b/package-lock.json index 91da46972..f9e787137 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,10 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20260630.0", "@vitest/coverage-v8": "^4.1.9", + "esbuild": "0.28.1", "typescript": "^5.9.0", "vitest": "^4.1.9", - "wrangler": "^4.25.0" + "workerd": "1.20260625.1" }, "engines": { "node": ">=22" @@ -78,32 +79,6 @@ "node": ">=18" } }, - "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", - "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", - "dev": true, - "license": "MIT OR Apache-2.0", - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@cloudflare/unenv-preset": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz", - "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==", - "dev": true, - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "unenv": "2.0.0-rc.24", - "workerd": ">1.20260305.0 <2.0.0-0" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } - } - }, "node_modules/@cloudflare/workerd-darwin-64": { "version": "1.20260625.1", "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260625.1.tgz", @@ -196,19 +171,6 @@ "dev": true, "license": "MIT OR Apache-2.0" }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -523,693 +485,166 @@ "x64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", - "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.2" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", - "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.2" - } - }, - "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", - "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "dependencies": { - "@img/sharp-wasm32": "0.35.3" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", - "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", - "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", - "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", - "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", - "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", - "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", - "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", - "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", - "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", - "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", - "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", - "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", - "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.2" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", - "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.2" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", - "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ - "s390x" + "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "netbsd" ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.2" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", - "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "netbsd" ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.2" + "node": ">=18" } }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", - "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "openbsd" ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + "node": ">=18" } }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", - "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "openbsd" ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + "node": ">=18" } }, - "node_modules/@img/sharp-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", - "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.11.1" - }, + "os": [ + "openharmony" + ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", - "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ - "wasm32" + "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, - "dependencies": { - "@img/sharp-wasm32": "0.35.3" - }, + "os": [ + "sunos" + ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", - "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", - "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": "^20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", - "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1229,17 +664,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -1269,35 +693,6 @@ "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@poppinss/colors": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", - "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^4.1.5" - } - }, - "node_modules/@poppinss/dumper": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", - "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@poppinss/colors": "^4.1.5", - "@sindresorhus/is": "^7.0.2", - "supports-color": "^10.0.0" - } - }, - "node_modules/@poppinss/exception": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", - "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", - "dev": true, - "license": "MIT" - }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", @@ -1562,26 +957,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/is": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", - "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@speed-highlight/core": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.17.tgz", - "integrity": "sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1802,13 +1177,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", - "dev": true, - "license": "MIT" - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1826,20 +1194,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1850,16 +1204,6 @@ "node": ">=8" } }, - "node_modules/error-stack-parser-es": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", - "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/es-module-lexer": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.2.0.tgz", @@ -2038,16 +1382,6 @@ "dev": true, "license": "MIT" }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -2347,27 +1681,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/miniflare": { - "version": "4.20260625.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260625.0.tgz", - "integrity": "sha512-3kKXwRUObJsnBYPBgR0NiNZYKF/yv8GFyha1cx2EeAEraxNODgRVcyeRo+F1ok1tg5Mg7iUpOWSkknQTHuFhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "sharp": "0.34.5", - "undici": "7.28.0", - "workerd": "1.20260625.1", - "ws": "8.21.0", - "youch": "4.1.0-beta.10" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=22.0.0" - } - }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", @@ -2401,13 +1714,6 @@ "node": ">=12.20.0" } }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2511,56 +1817,6 @@ "node": ">=10" } }, - "node_modules/sharp": { - "version": "0.35.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", - "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@img/colour": "^1.1.0", - "detect-libc": "^2.1.2", - "semver": "^7.8.5" - }, - "engines": { - "node": ">=20.9.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.3", - "@img/sharp-darwin-x64": "0.35.3", - "@img/sharp-freebsd-wasm32": "0.35.3", - "@img/sharp-libvips-darwin-arm64": "1.3.2", - "@img/sharp-libvips-darwin-x64": "1.3.2", - "@img/sharp-libvips-linux-arm": "1.3.2", - "@img/sharp-libvips-linux-arm64": "1.3.2", - "@img/sharp-libvips-linux-ppc64": "1.3.2", - "@img/sharp-libvips-linux-riscv64": "1.3.2", - "@img/sharp-libvips-linux-s390x": "1.3.2", - "@img/sharp-libvips-linux-x64": "1.3.2", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", - "@img/sharp-libvips-linuxmusl-x64": "1.3.2", - "@img/sharp-linux-arm": "0.35.3", - "@img/sharp-linux-arm64": "0.35.3", - "@img/sharp-linux-ppc64": "0.35.3", - "@img/sharp-linux-riscv64": "0.35.3", - "@img/sharp-linux-s390x": "0.35.3", - "@img/sharp-linux-x64": "0.35.3", - "@img/sharp-linuxmusl-arm64": "0.35.3", - "@img/sharp-linuxmusl-x64": "0.35.3", - "@img/sharp-webcontainers-wasm32": "0.35.3", - "@img/sharp-win32-arm64": "0.35.3", - "@img/sharp-win32-ia32": "0.35.3", - "@img/sharp-win32-x64": "0.35.3" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -2592,19 +1848,6 @@ "dev": true, "license": "MIT" }, - "node_modules/supports-color": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", - "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2671,26 +1914,6 @@ "node": ">=14.17" } }, - "node_modules/undici": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", - "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/unenv": { - "version": "2.0.0-rc.24", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", - "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3" - } - }, "node_modules/vite": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.1.tgz", @@ -2896,89 +2119,6 @@ "@cloudflare/workerd-linux-arm64": "1.20260625.1", "@cloudflare/workerd-windows-64": "1.20260625.1" } - }, - "node_modules/wrangler": { - "version": "4.105.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.105.0.tgz", - "integrity": "sha512-7dXFH6OLj1Fv0y6ZeRPUxFTkp+duWD7/xxVi/1c0vfOeEYwIFKWB7cdqnY05DvY1Ta3BnqAwRkXfLs8PDj538g==", - "dev": true, - "license": "MIT OR Apache-2.0", - "dependencies": { - "@cloudflare/kv-asset-handler": "0.5.0", - "@cloudflare/unenv-preset": "2.16.1", - "blake3-wasm": "2.1.5", - "esbuild": "0.28.1", - "miniflare": "4.20260625.0", - "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.24", - "workerd": "1.20260625.1" - }, - "bin": { - "cf-wrangler": "bin/cf-wrangler.js", - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=22.0.0" - }, - "optionalDependencies": { - "fsevents": "2.3.3" - }, - "peerDependencies": { - "@cloudflare/workers-types": "^4.20260625.1" - }, - "peerDependenciesMeta": { - "@cloudflare/workers-types": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/youch": { - "version": "4.1.0-beta.10", - "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", - "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@poppinss/colors": "^4.1.5", - "@poppinss/dumper": "^0.6.4", - "@speed-highlight/core": "^1.2.7", - "cookie": "^1.0.2", - "youch-core": "^0.3.3" - } - }, - "node_modules/youch-core": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", - "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@poppinss/exception": "^1.2.2", - "error-stack-parser-es": "^1.0.5" - } } } } diff --git a/package.json b/package.json index a8bcbf59f..b84959e05 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "workerd@1.20260625.1": true }, "scripts": { - "deploy": "wrangler deploy", - "dev": "wrangler dev", + "deploy": "node scripts/cloudflare-worker-deploy.mjs", + "dev": "node scripts/cloudflare-worker-dev.mjs", "kpi:compute": "node scripts/compute-kpi.mjs", "kpi:collect": "bash scripts/collect-kpi-logs.sh", "kpi:check": "node scripts/check-kpi.mjs", @@ -62,12 +62,12 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20260630.0", "@vitest/coverage-v8": "^4.1.9", + "esbuild": "0.28.1", "typescript": "^5.9.0", "vitest": "^4.1.9", - "wrangler": "^4.25.0" + "workerd": "1.20260625.1" }, "overrides": { - "sharp": "0.35.3", "postcss": "^8.5.18", "undici": "7.29.0" } diff --git a/scripts/cloudflare-worker-deploy.mjs b/scripts/cloudflare-worker-deploy.mjs new file mode 100644 index 000000000..db25df84a --- /dev/null +++ b/scripts/cloudflare-worker-deploy.mjs @@ -0,0 +1,234 @@ +#!/usr/bin/env node +import { execFileSync } from "node:child_process"; +import { readFile, rm } from "node:fs/promises"; +import { mkdtemp } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { build } from "esbuild"; +import { + readNoemaWorkerConfig, + validateExistingDurableObjectBindings, +} from "./lib/cloudflare-worker-config.mjs"; + +const API_ORIGIN = "https://api.cloudflare.com"; +const API_PREFIX = "/client/v4"; +const REPOSITORY_URL = "https://github.com/ContextualWisdomLab/noema"; +const REQUIRED_SECRET_BINDINGS = ["GITHUB_APP_ID", "GITHUB_APP_PRIVATE_KEY_PEM"]; +const OPTIONAL_SECRET_BINDINGS = ["GITHUB_APP_INSTALLATION_ID"]; +const MAX_RESPONSE_BYTES = 1024 * 1024; +const SHA_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u; +const ACCOUNT_ID_PATTERN = /^[A-Za-z0-9_-]{1,32}$/u; +const SCRIPT_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u; + +function requiredEnvironment(name) { + const value = process.env[name]?.trim(); + if (!value) throw new Error(`Missing required environment variable: ${name}`); + return value; +} + +function repositorySourceSha(repositoryRoot) { + const head = execFileSync("git", ["rev-parse", "HEAD"], { + cwd: repositoryRoot, + encoding: "utf8", + }).trim().toLowerCase(); + if (!SHA_PATTERN.test(head)) throw new Error("Repository HEAD is not a full commit SHA"); + + const dirty = execFileSync("git", ["status", "--porcelain=v1", "--untracked-files=all"], { + cwd: repositoryRoot, + encoding: "utf8", + }); + if (dirty !== "") { + throw new Error("Refusing deployment from a dirty checkout; commit the exact source first"); + } + + const declared = process.env.GITHUB_SHA?.trim().toLowerCase(); + if (declared && declared !== head) { + throw new Error("GITHUB_SHA does not match the exact checked-out repository HEAD"); + } + if (process.env.GITHUB_REPOSITORY && process.env.GITHUB_REPOSITORY !== "ContextualWisdomLab/noema") { + throw new Error("GITHUB_REPOSITORY does not identify ContextualWisdomLab/noema"); + } + return head; +} + +async function parseCloudflareResponse(response, operation) { + const text = await response.text(); + if (Buffer.byteLength(text, "utf8") > MAX_RESPONSE_BYTES) { + throw new Error(`${operation} returned an oversized response`); + } + let payload; + try { + payload = JSON.parse(text); + } catch { + throw new Error(`${operation} returned non-JSON data (HTTP ${response.status})`); + } + if (!response.ok || payload?.success === false) { + const codes = Array.isArray(payload?.errors) + ? payload.errors.map((error) => error?.code).filter(Boolean).join(",") + : ""; + throw new Error(`${operation} failed (HTTP ${response.status}${codes ? `; codes=${codes}` : ""})`); + } + return payload?.result ?? payload; +} + +async function cloudflareJson(url, token, operation, init = {}) { + const response = await fetch(url, { + ...init, + headers: { + authorization: `Bearer ${token}`, + ...(init.headers ?? {}), + }, + signal: AbortSignal.timeout(120_000), + }); + return parseCloudflareResponse(response, operation); +} + +function verifyExistingRuntimeBindings(config, settings) { + const current = validateExistingDurableObjectBindings(config, settings); + for (const secretName of REQUIRED_SECRET_BINDINGS) { + if (current.get(secretName)?.type !== "secret_text") { + throw new Error(`Existing Worker is missing required secret binding: ${secretName}`); + } + } + return current; +} + +function uploadBindings(config, currentBindings) { + const bindings = [ + ...Object.entries(config.vars).map(([name, text]) => ({ + type: "plain_text", + name, + text, + })), + ...config.durableObjects.map(({ name, class_name }) => ({ + type: "durable_object_namespace", + name, + class_name, + })), + ...REQUIRED_SECRET_BINDINGS.map((name) => ({ + type: "inherit", + name, + version_id: "latest", + })), + ]; + for (const name of OPTIONAL_SECRET_BINDINGS) { + if (currentBindings.get(name)?.type === "secret_text") { + bindings.push({ type: "inherit", name, version_id: "latest" }); + } + } + return bindings; +} + +async function bundleWorker(repositoryRoot, entryPoint, outputFile) { + await build({ + absWorkingDir: repositoryRoot, + entryPoints: [entryPoint], + outfile: outputFile, + bundle: true, + format: "esm", + platform: "browser", + target: "es2022", + conditions: ["workerd", "worker", "browser"], + sourcemap: false, + legalComments: "none", + logLevel: "warning", + }); +} + +async function main() { + const repositoryRoot = resolve(fileURLToPath(new URL("..", import.meta.url))); + const config = await readNoemaWorkerConfig(repositoryRoot); + const accountId = requiredEnvironment("CLOUDFLARE_ACCOUNT_ID"); + const apiToken = requiredEnvironment("CLOUDFLARE_API_TOKEN"); + const scriptName = process.env.CLOUDFLARE_WORKER_NAME?.trim() || config.name; + if (!ACCOUNT_ID_PATTERN.test(accountId)) throw new Error("CLOUDFLARE_ACCOUNT_ID is malformed"); + if (!SCRIPT_NAME_PATTERN.test(scriptName)) throw new Error("CLOUDFLARE_WORKER_NAME is malformed"); + + const sourceSha = repositorySourceSha(repositoryRoot); + const encodedAccount = encodeURIComponent(accountId); + const encodedScript = encodeURIComponent(scriptName); + const settingsUrl = `${API_ORIGIN}${API_PREFIX}/accounts/${encodedAccount}/workers/scripts/${encodedScript}/settings`; + const settings = await cloudflareJson(settingsUrl, apiToken, "Worker settings read"); + const currentBindings = verifyExistingRuntimeBindings(config, settings); + + const temporaryDirectory = await mkdtemp(join(tmpdir(), "noema-worker-deploy-")); + const moduleName = "worker.mjs"; + const outputFile = join(temporaryDirectory, moduleName); + try { + await bundleWorker(repositoryRoot, config.main, outputFile); + const moduleBytes = await readFile(outputFile); + const metadata = { + main_module: moduleName, + compatibility_date: config.compatibilityDate, + annotations: { + "workers/commit_sha": sourceSha, + "workers/repository_url": REPOSITORY_URL, + "workers/message": `Noema source ${sourceSha}`, + "workers/tag": sourceSha.slice(0, 12), + }, + exports: config.exports, + bindings: uploadBindings(config, currentBindings), + }; + const form = new FormData(); + form.append( + "metadata", + new Blob([JSON.stringify(metadata)], { type: "application/json" }), + "metadata.json", + ); + form.append( + moduleName, + new Blob([moduleBytes], { type: "application/javascript+module" }), + moduleName, + ); + + const versionsPath = `/accounts/${encodedAccount}/workers/scripts/${encodedScript}/versions`; + const version = await cloudflareJson( + `${API_ORIGIN}${API_PREFIX}${versionsPath}?bindings_inherit=strict`, + apiToken, + "Worker version upload", + { method: "POST", body: form }, + ); + const versionId = version?.id; + if (typeof versionId !== "string" || versionId.length === 0) { + throw new Error("Worker version upload returned no version id"); + } + + const deployment = await cloudflareJson( + `${API_ORIGIN}${API_PREFIX}/accounts/${encodedAccount}/workers/scripts/${encodedScript}/deployments`, + apiToken, + "Worker deployment", + { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + strategy: "percentage", + versions: [{ version_id: versionId, percentage: 100 }], + annotations: { + "workers/message": `Deploy Noema ${sourceSha}`, + "workers/triggered_by": "noema-direct-api-toolchain", + }, + }), + }, + ); + const deploymentId = deployment?.id; + if (typeof deploymentId !== "string" || deploymentId.length === 0) { + throw new Error("Worker deployment returned no deployment id"); + } + + process.stdout.write(`${JSON.stringify({ + worker: scriptName, + source_sha: sourceSha, + version_id: versionId, + deployment_id: deploymentId, + })}\n`); + } finally { + await rm(temporaryDirectory, { recursive: true, force: true }); + } +} + +main().catch((error) => { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`Noema Worker deployment failed: ${message}\n`); + process.exitCode = 1; +}); diff --git a/scripts/cloudflare-worker-dev.mjs b/scripts/cloudflare-worker-dev.mjs new file mode 100644 index 000000000..145b5c3ea --- /dev/null +++ b/scripts/cloudflare-worker-dev.mjs @@ -0,0 +1,165 @@ +#!/usr/bin/env node +import { spawn } from "node:child_process"; +import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { build } from "esbuild"; +import { + localDurableObjectStorageKey, + readNoemaWorkerConfig, +} from "./lib/cloudflare-worker-config.mjs"; + +const REQUIRED_LOCAL_SECRETS = ["GITHUB_APP_ID", "GITHUB_APP_PRIVATE_KEY_PEM"]; +const OPTIONAL_LOCAL_SECRETS = ["GITHUB_APP_INSTALLATION_ID"]; +const workerdCommand = "workerd serve"; + +function capnpText(value) { + return JSON.stringify(String(value)); +} + +function requireLocalSecrets() { + for (const name of REQUIRED_LOCAL_SECRETS) { + if (!process.env[name]) throw new Error(`Missing required local Worker binding: ${name}`); + } +} + +function bindingLines(config) { + const lines = []; + for (const [name, value] of Object.entries(config.vars)) { + lines.push(` (name = ${capnpText(name)}, text = ${capnpText(value)})`); + } + for (const { name, class_name } of config.durableObjects) { + lines.push( + ` (name = ${capnpText(name)}, durableObjectNamespace = ${capnpText(class_name)})`, + ); + } + for (const name of REQUIRED_LOCAL_SECRETS) { + lines.push(` (name = ${capnpText(name)}, fromEnvironment = ${capnpText(name)})`); + } + for (const name of OPTIONAL_LOCAL_SECRETS) { + if (process.env[name]) { + lines.push(` (name = ${capnpText(name)}, fromEnvironment = ${capnpText(name)})`); + } + } + return lines.join(",\n"); +} + +function durableObjectNamespaceLines(config) { + return config.durableObjects.map((binding) => [ + " (", + ` className = ${capnpText(binding.class_name)},`, + ` uniqueKey = ${capnpText(localDurableObjectStorageKey(binding))},`, + " enableSql = true", + " )", + ].join("\n")).join(",\n"); +} + +function workerdConfig(config, storageDirectory) { + return `using Workerd = import "/workerd/workerd.capnp"; + +const config :Workerd.Config = ( + services = [ + (name = "main", worker = .mainWorker), + (name = "do-storage", disk = (path = ${capnpText(storageDirectory)}, writable = true)), + (name = "internet", network = (allow = ["public"], tlsOptions = (trustBrowserCas = true))) + ], + sockets = [ + ( + name = "http", + address = "127.0.0.1:8787", + http = (), + service = "main" + ) + ] +); + +const mainWorker :Workerd.Worker = ( + modules = [(name = "worker.mjs", esModule = embed "worker.mjs")], + compatibilityDate = ${capnpText(config.compatibilityDate)}, + bindings = [ +${bindingLines(config)} + ], + durableObjectNamespaces = [ +${durableObjectNamespaceLines(config)} + ], + durableObjectStorage = (localDisk = "do-storage") +); +`; +} + +async function bundleWorker(repositoryRoot, config, outputFile) { + await build({ + absWorkingDir: repositoryRoot, + entryPoints: [config.main], + outfile: outputFile, + bundle: true, + format: "esm", + platform: "browser", + target: "es2022", + conditions: ["workerd", "worker", "browser"], + sourcemap: false, + legalComments: "none", + logLevel: "warning", + }); +} + +async function runWorkerd(executable, configPath, repositoryRoot) { + const child = spawn(executable, ["serve", configPath], { + cwd: repositoryRoot, + env: process.env, + stdio: "inherit", + }); + const forwardSignal = (signal) => { + if (!child.killed) child.kill(signal); + }; + process.once("SIGINT", forwardSignal); + process.once("SIGTERM", forwardSignal); + try { + return await new Promise((resolvePromise, reject) => { + child.once("error", reject); + child.once("exit", (code, signal) => { + if (signal) reject(new Error(`${workerdCommand} exited from signal ${signal}`)); + else resolvePromise(code ?? 1); + }); + }); + } finally { + process.removeListener("SIGINT", forwardSignal); + process.removeListener("SIGTERM", forwardSignal); + } +} + +async function main() { + const repositoryRoot = resolve(fileURLToPath(new URL("..", import.meta.url))); + const config = await readNoemaWorkerConfig(repositoryRoot); + requireLocalSecrets(); + + const executable = join( + repositoryRoot, + "node_modules", + ".bin", + process.platform === "win32" ? "workerd.cmd" : "workerd", + ); + await access(executable); + + const storageDirectory = join(repositoryRoot, ".noema-dev", "durable-objects"); + await mkdir(storageDirectory, { recursive: true }); + const temporaryDirectory = await mkdtemp(join(tmpdir(), "noema-worker-dev-")); + const outputFile = join(temporaryDirectory, "worker.mjs"); + const configPath = join(temporaryDirectory, "config.capnp"); + + try { + await bundleWorker(repositoryRoot, config, outputFile); + await writeFile(configPath, workerdConfig(config, storageDirectory), { mode: 0o600 }); + const exitCode = await runWorkerd(executable, configPath, repositoryRoot); + if (exitCode !== 0) throw new Error(`${workerdCommand} exited with code ${exitCode}`); + } finally { + await rm(temporaryDirectory, { recursive: true, force: true }); + } +} + +main().catch((error) => { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`Noema local Worker failed: ${message}\n`); + process.exitCode = 1; +}); diff --git a/scripts/lib/cloudflare-worker-config.mjs b/scripts/lib/cloudflare-worker-config.mjs new file mode 100644 index 000000000..6d7e1a595 --- /dev/null +++ b/scripts/lib/cloudflare-worker-config.mjs @@ -0,0 +1,164 @@ +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +const ROOT_KEYS = new Set(["name", "main", "compatibility_date"]); +const DURABLE_OBJECT_KEYS = new Set(["name", "class_name"]); +const EXPORT_KEYS = new Set(["type", "storage"]); +const ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"([^"\\]*)"$/; +const EXPORT_SECTION = /^\[exports\.([A-Za-z_][A-Za-z0-9_]*)\]$/; + +function assignUnique(target, key, value, context) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + throw new Error(`Duplicate ${context} key: ${key}`); + } + target[key] = value; +} + +/** + * Derive the persistent local workerd namespace identity from the binding authority. + * + * Workerd uses `uniqueKey` as the durable namespace identity. Binding order and implementation + * class names may change without intending to replace a namespace, so neither can participate in + * the key. Renaming the binding is the explicit local namespace replacement boundary. + */ +export function localDurableObjectStorageKey(binding) { + return `noema-local-${binding.name}`; +} + +/** + * Validate already-provisioned Durable Object bindings without rejecting newly declared exports. + * + * A missing binding is allowed because Cloudflare's declarative `exports` reconciliation creates + * a new namespace during the version upload. If a binding already exists, however, its type and + * class identity must match exactly so a deployment cannot silently attach Noema to foreign state. + */ +export function validateExistingDurableObjectBindings(config, settings) { + const bindings = Array.isArray(settings?.bindings) ? settings.bindings : []; + const current = new Map(bindings.map((binding) => [binding?.name, binding])); + + for (const durableObject of config.durableObjects) { + const binding = current.get(durableObject.name); + if (binding === undefined) continue; + if ( + binding?.type !== "durable_object_namespace" + || binding?.class_name !== durableObject.class_name + ) { + throw new Error(`Existing Durable Object binding does not match ${durableObject.name}`); + } + } + return current; +} + +/** + * Read the narrow Worker configuration surface that Noema owns. + * + * The parser is intentionally fail-closed instead of implementing general TOML. It accepts + * only the root identity, Durable Object bindings/exports, and plain-text vars currently used + * by Noema. Any new configuration shape must receive an explicit adapter decision rather than + * being silently omitted from direct Cloudflare API uploads or local workerd development. + */ +export async function readNoemaWorkerConfig(repositoryRoot) { + const source = await readFile(join(repositoryRoot, "wrangler.toml"), "utf8"); + const root = {}; + const durableObjects = []; + const exportsByClass = new Map(); + const vars = {}; + let section = "root"; + let currentDurableObject = null; + let currentExport = null; + + for (const [index, rawLine] of source.split(/\r?\n/u).entries()) { + const line = rawLine.trim(); + if (line === "" || line.startsWith("#")) continue; + + if (line === "[[durable_objects.bindings]]") { + currentDurableObject = {}; + durableObjects.push(currentDurableObject); + currentExport = null; + section = "durable-object"; + continue; + } + if (line === "[vars]") { + currentDurableObject = null; + currentExport = null; + section = "vars"; + continue; + } + const exportMatch = EXPORT_SECTION.exec(line); + if (exportMatch) { + const className = exportMatch[1]; + if (exportsByClass.has(className)) { + throw new Error(`Duplicate Worker export section: ${className}`); + } + currentExport = {}; + exportsByClass.set(className, currentExport); + currentDurableObject = null; + section = "export"; + continue; + } + if (line.startsWith("[") || line.startsWith("[[")) { + throw new Error(`Unsupported Worker configuration section at line ${index + 1}: ${line}`); + } + + const assignment = ASSIGNMENT.exec(line); + if (!assignment) { + throw new Error(`Unsupported Worker configuration syntax at line ${index + 1}`); + } + const [, key, value] = assignment; + + if (section === "root") { + if (!ROOT_KEYS.has(key)) throw new Error(`Unsupported root Worker key: ${key}`); + assignUnique(root, key, value, "root Worker"); + continue; + } + if (section === "durable-object") { + if (!currentDurableObject || !DURABLE_OBJECT_KEYS.has(key)) { + throw new Error(`Unsupported Durable Object binding key: ${key}`); + } + assignUnique(currentDurableObject, key, value, "Durable Object binding"); + continue; + } + if (section === "export") { + if (!currentExport || !EXPORT_KEYS.has(key)) { + throw new Error(`Unsupported Worker export key: ${key}`); + } + assignUnique(currentExport, key, value, "Worker export"); + continue; + } + assignUnique(vars, key, value, "Worker var"); + } + + for (const required of ROOT_KEYS) { + if (!root[required]) throw new Error(`Missing required Worker key: ${required}`); + } + if (durableObjects.length === 0) throw new Error("No Durable Object bindings configured"); + + for (const binding of durableObjects) { + if (!binding.name || !binding.class_name) { + throw new Error("Durable Object bindings require name and class_name"); + } + const exported = exportsByClass.get(binding.class_name); + if (!exported || exported.type !== "durable-object" || exported.storage !== "sqlite") { + throw new Error(`Durable Object export ${binding.class_name} must remain durable-object/sqlite`); + } + } + if (exportsByClass.size !== durableObjects.length) { + throw new Error("Every Worker export must correspond to exactly one Durable Object binding"); + } + + const exports = Object.fromEntries( + [...exportsByClass.entries()].map(([className, exported]) => [ + className, + Object.freeze({ ...exported }), + ]), + ); + + return Object.freeze({ + name: root.name, + main: root.main, + compatibilityDate: root.compatibility_date, + durableObjects: durableObjects.map((binding) => Object.freeze({ ...binding })), + exports: Object.freeze(exports), + vars: Object.freeze({ ...vars }), + }); +} diff --git a/scripts/lockfile-change-policy-candidate.mjs b/scripts/lockfile-change-policy-candidate.mjs new file mode 100644 index 000000000..c2f2bddc1 --- /dev/null +++ b/scripts/lockfile-change-policy-candidate.mjs @@ -0,0 +1,82 @@ +import { readFileSync } from "node:fs"; +import { + lockfileMetadataDigest, + lockfilePackagesDigest, + packageObjectDigest, +} from "./lockfile-change-control.mjs"; + +function parseLockfile(path) { + const value = JSON.parse(readFileSync(path, "utf8")); + if (value === null || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`lockfile at ${path} must be a JSON object`); + } + if (value.packages === null || typeof value.packages !== "object" || Array.isArray(value.packages)) { + throw new Error(`lockfile at ${path} must contain a packages object`); + } + return value; +} + +/** + * Build exact schema-v3 lockfile change-control evidence from one reviewed base/head pair. + * + * The candidate is diagnostic only: writing it to the policy file still requires review of the + * changed package set, justification, and source provenance. Reusing the enforcement gate's + * exported digest functions prevents an independent hashing implementation from drifting. + */ +export function buildLockfileChangePolicyCandidate({ basePath, headPath, baseSha }) { + if (typeof baseSha !== "string" || !/^[0-9a-f]{40}$/u.test(baseSha)) { + throw new Error("candidate generation requires an exact lowercase 40-character base SHA"); + } + const base = parseLockfile(basePath); + const head = parseLockfile(headPath); + const packageKeys = [...new Set([ + ...Object.keys(base.packages), + ...Object.keys(head.packages), + ])].sort(); + const targetPackages = packageKeys.filter( + (packagePath) => packageObjectDigest(base.packages[packagePath]) !== packageObjectDigest(head.packages[packagePath]), + ); + const packageDigests = Object.fromEntries( + targetPackages.map((packagePath) => [ + packagePath, + { + afterSha256: packageObjectDigest(head.packages[packagePath]), + beforeSha256: packageObjectDigest(base.packages[packagePath]), + }, + ]), + ); + const bulkChange = targetPackages.length <= 128 + ? null + : { + afterPackagesSha256: lockfilePackagesDigest(head), + beforePackagesSha256: lockfilePackagesDigest(base), + targetPackageCount: targetPackages.length, + }; + return { + baseSha, + bulkChange, + justification: "REVIEW REQUIRED: describe why this exact lockfile package set changes and what unrelated package metadata is preserved.", + packageDigests, + schemaVersion: 3, + sources: ["https://review-required.invalid/replace-with-reviewed-provenance"], + targetPackages, + topLevelMetadataDigests: { + afterSha256: lockfileMetadataDigest(head), + beforeSha256: lockfileMetadataDigest(base), + }, + }; +} + +if (import.meta.url === `file://${process.argv[1]}`) { + const basePath = process.env.NOEMA_LOCKFILE_BASE_PATH; + const baseSha = process.env.NOEMA_LOCKFILE_BASE_SHA; + if (!basePath || !baseSha) { + throw new Error("NOEMA_LOCKFILE_BASE_PATH and NOEMA_LOCKFILE_BASE_SHA are required"); + } + const candidate = buildLockfileChangePolicyCandidate({ + basePath, + headPath: "package-lock.json", + baseSha, + }); + process.stdout.write(`${JSON.stringify(candidate, null, 2)}\n`); +} diff --git a/test/cloudflare-toolchain-license-boundary.test.ts b/test/cloudflare-toolchain-license-boundary.test.ts new file mode 100644 index 000000000..857e1807c --- /dev/null +++ b/test/cloudflare-toolchain-license-boundary.test.ts @@ -0,0 +1,63 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +function readJson(path: string): Record { + return JSON.parse(readFileSync(new URL(path, import.meta.url), "utf8")) as Record; +} + +describe("Cloudflare Worker toolchain license boundary", () => { + it("keeps Wrangler, Miniflare, Sharp, and libvips out of the committed dependency graph", () => { + const pkg = readJson("../package.json") as { + scripts?: Record; + devDependencies?: Record; + }; + const lockText = readFileSync(new URL("../package-lock.json", import.meta.url), "utf8"); + + expect(pkg.devDependencies?.wrangler).toBeUndefined(); + expect(pkg.devDependencies?.esbuild).toBe("0.28.1"); + expect(pkg.devDependencies?.workerd).toBe("1.20260625.1"); + expect(pkg.scripts?.deploy).toBe("node scripts/cloudflare-worker-deploy.mjs"); + expect(pkg.scripts?.dev).toBe("node scripts/cloudflare-worker-dev.mjs"); + + for (const forbidden of [ + '"node_modules/wrangler"', + '"node_modules/miniflare"', + '"node_modules/sharp"', + '"node_modules/@img/sharp-libvips-', + '"LGPL-3.0', + '"GPL-3.0', + '"AGPL-3.0', + ]) { + expect(lockText).not.toContain(forbidden); + } + }); + + it("uses a direct Cloudflare API deployment boundary with immutable source and lifecycle metadata", () => { + const deploy = readFileSync( + new URL("../scripts/cloudflare-worker-deploy.mjs", import.meta.url), + "utf8", + ); + + expect(deploy).toContain("/workers/scripts/${encodedScript}/versions"); + expect(deploy).toContain('type: "durable_object_namespace"'); + expect(deploy).toContain("exports: config.exports"); + expect(deploy).toContain('"workers/commit_sha"'); + expect(deploy).toContain("CLOUDFLARE_API_TOKEN"); + expect(deploy).not.toContain("wrangler"); + expect(deploy).not.toContain("miniflare"); + }); + + it("runs local development on pinned workerd with local-only Durable Object storage", () => { + const dev = readFileSync( + new URL("../scripts/cloudflare-worker-dev.mjs", import.meta.url), + "utf8", + ); + + expect(dev).toContain("workerd serve"); + expect(dev).toContain("durableObjectNamespaces"); + expect(dev).toContain("localDisk"); + expect(dev).toContain('address = "127.0.0.1:8787"'); + expect(dev).not.toContain("wrangler"); + expect(dev).not.toContain("miniflare"); + }); +}); diff --git a/test/cloudflare-worker-config.test.mjs b/test/cloudflare-worker-config.test.mjs new file mode 100644 index 000000000..e54bd4da5 --- /dev/null +++ b/test/cloudflare-worker-config.test.mjs @@ -0,0 +1,150 @@ +import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { + localDurableObjectStorageKey, + readNoemaWorkerConfig, + validateExistingDurableObjectBindings, +} from "../scripts/lib/cloudflare-worker-config.mjs"; + +const temporaryRoots = []; + +async function fixture(source) { + const root = await mkdtemp(join(tmpdir(), "noema-worker-config-")); + temporaryRoots.push(root); + await mkdir(root, { recursive: true }); + await writeFile(join(root, "wrangler.toml"), source, "utf8"); + return root; +} + +const validConfig = ` +name = "noema" +main = "src/runtime-entrypoint.ts" +compatibility_date = "2026-06-30" + +[[durable_objects.bindings]] +name = "NOEMA_RATE_LIMITER" +class_name = "NoemaRateLimiter" + +[exports.NoemaRateLimiter] +type = "durable-object" +storage = "sqlite" + +[vars] +ALLOWED_ISSUER = "https://token.actions.githubusercontent.com" +`; + +afterEach(async () => { + await Promise.all( + temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })), + ); +}); + +describe("Noema Worker configuration adapter", () => { + it("preserves Worker identity, Durable Object bindings/exports, and plain-text vars", async () => { + const root = await fixture(validConfig); + + await expect(readNoemaWorkerConfig(root)).resolves.toEqual({ + name: "noema", + main: "src/runtime-entrypoint.ts", + compatibilityDate: "2026-06-30", + durableObjects: [ + { name: "NOEMA_RATE_LIMITER", class_name: "NoemaRateLimiter" }, + ], + exports: { + NoemaRateLimiter: { type: "durable-object", storage: "sqlite" }, + }, + vars: { + ALLOWED_ISSUER: "https://token.actions.githubusercontent.com", + }, + }); + }); + + it("fails closed when an unimplemented configuration section appears", async () => { + const root = await fixture(`${validConfig}\n[observability]\nenabled = "true"\n`); + + await expect(readNoemaWorkerConfig(root)).rejects.toThrow( + /Unsupported Worker configuration section/u, + ); + }); + + it("fails closed when a root field would be silently omitted", async () => { + // The unrecognized key must appear while the parser is still in the "root" section + // (i.e. before any `[[...]]`/`[section]` header). TOML section scoping means a line + // appended after `[vars]` belongs to `vars`, not root, and Noema's vars section is + // intentionally open-ended (operator-configured key/value pairs) rather than allow-listed. + const root = await fixture( + validConfig.replace( + 'compatibility_date = "2026-06-30"', + 'compatibility_date = "2026-06-30"\ncompatibility_flags = "nodejs_compat"', + ), + ); + + await expect(readNoemaWorkerConfig(root)).rejects.toThrow( + /Unsupported root Worker key: compatibility_flags/u, + ); + }); + + it("rejects duplicate configuration authority", async () => { + const root = await fixture(validConfig.replace( + 'ALLOWED_ISSUER = "https://token.actions.githubusercontent.com"', + 'ALLOWED_ISSUER = "https://token.actions.githubusercontent.com"\nALLOWED_ISSUER = "https://example.invalid"', + )); + + await expect(readNoemaWorkerConfig(root)).rejects.toThrow(/Duplicate Worker var key/u); + }); + + it("requires every Durable Object binding to keep its declared sqlite export", async () => { + const root = await fixture(validConfig.replace('storage = "sqlite"', 'storage = "memory"')); + + await expect(readNoemaWorkerConfig(root)).rejects.toThrow( + /must remain durable-object\/sqlite/u, + ); + }); + + it("permits a newly declared Durable Object while rejecting drift in an existing binding", () => { + const config = { + durableObjects: [ + { name: "NOEMA_RATE_LIMITER", class_name: "NoemaRateLimiter" }, + { name: "NOEMA_WORKFLOW_STATE", class_name: "NoemaWorkflowState" }, + ], + }; + + expect(() => validateExistingDurableObjectBindings(config, { + bindings: [ + { + type: "durable_object_namespace", + name: "NOEMA_RATE_LIMITER", + class_name: "NoemaRateLimiter", + }, + ], + })).not.toThrow(); + + expect(() => validateExistingDurableObjectBindings(config, { + bindings: [ + { + type: "durable_object_namespace", + name: "NOEMA_RATE_LIMITER", + class_name: "WrongClass", + }, + ], + })).toThrow(/Existing Durable Object binding does not match NOEMA_RATE_LIMITER/u); + }); + + it("keeps local Durable Object storage identity stable across class renames and declaration order", () => { + const original = { name: "NOEMA_RATE_LIMITER", class_name: "NoemaRateLimiter" }; + const renamedClass = { name: "NOEMA_RATE_LIMITER", class_name: "RenamedRateLimiter" }; + const other = { name: "NOEMA_OIDC_REPLAY_GUARD", class_name: "NoemaOidcReplayGuard" }; + + expect(localDurableObjectStorageKey(original)).toBe(localDurableObjectStorageKey(renamedClass)); + expect(localDurableObjectStorageKey(original)).toBe("noema-local-NOEMA_RATE_LIMITER"); + expect([ + localDurableObjectStorageKey(original), + localDurableObjectStorageKey(other), + ]).toEqual([ + "noema-local-NOEMA_RATE_LIMITER", + "noema-local-NOEMA_OIDC_REPLAY_GUARD", + ]); + }); +}); diff --git a/test/lockfile-reproducibility-workflow.test.ts b/test/lockfile-reproducibility-workflow.test.ts new file mode 100644 index 000000000..b011c7b2a --- /dev/null +++ b/test/lockfile-reproducibility-workflow.test.ts @@ -0,0 +1,40 @@ +import { existsSync, readFileSync } from "node:fs"; + +import { describe, expect, it } from "vitest"; + +const ciWorkflowPath = ".github/workflows/ci.yml"; +const retiredLockfileWorkflowPath = ".github/workflows/lockfile-reproducibility.yml"; +const validatorWorkflowPath = ".github/workflows/patch-validator-image.yml"; + +function readWorkflow(path: string): string { + return readFileSync(path, "utf8"); +} + +describe("Cloudflare toolchain lockfile and validator isolation", () => { + it("keeps canonical lockfile regeneration on the established application CI identity", () => { + const workflow = readWorkflow(ciWorkflowPath); + + expect(workflow).toContain("name: ci"); + expect(workflow).toContain("npm install"); + expect(workflow).toContain("--package-lock-only"); + expect(workflow).toContain("cmp --silent package-lock.json"); + expect(workflow).toContain("npm ci"); + expect(workflow).toContain("persist-credentials: false"); + expect(workflow).toContain("upload regenerated lockfile evidence"); + expect(workflow).toContain( + "test \"$(git rev-parse HEAD)\" = \"$NOEMA_EXPECTED_HEAD_SHA\"", + ); + expect(existsSync(retiredLockfileWorkflowPath)).toBe(false); + }); + + it("prunes builder-only workerd and esbuild from patch-validator dependencies", () => { + const workflow = readWorkflow(validatorWorkflowPath); + + expect(workflow).toContain("devDependencies.workerd"); + expect(workflow).toContain("devDependencies.esbuild"); + expect(workflow).toContain("test ! -e node_modules/workerd"); + expect(workflow).toContain("test ! -e node_modules/esbuild"); + expect(workflow).toContain("test ! -e node_modules/@esbuild"); + expect(workflow).toContain("test ! -e node_modules/miniflare"); + }); +}); \ No newline at end of file diff --git a/test/patch-validator-image-contract.test.ts b/test/patch-validator-image-contract.test.ts index 53be0d0e9..10d698699 100644 --- a/test/patch-validator-image-contract.test.ts +++ b/test/patch-validator-image-contract.test.ts @@ -154,9 +154,11 @@ describe("patch-validator image contract", () => { expect(dockerfile).not.toContain("npm_config_cpu=wasm32"); expect(imageWorkflow).toContain("npm_config_os=wasip1-threads"); expect(imageWorkflow).toContain("npm_config_cpu=wasm32"); - expect(imageWorkflow).toContain( - "npm pkg delete devDependencies.@cloudflare/workers-types devDependencies.wrangler", - ); + expect(imageWorkflow).toContain("npm pkg delete \\"); + expect(imageWorkflow).toContain("devDependencies.@cloudflare/workers-types \\"); + expect(imageWorkflow).toContain("devDependencies.wrangler \\"); + expect(imageWorkflow).toContain("devDependencies.workerd \\"); + expect(imageWorkflow).toContain("devDependencies.esbuild"); expect(imageWorkflow).toContain( "npm prune --include=optional --ignore-scripts --no-audit --no-fund", ); @@ -166,6 +168,8 @@ describe("patch-validator image contract", () => { expect(imageWorkflow).toContain("test ! -e node_modules/@cloudflare/workers-types"); expect(imageWorkflow).toContain("test ! -e node_modules/wrangler"); expect(imageWorkflow).toContain("test ! -e node_modules/workerd"); + expect(imageWorkflow).toContain("test ! -e node_modules/esbuild"); + expect(imageWorkflow).toContain("test ! -e node_modules/@esbuild"); expect(imageWorkflow).toContain("test ! -e node_modules/miniflare"); }); }); diff --git a/test/upload-artifact-node24-integrity.test.ts b/test/upload-artifact-node24-integrity.test.ts index e917f0b3a..9b2d8bd2d 100644 --- a/test/upload-artifact-node24-integrity.test.ts +++ b/test/upload-artifact-node24-integrity.test.ts @@ -10,6 +10,7 @@ const supportedWorkflowPaths = [ ".github/workflows/acquisition-readiness-scan.yml", ".github/workflows/cd.yml", ".github/workflows/central-review.yml", + ".github/workflows/ci.yml", ".github/workflows/hourly-commercial-readiness.yml", ".github/workflows/hourly-product-development.yml", ".github/workflows/maintainer-app-readiness.yml",