From c5e8d8fc9466441be013665c3c6140fc47847557 Mon Sep 17 00:00:00 2001 From: ael-dev3 Date: Fri, 31 Jul 2026 13:00:51 +0200 Subject: [PATCH] Guard code-only publication from active v13 --- docs/operations/alpha-activation.md | 8 +- scripts/publish-spacetime-dev.d.mts | 4 + scripts/publish-spacetime-dev.mjs | 194 +++++++++++++++++++++--- tests/activationToolingSecurity.test.ts | 169 +++++++++++++++++++++ 4 files changed, 355 insertions(+), 20 deletions(-) diff --git a/docs/operations/alpha-activation.md b/docs/operations/alpha-activation.md index 4af7d3df..0e3feb20 100644 --- a/docs/operations/alpha-activation.md +++ b/docs/operations/alpha-activation.md @@ -95,7 +95,7 @@ npm run stdb:publish:dev -- --dry-run \ --resource-rollout-stage=ready \ --genesis-world-stage=expanded \ --worker-rollout-stage=active \ - --worker-module-predecessor=exact-v12-active \ + --worker-module-predecessor=exact-v13-active \ --worker-forward-repair=none ``` @@ -126,6 +126,12 @@ npm run stdb:worker-return-repair:apply -- --confirm The apply path records an aggregate-only intent before submission and a second terminal receipt after the fresh post-inspection. +`--worker-module-predecessor=exact-v13-active` is the normal code-only lane +after the private v13 access-request suffix is deployed. Its anonymous +preflight binds both proven schema digests and all 54 table signatures, accepts +only the exact active or candidate Worker ABI, and requires the identical table +boundary plus the candidate ABI after publication. + `--worker-module-predecessor=exact-v12-empty` is the explicit code-only exception for an already-appended but still-inert v12 suffix. It requires the proven 53-table digest, captures and preserves every table signature, accepts diff --git a/scripts/publish-spacetime-dev.d.mts b/scripts/publish-spacetime-dev.d.mts index a04eb7d1..4c216d7d 100644 --- a/scripts/publish-spacetime-dev.d.mts +++ b/scripts/publish-spacetime-dev.d.mts @@ -48,6 +48,7 @@ export function verifyFreshPublishExactV12Aggregate(...args: any[]): any; export function verifyFreshPublishPreV12Aggregate(...args: any[]): any; export function verifyFreshProductionV11Schema(...args: any[]): any; export function verifyFreshProductionV12ModuleSchema(...args: any[]): any; +export function verifyFreshProductionV13ModuleSchema(...args: any[]): any; export function verifyFreshFoundedProtocolV3Aggregate(...args: any[]): any; export function verifyFreshResourceProtocolV4PrebackfillAggregate(...args: any[]): any; export function verifyFreshResourceProtocolV4ReadyAggregate(...args: any[]): any; @@ -61,6 +62,7 @@ export function verifyPostPublishFoundedProtocolV3Aggregate(...args: any[]): any export function verifyPostPublishProductionV12Schema(...args: any[]): any; export function verifyPostPublishProductionV12ModuleSchema(...args: any[]): any; export function verifyPostPublishProductionV13ModuleSchema(...args: any[]): any; +export function verifyPostPublishProductionV13ActiveModuleSchema(...args: any[]): any; export function verifyPostPublishProductionV13SchemaFromV11(...args: any[]): any; export function verifyPostPublishResourceProtocolV4PrebackfillAggregate(...args: any[]): any; export function verifyPostPublishResourceProtocolV4ReadyAggregate(...args: any[]): any; @@ -79,5 +81,7 @@ export function verifyExactProductionV12Schema(...args: any[]): any; export function verifyExactProductionV12ModuleSchema(...args: any[]): any; export function verifyExactProductionV13Schema(...args: any[]): any; export function verifyExactProductionV13SchemaFromV11(...args: any[]): any; +export function verifyExactProductionV13ModuleSchema(...args: any[]): any; export function verifyWorkerV12ModuleAbi(...args: any[]): any; export function verifyWorkerV12ModulePredecessor(...args: any[]): any; +export function verifyWorkerV13ModulePredecessor(...args: any[]): any; diff --git a/scripts/publish-spacetime-dev.mjs b/scripts/publish-spacetime-dev.mjs index 7b2ea8f4..17f8f530 100644 --- a/scripts/publish-spacetime-dev.mjs +++ b/scripts/publish-spacetime-dev.mjs @@ -98,6 +98,7 @@ export const WORKER_MODULE_PREDECESSOR = Object.freeze({ V11: 'v11', EXACT_V12_EMPTY: 'exact-v12-empty', EXACT_V12_ACTIVE: 'exact-v12-active', + EXACT_V13_ACTIVE: 'exact-v13-active', }); export const WORKER_FORWARD_REPAIR = Object.freeze({ NONE: 'none', @@ -1073,7 +1074,7 @@ export function parsePublishArguments(arguments_ = process.argv.slice(2)) { continue; } } - fail('Usage: publish-spacetime-dev.mjs [--dry-run] --resource-rollout-stage= --genesis-world-stage= --worker-rollout-stage= [--worker-module-predecessor=] --worker-forward-repair=. Unknown or duplicate arguments are rejected.'); + fail('Usage: publish-spacetime-dev.mjs [--dry-run] --resource-rollout-stage= --genesis-world-stage= --worker-rollout-stage= [--worker-module-predecessor=] --worker-forward-repair=. Unknown or duplicate arguments are rejected.'); } if (resourceRolloutStage === undefined) { fail('An explicit resource rollout stage is required: prebackfill for the first additive publication or ready for an already-backfilled republish.'); @@ -1088,10 +1089,13 @@ export function parsePublishArguments(arguments_ = process.argv.slice(2)) { fail('An explicit Worker forward-repair selection is required.'); } if ( - (workerModulePredecessor === WORKER_MODULE_PREDECESSOR.EXACT_V12_ACTIVE) + ( + workerModulePredecessor === WORKER_MODULE_PREDECESSOR.EXACT_V12_ACTIVE + || workerModulePredecessor === WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE + ) !== (workerRolloutStage === WORKER_PUBLISH_ROLLOUT_STAGE.ACTIVE) ) { - fail('The exact active-v12 module predecessor requires the active Worker rollout stage, and no other predecessor may use it.'); + fail('The exact active-v12 module predecessor or exact active-v13 module predecessor requires the active Worker rollout stage, and no other predecessor may use it.'); } if ( workerForwardRepair === WORKER_FORWARD_REPAIR.RETURN_NODE_REUSE_V1 @@ -1099,10 +1103,13 @@ export function parsePublishArguments(arguments_ = process.argv.slice(2)) { resourceRolloutStage !== RESOURCE_PUBLISH_ROLLOUT_STAGE.READY || genesisWorldRolloutStage !== GENESIS_WORLD_PUBLISH_STAGE.EXPANDED || workerRolloutStage !== WORKER_PUBLISH_ROLLOUT_STAGE.ACTIVE - || workerModulePredecessor !== WORKER_MODULE_PREDECESSOR.EXACT_V12_ACTIVE + || ( + workerModulePredecessor !== WORKER_MODULE_PREDECESSOR.EXACT_V12_ACTIVE + && workerModulePredecessor !== WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE + ) ) ) { - fail('The return-node-reuse-v1 forward repair requires the exact ready, expanded, active-v12 production predecessor.'); + fail('The return-node-reuse-v1 forward repair requires the exact ready, expanded, active-v12 production predecessor or exact active-v13 production predecessor.'); } return Object.freeze({ dryRun, @@ -1871,6 +1878,23 @@ export function verifyWorkerV12ModulePredecessor( return moduleState; } +/** Bind the code-only v13 lane to an exact active or already-candidate ABI. */ +export function verifyWorkerV13ModulePredecessor( + moduleState, + workerModulePredecessor, +) { + if ( + workerModulePredecessor !== WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE + || ( + moduleState !== 'active-predecessor' + && moduleState !== 'candidate' + ) + ) { + fail('The selected Worker module predecessor did not match the exact active production v13 ABI.'); + } + return moduleState; +} + export function planWorkerV12CodePublication( moduleState, workerForwardRepair, @@ -1973,6 +1997,64 @@ export function verifyFreshProductionV12ModuleSchema( } } +/** + * Code-only v13 preflight. Both proven v12/v13 digests, every table contract, + * every product-type reference, and all 54 complete signatures are bound to + * one anonymous schema description before publication is allowed. + */ +export function verifyExactProductionV13ModuleSchema( + description, + expectedV12TableSchemaDigest, + expectedV13TableSchemaDigest, +) { + const contracts = verifyProductionV13Contracts( + description, + expectedV12TableSchemaDigest, + expectedV13TableSchemaDigest, + ); + const moduleState = verifyWorkerV12ModuleAbi(description); + if ( + moduleState !== 'active-predecessor' + && moduleState !== 'candidate' + ) { + fail('The exact production v13 module did not expose an accepted active-or-candidate Worker ABI.'); + } + const tableSignatures = Object.freeze(Object.fromEntries( + Object.keys(contracts.v13Refs).map(name => [ + name, + canonicalJson(schemaTableSignature(description, name)), + ]), + )); + return Object.freeze({ + moduleState, + tableSignatures, + totalTableCount: Object.keys(contracts.v13Refs).length, + }); +} + +export function verifyFreshProductionV13ModuleSchema( + executable, + expectedV12TableSchemaDigest, + expectedV13TableSchemaDigest, + spawnSyncProcess = spawnSync, +) { + try { + const result = runBoundedSync( + executable, + canonicalSchemaDescribeChildArguments(), + { timeout: 30_000 }, + spawnSyncProcess, + ); + return verifyExactProductionV13ModuleSchema( + parseCanonicalSchemaDescription(result.stdout), + expectedV12TableSchemaDigest, + expectedV13TableSchemaDigest, + ); + } catch { + fail('Exact production v13 schema and active-or-candidate module-ABI preflight failed. No publish was attempted.'); + } +} + export function verifyPostPublishProductionV12ModuleSchema( executable, predecessor, @@ -2055,6 +2137,55 @@ export function verifyPostPublishProductionV13ModuleSchema( } } +/** + * Code-only v13 postflight. No table is appended here: all 54 captured table + * signatures must remain byte-for-byte identical while the exact candidate + * Worker ABI is installed under the same two proven schema digests. + */ +export function verifyPostPublishProductionV13ActiveModuleSchema( + executable, + predecessor, + expectedV12TableSchemaDigest, + expectedV13TableSchemaDigest, + spawnSyncProcess = spawnSync, +) { + try { + if ( + !predecessor + || ( + predecessor.moduleState !== 'active-predecessor' + && predecessor.moduleState !== 'candidate' + ) + ) fail('The captured production v13 predecessor ABI was invalid.'); + requireCapturedTableSignatures( + predecessor.tableSignatures, + productionV13TableRefs(), + 'v13', + ); + const result = runBoundedSync( + executable, + canonicalSchemaDescribeChildArguments(), + { timeout: 30_000 }, + spawnSyncProcess, + ); + const after = verifyExactProductionV13ModuleSchema( + parseCanonicalSchemaDescription(result.stdout), + expectedV12TableSchemaDigest, + expectedV13TableSchemaDigest, + ); + if ( + after.moduleState !== 'candidate' + || canonicalJson(after.tableSignatures) + !== canonicalJson(predecessor.tableSignatures) + ) { + fail('The code-only v13 publication did not preserve every table signature and install the exact candidate ABI.'); + } + return after; + } catch { + fail('Post-publication active-v13 module checkpoint is indeterminate; perform a fresh anonymous read-only schema and ABI inspection before any Worker command, client deployment, or further publication decision.'); + } +} + export function verifyFreshProductionV11Schema( executable, expectedTableSchemaDigest, @@ -3221,15 +3352,31 @@ async function main() { if ( workerModulePredecessor === WORKER_MODULE_PREDECESSOR.EXACT_V12_EMPTY || workerModulePredecessor === WORKER_MODULE_PREDECESSOR.EXACT_V12_ACTIVE + || workerModulePredecessor === WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE ) { - const predecessorSchema = verifyFreshProductionV12ModuleSchema( - executable, - artifactReceipt.v12TableSchemaDigest, - ); - verifyWorkerV12ModulePredecessor( - predecessorSchema.moduleState, - workerModulePredecessor, - ); + const exactV13Active = workerModulePredecessor + === WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE; + const predecessorSchema = exactV13Active + ? verifyFreshProductionV13ModuleSchema( + executable, + artifactReceipt.v12TableSchemaDigest, + artifactReceipt.v13TableSchemaDigest, + ) + : verifyFreshProductionV12ModuleSchema( + executable, + artifactReceipt.v12TableSchemaDigest, + ); + if (exactV13Active) { + verifyWorkerV13ModulePredecessor( + predecessorSchema.moduleState, + workerModulePredecessor, + ); + } else { + verifyWorkerV12ModulePredecessor( + predecessorSchema.moduleState, + workerModulePredecessor, + ); + } const publicationPlan = planWorkerV12CodePublication( predecessorSchema.moduleState, workerForwardRepair, @@ -3245,12 +3392,21 @@ async function main() { publicationPlan.prePublicationCheckpoint, ); await publishModule(executable, CANONICAL_DATABASE_IDENTITY, artifactReceipt); - verifyPostPublishProductionV13ModuleSchema( - executable, - predecessorSchema, - artifactReceipt.v12TableSchemaDigest, - artifactReceipt.v13TableSchemaDigest, - ); + if (exactV13Active) { + verifyPostPublishProductionV13ActiveModuleSchema( + executable, + predecessorSchema, + artifactReceipt.v12TableSchemaDigest, + artifactReceipt.v13TableSchemaDigest, + ); + } else { + verifyPostPublishProductionV13ModuleSchema( + executable, + predecessorSchema, + artifactReceipt.v12TableSchemaDigest, + artifactReceipt.v13TableSchemaDigest, + ); + } verifyPostPublishResourcePublicationCheckpoints( adminTokenSecret, foundedExpectations, diff --git a/tests/activationToolingSecurity.test.ts b/tests/activationToolingSecurity.test.ts index 1e185769..0e6add1e 100644 --- a/tests/activationToolingSecurity.test.ts +++ b/tests/activationToolingSecurity.test.ts @@ -48,6 +48,7 @@ import { verifyFreshPublishPreV12Aggregate, verifyFreshProductionV11Schema, verifyFreshProductionV12ModuleSchema, + verifyFreshProductionV13ModuleSchema, verifyFreshFoundedProtocolV3Aggregate, verifyFreshResourceProtocolV4PrebackfillAggregate, verifyFreshResourceProtocolV4ReadyAggregate, @@ -61,6 +62,7 @@ import { verifyPostPublishProductionV12Schema, verifyPostPublishProductionV12ModuleSchema, verifyPostPublishProductionV13ModuleSchema, + verifyPostPublishProductionV13ActiveModuleSchema, verifyPostPublishProductionV13SchemaFromV11, verifyPostPublishResourceProtocolV4PrebackfillAggregate, verifyPostPublishResourceProtocolV4ReadyAggregate, @@ -81,6 +83,8 @@ import { verifyWorkerV12ModulePredecessor, verifyExactProductionV13Schema, verifyExactProductionV13SchemaFromV11, + verifyExactProductionV13ModuleSchema, + verifyWorkerV13ModulePredecessor, } from '../scripts/publish-spacetime-dev.mjs'; import { readPrivateSpacetimePublishSuccessReceipt, @@ -1663,6 +1667,146 @@ describe('activation publish safety', () => { expect(indeterminate).not.toThrow(/private|retry/i); }); + it('guards an exact code-only active-v13 publication with complete signatures and candidate ABI', () => { + const activeV12 = workerModuleSchemaDescription('active-predecessor'); + const activeV13 = workerModuleSchemaDescription('active-predecessor', true); + const candidateV13 = workerModuleSchemaDescription('candidate', true); + const v12TableNames = [ + ...Object.keys(PRODUCTION_V11_TABLE_PRODUCT_TYPE_REFS), + ...Object.keys(WORKER_V12_TABLE_CONTRACTS), + ]; + const v13TableNames = [ + ...v12TableNames, + ...Object.keys(ACCESS_REQUEST_V13_TABLE_CONTRACTS), + ]; + const v12TableSchemaDigest = canonicalTableSchemaBoundaryDigest( + activeV12, + v12TableNames, + ); + const v13TableSchemaDigest = canonicalTableSchemaBoundaryDigest( + activeV13, + v13TableNames, + ); + expect(canonicalTableSchemaBoundaryDigest(candidateV13, v13TableNames)) + .toBe(v13TableSchemaDigest); + + const predecessor = verifyExactProductionV13ModuleSchema( + activeV13, + v12TableSchemaDigest, + v13TableSchemaDigest, + ); + expect(predecessor.moduleState).toBe('active-predecessor'); + expect(predecessor.totalTableCount).toBe(54); + expect(Object.keys(predecessor.tableSignatures)).toHaveLength(54); + expect(verifyWorkerV13ModulePredecessor( + predecessor.moduleState, + WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE, + )).toBe('active-predecessor'); + + const alreadyCandidate = verifyExactProductionV13ModuleSchema( + candidateV13, + v12TableSchemaDigest, + v13TableSchemaDigest, + ); + expect(verifyWorkerV13ModulePredecessor( + alreadyCandidate.moduleState, + WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE, + )).toBe('candidate'); + expect(() => verifyWorkerV13ModulePredecessor( + 'predecessor', + WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE, + )).toThrow(/exact active production v13 ABI/i); + expect(() => verifyWorkerV13ModulePredecessor( + 'active-predecessor', + WORKER_MODULE_PREDECESSOR.EXACT_V12_ACTIVE, + )).toThrow(/exact active production v13 ABI/i); + + const preflightCalls: unknown[][] = []; + expect(verifyFreshProductionV13ModuleSchema( + 'spacetime', + v12TableSchemaDigest, + v13TableSchemaDigest, + ((...args: unknown[]) => { + preflightCalls.push(args); + return { + status: 0, + signal: null, + stdout: JSON.stringify(activeV13), + stderr: '', + }; + }) as never, + )).toEqual(predecessor); + expect(preflightCalls).toHaveLength(1); + expect(preflightCalls[0]?.[1]).toEqual(canonicalSchemaDescribeChildArguments()); + + expect(verifyPostPublishProductionV13ActiveModuleSchema( + 'spacetime', + predecessor, + v12TableSchemaDigest, + v13TableSchemaDigest, + (() => ({ + status: 0, + signal: null, + stdout: JSON.stringify(candidateV13), + stderr: '', + })) as never, + )).toEqual(alreadyCandidate); + expect(verifyPostPublishProductionV13ActiveModuleSchema( + 'spacetime', + alreadyCandidate, + v12TableSchemaDigest, + v13TableSchemaDigest, + (() => ({ + status: 0, + signal: null, + stdout: JSON.stringify(candidateV13), + stderr: '', + })) as never, + )).toEqual(alreadyCandidate); + + expect(() => verifyExactProductionV13ModuleSchema( + workerModuleSchemaDescription('predecessor', true), + v12TableSchemaDigest, + v13TableSchemaDigest, + )).toThrow(/active-or-candidate Worker ABI/i); + expect(() => verifyExactProductionV13ModuleSchema( + activeV13, + v12TableSchemaDigest, + '0'.repeat(64), + )).toThrow(/v13 table schema.*proven publication boundary/i); + + const driftedCandidate = structuredClone(candidateV13); + driftedCandidate.tables + .find(table => table.name === 'access_request_v1')!.indexes.push({ + name: 'unexpected_access_request_index', + algorithm: { BTree: { columns: [0] } }, + }); + expect(() => verifyPostPublishProductionV13ActiveModuleSchema( + 'spacetime', + predecessor, + v12TableSchemaDigest, + v13TableSchemaDigest, + (() => ({ + status: 0, + signal: null, + stdout: JSON.stringify(driftedCandidate), + stderr: '', + })) as never, + )).toThrow(/post-publication active-v13 module checkpoint is indeterminate/i); + expect(() => verifyPostPublishProductionV13ActiveModuleSchema( + 'spacetime', + predecessor, + v12TableSchemaDigest, + v13TableSchemaDigest, + (() => ({ + status: 0, + signal: null, + stdout: JSON.stringify(activeV13), + stderr: '', + })) as never, + )).toThrow(/post-publication active-v13 module checkpoint is indeterminate/i); + }); + it('retains the reviewed v11 lane while proving both v12 and v13 append boundaries', () => { const v11 = productionSchemaDescription(false); const v11TableNames = Object.keys(PRODUCTION_V11_TABLE_PRODUCT_TYPE_REFS); @@ -1746,6 +1890,10 @@ describe('activation publish safety', () => { expect(publisher).toMatch( /verifyFreshProductionV11Schema\([\s\S]*await publishModule\(executable, CANONICAL_DATABASE_IDENTITY, artifactReceipt\);[\s\S]*verifyPostPublishProductionV13SchemaFromV11\([\s\S]*artifactReceipt\.v12TableSchemaDigest,[\s\S]*artifactReceipt\.v13TableSchemaDigest/, ); + expect(publisher).toMatch( + /EXACT_V13_ACTIVE[\s\S]*verifyFreshProductionV13ModuleSchema\([\s\S]*artifactReceipt\.v12TableSchemaDigest,[\s\S]*artifactReceipt\.v13TableSchemaDigest,[\s\S]*await publishModule\(executable, CANONICAL_DATABASE_IDENTITY, artifactReceipt\);[\s\S]*verifyPostPublishProductionV13ActiveModuleSchema\([\s\S]*artifactReceipt\.v12TableSchemaDigest,[\s\S]*artifactReceipt\.v13TableSchemaDigest/, + ); + expect(publisher).toContain("'--delete-data=never'"); const privateWorkerReceipt = publisher.slice( publisher.indexOf('const receipt = writePrivateSpacetimePublishSuccessReceipt({'), publisher.indexOf("console.log(JSON.stringify({\n publication: 'verified'", publisher.indexOf( @@ -2306,6 +2454,20 @@ describe('activation publish safety', () => { workerModulePredecessor: WORKER_MODULE_PREDECESSOR.EXACT_V12_ACTIVE, workerForwardRepair: WORKER_FORWARD_REPAIR.NONE, }); + expect(parsePublishArguments([ + '--resource-rollout-stage=ready', + '--genesis-world-stage=expanded', + '--worker-rollout-stage=active', + '--worker-module-predecessor=exact-v13-active', + '--worker-forward-repair=none', + ])).toEqual({ + dryRun: false, + resourceRolloutStage: RESOURCE_PUBLISH_ROLLOUT_STAGE.READY, + genesisWorldRolloutStage: GENESIS_WORLD_PUBLISH_STAGE.EXPANDED, + workerRolloutStage: WORKER_PUBLISH_ROLLOUT_STAGE.ACTIVE, + workerModulePredecessor: WORKER_MODULE_PREDECESSOR.EXACT_V13_ACTIVE, + workerForwardRepair: WORKER_FORWARD_REPAIR.NONE, + }); expect(parsePublishArguments([ '--resource-rollout-stage=ready', '--genesis-world-stage=expanded', @@ -2394,6 +2556,13 @@ describe('activation publish safety', () => { '--worker-module-predecessor=exact-v12-active', '--worker-forward-repair=none', ])).toThrow(/exact active-v12 module predecessor/i); + expect(() => parsePublishArguments([ + '--resource-rollout-stage=ready', + '--genesis-world-stage=expanded', + '--worker-rollout-stage=empty', + '--worker-module-predecessor=exact-v13-active', + '--worker-forward-repair=none', + ])).toThrow(/exact active-v13 module predecessor/i); expect(() => parsePublishArguments([ '--resource-rollout-stage=ready', '--genesis-world-stage=expanded',