From 36b87b7780572186be75735e29da944d60dc961e Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Thu, 13 Aug 2026 02:26:28 -0400 Subject: [PATCH 1/2] [ABI] Prepare exact sysroots before request classification Clean exact-head request classification ran the candidate ABI check before either SDK sysroot existed. ABI 43 therefore produced an invalid structural report even though its snapshot was consistent. Materialize the exact pinned musl source, build both SDK sysroots inside the stripped candidate environment, and keep the classifier fail-closed. Bind the workflow change in request policy v14 and extend the mutation coverage. --- .../workflows/abi-staging-request-feed.yml | 38 ++++++--- abi/staging/request-policy.generated.json | 2 +- abi/staging/request-policy.toml | 2 +- scripts/check-abi-staging-request-workflow.rb | 79 +++++++++++++++++++ 4 files changed, 106 insertions(+), 15 deletions(-) diff --git a/.github/workflows/abi-staging-request-feed.yml b/.github/workflows/abi-staging-request-feed.yml index 6a76daeb61..c9c17ddc72 100644 --- a/.github/workflows/abi-staging-request-feed.yml +++ b/.github/workflows/abi-staging-request-feed.yml @@ -176,6 +176,9 @@ jobs: actual_tree=$(git -C "$head_root" rev-parse 'HEAD^{tree}') [[ $actual_head == "$head" ]] env -u GH_TOKEN -u GITHUB_TOKEN git -C "$head_root" fetch --no-tags origin "$PROTECTED_SHA" + bash authority/scripts/fetch-exact-musl-gitlink.sh \ + --source-root "$head_root" \ + --commit "$head" target_abi=$(sed -nE 's/^pub const ABI_VERSION: u32 = ([0-9]+);$/\1/p' \ "$head_root/crates/shared/src/lib.rs") @@ -185,25 +188,34 @@ jobs: check_log="$evidence/reports/$pr-check.log" set +e ( + set -euo pipefail cd "$head_root" candidate_home="$evidence/candidate-homes/$pr" mkdir -p "$candidate_home/tmp" chmod 700 "$candidate_home" "$candidate_home/tmp" nix_bin=$(command -v nix) bash_bin=$(command -v bash) - env -u GH_TOKEN -u GITHUB_TOKEN -u ACTIONS_RUNTIME_TOKEN \ - -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ - env -i \ - HOME="$candidate_home" \ - TMPDIR="$candidate_home/tmp" \ - CI=true \ - LOGNAME=kandelo \ - USER=kandelo \ - KANDELO_NIX_BIN="$nix_bin" \ - PATH=/usr/bin:/bin \ - "$bash_bin" scripts/dev-shell.sh \ - env ABI_CHECK_BASE_REF="$PROTECTED_SHA" \ - bash scripts/check-abi-version.sh + candidate_env=( + env -u GH_TOKEN -u GITHUB_TOKEN -u ACTIONS_RUNTIME_TOKEN + -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL + env -i + HOME="$candidate_home" + TMPDIR="$candidate_home/tmp" + CI=true + LOGNAME=kandelo + USER=kandelo + KANDELO_NIX_BIN="$nix_bin" + PATH=/usr/bin:/bin + ) + # The ABI snapshot build resolves both SDK architectures. A clean + # exact head has neither sysroot, so prepare both before checking. + "${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh \ + bash scripts/build-musl.sh 2>&1 | tail -c 131072 + "${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh \ + bash scripts/build-musl.sh --arch wasm64posix 2>&1 | tail -c 131072 + "${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh \ + env ABI_CHECK_BASE_REF="$PROTECTED_SHA" \ + bash scripts/check-abi-version.sh ) 2>&1 | head -c 1048576 >"$check_log" check_status=${PIPESTATUS[0]} set -e diff --git a/abi/staging/request-policy.generated.json b/abi/staging/request-policy.generated.json index 2cf67e2ce5..90d522c19f 100644 --- a/abi/staging/request-policy.generated.json +++ b/abi/staging/request-policy.generated.json @@ -1 +1 @@ -{"addressed_taps":["kandelo-dev/homebrew-tap-core"],"automatic_same_repository":true,"fork_authorization":"disabled","implementation":[{"path":".github/scripts/update-abi-staging-check.sh","sha256":"78d49f35fdf27d426dcae6d6a19aa3eeca4d8a879bda0df319b70a4d4cf31ff8"},{"path":".github/workflows/abi-staging-merge-gate.yml","sha256":"a238f389b670ac6a54438c5894ca73f0f897fb393dbe24e8ed31f93c018e568f"},{"path":".github/workflows/abi-staging-pages-canary.yml","sha256":"3f9ecd19eb0224b2c11402ed3828b6ac2de1f6e291c84cfdf2fe59bae3a18676"},{"path":".github/workflows/abi-staging-pr-check.yml","sha256":"85a9b363caafb3e20b909c4145e45ddf6e0bc871547a986213e56c2d859ccf3c"},{"path":".github/workflows/abi-staging-request-feed.yml","sha256":"3e868b33ce148d960d0ad7e61e5f58f327dae037af3b10d6068297805adbdb51"},{"path":"Cargo.lock","sha256":"80447a16efcee77c60f047afbb25725fd24967934a255af0b9def6e3f483721e"},{"path":"Cargo.toml","sha256":"e033e7c8489f41a9d12517e01be99d89054bd5ee831d74148702a50504c9ff02"},{"path":"abi/staging/evidence-definitions.generated.json","sha256":"2189a671c22b26164de85ac66e714592e30eef76666cc921441281ac21a1d30c"},{"path":"abi/staging/evidence-definitions.toml","sha256":"1ff246db2cf82357f348af2e0ae8bcc30fa149705b523562f507106ff60eb416"},{"path":"apps/browser-demos/abi-staging-browser-harness.config.ts","sha256":"314d59a2b912625b3878d4f46e04c9f256615ed5cdd7bd85ae7aef8351e99202"},{"path":"apps/browser-demos/abi-staging-browser-host.config.ts","sha256":"af348730b45bf7ecc60c1f03e518979000f709de580fa05066dac6372aa4bc2c"},{"path":"apps/browser-demos/abi-staging-browser-host.ts","sha256":"beca82b52c32b0ef56bcf2cdc1f04138213993cb43e117c09661feae2ccc7004"},{"path":"apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","sha256":"bc7d6655898d2fd7b712a20565723996a5d4cf9c23eb46989522aaa7d874ea98"},{"path":"apps/browser-demos/lib/homebrew-closed-acceptance.ts","sha256":"5e88d8ec55b7f495647c83db8c2db6e51254a6c437edeac45728c8a535e02b2d"},{"path":"apps/browser-demos/lib/mysql-client.ts","sha256":"bcb3b2fc913366bd9a828125cad80e41cdd1fac9ce37f3c2b45910463290b05c"},{"path":"apps/browser-demos/lib/redis-client.ts","sha256":"cde9e40502bdf6cb85f3b421273a023f290919c57f162ab71b7805c78d08a6ae"},{"path":"apps/browser-demos/package-lock.json","sha256":"12b6409372d589d931b39bbc3bce0e9ad01f0dd6a5dbce8dfbef854ab5f2193b"},{"path":"apps/browser-demos/package.json","sha256":"bac6058f02c11ecd55957bae288b0ffc2da4e5dc9ca1673447b1cfb416fcec2a"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/index.html","sha256":"889d1e8afab89d05ea02bc1c18459ccecfe585dab0b52d50aeff3dec979e08e0"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/main.ts","sha256":"19ac44a9631a42087111ce85dc65b60dd8fe5a14ec67d72492a382760a93f898"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","sha256":"f33bcec8e48bad54ffa9317ef6e16442e6328b3a093b3f208cb751054f582eaf"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","sha256":"d707a48c6355c9d451251be599f37f7c031fb82326b0b01bfa3af062d2b57c49"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","sha256":"77d08bd3b94ef4f74ce5b486045e2dd168ea8606a262249736c6372357a3de0f"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","sha256":"2ea2dfa31054b7b041ab342a1c1d1626088ac9f207df8c12d2040030457677bb"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","sha256":"60ff6a4cb6104f474e387b90638c2b71ec48587397fa19a9ef34da2dfb6192d9"},{"path":"apps/browser-demos/pages/network/network-demo-worker.ts","sha256":"a59eb6c7de64460456b43fec0930975987dee3c487acb8f2d6d4f93c2bcc9649"},{"path":"apps/browser-demos/playwright-closed-acceptance.ts","sha256":"430fbc4eea4a92ccd8a36c0aeb1c5b23aadf22e95d7151f09dadc4ce4f49b43b"},{"path":"apps/browser-demos/playwright-server-policy.ts","sha256":"eda5aa8651bc6854d1634be1c5ffdbf3a91a97c87b91d9b065f997e12afa420f"},{"path":"apps/browser-demos/playwright.config.ts","sha256":"e6d9a7cc6dab59be4d12aca97da856dffe5120facb1c5812225e3526cac37b42"},{"path":"apps/browser-demos/test/abi-staging-product-evidence.spec.ts","sha256":"e90b265133cfcde059c91d066261e243b761e20750ea8377e7679e4d9d877706"},{"path":"apps/browser-demos/test/support/terminal-command.ts","sha256":"919e0898cd9f317c95eb29d6a0fb70d398f18fdca6192f705b8eee5093eecac3"},{"path":"apps/browser-demos/vite.config.ts","sha256":"6d0565eac538e9d42fe4dffea659ec83b6f994a6e446fd7f7caba19233cecb0d"},{"path":"flake.lock","sha256":"2df2e3397a8c75ecf4fb6c897959650c76f27f65721bf3ea5a08304b480ad6d8"},{"path":"flake.nix","sha256":"86a72ab6b1c9c6307c1d8dc9c42859a7d5868d4f8703962ddb08ca0993564e01"},{"path":"host/package-lock.json","sha256":"fc1a12528aa0f0d00fdb4895864b9ebf288d0e5b908bee5880e908128a2682dd"},{"path":"host/package.json","sha256":"a1e154db420da65f58095771b6fd7fd1b30141646cdcc98e30ad359553bf2be0"},{"path":"host/src/generated/abi.ts","sha256":"9d557cf739333272dc386b4bd593d8c8f5c08f968955d5374d9a8e0254125fc4"},{"path":"host/src/homebrew-bottle-relocation.ts","sha256":"598cf3a732253f35ca041ec99f60aac9494b510f65b5411c733ce332eefdf4c1"},{"path":"host/src/homebrew-guest-layout.ts","sha256":"84866dbb5730ff6c6df1a6cfe23384f50ac9e438b18e40b20678b2b63b9eccef"},{"path":"host/src/pathconf.ts","sha256":"ae331cf37a7173ee1cf7beec5782368a2d82034da13b426b084066556202ed3c"},{"path":"host/src/statfs.ts","sha256":"3e11cd17f6460b7ee5965738f5a04295b3aec5da14c9c0251c6a2dc1283194a7"},{"path":"host/src/vfs/closed-lazy-assets.ts","sha256":"278e8cd36cd1c47ad876fdf310af4014d91e174f3717acda55c125c0b7d269fd"},{"path":"host/src/vfs/deferred-tree-limits.ts","sha256":"1a0cc35809f3708724a68a99a08890c1072267a74bc4c63eab0cb774f0332da6"},{"path":"host/src/vfs/hardlink-graph.ts","sha256":"9d19366a7e739d55ef81cea074ab9633f255e65f9332e5aa82da70b1d1ccd1cf"},{"path":"host/src/vfs/load-image.ts","sha256":"df62f1b2faf98fb2e9900a6f6b428ae16e7f43494a6ade01f709006ef764744b"},{"path":"host/src/vfs/memory-fs.ts","sha256":"b197061158467e779ea3999dd7f5ba75a6894efd756d54c050c60a31156ddd19"},{"path":"host/src/vfs/product-mount-contract.ts","sha256":"2aeb61b4eff2689a7c054a8919fe622f46e621006d64050390a04af3ef05f4f0"},{"path":"host/src/vfs/sharedfs-vendor.ts","sha256":"a5bf8c9691b4d0ab170e68b0208db3b54fece2a4924e98ad472a36f28b441088"},{"path":"host/tsup.config.ts","sha256":"1d811ee5e7d2c07d4731c2e6df9181be6e511ecf4d1724495085d22f4af51a47"},{"path":"images/vfs/scripts/repository-path-bundle.ts","sha256":"64bad99dca7b32d57c104a9da1dd4977f0e8de56dc5950fd94bc3cda19ac987a"},{"path":"package-lock.json","sha256":"a46b2c8b6c8e56dd235a826c266a6fad7d702a340eb0b42cf0b6c99aaa428c71"},{"path":"package.json","sha256":"7411d3663ef7a3458da32f39e3919bda133377246ff629fc4db98f75792db2df"},{"path":"packages/registry/program-packages.json","sha256":"eea178faf24242ab946b97edaa8338f21165865d76983c7faf402e1e368238d3"},{"path":"scripts/abi-staging-build-toolchain.sh","sha256":"ed73bcd74bb7e2c409c2ae6887b4717c97b82f35e4f3347f523a7a240f47e815"},{"path":"scripts/abi-staging-collect-product-inputs.ts","sha256":"4ecd65b0789e21cb0ba3c5d133c9f3589da280258d382a3c208d859eefa03221"},{"path":"scripts/abi-staging-pages-producer.ts","sha256":"067fbecd469f83e266ab71db076d8a6be904a43986d74320caa80bd07dfd777e"},{"path":"scripts/abi-staging-pages-readiness.ts","sha256":"a254cf3f7fedef367ecc6fb91fa2175fc255f87a6458c02d0bf4caad23019f3b"},{"path":"scripts/abi-staging-pages-site-builder.ts","sha256":"97ce037272c1f8200ae48a73d027371f90c13dbd16b7194846b261eb51326be7"},{"path":"scripts/abi-staging-prepare-runtime.sh","sha256":"38ff5658d1147c6f82c542559078204e89decc2475f6ddb626673cafb1961c26"},{"path":"scripts/abi-staging-product-browser-evidence.ts","sha256":"458c31795759e2745f8eb071603f51c947f123cdea51cea4d7efebbfb3aa2a98"},{"path":"scripts/abi-staging-product-input-sources.ts","sha256":"eca75b92c09a5a28c745ecc6ae6e2407b9637c8710d022b76f7aab9e05e5be14"},{"path":"scripts/abi-staging-product-node-evidence.ts","sha256":"786fe35d64cbbf7b4898b9ac58f5c4483d8cc6f2b90f6644beb782126034ff15"},{"path":"scripts/abi-staging-protected-browser-operation.ts","sha256":"858b417d9562cf8618a416893d47e3bc616cedb7b1cfed673c467f1d0737b935"},{"path":"scripts/check-dev-shell-tools.sh","sha256":"f9187fc855b9949cb20ac7593ea17292948e525684bec603efcabea176096185"},{"path":"scripts/check-pages-vfs-product-registry.mjs","sha256":"6cbb24557f1e1a6961bd0fa7f11cbf2bae3035f5458971cf75ca31a891af2b0b"},{"path":"scripts/dev-shell.sh","sha256":"3931770c7b9f50ccee5a89e2957ed7a9856d4713e7fb6cb2631b29d0c6c4cff7"},{"path":"scripts/fetch-exact-musl-gitlink.sh","sha256":"8a14b1c97e2331f104d6f061f9a74874f1d0e94a3d58b413d69f73412701b8cc"},{"path":"scripts/run-vfs-product-builder.ts","sha256":"95654c6298474d02a64b7d93bc19ad7a54776bb636515efffa4ae518fc7e8a82"},{"path":"scripts/vfs-product-catalog.mjs","sha256":"3bb39bddb2add9c05b15f80964a22acdbb33648e43118d4d956b0ae062cd7eb0"},{"path":"tools/xtask/Cargo.toml","sha256":"b30b900521eaa9652937371cb7a9ebe38302a5e9d58aabb6743996acece87155"},{"path":"tools/xtask/src/abi_staging/builder_contract.rs","sha256":"7b52f1e143039dc32708e1e0bbbdb7075530ef682b73607e5d77818efa80de8f"},{"path":"tools/xtask/src/abi_staging/canonical_json.rs","sha256":"50e42796a53d517268e9afeea7f594e1db47b65e2e903e9d17e9db5dd9e1a6cf"},{"path":"tools/xtask/src/abi_staging/check_projection.rs","sha256":"d9f700f27b2a10238e318abd6d47c1ed205ace5a2fb92b6a0afc891eb96c7b5f"},{"path":"tools/xtask/src/abi_staging/consumer_registry.rs","sha256":"1985490c56ffd89feb8d41f5c2bef0f41432c37e26a4282b27da39d3476574bb"},{"path":"tools/xtask/src/abi_staging/evidence_policy.rs","sha256":"45676d5a430f5d4f19b3519862b29bb9204eb8fb1f7ad5c987887dc57874882d"},{"path":"tools/xtask/src/abi_staging/guard_registry.rs","sha256":"eed45cfe19c48716da9d205f1aa6a3bbbc0e96e0e96e1e50bcaf6329cfc663fe"},{"path":"tools/xtask/src/abi_staging/mod.rs","sha256":"65eec6b0d987138f8159edd6de627643a39fc1fa01ced5836bf5e878b4367d75"},{"path":"tools/xtask/src/abi_staging/product_evidence.rs","sha256":"c5ce7790e478666d75381834ea3eddec26428bdd7c6707fdd5128259fbb50e8c"},{"path":"tools/xtask/src/abi_staging/product_manifest.rs","sha256":"4db3dd8e01bbbbd52a3b8c862f27b2e1feb685caa038cb7b8913d13cd9ac72e2"},{"path":"tools/xtask/src/abi_staging/records.rs","sha256":"6d21b7325d70874804da528a4714e5a488ed23fdeac0fd77ef61c68402d84203"},{"path":"tools/xtask/src/abi_staging/request_derivation.rs","sha256":"02190e3e19aed2d5b00cab14a2cd9ef761e9ba7d276405058db973a8aa36c8c7"},{"path":"tools/xtask/src/abi_staging/request_policy.rs","sha256":"91f5e8db053b702ab9a57fa44178192f6e53957dc02b4d413b65dd3cd8a15270"},{"path":"tools/xtask/src/abi_staging/selection.rs","sha256":"1c8c517a31d7fa2d7abc97b3d57db75df38c20fc98170d430e94dcafd82da2eb"}],"implementation_paths":[".github/scripts/update-abi-staging-check.sh",".github/workflows/abi-staging-merge-gate.yml",".github/workflows/abi-staging-pages-canary.yml",".github/workflows/abi-staging-pr-check.yml",".github/workflows/abi-staging-request-feed.yml","Cargo.lock","Cargo.toml","abi/staging/evidence-definitions.generated.json","abi/staging/evidence-definitions.toml","apps/browser-demos/abi-staging-browser-harness.config.ts","apps/browser-demos/abi-staging-browser-host.config.ts","apps/browser-demos/abi-staging-browser-host.ts","apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","apps/browser-demos/lib/homebrew-closed-acceptance.ts","apps/browser-demos/lib/mysql-client.ts","apps/browser-demos/lib/redis-client.ts","apps/browser-demos/package-lock.json","apps/browser-demos/package.json","apps/browser-demos/pages/abi-staging-product-evidence/index.html","apps/browser-demos/pages/abi-staging-product-evidence/main.ts","apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","apps/browser-demos/pages/network/network-demo-worker.ts","apps/browser-demos/playwright-closed-acceptance.ts","apps/browser-demos/playwright-server-policy.ts","apps/browser-demos/playwright.config.ts","apps/browser-demos/test/abi-staging-product-evidence.spec.ts","apps/browser-demos/test/support/terminal-command.ts","apps/browser-demos/vite.config.ts","flake.lock","flake.nix","host/package-lock.json","host/package.json","host/src/generated/abi.ts","host/src/homebrew-bottle-relocation.ts","host/src/homebrew-guest-layout.ts","host/src/pathconf.ts","host/src/statfs.ts","host/src/vfs/closed-lazy-assets.ts","host/src/vfs/deferred-tree-limits.ts","host/src/vfs/hardlink-graph.ts","host/src/vfs/load-image.ts","host/src/vfs/memory-fs.ts","host/src/vfs/product-mount-contract.ts","host/src/vfs/sharedfs-vendor.ts","host/tsup.config.ts","images/vfs/scripts/repository-path-bundle.ts","package-lock.json","package.json","packages/registry/program-packages.json","scripts/abi-staging-build-toolchain.sh","scripts/abi-staging-collect-product-inputs.ts","scripts/abi-staging-pages-producer.ts","scripts/abi-staging-pages-readiness.ts","scripts/abi-staging-pages-site-builder.ts","scripts/abi-staging-prepare-runtime.sh","scripts/abi-staging-product-browser-evidence.ts","scripts/abi-staging-product-input-sources.ts","scripts/abi-staging-product-node-evidence.ts","scripts/abi-staging-protected-browser-operation.ts","scripts/check-dev-shell-tools.sh","scripts/check-pages-vfs-product-registry.mjs","scripts/dev-shell.sh","scripts/fetch-exact-musl-gitlink.sh","scripts/run-vfs-product-builder.ts","scripts/vfs-product-catalog.mjs","tools/xtask/Cargo.toml","tools/xtask/src/abi_staging/builder_contract.rs","tools/xtask/src/abi_staging/canonical_json.rs","tools/xtask/src/abi_staging/check_projection.rs","tools/xtask/src/abi_staging/consumer_registry.rs","tools/xtask/src/abi_staging/evidence_policy.rs","tools/xtask/src/abi_staging/guard_registry.rs","tools/xtask/src/abi_staging/mod.rs","tools/xtask/src/abi_staging/product_evidence.rs","tools/xtask/src/abi_staging/product_manifest.rs","tools/xtask/src/abi_staging/records.rs","tools/xtask/src/abi_staging/request_derivation.rs","tools/xtask/src/abi_staging/request_policy.rs","tools/xtask/src/abi_staging/selection.rs"],"issuer_repository":"Automattic/kandelo","issuer_workflow":".github/workflows/abi-staging-request-feed.yml","kind":"kandelo-abi-staging-request-policy","max_evidence_bindings":512,"max_products":256,"request_asset_max_bytes":4194304,"request_release_tag_prefix":"abi-staging-pr-","schema":1,"version":13} +{"addressed_taps":["kandelo-dev/homebrew-tap-core"],"automatic_same_repository":true,"fork_authorization":"disabled","implementation":[{"path":".github/scripts/update-abi-staging-check.sh","sha256":"78d49f35fdf27d426dcae6d6a19aa3eeca4d8a879bda0df319b70a4d4cf31ff8"},{"path":".github/workflows/abi-staging-merge-gate.yml","sha256":"a238f389b670ac6a54438c5894ca73f0f897fb393dbe24e8ed31f93c018e568f"},{"path":".github/workflows/abi-staging-pages-canary.yml","sha256":"3f9ecd19eb0224b2c11402ed3828b6ac2de1f6e291c84cfdf2fe59bae3a18676"},{"path":".github/workflows/abi-staging-pr-check.yml","sha256":"85a9b363caafb3e20b909c4145e45ddf6e0bc871547a986213e56c2d859ccf3c"},{"path":".github/workflows/abi-staging-request-feed.yml","sha256":"8cb53b1e466c7df016d369e5f0d93e1e53e17277e8f6771d329baf36367682fd"},{"path":"Cargo.lock","sha256":"80447a16efcee77c60f047afbb25725fd24967934a255af0b9def6e3f483721e"},{"path":"Cargo.toml","sha256":"e033e7c8489f41a9d12517e01be99d89054bd5ee831d74148702a50504c9ff02"},{"path":"abi/staging/evidence-definitions.generated.json","sha256":"2189a671c22b26164de85ac66e714592e30eef76666cc921441281ac21a1d30c"},{"path":"abi/staging/evidence-definitions.toml","sha256":"1ff246db2cf82357f348af2e0ae8bcc30fa149705b523562f507106ff60eb416"},{"path":"apps/browser-demos/abi-staging-browser-harness.config.ts","sha256":"314d59a2b912625b3878d4f46e04c9f256615ed5cdd7bd85ae7aef8351e99202"},{"path":"apps/browser-demos/abi-staging-browser-host.config.ts","sha256":"af348730b45bf7ecc60c1f03e518979000f709de580fa05066dac6372aa4bc2c"},{"path":"apps/browser-demos/abi-staging-browser-host.ts","sha256":"beca82b52c32b0ef56bcf2cdc1f04138213993cb43e117c09661feae2ccc7004"},{"path":"apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","sha256":"bc7d6655898d2fd7b712a20565723996a5d4cf9c23eb46989522aaa7d874ea98"},{"path":"apps/browser-demos/lib/homebrew-closed-acceptance.ts","sha256":"5e88d8ec55b7f495647c83db8c2db6e51254a6c437edeac45728c8a535e02b2d"},{"path":"apps/browser-demos/lib/mysql-client.ts","sha256":"bcb3b2fc913366bd9a828125cad80e41cdd1fac9ce37f3c2b45910463290b05c"},{"path":"apps/browser-demos/lib/redis-client.ts","sha256":"cde9e40502bdf6cb85f3b421273a023f290919c57f162ab71b7805c78d08a6ae"},{"path":"apps/browser-demos/package-lock.json","sha256":"12b6409372d589d931b39bbc3bce0e9ad01f0dd6a5dbce8dfbef854ab5f2193b"},{"path":"apps/browser-demos/package.json","sha256":"bac6058f02c11ecd55957bae288b0ffc2da4e5dc9ca1673447b1cfb416fcec2a"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/index.html","sha256":"889d1e8afab89d05ea02bc1c18459ccecfe585dab0b52d50aeff3dec979e08e0"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/main.ts","sha256":"19ac44a9631a42087111ce85dc65b60dd8fe5a14ec67d72492a382760a93f898"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","sha256":"f33bcec8e48bad54ffa9317ef6e16442e6328b3a093b3f208cb751054f582eaf"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","sha256":"d707a48c6355c9d451251be599f37f7c031fb82326b0b01bfa3af062d2b57c49"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","sha256":"77d08bd3b94ef4f74ce5b486045e2dd168ea8606a262249736c6372357a3de0f"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","sha256":"2ea2dfa31054b7b041ab342a1c1d1626088ac9f207df8c12d2040030457677bb"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","sha256":"60ff6a4cb6104f474e387b90638c2b71ec48587397fa19a9ef34da2dfb6192d9"},{"path":"apps/browser-demos/pages/network/network-demo-worker.ts","sha256":"a59eb6c7de64460456b43fec0930975987dee3c487acb8f2d6d4f93c2bcc9649"},{"path":"apps/browser-demos/playwright-closed-acceptance.ts","sha256":"430fbc4eea4a92ccd8a36c0aeb1c5b23aadf22e95d7151f09dadc4ce4f49b43b"},{"path":"apps/browser-demos/playwright-server-policy.ts","sha256":"eda5aa8651bc6854d1634be1c5ffdbf3a91a97c87b91d9b065f997e12afa420f"},{"path":"apps/browser-demos/playwright.config.ts","sha256":"e6d9a7cc6dab59be4d12aca97da856dffe5120facb1c5812225e3526cac37b42"},{"path":"apps/browser-demos/test/abi-staging-product-evidence.spec.ts","sha256":"e90b265133cfcde059c91d066261e243b761e20750ea8377e7679e4d9d877706"},{"path":"apps/browser-demos/test/support/terminal-command.ts","sha256":"919e0898cd9f317c95eb29d6a0fb70d398f18fdca6192f705b8eee5093eecac3"},{"path":"apps/browser-demos/vite.config.ts","sha256":"6d0565eac538e9d42fe4dffea659ec83b6f994a6e446fd7f7caba19233cecb0d"},{"path":"flake.lock","sha256":"2df2e3397a8c75ecf4fb6c897959650c76f27f65721bf3ea5a08304b480ad6d8"},{"path":"flake.nix","sha256":"86a72ab6b1c9c6307c1d8dc9c42859a7d5868d4f8703962ddb08ca0993564e01"},{"path":"host/package-lock.json","sha256":"fc1a12528aa0f0d00fdb4895864b9ebf288d0e5b908bee5880e908128a2682dd"},{"path":"host/package.json","sha256":"a1e154db420da65f58095771b6fd7fd1b30141646cdcc98e30ad359553bf2be0"},{"path":"host/src/generated/abi.ts","sha256":"9d557cf739333272dc386b4bd593d8c8f5c08f968955d5374d9a8e0254125fc4"},{"path":"host/src/homebrew-bottle-relocation.ts","sha256":"598cf3a732253f35ca041ec99f60aac9494b510f65b5411c733ce332eefdf4c1"},{"path":"host/src/homebrew-guest-layout.ts","sha256":"84866dbb5730ff6c6df1a6cfe23384f50ac9e438b18e40b20678b2b63b9eccef"},{"path":"host/src/pathconf.ts","sha256":"ae331cf37a7173ee1cf7beec5782368a2d82034da13b426b084066556202ed3c"},{"path":"host/src/statfs.ts","sha256":"3e11cd17f6460b7ee5965738f5a04295b3aec5da14c9c0251c6a2dc1283194a7"},{"path":"host/src/vfs/closed-lazy-assets.ts","sha256":"278e8cd36cd1c47ad876fdf310af4014d91e174f3717acda55c125c0b7d269fd"},{"path":"host/src/vfs/deferred-tree-limits.ts","sha256":"1a0cc35809f3708724a68a99a08890c1072267a74bc4c63eab0cb774f0332da6"},{"path":"host/src/vfs/hardlink-graph.ts","sha256":"9d19366a7e739d55ef81cea074ab9633f255e65f9332e5aa82da70b1d1ccd1cf"},{"path":"host/src/vfs/load-image.ts","sha256":"df62f1b2faf98fb2e9900a6f6b428ae16e7f43494a6ade01f709006ef764744b"},{"path":"host/src/vfs/memory-fs.ts","sha256":"b197061158467e779ea3999dd7f5ba75a6894efd756d54c050c60a31156ddd19"},{"path":"host/src/vfs/product-mount-contract.ts","sha256":"2aeb61b4eff2689a7c054a8919fe622f46e621006d64050390a04af3ef05f4f0"},{"path":"host/src/vfs/sharedfs-vendor.ts","sha256":"a5bf8c9691b4d0ab170e68b0208db3b54fece2a4924e98ad472a36f28b441088"},{"path":"host/tsup.config.ts","sha256":"1d811ee5e7d2c07d4731c2e6df9181be6e511ecf4d1724495085d22f4af51a47"},{"path":"images/vfs/scripts/repository-path-bundle.ts","sha256":"64bad99dca7b32d57c104a9da1dd4977f0e8de56dc5950fd94bc3cda19ac987a"},{"path":"package-lock.json","sha256":"a46b2c8b6c8e56dd235a826c266a6fad7d702a340eb0b42cf0b6c99aaa428c71"},{"path":"package.json","sha256":"7411d3663ef7a3458da32f39e3919bda133377246ff629fc4db98f75792db2df"},{"path":"packages/registry/program-packages.json","sha256":"eea178faf24242ab946b97edaa8338f21165865d76983c7faf402e1e368238d3"},{"path":"scripts/abi-staging-build-toolchain.sh","sha256":"ed73bcd74bb7e2c409c2ae6887b4717c97b82f35e4f3347f523a7a240f47e815"},{"path":"scripts/abi-staging-collect-product-inputs.ts","sha256":"4ecd65b0789e21cb0ba3c5d133c9f3589da280258d382a3c208d859eefa03221"},{"path":"scripts/abi-staging-pages-producer.ts","sha256":"067fbecd469f83e266ab71db076d8a6be904a43986d74320caa80bd07dfd777e"},{"path":"scripts/abi-staging-pages-readiness.ts","sha256":"a254cf3f7fedef367ecc6fb91fa2175fc255f87a6458c02d0bf4caad23019f3b"},{"path":"scripts/abi-staging-pages-site-builder.ts","sha256":"97ce037272c1f8200ae48a73d027371f90c13dbd16b7194846b261eb51326be7"},{"path":"scripts/abi-staging-prepare-runtime.sh","sha256":"38ff5658d1147c6f82c542559078204e89decc2475f6ddb626673cafb1961c26"},{"path":"scripts/abi-staging-product-browser-evidence.ts","sha256":"458c31795759e2745f8eb071603f51c947f123cdea51cea4d7efebbfb3aa2a98"},{"path":"scripts/abi-staging-product-input-sources.ts","sha256":"eca75b92c09a5a28c745ecc6ae6e2407b9637c8710d022b76f7aab9e05e5be14"},{"path":"scripts/abi-staging-product-node-evidence.ts","sha256":"786fe35d64cbbf7b4898b9ac58f5c4483d8cc6f2b90f6644beb782126034ff15"},{"path":"scripts/abi-staging-protected-browser-operation.ts","sha256":"858b417d9562cf8618a416893d47e3bc616cedb7b1cfed673c467f1d0737b935"},{"path":"scripts/check-dev-shell-tools.sh","sha256":"f9187fc855b9949cb20ac7593ea17292948e525684bec603efcabea176096185"},{"path":"scripts/check-pages-vfs-product-registry.mjs","sha256":"6cbb24557f1e1a6961bd0fa7f11cbf2bae3035f5458971cf75ca31a891af2b0b"},{"path":"scripts/dev-shell.sh","sha256":"3931770c7b9f50ccee5a89e2957ed7a9856d4713e7fb6cb2631b29d0c6c4cff7"},{"path":"scripts/fetch-exact-musl-gitlink.sh","sha256":"8a14b1c97e2331f104d6f061f9a74874f1d0e94a3d58b413d69f73412701b8cc"},{"path":"scripts/run-vfs-product-builder.ts","sha256":"95654c6298474d02a64b7d93bc19ad7a54776bb636515efffa4ae518fc7e8a82"},{"path":"scripts/vfs-product-catalog.mjs","sha256":"3bb39bddb2add9c05b15f80964a22acdbb33648e43118d4d956b0ae062cd7eb0"},{"path":"tools/xtask/Cargo.toml","sha256":"b30b900521eaa9652937371cb7a9ebe38302a5e9d58aabb6743996acece87155"},{"path":"tools/xtask/src/abi_staging/builder_contract.rs","sha256":"7b52f1e143039dc32708e1e0bbbdb7075530ef682b73607e5d77818efa80de8f"},{"path":"tools/xtask/src/abi_staging/canonical_json.rs","sha256":"50e42796a53d517268e9afeea7f594e1db47b65e2e903e9d17e9db5dd9e1a6cf"},{"path":"tools/xtask/src/abi_staging/check_projection.rs","sha256":"d9f700f27b2a10238e318abd6d47c1ed205ace5a2fb92b6a0afc891eb96c7b5f"},{"path":"tools/xtask/src/abi_staging/consumer_registry.rs","sha256":"1985490c56ffd89feb8d41f5c2bef0f41432c37e26a4282b27da39d3476574bb"},{"path":"tools/xtask/src/abi_staging/evidence_policy.rs","sha256":"45676d5a430f5d4f19b3519862b29bb9204eb8fb1f7ad5c987887dc57874882d"},{"path":"tools/xtask/src/abi_staging/guard_registry.rs","sha256":"eed45cfe19c48716da9d205f1aa6a3bbbc0e96e0e96e1e50bcaf6329cfc663fe"},{"path":"tools/xtask/src/abi_staging/mod.rs","sha256":"65eec6b0d987138f8159edd6de627643a39fc1fa01ced5836bf5e878b4367d75"},{"path":"tools/xtask/src/abi_staging/product_evidence.rs","sha256":"c5ce7790e478666d75381834ea3eddec26428bdd7c6707fdd5128259fbb50e8c"},{"path":"tools/xtask/src/abi_staging/product_manifest.rs","sha256":"4db3dd8e01bbbbd52a3b8c862f27b2e1feb685caa038cb7b8913d13cd9ac72e2"},{"path":"tools/xtask/src/abi_staging/records.rs","sha256":"6d21b7325d70874804da528a4714e5a488ed23fdeac0fd77ef61c68402d84203"},{"path":"tools/xtask/src/abi_staging/request_derivation.rs","sha256":"02190e3e19aed2d5b00cab14a2cd9ef761e9ba7d276405058db973a8aa36c8c7"},{"path":"tools/xtask/src/abi_staging/request_policy.rs","sha256":"91f5e8db053b702ab9a57fa44178192f6e53957dc02b4d413b65dd3cd8a15270"},{"path":"tools/xtask/src/abi_staging/selection.rs","sha256":"1c8c517a31d7fa2d7abc97b3d57db75df38c20fc98170d430e94dcafd82da2eb"}],"implementation_paths":[".github/scripts/update-abi-staging-check.sh",".github/workflows/abi-staging-merge-gate.yml",".github/workflows/abi-staging-pages-canary.yml",".github/workflows/abi-staging-pr-check.yml",".github/workflows/abi-staging-request-feed.yml","Cargo.lock","Cargo.toml","abi/staging/evidence-definitions.generated.json","abi/staging/evidence-definitions.toml","apps/browser-demos/abi-staging-browser-harness.config.ts","apps/browser-demos/abi-staging-browser-host.config.ts","apps/browser-demos/abi-staging-browser-host.ts","apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","apps/browser-demos/lib/homebrew-closed-acceptance.ts","apps/browser-demos/lib/mysql-client.ts","apps/browser-demos/lib/redis-client.ts","apps/browser-demos/package-lock.json","apps/browser-demos/package.json","apps/browser-demos/pages/abi-staging-product-evidence/index.html","apps/browser-demos/pages/abi-staging-product-evidence/main.ts","apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","apps/browser-demos/pages/network/network-demo-worker.ts","apps/browser-demos/playwright-closed-acceptance.ts","apps/browser-demos/playwright-server-policy.ts","apps/browser-demos/playwright.config.ts","apps/browser-demos/test/abi-staging-product-evidence.spec.ts","apps/browser-demos/test/support/terminal-command.ts","apps/browser-demos/vite.config.ts","flake.lock","flake.nix","host/package-lock.json","host/package.json","host/src/generated/abi.ts","host/src/homebrew-bottle-relocation.ts","host/src/homebrew-guest-layout.ts","host/src/pathconf.ts","host/src/statfs.ts","host/src/vfs/closed-lazy-assets.ts","host/src/vfs/deferred-tree-limits.ts","host/src/vfs/hardlink-graph.ts","host/src/vfs/load-image.ts","host/src/vfs/memory-fs.ts","host/src/vfs/product-mount-contract.ts","host/src/vfs/sharedfs-vendor.ts","host/tsup.config.ts","images/vfs/scripts/repository-path-bundle.ts","package-lock.json","package.json","packages/registry/program-packages.json","scripts/abi-staging-build-toolchain.sh","scripts/abi-staging-collect-product-inputs.ts","scripts/abi-staging-pages-producer.ts","scripts/abi-staging-pages-readiness.ts","scripts/abi-staging-pages-site-builder.ts","scripts/abi-staging-prepare-runtime.sh","scripts/abi-staging-product-browser-evidence.ts","scripts/abi-staging-product-input-sources.ts","scripts/abi-staging-product-node-evidence.ts","scripts/abi-staging-protected-browser-operation.ts","scripts/check-dev-shell-tools.sh","scripts/check-pages-vfs-product-registry.mjs","scripts/dev-shell.sh","scripts/fetch-exact-musl-gitlink.sh","scripts/run-vfs-product-builder.ts","scripts/vfs-product-catalog.mjs","tools/xtask/Cargo.toml","tools/xtask/src/abi_staging/builder_contract.rs","tools/xtask/src/abi_staging/canonical_json.rs","tools/xtask/src/abi_staging/check_projection.rs","tools/xtask/src/abi_staging/consumer_registry.rs","tools/xtask/src/abi_staging/evidence_policy.rs","tools/xtask/src/abi_staging/guard_registry.rs","tools/xtask/src/abi_staging/mod.rs","tools/xtask/src/abi_staging/product_evidence.rs","tools/xtask/src/abi_staging/product_manifest.rs","tools/xtask/src/abi_staging/records.rs","tools/xtask/src/abi_staging/request_derivation.rs","tools/xtask/src/abi_staging/request_policy.rs","tools/xtask/src/abi_staging/selection.rs"],"issuer_repository":"Automattic/kandelo","issuer_workflow":".github/workflows/abi-staging-request-feed.yml","kind":"kandelo-abi-staging-request-policy","max_evidence_bindings":512,"max_products":256,"request_asset_max_bytes":4194304,"request_release_tag_prefix":"abi-staging-pr-","schema":1,"version":14} diff --git a/abi/staging/request-policy.toml b/abi/staging/request-policy.toml index 685245f5e4..98b71983ff 100644 --- a/abi/staging/request-policy.toml +++ b/abi/staging/request-policy.toml @@ -1,6 +1,6 @@ schema = 1 kind = "kandelo-abi-staging-request-policy" -version = 13 +version = 14 issuer_repository = "Automattic/kandelo" issuer_workflow = ".github/workflows/abi-staging-request-feed.yml" automatic_same_repository = true diff --git a/scripts/check-abi-staging-request-workflow.rb b/scripts/check-abi-staging-request-workflow.rb index 861f0d16ed..bbbd07049c 100755 --- a/scripts/check-abi-staging-request-workflow.rb +++ b/scripts/check-abi-staging-request-workflow.rb @@ -155,12 +155,36 @@ def check_workflow(workflow) classify_source.include?("env -u GH_TOKEN -u GITHUB_TOKEN") && classify_source.include?("env -i") && classify_source.include?('HOME="$candidate_home"') && + classify_source.include?("authority/scripts/fetch-exact-musl-gitlink.sh") && + classify_source.include?('--source-root "$head_root"') && + classify_source.include?('--commit "$head"') && + classify_source.include?("bash scripts/build-musl.sh") && + classify_source.include?("bash scripts/build-musl.sh --arch wasm64posix") && + classify_source.scan("tail -c 131072").length == 2 && classify_source.include?('env ABI_CHECK_BASE_REF="$PROTECTED_SHA"') && classify_source.include?("head -c 1048576") && classify_source.include?("scripts/check-abi-version.sh") && classify_source.include?("structural-report.json") && classify_source.include?("HEAD^{tree}"), "classification does not bind and check each uncredentialed exact head") + wasm32_build = classify_source.index("bash scripts/build-musl.sh") + wasm64_build = classify_source.index("bash scripts/build-musl.sh --arch wasm64posix") + candidate_prefix = '"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh' + fail_closed_sequence = <<~'SH'.strip + check_log="$evidence/reports/$pr-check.log" + set +e + ( + set -euo pipefail + cd "$head_root" + SH + check(classify_source.include?(fail_closed_sequence) && + classify_source.scan('"$bash_bin" scripts/dev-shell.sh').length == 3 && + classify_source.scan(candidate_prefix).length == 3 && + !classify_source.include?("git submodule") && + classify_source.index("fetch-exact-musl-gitlink.sh") < wasm32_build && + wasm32_build < wasm64_build && + wasm64_build < classify_source.index("bash scripts/check-abi-version.sh"), + "classification does not prepare the exact sysroot in one fail-closed stripped environment") check(!classify_source.match?(/gh\s+(?:release|api\s+--method)/), "classification may not write through GitHub") check(!classify.fetch("env", {}).key?("GH_TOKEN"), @@ -279,6 +303,61 @@ def rejected_mutation(workflow, label) step = copy.dig("jobs", "derive-request", "steps").find { |item| item["run"]&.include?("structural-report validate") } step["run"] = step.fetch("run").gsub("structural-report validate", "echo trust-report") }, + "missing classification musl materialization" => lambda { |copy| + step = copy.dig("jobs", "classify-exact-head", "steps").find do |item| + item["run"]&.include?("fetch-exact-musl-gitlink.sh") + end + step["run"] = step.fetch("run").gsub( + /^\s*bash authority\/scripts\/fetch-exact-musl-gitlink\.sh.*?^\s*--commit "\$head"\n/m, + "" + ) + }, + "missing classification sysroot build" => lambda { |copy| + step = copy.dig("jobs", "classify-exact-head", "steps").find do |item| + item["run"]&.include?("bash scripts/build-musl.sh") + end + step["run"] = step.fetch("run").gsub( + "bash scripts/build-musl.sh 2>&1", + "true # ABI sysroot build removed" + ) + }, + "missing classification wasm64 sysroot build" => lambda { |copy| + step = copy.dig("jobs", "classify-exact-head", "steps").find do |item| + item["run"]&.include?("bash scripts/build-musl.sh --arch wasm64posix") + end + step["run"] = step.fetch("run").gsub( + "bash scripts/build-musl.sh --arch wasm64posix", + "true # wasm64 ABI sysroot build removed" + ) + }, + "classification sysroot failure can continue" => lambda { |copy| + step = copy.dig("jobs", "classify-exact-head", "steps").find do |item| + item["run"]&.include?("candidate_env=(") + end + step["run"] = step.fetch("run").sub( + " set +e\n (\n set -euo pipefail\n", + " set +e\n (\n" + ) + }, + "classification wasm32 build escapes stripped environment" => lambda { |copy| + step = copy.dig("jobs", "classify-exact-head", "steps").find do |item| + item["run"]&.include?("candidate_env=(") + end + step["run"] = step.fetch("run").sub( + '"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh', + 'env "$bash_bin" scripts/dev-shell.sh' + ) + }, + "classification ABI check escapes stripped environment" => lambda { |copy| + step = copy.dig("jobs", "classify-exact-head", "steps").find do |item| + item["run"]&.include?("candidate_env=(") + end + prefix = '"${candidate_env[@]}" "$bash_bin" scripts/dev-shell.sh' + source = step.fetch("run") + position = source.rindex(prefix) + step["run"] = source.dup + step["run"][position, prefix.length] = 'env "$bash_bin" scripts/dev-shell.sh' + }, "missing exact musl materialization" => lambda { |copy| step = copy.dig("jobs", "derive-request", "steps").find do |item| item["run"]&.include?("fetch-exact-musl-gitlink.sh") From 3a1bae5a3c7ce3da81ea61b153a9195833ea4708 Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Thu, 13 Aug 2026 03:25:30 -0400 Subject: [PATCH 2/2] [ABI] Seal exact requests before publication GitHub immutable releases reject assets appended after publication, so the active request feed could create an empty public prerelease but could not publish its request bytes. That stopped tap reconciliation before any staged bottle build could begin. Give each canonical request a digest-addressed tag and prerelease. Create the release as a draft, upload and authenticate the one exact request, publish it once, then require immutable state plus anonymous byte-for-byte readback. Bind the direct tag, release target, issuer workflow, and protected main commit together, and rotate request policy to version 15. --- .../scripts/publish-abi-staging-request.sh | 356 ++++++++++++++---- .../test-publish-abi-staging-request.sh | 260 +++++++++---- abi/staging/request-policy.generated.json | 2 +- abi/staging/request-policy.toml | 9 +- docs/repository-organization.md | 13 +- .../xtask/src/abi_staging/check_projection.rs | 11 +- tools/xtask/src/abi_staging/request_feed.rs | 115 ++++-- tools/xtask/src/abi_staging/request_policy.rs | 13 +- 8 files changed, 571 insertions(+), 208 deletions(-) diff --git a/.github/scripts/publish-abi-staging-request.sh b/.github/scripts/publish-abi-staging-request.sh index cbd0295683..1553d1b99e 100755 --- a/.github/scripts/publish-abi-staging-request.sh +++ b/.github/scripts/publish-abi-staging-request.sh @@ -38,17 +38,25 @@ done echo "publish-abi-staging-request: GITHUB_OUTPUT is required" >&2 exit 2 } -command -v gh >/dev/null || { - echo "publish-abi-staging-request: gh is required" >&2 - exit 2 -} -command -v jq >/dev/null || { - echo "publish-abi-staging-request: jq is required" >&2 - exit 2 -} +for command in gh jq curl shasum; do + command -v "$command" >/dev/null || { + echo "publish-abi-staging-request: $command is required" >&2 + exit 2 + } +done TMP_ROOT=$(mktemp -d) trap 'rm -rf "$TMP_ROOT"' EXIT +mkdir -p "$TMP_ROOT/public-home" +RETRY_DELAY=${ABI_STAGING_REQUEST_RETRY_DELAY_SECONDS:-1} +[[ $RETRY_DELAY =~ ^[0-9]+$ && $RETRY_DELAY -le 10 ]] || { + echo "publish-abi-staging-request: retry delay is invalid" >&2 + exit 2 +} + +pause_before_retry() { + ((RETRY_DELAY == 0)) || sleep "$RETRY_DELAY" +} jq -e -cS . "$PLAN" >"$TMP_ROOT/plan.canonical" || { echo "publish-abi-staging-request: plan is invalid JSON" >&2 @@ -89,14 +97,14 @@ PR_NUMBER=$(jq -r '.pull_request_number' "$PLAN") echo "publish-abi-staging-request: plan repository mismatch" >&2 exit 1 } -[[ $TAG == "abi-staging-pr-${PR_NUMBER}" ]] || { - echo "publish-abi-staging-request: plan tag mismatch" >&2 - exit 1 -} [[ $ASSET_SHA256 =~ ^[0-9a-f]{64}$ ]] || { echo "publish-abi-staging-request: invalid plan digest" >&2 exit 1 } +[[ $TAG == "abi-staging-pr-${PR_NUMBER}-sha256-${ASSET_SHA256}" ]] || { + echo "publish-abi-staging-request: plan tag mismatch" >&2 + exit 1 +} [[ $PLAN_ACTION != reject-name-collision ]] || { echo "publish-abi-staging-request: protected plan rejects an asset-name collision" >&2 exit 1 @@ -122,6 +130,7 @@ REQUEST_PR=$(jq -er '.pull_request.number' "$REQUEST") REQUEST_HEAD=$(jq -er '.build_source.commit' "$REQUEST") AUTHORIZATION_HEAD=$(jq -er '.issuance.authorization.head' "$REQUEST") AUTHORIZATION_MODE=$(jq -er '.issuance.authorization.mode' "$REQUEST") +ISSUER_WORKFLOW_REF=$(jq -er '.issuance.issuer_workflow_ref' "$REQUEST") [[ $REQUEST_REPOSITORY == "$REPOSITORY" && $REQUEST_PR == "$PR_NUMBER" ]] || { echo "publish-abi-staging-request: request pull-request identity mismatch" >&2 exit 1 @@ -134,6 +143,11 @@ AUTHORIZATION_MODE=$(jq -er '.issuance.authorization.mode' "$REQUEST") echo "publish-abi-staging-request: request is not exact-head same-repository authorized" >&2 exit 1 } +EXPECTED_ISSUER_WORKFLOW_REF="${REPOSITORY}/.github/workflows/abi-staging-request-feed.yml@${PROTECTED_TARGET}" +[[ $ISSUER_WORKFLOW_REF == "$EXPECTED_ISSUER_WORKFLOW_REF" ]] || { + echo "publish-abi-staging-request: request issuer workflow does not bind protected main" >&2 + exit 1 +} EXPECTED_NAME="candidate-request-${REQUEST_HEAD}-sha256-${ACTUAL_SHA256}.json" EXPECTED_URL="https://github.com/${REPOSITORY}/releases/download/${TAG}/${EXPECTED_NAME}" [[ $ASSET_NAME == "$EXPECTED_NAME" && $PUBLIC_URL == "$EXPECTED_URL" ]] || { @@ -141,100 +155,278 @@ EXPECTED_URL="https://github.com/${REPOSITORY}/releases/download/${TAG}/${EXPECT exit 1 } +TITLE="ABI staging request for PR #${PR_NUMBER}" +BODY="Public, nonendorsed exact-head request. Promotion requires separate protected verification and admission." RELEASE_JSON="$TMP_ROOT/release.json" -if gh api "/repos/${REPOSITORY}/releases/tags/${TAG}" >"$RELEASE_JSON" 2>"$TMP_ROOT/release-get.err"; then - ACTION=append-asset -else - gh api --method POST "/repos/${REPOSITORY}/releases" \ - -f "tag_name=${TAG}" \ - -f "target_commitish=${PROTECTED_TARGET}" \ - -f "name=ABI staging requests for PR #${PR_NUMBER}" \ - -f "body=Public candidate requests are nonendorsed inputs. Identity, verification, and admission remain separate." \ - -F prerelease=true \ - -F draft=false >"$RELEASE_JSON" - ACTION=create-prerelease -fi +ASSETS_JSON="$TMP_ROOT/assets.json" + +validate_direct_tag() { + jq -e --arg tag "$TAG" --arg target "$PROTECTED_TARGET" ' + .ref == ("refs/tags/" + $tag) and + .object.type == "commit" and .object.sha == $target + ' "$TMP_ROOT/tag.json" >/dev/null || { + echo "publish-abi-staging-request: request tag is not a direct protected-commit reference" >&2 + return 1 + } +} + +require_protected_main() { + gh api "/repos/${REPOSITORY}/git/ref/heads/main" >"$TMP_ROOT/main.json" || return 1 + jq -e --arg target "$PROTECTED_TARGET" ' + .ref == "refs/heads/main" and + .object.type == "commit" and .object.sha == $target + ' "$TMP_ROOT/main.json" >/dev/null || { + echo "publish-abi-staging-request: protected main moved during request publication" >&2 + return 1 + } +} -jq -e \ - --arg tag "$TAG" \ - --arg target "$PROTECTED_TARGET" ' +ensure_direct_tag() { + local attempt + for attempt in 1 2 3; do + if gh api "/repos/${REPOSITORY}/git/ref/tags/${TAG}" >"$TMP_ROOT/tag.json" 2>/dev/null; then + validate_direct_tag || return 1 + return + fi + pause_before_retry + require_protected_main || return 1 + gh api --method POST "/repos/${REPOSITORY}/git/refs" \ + -f "ref=refs/tags/${TAG}" -f "sha=${PROTECTED_TARGET}" \ + >"$TMP_ROOT/tag-create.json" 2>/dev/null || true + if gh api "/repos/${REPOSITORY}/git/ref/tags/${TAG}" >"$TMP_ROOT/tag.json" 2>/dev/null; then + validate_direct_tag || return 1 + return + fi + done + echo "publish-abi-staging-request: exact request tag creation remained uncertain" >&2 + return 1 +} + +validate_release_identity() { + jq -e --arg tag "$TAG" --arg target "$PROTECTED_TARGET" \ + --arg title "$TITLE" --arg body "$BODY" ' + type == "object" and (.id | type == "number" and . >= 1 and floor == .) and - .tag_name == $tag and - .target_commitish == $target and - .prerelease == true and - .draft == false + .tag_name == $tag and .target_commitish == $target and + .name == $title and .body == $body and .prerelease == true and + (.draft | type == "boolean") and (.immutable | type == "boolean") and + ((.draft == true and .immutable == false) or + (.draft == false and .immutable == true)) ' "$RELEASE_JSON" >/dev/null || { - echo "publish-abi-staging-request: existing Release identity or flags are invalid" >&2 - exit 1 + echo "publish-abi-staging-request: request Release identity or immutable state is invalid" >&2 + return 1 + } } -RELEASE_ID=$(jq -r '.id' "$RELEASE_JSON") -list_assets() { - local output=$1 +refresh_assets() { + local release_id pages="$TMP_ROOT/asset-pages.json" + release_id=$(jq -er '.id' "$RELEASE_JSON") || return 1 gh api --paginate --slurp \ - "/repos/${REPOSITORY}/releases/${RELEASE_ID}/assets?per_page=100" >"$output" + "/repos/${REPOSITORY}/releases/${release_id}/assets?per_page=100" >"$pages" || return 1 jq -e ' type == "array" and all(.[]; type == "array") and - ([.[][]] | length <= 4096) and + ([.[][]] | length <= 1) and all(.[][]; - (keys | sort) as $keys | - ($keys | index("id")) != null and - ($keys | index("name")) != null and - ($keys | index("browser_download_url")) != null and (.id | type == "number" and . >= 1 and floor == .) and (.name | type == "string" and length >= 1 and length <= 512) and - (.browser_download_url | type == "string" and startswith("https://")) - ) - ' "$output" >/dev/null || { - echo "publish-abi-staging-request: Release asset inventory is invalid or too large" >&2 - exit 1 + (.browser_download_url | type == "string" and startswith("https://")) and + (.state | type == "string") and + (.size | type == "number" and . >= 0 and floor == .) and + ((.digest == null) or (.digest | type == "string"))) + ' "$pages" >/dev/null || { + echo "publish-abi-staging-request: Release asset inventory is invalid" >&2 + return 1 + } + jq '[.[][]]' "$pages" >"$ASSETS_JSON" || return 1 +} + +refresh_release() { + local release_id=$1 + gh api "/repos/${REPOSITORY}/releases/${release_id}" >"$RELEASE_JSON" || return 1 + validate_release_identity || return 1 + refresh_assets || return 1 +} + +discover_release() { + local pages="$TMP_ROOT/release-pages.json" matches="$TMP_ROOT/release-matches.json" release_id + gh api --paginate --slurp "/repos/${REPOSITORY}/releases?per_page=100" >"$pages" || return 1 + jq -e 'type == "array" and all(.[]; type == "array") and ([.[][]] | length <= 4096)' \ + "$pages" >/dev/null || { + echo "publish-abi-staging-request: Release discovery inventory is invalid or too large" >&2 + return 1 } + jq --arg tag "$TAG" '[.[][] | select(.tag_name == $tag)]' "$pages" >"$matches" || return 1 + case $(jq -r 'length' "$matches") in + 0) return 44 ;; + 1) + release_id=$(jq -er '.[0].id' "$matches") || return 1 + refresh_release "$release_id" || return 1 + ;; + *) + echo "publish-abi-staging-request: request tag resolves to multiple Releases" >&2 + return 1 + ;; + esac +} + +CREATED=false +create_or_discover_release() { + local attempt rc release_id + for attempt in 1 2 3; do + rc=0 + discover_release || rc=$? + if [[ $rc == 0 ]]; then + return + elif [[ $rc != 44 ]]; then + return 1 + fi + require_protected_main || return 1 + if gh api --method POST "/repos/${REPOSITORY}/releases" \ + -f "tag_name=${TAG}" -f "target_commitish=${PROTECTED_TARGET}" \ + -f "name=${TITLE}" -f "body=${BODY}" -f make_latest=false \ + -F prerelease=true -F draft=true >"$TMP_ROOT/release-create.json" + then + release_id=$(jq -er '.id | select(type == "number" and . >= 1)' \ + "$TMP_ROOT/release-create.json" 2>/dev/null || true) + if [[ -n $release_id ]] && refresh_release "$release_id"; then + CREATED=true + return + fi + fi + pause_before_retry + done + echo "publish-abi-staging-request: draft request Release creation remained uncertain" >&2 + return 1 } -ASSET_PAGES="$TMP_ROOT/assets.json" -list_assets "$ASSET_PAGES" -MATCH_COUNT=$(jq --arg name "$ASSET_NAME" '[.[][] | select(.name == $name)] | length' "$ASSET_PAGES") -if [[ $MATCH_COUNT == 0 ]]; then - cp "$REQUEST" "$TMP_ROOT/$ASSET_NAME" - gh release upload "$TAG" "$TMP_ROOT/$ASSET_NAME" --repo "$REPOSITORY" - [[ $ACTION == create-prerelease ]] || ACTION=append-asset - list_assets "$ASSET_PAGES" - MATCH_COUNT=$(jq --arg name "$ASSET_NAME" '[.[][] | select(.name == $name)] | length' "$ASSET_PAGES") -elif [[ $MATCH_COUNT == 1 ]]; then - EXISTING_ASSET_ID=$(jq -r --arg name "$ASSET_NAME" '.[][] | select(.name == $name) | .id' "$ASSET_PAGES") +verify_authenticated_asset() { + local asset_id + jq -e --arg name "$ASSET_NAME" --arg url "$PUBLIC_URL" \ + --arg digest "sha256:${ASSET_SHA256}" --argjson bytes "$ASSET_BYTES" ' + length == 1 and .[0].name == $name and + .[0].browser_download_url == $url and .[0].state == "uploaded" and + .[0].size == $bytes and .[0].digest == $digest + ' "$ASSETS_JSON" >/dev/null || { + echo "publish-abi-staging-request: request asset metadata differs from its plan" >&2 + return 1 + } + asset_id=$(jq -er '.[0].id' "$ASSETS_JSON") || return 1 gh api -H 'Accept: application/octet-stream' \ - "/repos/${REPOSITORY}/releases/assets/${EXISTING_ASSET_ID}" >"$TMP_ROOT/existing-request.json" - cmp -s "$REQUEST" "$TMP_ROOT/existing-request.json" || { - echo "publish-abi-staging-request: immutable asset-name collision" >&2 - exit 1 + "/repos/${REPOSITORY}/releases/assets/${asset_id}" >"$TMP_ROOT/authenticated-request.json" || + return 1 + cmp -s "$REQUEST" "$TMP_ROOT/authenticated-request.json" || { + echo "publish-abi-staging-request: authenticated request readback differs" >&2 + return 1 } - ACTION=asset-already-identical -else - echo "publish-abi-staging-request: duplicate exact-name Release assets" >&2 - exit 1 -fi +} -[[ $MATCH_COUNT == 1 ]] || { - echo "publish-abi-staging-request: upload did not create exactly one named asset" >&2 - exit 1 +ensure_asset() { + local attempt release_id + release_id=$(jq -er '.id' "$RELEASE_JSON") || return 1 + for attempt in 1 2 3; do + if ! refresh_release "$release_id"; then + pause_before_retry + continue + fi + case $(jq -r 'length' "$ASSETS_JSON") in + 1) + if verify_authenticated_asset; then + return + fi + pause_before_retry + continue + ;; + 0) ;; + *) return 1 ;; + esac + [[ $(jq -r '.draft' "$RELEASE_JSON") == true ]] || { + echo "publish-abi-staging-request: immutable public request Release is missing its asset" >&2 + return 1 + } + cp "$REQUEST" "$TMP_ROOT/$ASSET_NAME" + require_protected_main || return 1 + gh release upload "$TAG" "$TMP_ROOT/$ASSET_NAME" --repo "$REPOSITORY" || true + pause_before_retry + done + echo "publish-abi-staging-request: request asset upload remained uncertain" >&2 + return 1 } -ASSET_ID=$(jq -r --arg name "$ASSET_NAME" '.[][] | select(.name == $name) | .id' "$ASSET_PAGES") -ASSET_URL=$(jq -r --arg name "$ASSET_NAME" '.[][] | select(.name == $name) | .browser_download_url' "$ASSET_PAGES") -[[ $ASSET_URL == "$PUBLIC_URL" ]] || { - echo "publish-abi-staging-request: published asset URL mismatch" >&2 - exit 1 + +seal_release() { + local attempt release_id + release_id=$(jq -er '.id' "$RELEASE_JSON") || return 1 + for attempt in 1 2 3; do + if ! refresh_release "$release_id" || ! verify_authenticated_asset; then + pause_before_retry + continue + fi + if [[ $(jq -r '.draft' "$RELEASE_JSON") == false ]]; then + return + fi + require_protected_main || return 1 + gh api --method PATCH "/repos/${REPOSITORY}/releases/${release_id}" \ + -f "name=${TITLE}" -f "body=${BODY}" -f make_latest=false \ + -F prerelease=true -F draft=false >"$TMP_ROOT/release-publish.json" || true + pause_before_retry + done + echo "publish-abi-staging-request: request Release publication remained uncertain" >&2 + return 1 } -# Descriptive prose is deliberately updated last and is never read as -# authority. If this call is interrupted, a retry rediscovers and verifies the -# already-appended immutable asset before repairing the prose. -gh api --method PATCH "/repos/${REPOSITORY}/releases/${RELEASE_ID}" \ - -f "body=Public, nonendorsed exact-head request feed for PR #${PR_NUMBER}. Request assets are append-only; promotion requires separate protected verification and admission." \ - >/dev/null +verify_anonymous_asset() { + local attempt + local -a public_env=(env -i PATH="$PATH" HOME="$TMP_ROOT/public-home") + [[ -z ${SSL_CERT_FILE:-} ]] || public_env+=(SSL_CERT_FILE="$SSL_CERT_FILE") + [[ -z ${NIX_SSL_CERT_FILE:-} ]] || public_env+=(NIX_SSL_CERT_FILE="$NIX_SSL_CERT_FILE") + for attempt in 1 2 3 4; do + if "${public_env[@]}" curl --disable --fail --location --silent --show-error \ + --connect-timeout 10 --max-time 60 "$PUBLIC_URL" \ + --output "$TMP_ROOT/anonymous-request.json" && + cmp -s "$REQUEST" "$TMP_ROOT/anonymous-request.json" + then + return + fi + pause_before_retry + done + echo "publish-abi-staging-request: anonymous immutable request readback failed" >&2 + return 1 +} +ensure_direct_tag || exit 1 +create_or_discover_release || exit 1 +INITIAL_DRAFT=$(jq -r '.draft' "$RELEASE_JSON") +[[ $INITIAL_DRAFT == true || $INITIAL_DRAFT == false ]] || exit 1 +ensure_asset || exit 1 +seal_release || exit 1 +RELEASE_ID=$(jq -er '.id' "$RELEASE_JSON") || exit 1 +for attempt in 1 2 3; do + if refresh_release "$RELEASE_ID" && verify_authenticated_asset; then + break + fi + ((attempt < 3)) || { + echo "publish-abi-staging-request: final authenticated reconciliation failed" >&2 + exit 1 + } + pause_before_retry +done +[[ $(jq -r '.draft' "$RELEASE_JSON") == false && + $(jq -r '.immutable' "$RELEASE_JSON") == true ]] || { + echo "publish-abi-staging-request: published request Release is not immutable" >&2 + exit 1 +} +verify_anonymous_asset || exit 1 + +ASSET_ID=$(jq -er '.[0].id' "$ASSETS_JSON") || exit 1 +if [[ $CREATED == true ]]; then + ACTION=create-prerelease +elif [[ $INITIAL_DRAFT == true ]]; then + ACTION=append-asset +else + ACTION=asset-already-identical +fi { printf 'release_id=%s\n' "$RELEASE_ID" printf 'asset_id=%s\n' "$ASSET_ID" - printf 'asset_url=%s\n' "$ASSET_URL" + printf 'asset_url=%s\n' "$PUBLIC_URL" printf 'action=%s\n' "$ACTION" } >>"$GITHUB_OUTPUT" diff --git a/.github/scripts/test-publish-abi-staging-request.sh b/.github/scripts/test-publish-abi-staging-request.sh index 92795ec926..32eed0ab1c 100755 --- a/.github/scripts/test-publish-abi-staging-request.sh +++ b/.github/scripts/test-publish-abi-staging-request.sh @@ -12,17 +12,65 @@ fail() { exit 1 } -if [[ ! -x "$PUBLISHER" ]]; then - fail "publisher is absent or not executable" -fi - +[[ -x $PUBLISHER ]] || fail "publisher is absent or not executable" mkdir -p "$TMP_ROOT/bin" +cp "$REQUEST" "$TMP_ROOT/request.json" + +cat >"$TMP_ROOT/bin/curl" <<'FAKE_CURL' +#!/usr/bin/env bash +set -euo pipefail +[[ -z ${GH_TOKEN+x} && -z ${GITHUB_TOKEN+x} ]] || exit 1 +output= +while (($#)); do + case "$1" in + --output) output=$2; shift 2 ;; + *) shift ;; + esac +done +[[ -n $output ]] +cp "$(dirname "$0")/../request.json" "$output" +: >"$(dirname "$0")/../anonymous-readback" +FAKE_CURL +chmod +x "$TMP_ROOT/bin/curl" + cat >"$TMP_ROOT/bin/gh" <<'FAKE_GH' #!/usr/bin/env bash set -euo pipefail printf '%q ' "$@" >>"$FAKE_STATE/calls.log" printf '\n' >>"$FAKE_STATE/calls.log" +release_json() { + local draft=true immutable=false target=$EXPECT_TARGET prerelease=true + [[ -f $FAKE_STATE/published || $FAKE_RELEASE_MODE == public-identical || + $FAKE_RELEASE_MODE == immutable-empty ]] && draft=false immutable=true + [[ $FAKE_RELEASE_MODE == wrong-target ]] && target=$(printf '9%.0s' {1..40}) + [[ $FAKE_RELEASE_MODE == wrong-prerelease ]] && prerelease=false + jq -cn --arg tag "$EXPECT_TAG" --arg target "$target" \ + --arg title "$EXPECT_TITLE" --arg body "$EXPECT_BODY" \ + --argjson prerelease "$prerelease" --argjson draft "$draft" \ + --argjson immutable "$immutable" \ + '{id:71,tag_name:$tag,target_commitish:$target,name:$title,body:$body,prerelease:$prerelease,draft:$draft,immutable:$immutable}' +} + +asset_pages() { + if [[ -f $FAKE_STATE/uploaded || $FAKE_RELEASE_MODE == draft-identical || + $FAKE_RELEASE_MODE == public-identical || $FAKE_RELEASE_MODE == collision ]]; then + jq -cn --arg name "$EXPECT_NAME" --arg url "$EXPECT_URL" \ + --arg digest "sha256:$EXPECT_DIGEST" --argjson bytes "$EXPECT_BYTES" \ + '[[{id:81,name:$name,browser_download_url:$url,state:"uploaded",size:$bytes,digest:$digest}]]' + elif [[ $FAKE_RELEASE_MODE == multiple ]]; then + jq -cn --arg name "$EXPECT_NAME" --arg url "$EXPECT_URL" \ + --arg digest "sha256:$EXPECT_DIGEST" --argjson bytes "$EXPECT_BYTES" \ + '[[{id:81,name:$name,browser_download_url:$url,state:"uploaded",size:$bytes,digest:$digest},{id:82,name:$name,browser_download_url:$url,state:"uploaded",size:$bytes,digest:$digest}]]' + else + printf '[[]]\n' + fi +} + +release_exists() { + [[ -f $FAKE_STATE/created || $FAKE_RELEASE_MODE != absent ]] +} + if [[ ${1:-} == api ]]; then shift method=GET @@ -36,54 +84,66 @@ if [[ ${1:-} == api ]]; then esac done case "$method:$endpoint" in - GET:*/releases/tags/*) - if [[ $FAKE_RELEASE_MODE == absent && ! -f $FAKE_STATE/created ]]; then + GET:*/git/ref/heads/main) + main_target=$EXPECT_TARGET + [[ $FAKE_RELEASE_MODE == wrong-main ]] && main_target=$(printf '7%.0s' {1..40}) + jq -cn --arg target "$main_target" \ + '{ref:"refs/heads/main",object:{type:"commit",sha:$target}}' + ;; + GET:*/git/ref/tags/*) + if [[ $FAKE_RELEASE_MODE == absent && ! -f $FAKE_STATE/tag-created ]]; then exit 1 fi - target=$EXPECT_TARGET - prerelease=true - [[ $FAKE_RELEASE_MODE == wrong-target ]] && target=$(printf '9%.0s' {1..40}) - [[ $FAKE_RELEASE_MODE == wrong-prerelease ]] && prerelease=false - jq -cn --arg tag "$EXPECT_TAG" --arg target "$target" \ - --argjson prerelease "$prerelease" \ - '{id:71,tag_name:$tag,target_commitish:$target,prerelease:$prerelease,draft:false}' + tag_target=$EXPECT_TARGET + [[ $FAKE_RELEASE_MODE == wrong-tag-target ]] && tag_target=$(printf '8%.0s' {1..40}) + jq -cn --arg tag "$EXPECT_TAG" --arg target "$tag_target" \ + '{ref:("refs/tags/"+$tag),object:{type:"commit",sha:$target}}' ;; - GET:*/releases/71/assets?*) - if [[ -f $FAKE_STATE/uploaded ]]; then - jq -cn --arg name "$EXPECT_NAME" --arg url "$EXPECT_URL" \ - '[[{id:81,name:$name,browser_download_url:$url}]]' - elif [[ $FAKE_ASSET_MODE == none ]]; then - printf '[[]]\n' - elif [[ $FAKE_ASSET_MODE == multiple ]]; then - jq -cn --arg name "$EXPECT_NAME" --arg url "$EXPECT_URL" \ - '[[{id:81,name:$name,browser_download_url:$url},{id:82,name:$name,browser_download_url:$url}]]' + POST:*/git/refs) + [[ ${FAKE_TAG_FAILURE:-0} == 1 ]] && exit 1 + : >"$FAKE_STATE/tag-created" + jq -cn --arg tag "$EXPECT_TAG" --arg target "$EXPECT_TARGET" \ + '{ref:("refs/tags/"+$tag),object:{type:"commit",sha:$target}}' + ;; + GET:*/releases\?*) + if release_exists; then + release_json | jq -s '[.]' else - jq -cn --arg name "$EXPECT_NAME" --arg url "$EXPECT_URL" \ - '[[{id:81,name:$name,browser_download_url:$url}]]' + printf '[[]]\n' fi ;; + GET:*/releases/71) + release_exists || exit 1 + release_json + ;; + GET:*/releases/71/assets?*) asset_pages ;; GET:*/releases/assets/81) - if [[ -f $FAKE_STATE/uploaded || $FAKE_ASSET_MODE == identical ]]; then - cat "$REQUEST" - else + if [[ $FAKE_RELEASE_MODE == collision ]]; then printf 'different bytes\n' + else + cat "$REQUEST" fi ;; POST:*/releases) - [[ ${FAKE_CREATE_FAILURE:-0} == 1 ]] && exit 1 : >"$FAKE_STATE/created" - jq -cn --arg tag "$EXPECT_TAG" --arg target "$EXPECT_TARGET" \ - '{id:71,tag_name:$tag,target_commitish:$target,prerelease:true,draft:false}' + [[ ${FAKE_CREATE_FAILURE:-0} == 1 ]] && exit 1 + release_json ;; PATCH:*/releases/71) + : >"$FAKE_STATE/published" [[ ${FAKE_PATCH_FAILURE:-0} == 1 ]] && exit 1 - printf '{"id":71}\n' + release_json ;; *) echo "unexpected fake gh api call: $method $endpoint" >&2; exit 2 ;; esac elif [[ ${1:-} == release && ${2:-} == upload ]]; then - [[ ${FAKE_UPLOAD_FAILURE:-0} == 1 ]] && exit 1 + [[ -f $FAKE_STATE/published || $FAKE_RELEASE_MODE == public-identical || + $FAKE_RELEASE_MODE == immutable-empty ]] && { + echo "Cannot upload assets to an immutable release" >&2 + exit 1 + } : >"$FAKE_STATE/uploaded" + [[ ${FAKE_UPLOAD_FAILURE:-0} == 1 ]] && exit 1 else echo "unexpected fake gh call: $*" >&2 exit 2 @@ -94,90 +154,134 @@ chmod +x "$TMP_ROOT/bin/gh" HEAD=$(jq -r '.build_source.commit' "$REQUEST") DIGEST=$(shasum -a 256 "$REQUEST" | awk '{print $1}') ASSET_NAME="candidate-request-${HEAD}-sha256-${DIGEST}.json" -TAG=abi-staging-pr-19 +TAG="abi-staging-pr-19-sha256-${DIGEST}" TARGET=$(printf '3%.0s' {1..40}) URL="https://github.com/Automattic/kandelo/releases/download/${TAG}/${ASSET_NAME}" BYTES=$(wc -c <"$REQUEST" | tr -d ' ') +TITLE='ABI staging request for PR #19' +BODY='Public, nonendorsed exact-head request. Promotion requires separate protected verification and admission.' write_plan() { local action=$1 output=$2 jq -cnS \ - --arg action "$action" \ - --arg asset_name "$ASSET_NAME" \ - --arg asset_sha256 "$DIGEST" \ - --arg public_download_url "$URL" \ - --arg repository Automattic/kandelo \ - --arg tag "$TAG" \ - --argjson asset_bytes "$BYTES" \ - --argjson pull_request_number 19 \ + --arg action "$action" --arg asset_name "$ASSET_NAME" \ + --arg asset_sha256 "$DIGEST" --arg public_download_url "$URL" \ + --arg repository Automattic/kandelo --arg tag "$TAG" \ + --argjson asset_bytes "$BYTES" --argjson pull_request_number 19 \ '{action:$action,asset_bytes:$asset_bytes,asset_name:$asset_name,asset_sha256:$asset_sha256,public_download_url:$public_download_url,pull_request_number:$pull_request_number,repository:$repository,tag:$tag}' \ >"$output" } +invoke() { + local state=$1 mode=$2 + shift 2 + env PATH="$TMP_ROOT/bin:$PATH" GH_TOKEN=test-token GITHUB_OUTPUT="$state/output" \ + ABI_STAGING_REQUEST_RETRY_DELAY_SECONDS=0 \ + FAKE_STATE="$state" FAKE_RELEASE_MODE="$mode" \ + EXPECT_TAG="$TAG" EXPECT_TARGET="$TARGET" EXPECT_NAME="$ASSET_NAME" \ + EXPECT_URL="$URL" EXPECT_DIGEST="$DIGEST" EXPECT_BYTES="$BYTES" \ + EXPECT_TITLE="$TITLE" EXPECT_BODY="$BODY" REQUEST="$REQUEST" "$@" \ + "$PUBLISHER" --repository Automattic/kandelo \ + --protected-target "$TARGET" --plan "$state/plan.json" --request "$REQUEST" +} + run_case() { - local name=$1 release_mode=$2 asset_mode=$3 expected=$4 + local name=$1 release_mode=$2 expected_outcome=$3 local state="$TMP_ROOT/$name" mkdir -p "$state" write_plan create-prerelease "$state/plan.json" - if env PATH="$TMP_ROOT/bin:$PATH" GH_TOKEN=test-token GITHUB_OUTPUT="$state/output" \ - FAKE_STATE="$state" FAKE_RELEASE_MODE="$release_mode" FAKE_ASSET_MODE="$asset_mode" \ - EXPECT_TAG="$TAG" EXPECT_TARGET="$TARGET" EXPECT_NAME="$ASSET_NAME" EXPECT_URL="$URL" \ - REQUEST="$REQUEST" "$PUBLISHER" --repository Automattic/kandelo \ - --protected-target "$TARGET" --plan "$state/plan.json" --request "$REQUEST"; then - [[ $expected == success ]] || fail "$name unexpectedly succeeded" + if invoke "$state" "$release_mode"; then + [[ $expected_outcome == success ]] || fail "$name unexpectedly succeeded" grep -Fq 'release_id=71' "$state/output" || fail "$name omitted release output" grep -Fq 'asset_id=81' "$state/output" || fail "$name omitted asset output" else - [[ $expected == failure ]] || fail "$name unexpectedly failed" + [[ $expected_outcome == failure ]] || fail "$name unexpectedly failed" fi if [[ -f $state/calls.log ]]; then - ! grep -Eiq -- '--clobber|method DELETE|release delete|refs/tags' "$state/calls.log" || - fail "$name attempted a destructive or mutable-tag operation" + ! grep -Eiq -- '--clobber|method DELETE|release delete|method PATCH .*/git/refs' \ + "$state/calls.log" || fail "$name attempted a destructive operation or tag move" fi } -run_case absent absent none success -run_case append correct none success -run_case identical correct identical success -run_case collision correct collision failure -run_case duplicate correct multiple failure -run_case wrong-target wrong-target none failure -run_case wrong-prerelease wrong-prerelease none failure +run_case absent absent success +[[ -f $TMP_ROOT/anonymous-readback ]] || fail "absent case omitted anonymous readback" +grep -Fq -- '--method POST /repos/Automattic/kandelo/git/refs' "$TMP_ROOT/absent/calls.log" || + fail "absent case did not create an exact direct tag" +create_line=$(grep -n -- '--method POST /repos/Automattic/kandelo/releases' "$TMP_ROOT/absent/calls.log" | cut -d: -f1) +upload_line=$(grep -n -- 'release upload' "$TMP_ROOT/absent/calls.log" | cut -d: -f1) +publish_line=$(grep -n -- '--method PATCH /repos/Automattic/kandelo/releases/71' "$TMP_ROOT/absent/calls.log" | cut -d: -f1) +[[ $create_line -lt $upload_line && $upload_line -lt $publish_line ]] || + fail "request was not uploaded to a draft before immutable publication" +[[ $(grep -c -- '/git/ref/heads/main' "$TMP_ROOT/absent/calls.log") -ge 3 ]] || + fail "publisher did not recapture protected main before every mutation" + +run_case draft-empty draft-empty success +run_case draft-identical draft-identical success +run_case public-identical public-identical success +! grep -Eq -- 'release upload|--method (POST|PATCH) /repos/.*/releases' \ + "$TMP_ROOT/public-identical/calls.log" || fail "immutable retry attempted a Release write" +run_case collision collision failure +run_case duplicate multiple failure +run_case immutable-empty immutable-empty failure +run_case wrong-target wrong-target failure +run_case wrong-prerelease wrong-prerelease failure +run_case wrong-tag-target wrong-tag-target failure +run_case wrong-main wrong-main failure +! grep -Eq -- '--method (POST|PATCH)|release upload' "$TMP_ROOT/wrong-main/calls.log" || + fail "wrong protected main reached a GitHub mutation" state="$TMP_ROOT/upload-failure" mkdir -p "$state" write_plan append-asset "$state/plan.json" -if env PATH="$TMP_ROOT/bin:$PATH" GH_TOKEN=test-token GITHUB_OUTPUT="$state/output" \ - FAKE_STATE="$state" FAKE_RELEASE_MODE=correct FAKE_ASSET_MODE=none FAKE_UPLOAD_FAILURE=1 \ - EXPECT_TAG="$TAG" EXPECT_TARGET="$TARGET" EXPECT_NAME="$ASSET_NAME" EXPECT_URL="$URL" \ - REQUEST="$REQUEST" "$PUBLISHER" --repository Automattic/kandelo \ - --protected-target "$TARGET" --plan "$state/plan.json" --request "$REQUEST"; then - fail "upload failure unexpectedly succeeded" -fi +invoke "$state" draft-empty FAKE_UPLOAD_FAILURE=1 || + fail "ambiguous upload was not reconciled" state="$TMP_ROOT/patch-failure" mkdir -p "$state" write_plan append-asset "$state/plan.json" -if env PATH="$TMP_ROOT/bin:$PATH" GH_TOKEN=test-token GITHUB_OUTPUT="$state/output" \ - FAKE_STATE="$state" FAKE_RELEASE_MODE=correct FAKE_ASSET_MODE=none FAKE_PATCH_FAILURE=1 \ - EXPECT_TAG="$TAG" EXPECT_TARGET="$TARGET" EXPECT_NAME="$ASSET_NAME" EXPECT_URL="$URL" \ - REQUEST="$REQUEST" "$PUBLISHER" --repository Automattic/kandelo \ - --protected-target "$TARGET" --plan "$state/plan.json" --request "$REQUEST"; then - fail "description update failure unexpectedly succeeded" -fi -[[ -f $state/uploaded ]] || fail "description was updated before the asset was secured" +invoke "$state" draft-empty FAKE_PATCH_FAILURE=1 || + fail "ambiguous publication was not reconciled" +[[ -f $state/uploaded ]] || fail "draft was published before its asset was secured" state="$TMP_ROOT/preflight" mkdir -p "$state" write_plan append-asset "$state/plan.json" jq '.asset_sha256 = ("0" * 64)' "$state/plan.json" >"$state/bad-plan.json" -if env PATH="$TMP_ROOT/bin:$PATH" GH_TOKEN=test-token GITHUB_OUTPUT="$state/output" \ - FAKE_STATE="$state" FAKE_RELEASE_MODE=correct FAKE_ASSET_MODE=none \ - EXPECT_TAG="$TAG" EXPECT_TARGET="$TARGET" EXPECT_NAME="$ASSET_NAME" EXPECT_URL="$URL" \ - REQUEST="$REQUEST" "$PUBLISHER" --repository Automattic/kandelo \ - --protected-target "$TARGET" --plan "$state/bad-plan.json" --request "$REQUEST"; then +mv "$state/bad-plan.json" "$state/plan.json" +if invoke "$state" draft-empty; then fail "invalid preflight unexpectedly succeeded" fi [[ ! -e $state/calls.log ]] || fail "invalid preflight reached GitHub" +state="$TMP_ROOT/wrong-issuer" +mkdir -p "$state" +cp "$REQUEST" "$state/request.json" +jq -cS '.issuance.issuer_workflow_ref = + "Automattic/kandelo/.github/workflows/abi-staging-request-feed.yml@9999999999999999999999999999999999999999"' \ + "$state/request.json" >"$state/wrong-request.json" +wrong_digest=$(shasum -a 256 "$state/wrong-request.json" | awk '{print $1}') +wrong_name="candidate-request-${HEAD}-sha256-${wrong_digest}.json" +wrong_tag="abi-staging-pr-19-sha256-${wrong_digest}" +jq -cnS --arg name "$wrong_name" --arg digest "$wrong_digest" \ + --arg tag "$wrong_tag" \ + --arg url "https://github.com/Automattic/kandelo/releases/download/${wrong_tag}/${wrong_name}" \ + --argjson bytes "$(wc -c <"$state/wrong-request.json" | tr -d ' ')" \ + '{action:"create-prerelease",asset_bytes:$bytes,asset_name:$name, + asset_sha256:$digest,public_download_url:$url,pull_request_number:19, + repository:"Automattic/kandelo",tag:$tag}' >"$state/plan.json" +if env PATH="$TMP_ROOT/bin:$PATH" GH_TOKEN=test-token GITHUB_OUTPUT="$state/output" \ + ABI_STAGING_REQUEST_RETRY_DELAY_SECONDS=0 \ + FAKE_STATE="$state" FAKE_RELEASE_MODE=absent \ + EXPECT_TAG="$wrong_tag" EXPECT_TARGET="$TARGET" EXPECT_NAME="$wrong_name" \ + EXPECT_URL="https://github.com/Automattic/kandelo/releases/download/${wrong_tag}/${wrong_name}" \ + EXPECT_DIGEST="$wrong_digest" \ + EXPECT_BYTES="$(wc -c <"$state/wrong-request.json" | tr -d ' ')" \ + EXPECT_TITLE="$TITLE" EXPECT_BODY="$BODY" REQUEST="$state/wrong-request.json" \ + "$PUBLISHER" --repository Automattic/kandelo --protected-target "$TARGET" \ + --plan "$state/plan.json" --request "$state/wrong-request.json" +then + fail "request with another issuer commit unexpectedly reached publication" +fi +[[ ! -e $state/calls.log ]] || fail "wrong issuer request reached GitHub" + echo "test-publish-abi-staging-request: PASS" diff --git a/abi/staging/request-policy.generated.json b/abi/staging/request-policy.generated.json index 90d522c19f..8d565be27d 100644 --- a/abi/staging/request-policy.generated.json +++ b/abi/staging/request-policy.generated.json @@ -1 +1 @@ -{"addressed_taps":["kandelo-dev/homebrew-tap-core"],"automatic_same_repository":true,"fork_authorization":"disabled","implementation":[{"path":".github/scripts/update-abi-staging-check.sh","sha256":"78d49f35fdf27d426dcae6d6a19aa3eeca4d8a879bda0df319b70a4d4cf31ff8"},{"path":".github/workflows/abi-staging-merge-gate.yml","sha256":"a238f389b670ac6a54438c5894ca73f0f897fb393dbe24e8ed31f93c018e568f"},{"path":".github/workflows/abi-staging-pages-canary.yml","sha256":"3f9ecd19eb0224b2c11402ed3828b6ac2de1f6e291c84cfdf2fe59bae3a18676"},{"path":".github/workflows/abi-staging-pr-check.yml","sha256":"85a9b363caafb3e20b909c4145e45ddf6e0bc871547a986213e56c2d859ccf3c"},{"path":".github/workflows/abi-staging-request-feed.yml","sha256":"8cb53b1e466c7df016d369e5f0d93e1e53e17277e8f6771d329baf36367682fd"},{"path":"Cargo.lock","sha256":"80447a16efcee77c60f047afbb25725fd24967934a255af0b9def6e3f483721e"},{"path":"Cargo.toml","sha256":"e033e7c8489f41a9d12517e01be99d89054bd5ee831d74148702a50504c9ff02"},{"path":"abi/staging/evidence-definitions.generated.json","sha256":"2189a671c22b26164de85ac66e714592e30eef76666cc921441281ac21a1d30c"},{"path":"abi/staging/evidence-definitions.toml","sha256":"1ff246db2cf82357f348af2e0ae8bcc30fa149705b523562f507106ff60eb416"},{"path":"apps/browser-demos/abi-staging-browser-harness.config.ts","sha256":"314d59a2b912625b3878d4f46e04c9f256615ed5cdd7bd85ae7aef8351e99202"},{"path":"apps/browser-demos/abi-staging-browser-host.config.ts","sha256":"af348730b45bf7ecc60c1f03e518979000f709de580fa05066dac6372aa4bc2c"},{"path":"apps/browser-demos/abi-staging-browser-host.ts","sha256":"beca82b52c32b0ef56bcf2cdc1f04138213993cb43e117c09661feae2ccc7004"},{"path":"apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","sha256":"bc7d6655898d2fd7b712a20565723996a5d4cf9c23eb46989522aaa7d874ea98"},{"path":"apps/browser-demos/lib/homebrew-closed-acceptance.ts","sha256":"5e88d8ec55b7f495647c83db8c2db6e51254a6c437edeac45728c8a535e02b2d"},{"path":"apps/browser-demos/lib/mysql-client.ts","sha256":"bcb3b2fc913366bd9a828125cad80e41cdd1fac9ce37f3c2b45910463290b05c"},{"path":"apps/browser-demos/lib/redis-client.ts","sha256":"cde9e40502bdf6cb85f3b421273a023f290919c57f162ab71b7805c78d08a6ae"},{"path":"apps/browser-demos/package-lock.json","sha256":"12b6409372d589d931b39bbc3bce0e9ad01f0dd6a5dbce8dfbef854ab5f2193b"},{"path":"apps/browser-demos/package.json","sha256":"bac6058f02c11ecd55957bae288b0ffc2da4e5dc9ca1673447b1cfb416fcec2a"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/index.html","sha256":"889d1e8afab89d05ea02bc1c18459ccecfe585dab0b52d50aeff3dec979e08e0"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/main.ts","sha256":"19ac44a9631a42087111ce85dc65b60dd8fe5a14ec67d72492a382760a93f898"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","sha256":"f33bcec8e48bad54ffa9317ef6e16442e6328b3a093b3f208cb751054f582eaf"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","sha256":"d707a48c6355c9d451251be599f37f7c031fb82326b0b01bfa3af062d2b57c49"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","sha256":"77d08bd3b94ef4f74ce5b486045e2dd168ea8606a262249736c6372357a3de0f"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","sha256":"2ea2dfa31054b7b041ab342a1c1d1626088ac9f207df8c12d2040030457677bb"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","sha256":"60ff6a4cb6104f474e387b90638c2b71ec48587397fa19a9ef34da2dfb6192d9"},{"path":"apps/browser-demos/pages/network/network-demo-worker.ts","sha256":"a59eb6c7de64460456b43fec0930975987dee3c487acb8f2d6d4f93c2bcc9649"},{"path":"apps/browser-demos/playwright-closed-acceptance.ts","sha256":"430fbc4eea4a92ccd8a36c0aeb1c5b23aadf22e95d7151f09dadc4ce4f49b43b"},{"path":"apps/browser-demos/playwright-server-policy.ts","sha256":"eda5aa8651bc6854d1634be1c5ffdbf3a91a97c87b91d9b065f997e12afa420f"},{"path":"apps/browser-demos/playwright.config.ts","sha256":"e6d9a7cc6dab59be4d12aca97da856dffe5120facb1c5812225e3526cac37b42"},{"path":"apps/browser-demos/test/abi-staging-product-evidence.spec.ts","sha256":"e90b265133cfcde059c91d066261e243b761e20750ea8377e7679e4d9d877706"},{"path":"apps/browser-demos/test/support/terminal-command.ts","sha256":"919e0898cd9f317c95eb29d6a0fb70d398f18fdca6192f705b8eee5093eecac3"},{"path":"apps/browser-demos/vite.config.ts","sha256":"6d0565eac538e9d42fe4dffea659ec83b6f994a6e446fd7f7caba19233cecb0d"},{"path":"flake.lock","sha256":"2df2e3397a8c75ecf4fb6c897959650c76f27f65721bf3ea5a08304b480ad6d8"},{"path":"flake.nix","sha256":"86a72ab6b1c9c6307c1d8dc9c42859a7d5868d4f8703962ddb08ca0993564e01"},{"path":"host/package-lock.json","sha256":"fc1a12528aa0f0d00fdb4895864b9ebf288d0e5b908bee5880e908128a2682dd"},{"path":"host/package.json","sha256":"a1e154db420da65f58095771b6fd7fd1b30141646cdcc98e30ad359553bf2be0"},{"path":"host/src/generated/abi.ts","sha256":"9d557cf739333272dc386b4bd593d8c8f5c08f968955d5374d9a8e0254125fc4"},{"path":"host/src/homebrew-bottle-relocation.ts","sha256":"598cf3a732253f35ca041ec99f60aac9494b510f65b5411c733ce332eefdf4c1"},{"path":"host/src/homebrew-guest-layout.ts","sha256":"84866dbb5730ff6c6df1a6cfe23384f50ac9e438b18e40b20678b2b63b9eccef"},{"path":"host/src/pathconf.ts","sha256":"ae331cf37a7173ee1cf7beec5782368a2d82034da13b426b084066556202ed3c"},{"path":"host/src/statfs.ts","sha256":"3e11cd17f6460b7ee5965738f5a04295b3aec5da14c9c0251c6a2dc1283194a7"},{"path":"host/src/vfs/closed-lazy-assets.ts","sha256":"278e8cd36cd1c47ad876fdf310af4014d91e174f3717acda55c125c0b7d269fd"},{"path":"host/src/vfs/deferred-tree-limits.ts","sha256":"1a0cc35809f3708724a68a99a08890c1072267a74bc4c63eab0cb774f0332da6"},{"path":"host/src/vfs/hardlink-graph.ts","sha256":"9d19366a7e739d55ef81cea074ab9633f255e65f9332e5aa82da70b1d1ccd1cf"},{"path":"host/src/vfs/load-image.ts","sha256":"df62f1b2faf98fb2e9900a6f6b428ae16e7f43494a6ade01f709006ef764744b"},{"path":"host/src/vfs/memory-fs.ts","sha256":"b197061158467e779ea3999dd7f5ba75a6894efd756d54c050c60a31156ddd19"},{"path":"host/src/vfs/product-mount-contract.ts","sha256":"2aeb61b4eff2689a7c054a8919fe622f46e621006d64050390a04af3ef05f4f0"},{"path":"host/src/vfs/sharedfs-vendor.ts","sha256":"a5bf8c9691b4d0ab170e68b0208db3b54fece2a4924e98ad472a36f28b441088"},{"path":"host/tsup.config.ts","sha256":"1d811ee5e7d2c07d4731c2e6df9181be6e511ecf4d1724495085d22f4af51a47"},{"path":"images/vfs/scripts/repository-path-bundle.ts","sha256":"64bad99dca7b32d57c104a9da1dd4977f0e8de56dc5950fd94bc3cda19ac987a"},{"path":"package-lock.json","sha256":"a46b2c8b6c8e56dd235a826c266a6fad7d702a340eb0b42cf0b6c99aaa428c71"},{"path":"package.json","sha256":"7411d3663ef7a3458da32f39e3919bda133377246ff629fc4db98f75792db2df"},{"path":"packages/registry/program-packages.json","sha256":"eea178faf24242ab946b97edaa8338f21165865d76983c7faf402e1e368238d3"},{"path":"scripts/abi-staging-build-toolchain.sh","sha256":"ed73bcd74bb7e2c409c2ae6887b4717c97b82f35e4f3347f523a7a240f47e815"},{"path":"scripts/abi-staging-collect-product-inputs.ts","sha256":"4ecd65b0789e21cb0ba3c5d133c9f3589da280258d382a3c208d859eefa03221"},{"path":"scripts/abi-staging-pages-producer.ts","sha256":"067fbecd469f83e266ab71db076d8a6be904a43986d74320caa80bd07dfd777e"},{"path":"scripts/abi-staging-pages-readiness.ts","sha256":"a254cf3f7fedef367ecc6fb91fa2175fc255f87a6458c02d0bf4caad23019f3b"},{"path":"scripts/abi-staging-pages-site-builder.ts","sha256":"97ce037272c1f8200ae48a73d027371f90c13dbd16b7194846b261eb51326be7"},{"path":"scripts/abi-staging-prepare-runtime.sh","sha256":"38ff5658d1147c6f82c542559078204e89decc2475f6ddb626673cafb1961c26"},{"path":"scripts/abi-staging-product-browser-evidence.ts","sha256":"458c31795759e2745f8eb071603f51c947f123cdea51cea4d7efebbfb3aa2a98"},{"path":"scripts/abi-staging-product-input-sources.ts","sha256":"eca75b92c09a5a28c745ecc6ae6e2407b9637c8710d022b76f7aab9e05e5be14"},{"path":"scripts/abi-staging-product-node-evidence.ts","sha256":"786fe35d64cbbf7b4898b9ac58f5c4483d8cc6f2b90f6644beb782126034ff15"},{"path":"scripts/abi-staging-protected-browser-operation.ts","sha256":"858b417d9562cf8618a416893d47e3bc616cedb7b1cfed673c467f1d0737b935"},{"path":"scripts/check-dev-shell-tools.sh","sha256":"f9187fc855b9949cb20ac7593ea17292948e525684bec603efcabea176096185"},{"path":"scripts/check-pages-vfs-product-registry.mjs","sha256":"6cbb24557f1e1a6961bd0fa7f11cbf2bae3035f5458971cf75ca31a891af2b0b"},{"path":"scripts/dev-shell.sh","sha256":"3931770c7b9f50ccee5a89e2957ed7a9856d4713e7fb6cb2631b29d0c6c4cff7"},{"path":"scripts/fetch-exact-musl-gitlink.sh","sha256":"8a14b1c97e2331f104d6f061f9a74874f1d0e94a3d58b413d69f73412701b8cc"},{"path":"scripts/run-vfs-product-builder.ts","sha256":"95654c6298474d02a64b7d93bc19ad7a54776bb636515efffa4ae518fc7e8a82"},{"path":"scripts/vfs-product-catalog.mjs","sha256":"3bb39bddb2add9c05b15f80964a22acdbb33648e43118d4d956b0ae062cd7eb0"},{"path":"tools/xtask/Cargo.toml","sha256":"b30b900521eaa9652937371cb7a9ebe38302a5e9d58aabb6743996acece87155"},{"path":"tools/xtask/src/abi_staging/builder_contract.rs","sha256":"7b52f1e143039dc32708e1e0bbbdb7075530ef682b73607e5d77818efa80de8f"},{"path":"tools/xtask/src/abi_staging/canonical_json.rs","sha256":"50e42796a53d517268e9afeea7f594e1db47b65e2e903e9d17e9db5dd9e1a6cf"},{"path":"tools/xtask/src/abi_staging/check_projection.rs","sha256":"d9f700f27b2a10238e318abd6d47c1ed205ace5a2fb92b6a0afc891eb96c7b5f"},{"path":"tools/xtask/src/abi_staging/consumer_registry.rs","sha256":"1985490c56ffd89feb8d41f5c2bef0f41432c37e26a4282b27da39d3476574bb"},{"path":"tools/xtask/src/abi_staging/evidence_policy.rs","sha256":"45676d5a430f5d4f19b3519862b29bb9204eb8fb1f7ad5c987887dc57874882d"},{"path":"tools/xtask/src/abi_staging/guard_registry.rs","sha256":"eed45cfe19c48716da9d205f1aa6a3bbbc0e96e0e96e1e50bcaf6329cfc663fe"},{"path":"tools/xtask/src/abi_staging/mod.rs","sha256":"65eec6b0d987138f8159edd6de627643a39fc1fa01ced5836bf5e878b4367d75"},{"path":"tools/xtask/src/abi_staging/product_evidence.rs","sha256":"c5ce7790e478666d75381834ea3eddec26428bdd7c6707fdd5128259fbb50e8c"},{"path":"tools/xtask/src/abi_staging/product_manifest.rs","sha256":"4db3dd8e01bbbbd52a3b8c862f27b2e1feb685caa038cb7b8913d13cd9ac72e2"},{"path":"tools/xtask/src/abi_staging/records.rs","sha256":"6d21b7325d70874804da528a4714e5a488ed23fdeac0fd77ef61c68402d84203"},{"path":"tools/xtask/src/abi_staging/request_derivation.rs","sha256":"02190e3e19aed2d5b00cab14a2cd9ef761e9ba7d276405058db973a8aa36c8c7"},{"path":"tools/xtask/src/abi_staging/request_policy.rs","sha256":"91f5e8db053b702ab9a57fa44178192f6e53957dc02b4d413b65dd3cd8a15270"},{"path":"tools/xtask/src/abi_staging/selection.rs","sha256":"1c8c517a31d7fa2d7abc97b3d57db75df38c20fc98170d430e94dcafd82da2eb"}],"implementation_paths":[".github/scripts/update-abi-staging-check.sh",".github/workflows/abi-staging-merge-gate.yml",".github/workflows/abi-staging-pages-canary.yml",".github/workflows/abi-staging-pr-check.yml",".github/workflows/abi-staging-request-feed.yml","Cargo.lock","Cargo.toml","abi/staging/evidence-definitions.generated.json","abi/staging/evidence-definitions.toml","apps/browser-demos/abi-staging-browser-harness.config.ts","apps/browser-demos/abi-staging-browser-host.config.ts","apps/browser-demos/abi-staging-browser-host.ts","apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","apps/browser-demos/lib/homebrew-closed-acceptance.ts","apps/browser-demos/lib/mysql-client.ts","apps/browser-demos/lib/redis-client.ts","apps/browser-demos/package-lock.json","apps/browser-demos/package.json","apps/browser-demos/pages/abi-staging-product-evidence/index.html","apps/browser-demos/pages/abi-staging-product-evidence/main.ts","apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","apps/browser-demos/pages/network/network-demo-worker.ts","apps/browser-demos/playwright-closed-acceptance.ts","apps/browser-demos/playwright-server-policy.ts","apps/browser-demos/playwright.config.ts","apps/browser-demos/test/abi-staging-product-evidence.spec.ts","apps/browser-demos/test/support/terminal-command.ts","apps/browser-demos/vite.config.ts","flake.lock","flake.nix","host/package-lock.json","host/package.json","host/src/generated/abi.ts","host/src/homebrew-bottle-relocation.ts","host/src/homebrew-guest-layout.ts","host/src/pathconf.ts","host/src/statfs.ts","host/src/vfs/closed-lazy-assets.ts","host/src/vfs/deferred-tree-limits.ts","host/src/vfs/hardlink-graph.ts","host/src/vfs/load-image.ts","host/src/vfs/memory-fs.ts","host/src/vfs/product-mount-contract.ts","host/src/vfs/sharedfs-vendor.ts","host/tsup.config.ts","images/vfs/scripts/repository-path-bundle.ts","package-lock.json","package.json","packages/registry/program-packages.json","scripts/abi-staging-build-toolchain.sh","scripts/abi-staging-collect-product-inputs.ts","scripts/abi-staging-pages-producer.ts","scripts/abi-staging-pages-readiness.ts","scripts/abi-staging-pages-site-builder.ts","scripts/abi-staging-prepare-runtime.sh","scripts/abi-staging-product-browser-evidence.ts","scripts/abi-staging-product-input-sources.ts","scripts/abi-staging-product-node-evidence.ts","scripts/abi-staging-protected-browser-operation.ts","scripts/check-dev-shell-tools.sh","scripts/check-pages-vfs-product-registry.mjs","scripts/dev-shell.sh","scripts/fetch-exact-musl-gitlink.sh","scripts/run-vfs-product-builder.ts","scripts/vfs-product-catalog.mjs","tools/xtask/Cargo.toml","tools/xtask/src/abi_staging/builder_contract.rs","tools/xtask/src/abi_staging/canonical_json.rs","tools/xtask/src/abi_staging/check_projection.rs","tools/xtask/src/abi_staging/consumer_registry.rs","tools/xtask/src/abi_staging/evidence_policy.rs","tools/xtask/src/abi_staging/guard_registry.rs","tools/xtask/src/abi_staging/mod.rs","tools/xtask/src/abi_staging/product_evidence.rs","tools/xtask/src/abi_staging/product_manifest.rs","tools/xtask/src/abi_staging/records.rs","tools/xtask/src/abi_staging/request_derivation.rs","tools/xtask/src/abi_staging/request_policy.rs","tools/xtask/src/abi_staging/selection.rs"],"issuer_repository":"Automattic/kandelo","issuer_workflow":".github/workflows/abi-staging-request-feed.yml","kind":"kandelo-abi-staging-request-policy","max_evidence_bindings":512,"max_products":256,"request_asset_max_bytes":4194304,"request_release_tag_prefix":"abi-staging-pr-","schema":1,"version":14} +{"addressed_taps":["kandelo-dev/homebrew-tap-core"],"automatic_same_repository":true,"fork_authorization":"disabled","implementation":[{"path":".github/scripts/publish-abi-staging-request.sh","sha256":"80e19ad25a510e950d78e94f165b904798d5844372339ec478a14fb59dc47f6e"},{"path":".github/scripts/update-abi-staging-check.sh","sha256":"78d49f35fdf27d426dcae6d6a19aa3eeca4d8a879bda0df319b70a4d4cf31ff8"},{"path":".github/workflows/abi-staging-merge-gate.yml","sha256":"a238f389b670ac6a54438c5894ca73f0f897fb393dbe24e8ed31f93c018e568f"},{"path":".github/workflows/abi-staging-pages-canary.yml","sha256":"3f9ecd19eb0224b2c11402ed3828b6ac2de1f6e291c84cfdf2fe59bae3a18676"},{"path":".github/workflows/abi-staging-pr-check.yml","sha256":"85a9b363caafb3e20b909c4145e45ddf6e0bc871547a986213e56c2d859ccf3c"},{"path":".github/workflows/abi-staging-request-feed.yml","sha256":"8cb53b1e466c7df016d369e5f0d93e1e53e17277e8f6771d329baf36367682fd"},{"path":"Cargo.lock","sha256":"80447a16efcee77c60f047afbb25725fd24967934a255af0b9def6e3f483721e"},{"path":"Cargo.toml","sha256":"e033e7c8489f41a9d12517e01be99d89054bd5ee831d74148702a50504c9ff02"},{"path":"abi/staging/evidence-definitions.generated.json","sha256":"2189a671c22b26164de85ac66e714592e30eef76666cc921441281ac21a1d30c"},{"path":"abi/staging/evidence-definitions.toml","sha256":"1ff246db2cf82357f348af2e0ae8bcc30fa149705b523562f507106ff60eb416"},{"path":"apps/browser-demos/abi-staging-browser-harness.config.ts","sha256":"314d59a2b912625b3878d4f46e04c9f256615ed5cdd7bd85ae7aef8351e99202"},{"path":"apps/browser-demos/abi-staging-browser-host.config.ts","sha256":"af348730b45bf7ecc60c1f03e518979000f709de580fa05066dac6372aa4bc2c"},{"path":"apps/browser-demos/abi-staging-browser-host.ts","sha256":"beca82b52c32b0ef56bcf2cdc1f04138213993cb43e117c09661feae2ccc7004"},{"path":"apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","sha256":"bc7d6655898d2fd7b712a20565723996a5d4cf9c23eb46989522aaa7d874ea98"},{"path":"apps/browser-demos/lib/homebrew-closed-acceptance.ts","sha256":"5e88d8ec55b7f495647c83db8c2db6e51254a6c437edeac45728c8a535e02b2d"},{"path":"apps/browser-demos/lib/mysql-client.ts","sha256":"bcb3b2fc913366bd9a828125cad80e41cdd1fac9ce37f3c2b45910463290b05c"},{"path":"apps/browser-demos/lib/redis-client.ts","sha256":"cde9e40502bdf6cb85f3b421273a023f290919c57f162ab71b7805c78d08a6ae"},{"path":"apps/browser-demos/package-lock.json","sha256":"12b6409372d589d931b39bbc3bce0e9ad01f0dd6a5dbce8dfbef854ab5f2193b"},{"path":"apps/browser-demos/package.json","sha256":"bac6058f02c11ecd55957bae288b0ffc2da4e5dc9ca1673447b1cfb416fcec2a"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/index.html","sha256":"889d1e8afab89d05ea02bc1c18459ccecfe585dab0b52d50aeff3dec979e08e0"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/main.ts","sha256":"19ac44a9631a42087111ce85dc65b60dd8fe5a14ec67d72492a382760a93f898"},{"path":"apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","sha256":"f33bcec8e48bad54ffa9317ef6e16442e6328b3a093b3f208cb751054f582eaf"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","sha256":"d707a48c6355c9d451251be599f37f7c031fb82326b0b01bfa3af062d2b57c49"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","sha256":"77d08bd3b94ef4f74ce5b486045e2dd168ea8606a262249736c6372357a3de0f"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","sha256":"2ea2dfa31054b7b041ab342a1c1d1626088ac9f207df8c12d2040030457677bb"},{"path":"apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","sha256":"60ff6a4cb6104f474e387b90638c2b71ec48587397fa19a9ef34da2dfb6192d9"},{"path":"apps/browser-demos/pages/network/network-demo-worker.ts","sha256":"a59eb6c7de64460456b43fec0930975987dee3c487acb8f2d6d4f93c2bcc9649"},{"path":"apps/browser-demos/playwright-closed-acceptance.ts","sha256":"430fbc4eea4a92ccd8a36c0aeb1c5b23aadf22e95d7151f09dadc4ce4f49b43b"},{"path":"apps/browser-demos/playwright-server-policy.ts","sha256":"eda5aa8651bc6854d1634be1c5ffdbf3a91a97c87b91d9b065f997e12afa420f"},{"path":"apps/browser-demos/playwright.config.ts","sha256":"e6d9a7cc6dab59be4d12aca97da856dffe5120facb1c5812225e3526cac37b42"},{"path":"apps/browser-demos/test/abi-staging-product-evidence.spec.ts","sha256":"e90b265133cfcde059c91d066261e243b761e20750ea8377e7679e4d9d877706"},{"path":"apps/browser-demos/test/support/terminal-command.ts","sha256":"919e0898cd9f317c95eb29d6a0fb70d398f18fdca6192f705b8eee5093eecac3"},{"path":"apps/browser-demos/vite.config.ts","sha256":"6d0565eac538e9d42fe4dffea659ec83b6f994a6e446fd7f7caba19233cecb0d"},{"path":"flake.lock","sha256":"2df2e3397a8c75ecf4fb6c897959650c76f27f65721bf3ea5a08304b480ad6d8"},{"path":"flake.nix","sha256":"86a72ab6b1c9c6307c1d8dc9c42859a7d5868d4f8703962ddb08ca0993564e01"},{"path":"host/package-lock.json","sha256":"fc1a12528aa0f0d00fdb4895864b9ebf288d0e5b908bee5880e908128a2682dd"},{"path":"host/package.json","sha256":"a1e154db420da65f58095771b6fd7fd1b30141646cdcc98e30ad359553bf2be0"},{"path":"host/src/generated/abi.ts","sha256":"9d557cf739333272dc386b4bd593d8c8f5c08f968955d5374d9a8e0254125fc4"},{"path":"host/src/homebrew-bottle-relocation.ts","sha256":"598cf3a732253f35ca041ec99f60aac9494b510f65b5411c733ce332eefdf4c1"},{"path":"host/src/homebrew-guest-layout.ts","sha256":"84866dbb5730ff6c6df1a6cfe23384f50ac9e438b18e40b20678b2b63b9eccef"},{"path":"host/src/pathconf.ts","sha256":"ae331cf37a7173ee1cf7beec5782368a2d82034da13b426b084066556202ed3c"},{"path":"host/src/statfs.ts","sha256":"3e11cd17f6460b7ee5965738f5a04295b3aec5da14c9c0251c6a2dc1283194a7"},{"path":"host/src/vfs/closed-lazy-assets.ts","sha256":"278e8cd36cd1c47ad876fdf310af4014d91e174f3717acda55c125c0b7d269fd"},{"path":"host/src/vfs/deferred-tree-limits.ts","sha256":"1a0cc35809f3708724a68a99a08890c1072267a74bc4c63eab0cb774f0332da6"},{"path":"host/src/vfs/hardlink-graph.ts","sha256":"9d19366a7e739d55ef81cea074ab9633f255e65f9332e5aa82da70b1d1ccd1cf"},{"path":"host/src/vfs/load-image.ts","sha256":"df62f1b2faf98fb2e9900a6f6b428ae16e7f43494a6ade01f709006ef764744b"},{"path":"host/src/vfs/memory-fs.ts","sha256":"b197061158467e779ea3999dd7f5ba75a6894efd756d54c050c60a31156ddd19"},{"path":"host/src/vfs/product-mount-contract.ts","sha256":"2aeb61b4eff2689a7c054a8919fe622f46e621006d64050390a04af3ef05f4f0"},{"path":"host/src/vfs/sharedfs-vendor.ts","sha256":"a5bf8c9691b4d0ab170e68b0208db3b54fece2a4924e98ad472a36f28b441088"},{"path":"host/tsup.config.ts","sha256":"1d811ee5e7d2c07d4731c2e6df9181be6e511ecf4d1724495085d22f4af51a47"},{"path":"images/vfs/scripts/repository-path-bundle.ts","sha256":"64bad99dca7b32d57c104a9da1dd4977f0e8de56dc5950fd94bc3cda19ac987a"},{"path":"package-lock.json","sha256":"a46b2c8b6c8e56dd235a826c266a6fad7d702a340eb0b42cf0b6c99aaa428c71"},{"path":"package.json","sha256":"7411d3663ef7a3458da32f39e3919bda133377246ff629fc4db98f75792db2df"},{"path":"packages/registry/program-packages.json","sha256":"eea178faf24242ab946b97edaa8338f21165865d76983c7faf402e1e368238d3"},{"path":"scripts/abi-staging-build-toolchain.sh","sha256":"ed73bcd74bb7e2c409c2ae6887b4717c97b82f35e4f3347f523a7a240f47e815"},{"path":"scripts/abi-staging-collect-product-inputs.ts","sha256":"4ecd65b0789e21cb0ba3c5d133c9f3589da280258d382a3c208d859eefa03221"},{"path":"scripts/abi-staging-pages-producer.ts","sha256":"067fbecd469f83e266ab71db076d8a6be904a43986d74320caa80bd07dfd777e"},{"path":"scripts/abi-staging-pages-readiness.ts","sha256":"a254cf3f7fedef367ecc6fb91fa2175fc255f87a6458c02d0bf4caad23019f3b"},{"path":"scripts/abi-staging-pages-site-builder.ts","sha256":"97ce037272c1f8200ae48a73d027371f90c13dbd16b7194846b261eb51326be7"},{"path":"scripts/abi-staging-prepare-runtime.sh","sha256":"38ff5658d1147c6f82c542559078204e89decc2475f6ddb626673cafb1961c26"},{"path":"scripts/abi-staging-product-browser-evidence.ts","sha256":"458c31795759e2745f8eb071603f51c947f123cdea51cea4d7efebbfb3aa2a98"},{"path":"scripts/abi-staging-product-input-sources.ts","sha256":"eca75b92c09a5a28c745ecc6ae6e2407b9637c8710d022b76f7aab9e05e5be14"},{"path":"scripts/abi-staging-product-node-evidence.ts","sha256":"786fe35d64cbbf7b4898b9ac58f5c4483d8cc6f2b90f6644beb782126034ff15"},{"path":"scripts/abi-staging-protected-browser-operation.ts","sha256":"858b417d9562cf8618a416893d47e3bc616cedb7b1cfed673c467f1d0737b935"},{"path":"scripts/check-dev-shell-tools.sh","sha256":"f9187fc855b9949cb20ac7593ea17292948e525684bec603efcabea176096185"},{"path":"scripts/check-pages-vfs-product-registry.mjs","sha256":"6cbb24557f1e1a6961bd0fa7f11cbf2bae3035f5458971cf75ca31a891af2b0b"},{"path":"scripts/dev-shell.sh","sha256":"3931770c7b9f50ccee5a89e2957ed7a9856d4713e7fb6cb2631b29d0c6c4cff7"},{"path":"scripts/fetch-exact-musl-gitlink.sh","sha256":"8a14b1c97e2331f104d6f061f9a74874f1d0e94a3d58b413d69f73412701b8cc"},{"path":"scripts/run-vfs-product-builder.ts","sha256":"95654c6298474d02a64b7d93bc19ad7a54776bb636515efffa4ae518fc7e8a82"},{"path":"scripts/vfs-product-catalog.mjs","sha256":"3bb39bddb2add9c05b15f80964a22acdbb33648e43118d4d956b0ae062cd7eb0"},{"path":"tools/xtask/Cargo.toml","sha256":"b30b900521eaa9652937371cb7a9ebe38302a5e9d58aabb6743996acece87155"},{"path":"tools/xtask/src/abi_staging/builder_contract.rs","sha256":"7b52f1e143039dc32708e1e0bbbdb7075530ef682b73607e5d77818efa80de8f"},{"path":"tools/xtask/src/abi_staging/canonical_json.rs","sha256":"50e42796a53d517268e9afeea7f594e1db47b65e2e903e9d17e9db5dd9e1a6cf"},{"path":"tools/xtask/src/abi_staging/check_projection.rs","sha256":"9a2656947301affbaa792f53eaae08bb32800f0741c51be48d4a1424a9f30efe"},{"path":"tools/xtask/src/abi_staging/consumer_registry.rs","sha256":"1985490c56ffd89feb8d41f5c2bef0f41432c37e26a4282b27da39d3476574bb"},{"path":"tools/xtask/src/abi_staging/evidence_policy.rs","sha256":"45676d5a430f5d4f19b3519862b29bb9204eb8fb1f7ad5c987887dc57874882d"},{"path":"tools/xtask/src/abi_staging/guard_registry.rs","sha256":"eed45cfe19c48716da9d205f1aa6a3bbbc0e96e0e96e1e50bcaf6329cfc663fe"},{"path":"tools/xtask/src/abi_staging/mod.rs","sha256":"65eec6b0d987138f8159edd6de627643a39fc1fa01ced5836bf5e878b4367d75"},{"path":"tools/xtask/src/abi_staging/product_evidence.rs","sha256":"c5ce7790e478666d75381834ea3eddec26428bdd7c6707fdd5128259fbb50e8c"},{"path":"tools/xtask/src/abi_staging/product_manifest.rs","sha256":"4db3dd8e01bbbbd52a3b8c862f27b2e1feb685caa038cb7b8913d13cd9ac72e2"},{"path":"tools/xtask/src/abi_staging/records.rs","sha256":"6d21b7325d70874804da528a4714e5a488ed23fdeac0fd77ef61c68402d84203"},{"path":"tools/xtask/src/abi_staging/request_derivation.rs","sha256":"02190e3e19aed2d5b00cab14a2cd9ef761e9ba7d276405058db973a8aa36c8c7"},{"path":"tools/xtask/src/abi_staging/request_feed.rs","sha256":"38df80125ed5ce0559a40d85e8d0421353c5c82106306f9be7e4ad778142dfda"},{"path":"tools/xtask/src/abi_staging/request_policy.rs","sha256":"c96ea613def0ce69206fa5418bd43e7f7a8cb5648dfbff1c9e57621127d1bdf3"},{"path":"tools/xtask/src/abi_staging/selection.rs","sha256":"1c8c517a31d7fa2d7abc97b3d57db75df38c20fc98170d430e94dcafd82da2eb"}],"implementation_paths":[".github/scripts/publish-abi-staging-request.sh",".github/scripts/update-abi-staging-check.sh",".github/workflows/abi-staging-merge-gate.yml",".github/workflows/abi-staging-pages-canary.yml",".github/workflows/abi-staging-pr-check.yml",".github/workflows/abi-staging-request-feed.yml","Cargo.lock","Cargo.toml","abi/staging/evidence-definitions.generated.json","abi/staging/evidence-definitions.toml","apps/browser-demos/abi-staging-browser-harness.config.ts","apps/browser-demos/abi-staging-browser-host.config.ts","apps/browser-demos/abi-staging-browser-host.ts","apps/browser-demos/abi-staging-browser-no-default-artifacts.ts","apps/browser-demos/lib/homebrew-closed-acceptance.ts","apps/browser-demos/lib/mysql-client.ts","apps/browser-demos/lib/redis-client.ts","apps/browser-demos/package-lock.json","apps/browser-demos/package.json","apps/browser-demos/pages/abi-staging-product-evidence/index.html","apps/browser-demos/pages/abi-staging-product-evidence/main.ts","apps/browser-demos/pages/abi-staging-product-evidence/pty-command.ts","apps/browser-demos/pages/kandelo/kernel-host/candidate-evidence-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/live-setup.ts","apps/browser-demos/pages/kandelo/kernel-host/optional-demo-vfs.ts","apps/browser-demos/pages/kandelo/kernel-host/pages-vfs-product-loader.ts","apps/browser-demos/pages/network/network-demo-worker.ts","apps/browser-demos/playwright-closed-acceptance.ts","apps/browser-demos/playwright-server-policy.ts","apps/browser-demos/playwright.config.ts","apps/browser-demos/test/abi-staging-product-evidence.spec.ts","apps/browser-demos/test/support/terminal-command.ts","apps/browser-demos/vite.config.ts","flake.lock","flake.nix","host/package-lock.json","host/package.json","host/src/generated/abi.ts","host/src/homebrew-bottle-relocation.ts","host/src/homebrew-guest-layout.ts","host/src/pathconf.ts","host/src/statfs.ts","host/src/vfs/closed-lazy-assets.ts","host/src/vfs/deferred-tree-limits.ts","host/src/vfs/hardlink-graph.ts","host/src/vfs/load-image.ts","host/src/vfs/memory-fs.ts","host/src/vfs/product-mount-contract.ts","host/src/vfs/sharedfs-vendor.ts","host/tsup.config.ts","images/vfs/scripts/repository-path-bundle.ts","package-lock.json","package.json","packages/registry/program-packages.json","scripts/abi-staging-build-toolchain.sh","scripts/abi-staging-collect-product-inputs.ts","scripts/abi-staging-pages-producer.ts","scripts/abi-staging-pages-readiness.ts","scripts/abi-staging-pages-site-builder.ts","scripts/abi-staging-prepare-runtime.sh","scripts/abi-staging-product-browser-evidence.ts","scripts/abi-staging-product-input-sources.ts","scripts/abi-staging-product-node-evidence.ts","scripts/abi-staging-protected-browser-operation.ts","scripts/check-dev-shell-tools.sh","scripts/check-pages-vfs-product-registry.mjs","scripts/dev-shell.sh","scripts/fetch-exact-musl-gitlink.sh","scripts/run-vfs-product-builder.ts","scripts/vfs-product-catalog.mjs","tools/xtask/Cargo.toml","tools/xtask/src/abi_staging/builder_contract.rs","tools/xtask/src/abi_staging/canonical_json.rs","tools/xtask/src/abi_staging/check_projection.rs","tools/xtask/src/abi_staging/consumer_registry.rs","tools/xtask/src/abi_staging/evidence_policy.rs","tools/xtask/src/abi_staging/guard_registry.rs","tools/xtask/src/abi_staging/mod.rs","tools/xtask/src/abi_staging/product_evidence.rs","tools/xtask/src/abi_staging/product_manifest.rs","tools/xtask/src/abi_staging/records.rs","tools/xtask/src/abi_staging/request_derivation.rs","tools/xtask/src/abi_staging/request_feed.rs","tools/xtask/src/abi_staging/request_policy.rs","tools/xtask/src/abi_staging/selection.rs"],"issuer_repository":"Automattic/kandelo","issuer_workflow":".github/workflows/abi-staging-request-feed.yml","kind":"kandelo-abi-staging-request-policy","max_evidence_bindings":512,"max_products":256,"request_asset_max_bytes":4194304,"request_release_tag_prefix":"abi-staging-pr-","schema":1,"version":15} diff --git a/abi/staging/request-policy.toml b/abi/staging/request-policy.toml index 98b71983ff..4bcfe00e0c 100644 --- a/abi/staging/request-policy.toml +++ b/abi/staging/request-policy.toml @@ -1,6 +1,6 @@ schema = 1 kind = "kandelo-abi-staging-request-policy" -version = 14 +version = 15 issuer_repository = "Automattic/kandelo" issuer_workflow = ".github/workflows/abi-staging-request-feed.yml" automatic_same_repository = true @@ -12,9 +12,11 @@ max_evidence_bindings = 512 addressed_taps = ["kandelo-dev/homebrew-tap-core"] -# This is the approved MVP implementation identity. Publication remains -# observe-only until protected hosted canaries have supplied activation evidence. +# This is the protected request derivation, publication, and validation +# implementation identity. Active publication remains non-endorsing; candidate +# execution, verification, admission, and promotion have separate authorities. implementation_paths = [ + ".github/scripts/publish-abi-staging-request.sh", ".github/scripts/update-abi-staging-check.sh", ".github/workflows/abi-staging-merge-gate.yml", ".github/workflows/abi-staging-pages-canary.yml", @@ -96,6 +98,7 @@ implementation_paths = [ "tools/xtask/src/abi_staging/product_manifest.rs", "tools/xtask/src/abi_staging/records.rs", "tools/xtask/src/abi_staging/request_derivation.rs", + "tools/xtask/src/abi_staging/request_feed.rs", "tools/xtask/src/abi_staging/request_policy.rs", "tools/xtask/src/abi_staging/selection.rs", ] diff --git a/docs/repository-organization.md b/docs/repository-organization.md index e879cabe73..c4f4993ef9 100644 --- a/docs/repository-organization.md +++ b/docs/repository-organization.md @@ -89,10 +89,15 @@ validation and a deterministic local transition miniature. The protected request workflow is owned by `.github/workflows/abi-staging-request-feed.yml`; its source policy and activation are owned by `abi/staging/request-policy.toml` and -`abi/staging/request-feed-activation.toml`. Active mode publishes an exact, -append-only, non-endorsing request Release asset for protected tap-side -reconciliation. The companion tap workflow remains observe-only and -read-only. +`abi/staging/request-feed-activation.toml`. Active mode publishes each exact, +non-endorsing request in its own content-addressed prerelease for protected +tap-side reconciliation. The publisher uploads only while the prerelease is a +draft, then makes it public and requires GitHub immutable-release protection +plus an anonymous byte-for-byte readback before reporting success. Historical +requests remain separate immutable records; a later head or policy creates a +new prerelease instead of appending to an existing public release. The +companion tap workflow remains read-only until its separately reviewed +activation changes. Candidate execution and publication, enforced GitHub Check updates, promotion, protected ABI-history mutation, and production Pages deployment diff --git a/tools/xtask/src/abi_staging/check_projection.rs b/tools/xtask/src/abi_staging/check_projection.rs index 2a2a64be22..f09fc8cbdc 100644 --- a/tools/xtask/src/abi_staging/check_projection.rs +++ b/tools/xtask/src/abi_staging/check_projection.rs @@ -5,6 +5,7 @@ use crate::abi_staging::consumer_registry::ApplicabilityV1; use crate::abi_staging::guard_registry::GuardCodeV1; use crate::abi_staging::product_manifest::VfsArchitectureV1; use crate::abi_staging::product_manifest::{atomic_write_regular, read_bounded_regular_file}; +use crate::abi_staging::request_feed::request_asset_url; use crate::abi_staging::records::{ AbiStagingRecordV1, AbiStagingRequestV1, PromotionStateV1, RecordCommonV1, RetryNextActionV1, SubjectKindV1, TerminalOutcomeV1, WorkStateV1, request_is_current, validate_record, @@ -593,6 +594,7 @@ fn validate_input(input: &CurrentCheckProjectionInputV1) -> Result<(), String> { validate_request_reference( &request_source.immutable_reference, &input.context.repository, + input.context.pull_request_number, &input.context.exact_head, &request_source.digest, )?; @@ -1150,14 +1152,13 @@ fn validate_record_link(link: &RecordLinkV1) -> Result<(), String> { fn validate_request_reference( reference: &str, repository: &str, + pull_request: u64, head: &str, digest: &str, ) -> Result<(), String> { - let filename = format!("candidate-request-{head}-sha256-{digest}.json"); - let prefix = format!("https://github.com/{repository}/releases/download/"); + let expected = request_asset_url(repository, pull_request, head, digest); if reference.len() > MAX_REFERENCE_BYTES - || !reference.starts_with(&prefix) - || !reference.ends_with(&filename) + || reference != expected || reference.contains('?') || reference.contains('#') || reference @@ -1743,7 +1744,7 @@ mod tests { request: Some(CurrentRequestProjectionV1 { digest: request_digest.clone(), immutable_reference: format!( - "https://github.com/Automattic/kandelo/releases/download/abi-staging-pr-19/candidate-request-{HEAD}-sha256-{request_digest}.json" + "https://github.com/Automattic/kandelo/releases/download/abi-staging-pr-19-sha256-{request_digest}/candidate-request-{HEAD}-sha256-{request_digest}.json" ), request, }), diff --git a/tools/xtask/src/abi_staging/request_feed.rs b/tools/xtask/src/abi_staging/request_feed.rs index 69635826c5..843b316e37 100644 --- a/tools/xtask/src/abi_staging/request_feed.rs +++ b/tools/xtask/src/abi_staging/request_feed.rs @@ -68,6 +68,8 @@ pub struct ExistingRequestReleaseV1 { pub tag: String, pub target_commitish: String, pub prerelease: bool, + pub draft: bool, + pub immutable: bool, pub assets: Vec, } @@ -140,6 +142,26 @@ pub fn select_current_request( continue; } }; + let request_digest = match canonical_sha256(&request) { + Ok(digest) => digest, + Err(error) => { + errors.push(format!("asset {:?}: {error}", asset.name)); + continue; + } + }; + let expected_url = request_asset_url( + &request.pull_request.repository, + request.pull_request.number, + &request.build_source.commit, + &request_digest, + ); + if asset.browser_download_url != expected_url { + errors.push(format!( + "asset {:?}: URL differs from its exact content-addressed request Release", + asset.name + )); + continue; + } if request_is_current( &request, exact_head, @@ -149,10 +171,7 @@ pub fn select_current_request( guard_registry_version, guard_registry_sha256, ) { - match canonical_sha256(&request) { - Ok(digest) => current.push((asset, request, digest)), - Err(error) => errors.push(format!("asset {:?}: {error}", asset.name)), - } + current.push((asset, request, request_digest)); } } if !errors.is_empty() { @@ -209,27 +228,21 @@ pub fn plan_request_feed_write( } let asset_sha256 = canonical_sha256(&request)?; let asset_name = candidate_request_asset_name(&request.build_source.commit, &asset_sha256)?; - let tag = format!( - "{}{}", - policy.request_release_tag_prefix, request.pull_request.number - ); - let public_download_url = format!( - "https://github.com/{}/releases/download/{tag}/{asset_name}", - policy.issuer_repository + let tag = request_release_tag(request.pull_request.number, &asset_sha256); + let public_download_url = request_asset_url( + &policy.issuer_repository, + request.pull_request.number, + &request.build_source.commit, + &asset_sha256, ); let action = match existing_release { None => RequestFeedActionV1::CreatePrerelease, Some(release) => { validate_existing_release(release, &tag, protected_target)?; - let named = release - .assets - .iter() - .filter(|asset| asset.name == asset_name) - .collect::>(); - match named.as_slice() { - [] => RequestFeedActionV1::AppendAsset, - [asset] if asset.canonical_bytes == request_bytes => { + match release.assets.as_slice() { + [] if release.draft => RequestFeedActionV1::AppendAsset, + [asset] if asset.name == asset_name && asset.canonical_bytes == request_bytes => { RequestFeedActionV1::AssetAlreadyIdentical } _ => RequestFeedActionV1::RejectNameCollision, @@ -368,6 +381,16 @@ fn validate_public_asset_url(value: &str) -> Result<(), String> { Ok(()) } +pub fn request_release_tag(pull_request: u64, digest: &str) -> String { + format!("abi-staging-pr-{pull_request}-sha256-{digest}") +} + +pub fn request_asset_url(repository: &str, pull_request: u64, head: &str, digest: &str) -> String { + let name = format!("candidate-request-{head}-sha256-{digest}.json"); + let tag = request_release_tag(pull_request, digest); + format!("https://github.com/{repository}/releases/download/{tag}/{name}") +} + fn validate_existing_release( release: &ExistingRequestReleaseV1, expected_tag: &str, @@ -380,13 +403,16 @@ fn validate_existing_release( if release.target_commitish != protected_target { return Err("existing request Release target must not move".to_string()); } - if !release.prerelease { - return Err("existing request Release must remain a prerelease".to_string()); + if !release.prerelease + || !((release.draft && !release.immutable) || (!release.draft && release.immutable)) + { + return Err( + "existing request Release must be a mutable draft or immutable public prerelease" + .to_string(), + ); } - if release.assets.len() > MAX_RELEASE_ASSETS { - return Err(format!( - "existing request Release exceeds {MAX_RELEASE_ASSETS} assets" - )); + if release.assets.len() > 1 { + return Err("content-addressed request Release contains more than one asset".to_string()); } for asset in &release.assets { if asset.name.is_empty() || asset.name.len() > 512 || asset.name.contains(['/', '\0']) { @@ -480,7 +506,7 @@ mod tests { let name = candidate_request_asset_name(&request.build_source.commit, &digest).unwrap(); RequestAssetV1 { browser_download_url: format!( - "https://github.com/Automattic/kandelo/releases/download/abi-staging-pr-19/{name}" + "https://github.com/Automattic/kandelo/releases/download/abi-staging-pr-19-sha256-{digest}/{name}" ), name, canonical_bytes, @@ -535,6 +561,20 @@ mod tests { )); } + #[test] + fn selection_rejects_a_request_outside_its_content_addressed_release() { + let current = request(); + let mut wrong_release = asset(¤t); + wrong_release.browser_download_url = format!( + "https://github.com/Automattic/kandelo/releases/download/abi-staging-pr-19/{}", + wrong_release.name + ); + assert!(matches!( + select(&[wrong_release]), + CurrentRequestSelectionV1::Invalid { .. } + )); + } + #[test] fn duplicate_current_assets_are_invalid_and_missing_is_explicit() { let current = asset(&request()); @@ -606,11 +646,20 @@ mod tests { let bytes = canonical_json_bytes(&request).unwrap(); let absent = plan_request_feed_write(&policy(), &"3".repeat(40), &bytes, None).unwrap(); assert_eq!(absent.action, RequestFeedActionV1::CreatePrerelease); + assert_eq!( + absent.tag, + format!( + "abi-staging-pr-19-sha256-{}", + canonical_sha256(&request).unwrap() + ) + ); let release = ExistingRequestReleaseV1 { tag: absent.tag.clone(), target_commitish: "3".repeat(40), prerelease: true, + draft: true, + immutable: false, assets: vec![], }; assert_eq!( @@ -622,6 +671,7 @@ mod tests { let mut identical = release.clone(); identical.assets.push(asset(&request)); + identical.assets[0].browser_download_url = absent.public_download_url.clone(); assert_eq!( plan_request_feed_write(&policy(), &"3".repeat(40), &bytes, Some(&identical)) .unwrap() @@ -648,4 +698,17 @@ mod tests { ) .is_err()); } + + #[test] + fn existing_release_schema_binds_draft_and_immutable_state() { + let release = serde_json::json!({ + "assets": [], + "draft": true, + "immutable": false, + "prerelease": true, + "tag": format!("abi-staging-pr-19-sha256-{}", "a".repeat(64)), + "target_commitish": "3".repeat(40), + }); + assert!(serde_json::from_value::(release).is_ok()); + } } diff --git a/tools/xtask/src/abi_staging/request_policy.rs b/tools/xtask/src/abi_staging/request_policy.rs index 74dd069f6a..ab60e58acf 100644 --- a/tools/xtask/src/abi_staging/request_policy.rs +++ b/tools/xtask/src/abi_staging/request_policy.rs @@ -292,15 +292,10 @@ fn validate_policy(policy: &RequestPolicyV1) -> Result<(), String> { if !policy.automatic_same_repository { return Err("request policy must require automatic same-repository issuance".to_string()); } - if policy.request_release_tag_prefix.is_empty() - || policy.request_release_tag_prefix.len() > 128 - || !policy.request_release_tag_prefix.bytes().all(|byte| { - byte.is_ascii_lowercase() - || byte.is_ascii_digit() - || matches!(byte, b'.' | b'_' | b'-') - }) - { - return Err("request release tag prefix is not a bounded stable prefix".to_string()); + if policy.request_release_tag_prefix != "abi-staging-pr-" { + return Err( + "request release tag prefix must use the canonical ABI staging namespace".to_string(), + ); } if policy.request_asset_max_bytes == 0 || policy.request_asset_max_bytes > MAX_REQUEST_ASSET_BYTES