Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions .github/workflows/console-p02-baseline-capture.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
workflow_dispatch:
inputs:
control_sha:
description: Reviewed control commit on main
description: Reviewed control commit in main history
type: string
required: true
permissions:
Expand All @@ -19,6 +19,7 @@ jobs:
timeout-minutes: 30
environment: runtime-strategy-switch
env:
P02_CONTROL_SHA: ${{ inputs.control_sha }}
DIGEST_CANDIDATES_GCS_URI: ${{ vars.DIGEST_CANDIDATES_GCS_URI }}
DIGEST_CANDIDATES_READER_WIF_PROVIDER: ${{ vars.DIGEST_CANDIDATES_READER_WIF_PROVIDER }}
DIGEST_CANDIDATES_READER_SERVICE_ACCOUNT: ${{ vars.DIGEST_CANDIDATES_READER_SERVICE_ACCOUNT }}
Expand All @@ -29,27 +30,55 @@ jobs:
with:
persist-credentials: false
fetch-depth: 0
- name: Check out the reviewed control revision
env:
QRS_P02_STORAGE_CAPABILITY_RECEIPT: ${{ secrets.QRS_P02_STORAGE_CAPABILITY_RECEIPT }}
run: |
set -euo pipefail
[[ "$P02_CONTROL_SHA" =~ ^[a-f0-9]{40}$ ]] || exit 1
[[ "$(git rev-parse HEAD)" == "$GITHUB_SHA" ]] || exit 1
git merge-base --is-ancestor "$P02_CONTROL_SHA" "$GITHUB_SHA" || exit 1
path=.github/workflows/console-p02-baseline-capture.yml
approved_workflow="$(git rev-parse --verify "$P02_CONTROL_SHA:$path")" || exit 1
dispatched_workflow="$(git rev-parse --verify "$GITHUB_SHA:$path")" || exit 1
[[ "$approved_workflow" == "$dispatched_workflow" ]] || exit 1
tree="$(git rev-parse --verify "${P02_CONTROL_SHA}^{tree}")" || exit 1
P02_CONTROL_TREE="$tree" python3 - <<'PY' || exit 1
import json
import os

try:
receipt = json.loads(os.environ["QRS_P02_STORAGE_CAPABILITY_RECEIPT"])
matches = (
receipt["schema"] == "qrs-p02-storage-capability-v1"
and receipt["control_sha"] == os.environ["P02_CONTROL_SHA"]
and receipt["control_tree"] == os.environ["P02_CONTROL_TREE"]
)
except (KeyError, TypeError, ValueError):
matches = False
if not matches:
raise SystemExit("R2 reviewed control binding does not match")
PY
git checkout --detach "$P02_CONTROL_SHA" || exit 1
[[ "$(git rev-parse HEAD)" == "$P02_CONTROL_SHA" ]] || exit 1
printf 'P02_CONTROL_TREE=%s\n' "$tree" >> "$GITHUB_ENV"
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: '22.23.4'
- name: Verify protected exact-object capability
env:
QRS_P02_STORAGE_CAPABILITY_RECEIPT: ${{ secrets.QRS_P02_STORAGE_CAPABILITY_RECEIPT }}
run: node scripts/console_p02_baseline_hosted.mjs verify-capability
- name: Verify control revision and fixed offline candidate
env:
CONTROL_SHA: ${{ inputs.control_sha }}
WRANGLER_SEND_METRICS: 'false'
CLOUDFLARE_CF_FETCH_ENABLED: 'false'
run: |
set -euo pipefail
[[ "$CONTROL_SHA" == "$GITHUB_SHA" ]]
[[ "$(git rev-parse HEAD)" == "$GITHUB_SHA" ]]
[[ "$(git ls-remote origin refs/heads/main | cut -f1)" == "$GITHUB_SHA" ]]
npm ci --ignore-scripts --prefix web/strategy-switch-console/frontend
node --test tests/console_p02_baseline_capture.test.mjs tests/console_p02_baseline_hosted.test.mjs
npx --yes wrangler@4.106.0 --version
bash scripts/console_p02_candidate_build.sh "$RUNNER_TEMP/qrs-p02-baseline-candidate"
- name: Verify protected exact-object capability
env:
QRS_P02_STORAGE_CAPABILITY_RECEIPT: ${{ secrets.QRS_P02_STORAGE_CAPABILITY_RECEIPT }}
run: node scripts/console_p02_baseline_hosted.mjs verify-capability
- name: Authenticate existing QRS WIF identity
id: auth
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
Expand All @@ -66,5 +95,7 @@ jobs:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID || vars.CLOUDFLARE_ACCOUNT_ID }}
run: |
set -euo pipefail
[[ "$(git ls-remote origin refs/heads/main | cut -f1)" == "$GITHUB_SHA" ]]
[[ "$(git rev-parse HEAD)" == "$P02_CONTROL_SHA" ]] || exit 1
[[ "$(git rev-parse 'HEAD^{tree}')" == "$P02_CONTROL_TREE" ]] || exit 1
git diff --quiet HEAD -- || exit 1
node scripts/console_p02_baseline_hosted.mjs capture "$RUNNER_TEMP/qrs-p02-baseline-candidate/worker.js"
10 changes: 6 additions & 4 deletions docs/console-p02-baseline-capture.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# P02 Worker baseline capture

The manual workflow captures one protected baseline for the fixed Worker. It does not upload or activate a Worker and does not change the release HOLD. The reference is the #1091 source tree with the #1089 asset blob; the dry-run bundle must have SHA-256 `912ad8b3efb9080b96112750146bdf050b6eefed6fae822b57dfa57e99feb57a`.
The manual workflow captures one protected baseline for the fixed Worker. It leaves the release HOLD in effect. The reference is the #1091 backend tree with the asset map from the reviewed #1097 source. The dry-run bundle must match the digest of the verified publication. The workflow stops if the active module or the three frontend declarations differ.

Before a run, an authorized human or Codex must review actual policy and identity evidence proving create and get on the exact object, its absence, and the bucket owner. Store the resulting JSON only in the protected `runtime-strategy-switch` environment secret `QRS_P02_STORAGE_CAPABILITY_RECEIPT`. Do not issue it from synthetic fixtures, an unverified assertion, or bucket-level `testIamPermissions`. The receipt has schema `qrs-p02-storage-capability-v1` and fields `bucket`, `owner_project`, `object`, `wif_provider`, `service_account`, `control_sha`, `issued_at`, `expires_at`, `policy_readback_reviewed`, `create_permitted`, `get_permitted`, and `target_absent`. The final four fields must be true. Times are UTC ISO timestamps, valid for at most 24 hours. The object is fixed in the script. Bind the exact main control SHA and the identity selected by the same reader-variable precedence as the daily workflow. The owner project is the verified bucket owner, which may differ from the consuming project. This secret is not configured by this change.
Before a run, an authorized operator must verify the exact object's absence, bucket owner, workload identity, and permission to create and get that object. Store the capability receipt only in the protected runtime-strategy-switch environment secret QRS_P02_STORAGE_CAPABILITY_RECEIPT. The qrs-p02-storage-capability-v1 receipt includes bucket, owner_project, object, wif_provider, service_account, control_sha, control_tree, issued_at, expires_at, policy_readback_reviewed, create_permitted, get_permitted, and target_absent. The last four fields must be true. Use UTC timestamps and a validity of at most 24 hours.

The workflow verifies the receipt and candidate before authentication. It then checks exact-object metadata for absence, reads the active deployment, module and configuration, and rechecks deployment identity: at most four fixed Worker GETs. It creates the fixed object once with `ifGenerationMatch=0`, then reads that exact generation. The metadata check and generation readback are additional GCS reads to the previously completed local storage preflight; there is no bucket listing. Provider values and the object contents do not enter the workflow log. The protected object contains the reviewed control SHA, capture timestamp, identity and hashes. When assets differ, it first compares the pinned historical source `9b695a4ade90bcc9931f53109e0ad33f63407927` if its rebuild passed the fixed digest; a matching historical module supplies the declarations. Otherwise it records the historical comparison or the reason it was unavailable, and extracts static literals from the active module. It contains only the three AST-decoded literal declarations and the digest of an in-memory reconstruction of the #1091 module. Reconstruction verifies declaration values and every byte outside those declarations, and remains on HOLD for review. Unsupported declarations stop capture. Object creation metadata, including generation, remains available to the owner through exact-object metadata readback; it is not printed.
Use the full reviewed control commit from main history as control_sha. Resolve its Git tree with git rev-parse on that commit's tree, and compare it with the reviewed tree before setting control_tree. The main dispatch revision must contain the control commit and the same workflow file. The run checks out the reviewed control commit and validates the receipt before installing dependencies. The existing reader variable precedence determines the workload identity.

An attempted write with an error or failed readback has an unknown outcome. Do not retry or dispatch again; the owner must inspect the exact object and generation through an authorized channel. Any later upload, activation, rollback or protected account read requires its own applicable authorization.
Use one dispatch for the batch. Check run history and record the attempt before sending it. The workflow checks object absence, reads the active deployment, module and configuration, and rechecks the active deployment with at most four Worker GETs. It creates the fixed object with ifGenerationMatch=0 and verifies that exact generation. The protected object stores the control commit and tree, capture time, active identity, module and configuration digests, and asset comparison. Provider values and object contents stay out of the workflow log.

If a dispatch, write or readback is unconfirmed, stop and inspect the existing run or exact result object through the authorized channel. A verified capture records a point in time. Before any later authorized upload, activation or rollback, verify the active version and configuration again.
9 changes: 6 additions & 3 deletions scripts/console_p02_baseline_capture.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const uuid=x=>typeof x==='string'&&/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}
export const HISTORICAL='0c423d325873d85f9a6b6506c6a9ae35f44753a157e90e3ef1c049b094e0aec2';
export const HISTORICAL_SOURCE='9b695a4ade90bcc9931f53109e0ad33f63407927';
export const REFERENCE='912ad8b3efb9080b96112750146bdf050b6eefed6fae822b57dfa57e99feb57a';
export const R2_ACTIVE_MODULE='599b86317957dbc77ed8a307274bdf15109683fa6c735a3263aed23e7aa49244';
export const R2_ACTIVE_MODULE='8e38776bdfb0a665ae87b7bd93146881d8813010461ea3dc9b77e23275e720ea';
const names=['V2_BUILD_ID','V2_PAGE_HTML','V2_ASSETS'];
function identity(value){
const ds=value?.deployments,d=ds?.[0],v=d?.versions?.[0];
Expand Down Expand Up @@ -86,10 +86,11 @@ export function rebuildWithAssets(candidate,active,ts){
return {declarations:observed.declarations,sha256:digest(bytes),bytes};
}
// Storage reservation must complete before any Worker reads. A failed or unknown write is never retried.
export async function capture({api,store,candidate,controlSha,alternate,expectedActive,now=()=>new Date(),ts=require('typescript'),reference=REFERENCE,alternateReference=HISTORICAL}){
export async function capture({api,store,candidate,controlSha,controlTree,alternate,expectedActive,now=()=>new Date(),ts=require('typescript'),reference=REFERENCE,alternateReference=HISTORICAL}){
let stage='candidate',writeAttempted=false;
try{
if(!/^[a-f0-9]{40}$/.test(controlSha??'')||!Buffer.isBuffer(candidate)||digest(candidate)!==reference)throw Error('candidate');
if(controlTree!==undefined&&!/^[a-f0-9]{40}$/.test(controlTree))throw Error('candidate');
if(!compareAssetDeclarations(candidate,candidate,ts).ok)throw Error('candidate');
stage='storage_preflight'; await store.reserve();
stage='active_before'; const before=identity(await api.deployments());
Expand Down Expand Up @@ -118,7 +119,9 @@ export async function capture({api,store,candidate,controlSha,alternate,expected
rebuilt=rebuildWithAssets(candidate,assets,ts);
}
const capturedAt=now().toISOString();
const record={schema:'qrs-worker-p02-baseline-v1',control_sha:controlSha,captured_at:capturedAt,...before,active_module_sha256:activeHash,
const record={schema:'qrs-worker-p02-baseline-v1',control_sha:controlSha,
...(controlTree===undefined?{}:{control_tree:controlTree}),
captured_at:capturedAt,...before,active_module_sha256:activeHash,
configuration_sha256:digest(config(r)),migration_tag_sha256:digest(r.script_runtime.migration_tag),
reference_module_sha256:reference,asset_comparison:compared.equal,...(rebuilt?{historical_comparison:historical,asset_declarations:rebuilt.declarations,rebuilt_module_sha256:rebuilt.sha256,release_state:'hold_review_required'}:{release_state:'hold'})};
stage='save'; writeAttempted=true; const bytes=Buffer.from(JSON.stringify(record));
Expand Down
14 changes: 9 additions & 5 deletions scripts/console_p02_baseline_hosted.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Hosted P02 capture: fixed object and routes; provider responses are never logged.
import {readFile} from 'node:fs/promises';
import {pathToFileURL} from 'node:url';
import {capture,REFERENCE,R2_ACTIVE_MODULE} from './console_p02_baseline_capture.mjs';
import {capture,R2_ACTIVE_MODULE} from './console_p02_baseline_capture.mjs';
import {createFixedApi} from './console_946_backend_release.mjs';
import {createHash} from 'node:crypto';

Expand All @@ -12,8 +12,10 @@ export function verifyCapability(env,now=Date.now()){
const account=env.DIGEST_CANDIDATES_READER_SERVICE_ACCOUNT||env.DIGEST_GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT;
let r;try{r=JSON.parse(env.QRS_P02_STORAGE_CAPABILITY_RECEIPT||'')}catch{throw Error('capability');}
const issued=Date.parse(r.issued_at),expires=Date.parse(r.expires_at);
if(!uri||!provider||!account||!/^[a-f0-9]{40}$/.test(env.GITHUB_SHA??'')||r.schema!=='qrs-p02-storage-capability-v1'||
r.bucket!==uri[1]||r.object!==KEY||r.wif_provider!==provider||r.service_account!==account||r.control_sha!==env.GITHUB_SHA||
if(!uri||!provider||!account||!/^[a-f0-9]{40}$/.test(env.P02_CONTROL_SHA??'')||
!/^[a-f0-9]{40}$/.test(env.P02_CONTROL_TREE??'')||r.schema!=='qrs-p02-storage-capability-v1'||
r.bucket!==uri[1]||r.object!==KEY||r.wif_provider!==provider||r.service_account!==account||
r.control_sha!==env.P02_CONTROL_SHA||r.control_tree!==env.P02_CONTROL_TREE||
Comment thread
Pigbibi marked this conversation as resolved.
Resolved
typeof r.owner_project!=='string'||!/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(r.owner_project)||
r.policy_readback_reviewed!==true||r.create_permitted!==true||r.get_permitted!==true||r.target_absent!==true||
!Number.isFinite(issued)||!Number.isFinite(expires)||issued>now||expires<=now||expires-issued>24*60*60*1000)
Expand Down Expand Up @@ -53,10 +55,12 @@ async function main(){
if(action==='verify-capability'){verifyCapability(process.env);console.log('{"ok":true,"capability_verified":true}');return;}
if(action!=='capture'||process.argv.length!==4)throw Error('input');
const candidate=await readFile(process.argv[3]);
if(createHash('sha256').update(candidate).digest('hex')!==REFERENCE)throw Error('candidate');
if(createHash('sha256').update(candidate).digest('hex')!==R2_ACTIVE_MODULE)throw Error('candidate');
const {bucket}=verifyCapability(process.env);
const store=storage({bucket,token:process.env.GOOGLE_ACCESS_TOKEN});
const result=await capture({api:readOnlyWorkerApi(createFixedApi()),store,candidate,expectedActive:R2_ACTIVE_MODULE,controlSha:process.env.GITHUB_SHA});
const result=await capture({api:readOnlyWorkerApi(createFixedApi()),store,candidate,
reference:R2_ACTIVE_MODULE,expectedActive:R2_ACTIVE_MODULE,
controlSha:process.env.P02_CONTROL_SHA,controlTree:process.env.P02_CONTROL_TREE});
console.log(JSON.stringify(result));if(!result.ok)process.exitCode=1;
}
if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href){
Expand Down
9 changes: 7 additions & 2 deletions scripts/console_p02_candidate_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ umask 077
readonly source=cabe5a1534e0c992713b084030af313eada758ca
readonly tree=42b404d5755b658deff73100b495ecf26fa3a6b1
readonly blob=5287f259185ed44f3784aa6374cb00412b3c6238
readonly assets_source=dd63b3c8b73b588ce411316d904efd26f2c4f382
readonly assets_tree=c08d5542b032abaa008e882dd97ed2c38b3ad236
readonly assets_blob=a7237657bb6e48c57e947875f629d28bafb5ea27
readonly asset=web/strategy-switch-console/v2_asset_map.js
readonly expected=912ad8b3efb9080b96112750146bdf050b6eefed6fae822b57dfa57e99feb57a
readonly expected=8e38776bdfb0a665ae87b7bd93146881d8813010461ea3dc9b77e23275e720ea
output="${1:?output directory required}"
[[ "$(git rev-parse "${source}^{tree}")" == "$tree" ]]
[[ "$(git rev-parse "$source:$asset")" == "$blob" ]]
[[ "$(git rev-parse "084ba4281cfff3304cf226ce511a2fc0a6b67b08:$asset")" == "$blob" ]]
[[ "$(git rev-parse "${assets_source}^{tree}")" == "$assets_tree" ]]
[[ "$(git rev-parse "$assets_source:$asset")" == "$assets_blob" ]]
[[ ! -e "$output" ]]
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$output"
output="$(cd "$output" && pwd)"
git archive "$source" | tar -x -C "$tmp"
git show "$assets_source:$asset" > "$tmp/$asset"
cp "$tmp/web/strategy-switch-console/wrangler.toml.example" "$tmp/web/strategy-switch-console/wrangler.toml"
(
cd "$tmp/web/strategy-switch-console"
Expand Down
12 changes: 8 additions & 4 deletions scripts/console_p02_prepare.mjs
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
// Offline preparation only. The result does not authorize an upload.
import {createHash} from 'node:crypto';
import {rebuildWithAssets, REFERENCE, HISTORICAL_SOURCE, HISTORICAL, R2_ACTIVE_MODULE} from './console_p02_baseline_capture.mjs';
import {rebuildWithAssets, HISTORICAL_SOURCE, HISTORICAL, R2_ACTIVE_MODULE} from './console_p02_baseline_capture.mjs';
import {POLICY} from './console_m09_release.mjs';

const digest=x=>createHash('sha256').update(x).digest('hex');
const hex=(x,n)=>typeof x==='string'&&new RegExp(`^[a-f0-9]{${n}}$`).test(x);
const uuid=x=>typeof x==='string'&&/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(x);
const names=['V2_BUILD_ID','V2_PAGE_HTML','V2_ASSETS'];
export const P02_PREPARE_PINS=Object.freeze({reference:REFERENCE,
export const P02_PREPARE_PINS=Object.freeze({reference:R2_ACTIVE_MODULE,
active:R2_ACTIVE_MODULE,
referenceFrontendBlob:'5287f259185ed44f3784aa6374cb00412b3c6238',
assetsSource:'dd63b3c8b73b588ce411316d904efd26f2c4f382',
assetsTree:'c08d5542b032abaa008e882dd97ed2c38b3ad236',
referenceFrontendBlob:'a7237657bb6e48c57e947875f629d28bafb5ea27',
historical:HISTORICAL_SOURCE,historicalBundle:HISTORICAL});

// The caller must obtain the protected record through its authorized channel.
// Dependency injection exists for offline fixtures, not as a production policy override.
export function prepareRecord(record,reference,evidence,ts,pins=P02_PREPARE_PINS){
if(record?.schema!=='qrs-worker-p02-baseline-v1'||!hex(pins.control,40)||record.control_sha!==pins.control||
(pins.controlTree!==undefined&&(!hex(pins.controlTree,40)||record.control_tree!==pins.controlTree))||
(pins.active!==undefined&&record.active_module_sha256!==pins.active)||
record.reference_module_sha256!==pins.reference||!Buffer.isBuffer(reference)||digest(reference)!==pins.reference||
!uuid(record.version)||!uuid(record.deployment)||
!['active_module_sha256','configuration_sha256','migration_tag_sha256'].every(k=>hex(record[k],64))||
evidence?.source!==POLICY.source||evidence?.tree!==POLICY.tree||evidence?.frontendBlob!==pins.referenceFrontendBlob)
evidence?.source!==POLICY.source||evidence?.tree!==POLICY.tree||evidence?.frontendBlob!==pins.referenceFrontendBlob||
(pins.assetsTree!==undefined&&evidence.assetsTree!==pins.assetsTree))
throw Error('baseline_invalid');
const comparison=record.asset_comparison;
if(!comparison||Object.keys(comparison).length!==names.length||!names.every(n=>typeof comparison[n]==='boolean'))
Expand Down
Loading
Loading