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
11 changes: 1 addition & 10 deletions .github/workflows/console-p02-baseline-capture.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,6 @@ jobs:
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: Prepare optional pinned historical module
run: |
set -euo pipefail
if bash scripts/console_946_reviewed_main_build.sh > "$RUNNER_TEMP/qrs-p02-historical-build.log" 2>&1; then
echo 'historical_build_verified'
else
rm -f "$RUNNER_TEMP/qrs-reviewed-main/worker.js"
echo 'historical_build_unavailable'
fi
- name: Verify protected exact-object capability
env:
QRS_P02_STORAGE_CAPABILITY_RECEIPT: ${{ secrets.QRS_P02_STORAGE_CAPABILITY_RECEIPT }}
Expand All @@ -76,4 +67,4 @@ jobs:
run: |
set -euo pipefail
[[ "$(git ls-remote origin refs/heads/main | cut -f1)" == "$GITHUB_SHA" ]]
node scripts/console_p02_baseline_hosted.mjs capture "$RUNNER_TEMP/qrs-p02-baseline-candidate/worker.js" "$RUNNER_TEMP/qrs-reviewed-main/worker.js"
node scripts/console_p02_baseline_hosted.mjs capture "$RUNNER_TEMP/qrs-p02-baseline-candidate/worker.js"
65 changes: 61 additions & 4 deletions scripts/console_m09_release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// deployment and verified rollback version have all been pinned in this file.
import {execFileSync} from 'node:child_process';
import {createHash} from 'node:crypto';
import {readFile} from 'node:fs/promises';
import {readFile,open} from 'node:fs/promises';
import {resolve,relative,isAbsolute} from 'node:path';
import {pathToFileURL} from 'node:url';
import {createFixedApi} from './console_946_backend_release.mjs';
import {execute} from './console_full_bundle_release.mjs';
Expand Down Expand Up @@ -160,11 +161,13 @@ export async function preflight(api,rollbackTargets={fixed:[],fixedSuccessor:nul
hold:true};
}
async function main() {
const [action,path,...extra]=process.argv.slice(2);
const [action,path,receiptPath,successorPath,...extra]=process.argv.slice(2);
const sourceOnly=['build-source','asset-candidate-source','preflight'];
const readOnly=['asset-candidate-source','asset-compare','preflight'].includes(action);
if (extra.length || ![...sourceOnly,'verify-inputs','asset-compare','release'].includes(action) ||
(sourceOnly.includes(action) ? path!==undefined : !path)) throw Error('arguments');
const split=['upload-only','activate-only'].includes(action);
if (extra.length || ![...sourceOnly,'verify-inputs','asset-compare','release','upload-only','activate-only'].includes(action) ||
(sourceOnly.includes(action) ? path!==undefined : !path) || (split ? !receiptPath : receiptPath!==undefined) ||
(action==='activate-only' ? !successorPath : successorPath!==undefined)) throw Error('arguments');
if (readOnly ? !sourcePinned(POLICY) : !pinned(POLICY)) throw Error('hold');
const git=(...args)=>execFileSync('git',args,{encoding:'utf8'}).trim();
git('merge-base','--is-ancestor',POLICY.source,'HEAD');
Expand All @@ -186,6 +189,60 @@ async function main() {
const candidate=await readFile(path);
if (hash(candidate)!==POLICY.candidate) throw Error('provenance');
if (action==='verify-inputs') return;
if (split) {
const root=git('rev-parse','--show-toplevel'), file=resolve(receiptPath);
const privatePath=path=>{
if(!isAbsolute(path))throw Error('receipt_location');
const resolved=resolve(path),rel=relative(root,resolved);
if(!rel||(!rel.startsWith('../')&&!isAbsolute(rel)))throw Error('receipt_location');
return resolved;
};
privatePath(receiptPath);
const {uploadOnly,activateOnly}=await import('./console_m09_split_release.mjs');
let result;
if (action==='upload-only') {
// Reserve a private destination before any provider write. An empty file
// after failure is an explicit recovery signal, never a valid receipt.
const handle=await open(file,'wx',0o600);
try {
const outcome=await uploadOnly({api:createFixedApi(),policy:POLICY,evidence,candidate});
result=outcome.result;
if(outcome.receipt) {
try { await handle.writeFile(JSON.stringify(outcome.receipt)); await handle.sync(); }
catch { result={ok:false,reason:'receipt_write_failed',upload_attempted:true,deploy_attempted:false,outcome:'UPLOAD_REQUIRES_READBACK',retry:false}; }
}
} finally {
try { await handle.close(); } catch {
result={ok:false,reason:'receipt_close_failed',upload_attempted:result?.upload_attempted===true,deploy_attempted:false,
outcome:result?.upload_attempted?'UPLOAD_REQUIRES_READBACK':'NOT_ATTEMPTED',retry:false};
}
}
} else {
const handle=await open(file,'r');
let receipt;
try {
const stat=await handle.stat();
if(!stat.isFile()||(stat.mode&0o077)!==0||stat.uid!==process.getuid()||stat.size>4096) throw Error('receipt_permissions');
receipt=JSON.parse(await handle.readFile('utf8'));
} finally { await handle.close(); }
const successor=privatePath(successorPath);
const output=await open(successor,'wx',0o600);
try {
const outcome=await activateOnly({api:createFixedApi(),policy:POLICY,evidence,candidate,receipt});
result=outcome.result;
if(outcome.receipt) {
try { await output.writeFile(JSON.stringify(outcome.receipt)); await output.sync(); }
catch { result={ok:false,reason:'receipt_write_failed',upload_attempted:false,deploy_attempted:true,outcome:'ACTIVATION_REQUIRES_READBACK',retry:false}; }
}
} finally {
try { await output.close(); } catch {
result={ok:false,reason:'receipt_close_failed',upload_attempted:false,deploy_attempted:result?.deploy_attempted===true,
outcome:result?.deploy_attempted?'ACTIVATION_REQUIRES_READBACK':'NOT_ATTEMPTED',retry:false};
}
}
}
console.log(JSON.stringify(result));if(!result.ok)process.exitCode=1;return;
}
const result=action==='asset-compare' ? await inspectActiveAssets(createPreflightApi(),candidate)
: await release({api:createFixedApi(),policy:POLICY,evidence,candidate,release:true});
console.log(JSON.stringify(result));
Expand Down
65 changes: 65 additions & 0 deletions scripts/console_m09_rollback.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Rollback is closed until the reviewed policy and actual successor are authorized.
import {createHash} from 'node:crypto';
import {pathToFileURL} from 'node:url';
import {resources,config} from './console_946_backend_release.mjs';
import {pinned} from './console_m09_release.mjs';

const sha=x=>createHash('sha256').update(x).digest('hex');
const uuid=x=>typeof x==='string'&&/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(x);
const digest=x=>typeof x==='string'&&/^[a-f0-9]{64}$/.test(x);
function bound(p,r){
return pinned(p)&&digest(p.configuration_sha256)&&digest(p.migration_tag_sha256)&&r&&
r.source===p.source&&r.tree===p.tree&&r.frontendBlob===p.frontendBlob&&r.before===p.before&&r.candidate===p.candidate&&
r.configuration_sha256===p.configuration_sha256&&r.migration_tag_sha256===p.migration_tag_sha256&&
r.active===p.version&&r.deployment===p.deployment&&uuid(r.previous)&&uuid(r.staged)&&
r.staged!==r.active&&r.staged!==r.previous&&r.successorVersion===r.staged&&uuid(r.successorDeployment)&&
r.successorDeployment!==r.deployment;
}
function active(value,deployment,version){
const list=value?.deployments,d=list?.[0];
if(!Array.isArray(list)||!list.length||list.length>10||d?.id!==deployment||d.strategy!=='percentage'||
!Array.isArray(d.versions)||d.versions.length!==1||d.versions[0]?.version_id!==version||d.versions[0]?.percentage!==100)
throw Error('active');
}
function moduleMatches(value,id,expected){
const m=value?.modules?.[0],s=m?.content_base64;
if(value?.id!==id||!Array.isArray(value.modules)||value.modules.length!==1||m?.name!=='worker.js'||
m.content_type!=='application/javascript+module'||typeof s!=='string'||!s.length||s.length>Math.ceil(10*1024*1024/3)*4)
throw Error('module');
const bytes=Buffer.from(s,'base64');
if(!bytes.length||bytes.length>10*1024*1024||bytes.toString('base64')!==s||sha(bytes)!==expected)throw Error('module');
}
function configurationMatches(value,id,p){
const r=resources(value,id),tag=r.script_runtime.migration_tag;
if(typeof tag!=='string'||!tag||sha(config(r))!==p.configuration_sha256||sha(tag)!==p.migration_tag_sha256)
throw Error('configuration');
}
// The optional receipt returned on success must remain in protected storage.
export async function rollback({api,policy,receipt}){
if(!bound(policy,receipt))return {result:{ok:false,reason:'hold_or_invalid_pins',deploy_attempted:false,retry:false}};
let attempted=false;
try{
active(await api.deployments(),receipt.successorDeployment,receipt.successorVersion);
moduleMatches(await api.betaVersion(policy.version),policy.version,policy.before);
configurationMatches(await api.scriptVersion(policy.version),policy.version,policy);
moduleMatches(await api.betaVersion(receipt.successorVersion),receipt.successorVersion,policy.candidate);
configurationMatches(await api.scriptVersion(receipt.successorVersion),receipt.successorVersion,policy);
active(await api.deployments(),receipt.successorDeployment,receipt.successorVersion);
attempted=true;
const result=await api.activate({strategy:'percentage',versions:[{version_id:policy.version,percentage:100}],annotations:{'workers/message':'Reviewed rollback'}});
if(!uuid(result?.id)||(result.strategy!==undefined&&result.strategy!=='percentage')||
(result.versions!==undefined&&(!Array.isArray(result.versions)||result.versions.length!==1||
result.versions[0]?.version_id!==policy.version||result.versions[0]?.percentage!==100)))throw Error('response');
active(await api.deployments(),result.id,policy.version);
moduleMatches(await api.betaVersion(policy.version),policy.version,policy.before);
configurationMatches(await api.scriptVersion(policy.version),policy.version,policy);
active(await api.deployments(),result.id,policy.version);
return {result:{ok:true,deploy_attempted:true,active_verified:true,retry:false,provider_cas:false},
receipt:{...receipt,rollbackVersion:policy.version,rollbackDeployment:result.id}};
}catch{return {result:{ok:false,reason:attempted?'activation_requires_readback':'verification_failed',
deploy_attempted:attempted,outcome:attempted?'UNKNOWN':'NOT_ATTEMPTED',retry:false}};}
}
// No credentials, files or provider client are accessed by the held CLI.
if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href){
console.error('{"ok":false,"reason":"hold","deploy_attempted":false,"retry":false}');process.exitCode=1;
}
77 changes: 77 additions & 0 deletions scripts/console_m09_split_release.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// M09/P02 split release. The production caller must supply the fixed policy and
// verified provenance; the existing #946 core remains unchanged.
import {createHash} from 'node:crypto';
import {execute} from './console_full_bundle_release.mjs';
import {pinned} from './console_m09_release.mjs';
import {resources,config} from './console_946_backend_release.mjs';

const sha=x=>createHash('sha256').update(x).digest('hex');
const uuid=x=>typeof x==='string'&&/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(x);
const valid=(p,e,c)=>pinned(p)&&/^[a-f0-9]{64}$/.test(p.configuration_sha256??'')&&
/^[a-f0-9]{64}$/.test(p.migration_tag_sha256??'')&&e?.source===p.source&&e?.tree===p.tree&&e?.frontendBlob===p.frontendBlob&&
Buffer.isBuffer(c)&&c.length>0&&c.length<=10*1024*1024&&sha(c)===p.candidate;
function checkedVersion(api,policy){
return async id=>{
const value=await api.scriptVersion(id);
if(id===policy.version){
const r=resources(value,id),tag=r.script_runtime.migration_tag;
if(typeof tag!=='string'||!tag||sha(config(r))!==policy.configuration_sha256||sha(tag)!==policy.migration_tag_sha256)
throw Error('baseline_configuration_drift');
}
return value;
};
}
const ids=x=>{
const a=x?.items;
if(!Array.isArray(a)||!a.length||a.length>10||a.some(v=>!uuid(v?.id))||new Set(a.map(v=>v.id)).size!==a.length)throw Error('versions');
return a.map(v=>v.id);
};
const noWrite={ok:false,reason:'hold_or_input_invalid',upload_attempted:false,deploy_attempted:false};

// receipt is returned only to the caller's protected storage, never to public logs.
export async function uploadOnly({api,policy,evidence,candidate}) {
if(!valid(policy,evidence,candidate))return {result:noWrite};
let uploaded=null,previous=null,verified=false;
const limited={deployments:api.deployments,versions:async()=>{
const v=await api.versions();if(previous===null)previous=ids(v)[0];return v;
},scriptVersion:checkedVersion(api,policy),betaVersion:api.betaVersion,
upload:async body=>{const value=await api.upload(body);if(uuid(value?.id))uploaded=value.id;return value;},
// This local sentinel is reached only after the core's complete upload readback,
// configuration, version ordering and active-traffic checks have succeeded.
activate:async()=>{verified=true;throw Error('split_stop');}};
const result=await execute({api:limited,policy,expected:{version:policy.version,deployment:policy.deployment},candidate,release:true});
if(!verified||!uploaded||!previous||result.stage!=='activate')
return {result:{...result,deploy_attempted:false,outcome:result.upload_attempted?'UPLOAD_REQUIRES_READBACK':'NOT_ATTEMPTED'}};
return {result:{ok:true,upload_attempted:true,deploy_attempted:false,staged_verified:true},
receipt:{source:policy.source,tree:policy.tree,frontendBlob:policy.frontendBlob,before:policy.before,candidate:policy.candidate,
configuration_sha256:policy.configuration_sha256,migration_tag_sha256:policy.migration_tag_sha256,
active:policy.version,deployment:policy.deployment,staged:uploaded,previous}};
}

// A synthetic upload response allows the unmodified core to re-run its exact
// candidate/readback checks on the staged version. No upload method is exposed.
export async function activateOnly({api,policy,evidence,candidate,receipt}) {
if(!valid(policy,evidence,candidate)||!receipt||receipt.source!==policy.source||receipt.tree!==policy.tree||
receipt.frontendBlob!==policy.frontendBlob||receipt.before!==policy.before||receipt.candidate!==policy.candidate||
receipt.configuration_sha256!==policy.configuration_sha256||receipt.migration_tag_sha256!==policy.migration_tag_sha256||
receipt.active!==policy.version||receipt.deployment!==policy.deployment||!uuid(receipt.staged)||!uuid(receipt.previous)||
receipt.staged===receipt.active||receipt.staged===receipt.previous)return {result:noWrite};
let first=true;
const limited={deployments:api.deployments,scriptVersion:checkedVersion(api,policy),betaVersion:api.betaVersion,
versions:async()=>{
const value=await api.versions(),list=ids(value);
if(list.length<2||list[0]!==receipt.staged||list[1]!==receipt.previous)throw Error('versions_drift');
if(first){first=false;return {...value,items:value.items.slice(1)};}
return value;
},
upload:async body=>{
if(first||body?.modules?.length!==1||body.modules[0]?.content_base64!==candidate.toString('base64'))throw Error('candidate');
return {id:receipt.staged};
},activate:api.activate};
const result=await execute({api:limited,policy,expected:{version:policy.version,deployment:policy.deployment},candidate,release:true});
// Core's upload flag denotes the local substitute, never a provider write.
const {version_id,deployment_id,...publicResult}=result;
if(!result.ok) return {result:{...publicResult,upload_attempted:false,
outcome:result.deploy_attempted?'ACTIVATION_REQUIRES_READBACK':'NOT_ATTEMPTED'}};
return {result:{...publicResult,upload_attempted:false},receipt:{...receipt,successorVersion:version_id,successorDeployment:deployment_id}};
}
11 changes: 7 additions & 4 deletions scripts/console_p02_baseline_capture.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +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';
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 @@ -82,23 +83,25 @@ export function rebuildWithAssets(candidate,active,ts){
const outside=(text,spans)=>{let p=0;const result=[];for(const span of spans){result.push(text.slice(p,span.start));p=span.end;}result.push(text.slice(p));return result;};
const oldParts=outside(original.text,ordered),newParts=outside(checked.text,after);
if(oldParts.some((part,i)=>!Buffer.from(part).equals(Buffer.from(newParts[i]))))throw Error('rebuild');
return {declarations:observed.declarations,sha256:digest(bytes)};
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,now=()=>new Date(),ts=require('typescript'),reference=REFERENCE,alternateReference=HISTORICAL}){
export async function capture({api,store,candidate,controlSha,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(!compareAssetDeclarations(candidate,candidate,ts).ok)throw Error('candidate');
stage='storage_preflight'; await store.reserve();
stage='active_before'; const before=identity(await api.deployments());
stage='module'; const active=moduleBytes(await api.betaVersion(before.version),before.version);
const activeHash=digest(active);
if(expectedActive!==undefined&&activeHash!==expectedActive)throw Error('module_drift');
stage='configuration'; const r=resources(await api.scriptVersion(before.version),before.version);
if(typeof r.script_runtime.migration_tag!=='string'||!r.script_runtime.migration_tag)throw Error('configuration');
stage='active_after'; const after=identity(await api.deployments());
if(before.version!==after.version||before.deployment!==after.deployment)throw Error('drift');
stage='assets'; const compared=compareAssetDeclarations(active,candidate,ts);
if(!compared.equal)throw Error('unsupported');
if(!compared.equal||(expectedActive!==undefined&&!compared.ok))throw Error('unsupported');
let rebuilt, historical;
if(!compared.ok){
historical={source:HISTORICAL_SOURCE,status:'unavailable',reason:'build_or_read_unavailable'};
Expand All @@ -115,7 +118,7 @@ export async function capture({api,store,candidate,controlSha,alternate,now=()=>
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:digest(active),
const record={schema:'qrs-worker-p02-baseline-v1',control_sha:controlSha,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
Loading
Loading