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
39 changes: 38 additions & 1 deletion .github/workflows/deploy-strategy-switch-console.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,46 @@ jobs:
if (found) found.count++; else categories.push({ signature, type, shape: fields, count: 1 });
}
}
// Fixed booleans identify the rejecting contract without revealing
// configuration values, binding identities or arbitrary field names.
const runtime = resources?.script_runtime, script = resources?.script;
const object = x => !!x && kind(x) === 'object';
const only = (x, keys) => object(x) && Object.keys(x).every(key => keys.includes(key));
const identifier = x => typeof x === 'string' && /^[A-Za-z_$][A-Za-z0-9_$]{0,127}$/.test(x);
const bindingName = x => typeof x === 'string' && /^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(x);
const hex = x => typeof x === 'string' && /^[0-9a-f]{32}$/i.test(x);
const bounded = (x, n) => typeof x === 'string' && !!x && x.length <= n;
const list = Array.isArray(bindings) && bindings.length > 0 && bindings.length <= 128 ? bindings : null;
const all = check => !!list && list.every(check);
const named = script?.named_handlers;
const contracts = {
resources_keys: only(resources, ['bindings','script','script_runtime']),
runtime_keys: only(runtime, ['compatibility_date','compatibility_flags','usage_model','limits','migration_tag','exports']),
script_keys: only(script, ['etag','handlers','named_handlers','last_deployed_from','placement_mode']),
binding_list_bounds: !!list,
etag_bounds: bounded(script?.etag, 256),
default_handlers: script?.handlers === null || (Array.isArray(script?.handlers) && script.handlers.every(x => typeof x === 'string')),
date_ymd: typeof runtime?.compatibility_date === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(runtime.compatibility_date),
date_midnight_utc: typeof runtime?.compatibility_date === 'string' && /^\d{4}-\d{2}-\d{2}T00:00:00(?:\.0{1,3})?Z$/.test(runtime.compatibility_date),
compatibility_flags: runtime?.compatibility_flags === undefined || (Array.isArray(runtime.compatibility_flags) && runtime.compatibility_flags.every(x => typeof x === 'string')),
usage_model: ['standard','bundled','unbound'].includes(runtime?.usage_model),
named_handlers: named === undefined || (Array.isArray(named) && named.length <= 128 && named.every(item => only(item,['name','handlers']) && identifier(item.name) && (item.handlers === undefined || (Array.isArray(item.handlers) && item.handlers.length <= 32 && item.handlers.every(identifier)))) && new Set(named.map(item => item.name)).size === named.length),
binding_names: all(b => object(b) && bindingName(b.name)),
binding_names_unique: !!list && new Set(list.map(b => b?.name)).size === list.length,
binding_types: all(b => ['plain_text','json','secret_text','kv_namespace','durable_object_namespace'].includes(b?.type)),
kv_namespace_hex: all(b => b?.type !== 'kv_namespace' || hex(b.namespace_id)),
do_namespace_hex: all(b => b?.type !== 'durable_object_namespace' || hex(b.namespace_id)),
do_class_name: all(b => b?.type !== 'durable_object_namespace' || identifier(b.class_name)),
do_optional_references: all(b => b?.type !== 'durable_object_namespace' || ['script_name','environment','dispatch_namespace'].every(key => b[key] === undefined || bounded(b[key],128))),
plain_text_bounds: all(b => b?.type !== 'plain_text' || (typeof b.text === 'string' && b.text.length <= 131072)),
json_present: all(b => b?.type !== 'json' || Object.hasOwn(b,'json')),
secret_shape: all(b => b?.type !== 'secret_text' || only(b,['name','type'])),
required_kv: !!list && list.some(b => b?.name === 'STRATEGY_SWITCH_CONFIG' && b.type === 'kv_namespace'),
required_do: !!list && list.some(b => b?.name === 'STRATEGY_SWITCH_RUNTIME_INSTANCES' && b.type === 'durable_object_namespace'),
};
console.log(JSON.stringify({ status: 'frontend_schema_categories', version_id: '2beea000-cce5-43a5-9255-9724f7318e83', result_id_matches: body.result?.id === '2beea000-cce5-43a5-9255-9724f7318e83', result: shape(body.result), resources: shape(resources),
binding_container: kind(bindings), binding_count: Array.isArray(bindings) ? bindings.length : null,
binding_categories: categories.map(({ signature, ...item }) => item), script: shape(resources?.script), runtime: shape(resources?.script_runtime),
binding_categories: categories.map(({ signature, ...item }) => item), script: shape(resources?.script), runtime: shape(resources?.script_runtime), contracts,
writes: 0, retry: false }));
} catch {
console.error(JSON.stringify({ error: 'frontend_schema_readback_failed', http_status: httpStatus, writes: 0, retry: false })); process.exitCode = 1;
Expand Down
32 changes: 32 additions & 0 deletions tests/console_deploy_safety_validation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,38 @@ assert.equal((await schemaCase(unknownSchema)).resources.unknown_fields,1);
await schemaCase({...schemaFixture,success:false},false);await schemaCase("{",false);await schemaCase("x".repeat(524289),false);
await schemaCase("PRIVATE_SCHEMA_SENTINEL",false,403);await schemaCase(schemaFixture,false,200,{GITHUB_REF:"refs/heads/other"});

// These fixed predicates diagnose an actual rejecting constraint; they do not
// authorize a different date, resource identity or usage model for publication.
const validSchema=structuredClone(schemaFixture);
validSchema.result.resources.bindings=[
{name:"STRATEGY_SWITCH_CONFIG",type:"kv_namespace",namespace_id:"1".repeat(32)},
{name:"STRATEGY_SWITCH_RUNTIME_INSTANCES",type:"durable_object_namespace",namespace_id:"2".repeat(32),class_name:"RuntimeInstance"},
{name:"PRIVATE_SCHEMA_SENTINEL",type:"secret_text"},
{name:"TEXT",type:"plain_text",text:"PRIVATE_SCHEMA_SENTINEL"},
{name:"JSON",type:"json",json:{private:"PRIVATE_SCHEMA_SENTINEL"}},
];
const validContracts=(await schemaCase(validSchema)).contracts;
assert.ok(Object.values(validContracts).every(x=>typeof x==="boolean"));
assert.deepEqual(Object.entries(validContracts).filter(([key,value])=>!value).map(([key])=>key),["date_midnight_utc"]);
const diagnosticCases=[
[x=>x.script_runtime.compatibility_date="2026-06-08T00:00:00Z","date_ymd"],
[x=>x.script_runtime.usage_model="PRIVATE_SCHEMA_SENTINEL","usage_model"],
[x=>x.bindings[0].namespace_id="PRIVATE_SCHEMA_SENTINEL","kv_namespace_hex"],
[x=>x.bindings[1].namespace_id="11111111-1111-4111-8111-111111111111","do_namespace_hex"],
[x=>x.bindings[1].class_name="PRIVATE/SCHEMA/SENTINEL","do_class_name"],
[x=>x.bindings.push(structuredClone(x.bindings[0])),"binding_names_unique"],
[x=>x.script.named_handlers=[{name:"PRIVATE/SCHEMA/SENTINEL",handlers:[]}],"named_handlers"],
[x=>x.bindings[2].text="PRIVATE_SCHEMA_SENTINEL","secret_shape"],
[x=>x.script.PRIVATE_SCHEMA_SENTINEL="PRIVATE_SCHEMA_SENTINEL","script_keys"],
[x=>x.bindings[0].name="PRIVATE_SCHEMA_SENTINEL","required_kv"],
];
for(const [mutate,flag]of diagnosticCases){
const changed=structuredClone(validSchema);mutate(changed.result.resources);
const result=await schemaCase(changed);assert.equal(result.contracts[flag],false,flag);
assert.ok(Object.values(result.contracts).every(x=>typeof x==="boolean"));
if(flag==="date_ymd")assert.equal(result.contracts.date_midnight_utc,true);
}

// Execute the exact selection shell with a fake Git boundary, never checkout the
// test repository or evaluate any selected Worker. Real object identities are
// separately frozen by the release evidence and the required ancestry check.
Expand Down
Loading