From 4fa7d683480b01d34795fce40812a12fb39e3e85 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 22 Oct 2026 21:45:41 -0500 Subject: [PATCH] row 292 (#1797) C1 and N3 for duckworth-0229: a pre-270 loader stand-in of its own (C1's loader without the row 270 concat, the list before bce491715) refuses compactionReadBytes and compactionStreams on a full activate and rollback, naming the key; C1's loader reads both; the missing-loader case uses compactionStreams 1024 and checks row 203's console and federation text word for word with no loader --- packages/cli/tests/stack-release.test.mjs | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/cli/tests/stack-release.test.mjs b/packages/cli/tests/stack-release.test.mjs index 882c5aea4..6b897d414 100644 --- a/packages/cli/tests/stack-release.test.mjs +++ b/packages/cli/tests/stack-release.test.mjs @@ -2622,6 +2622,28 @@ test('config-any-key: a non-console target refusal stops activate and rollback b assert.deepEqual(out.filter(l=>l.startsWith('refuses ')),[`refuses config-console-key: ${ANY_CONFIG_MSG(e,B,'rollback',why)}`]); }finally{undo();} }); +// Duckworth C1 (duckworth-0229): B's fixture loader (LOADER_OLD) is today's loader with a shorter +// first list, so it still reads the row 270 keys. This stand-in is C1's loader without the row 270 +// concat, the list the loader had before bce491715 added the first of the two keys. +const PRE270=(now)=>{const old=now.replace('.concat(["compactionReadBytes", "compactionStreams"])','');assert.notEqual(old,now);return old}; +test('config-any-key: a pre-270 target refuses each row 270 key on a full activate and rollback, naming the key',async t=>{ + const e=await mk(t);await seed(e); + let r=await run(e,'activate',C1,'acme');assert.equal(r.code,0,r.out+r.err); + const original=readFileSync(e.cfg,'utf8'),now=join(COPIES,C1,'scripts/mosaic-config.mjs'); + assert.equal(Object.hasOwn(JSON.parse(original),'console'),false); + const undo=anyConfigLoader(join(COPIES,B,'scripts/mosaic-config.mjs'),PRE270(readFileSync(now,'utf8'))); + try{ + for(const [key,value] of [['compactionReadBytes',1048576],['compactionStreams',1024]]){ + writeFileSync(e.cfg,JSON.stringify({...JSON.parse(original),[key]:value})); + // C1's own loader reads the key, so the refusal is the target's. + const ok=spawnSync(NODE,[now,'validate'],{encoding:'utf8',env:{...e.env,MOSAIC_CONFIG:e.cfg}});assert.equal(ok.status,0,ok.stderr); + const why=`mosaic-config: unsupported configuration key: "${key}"`; + await refused(e,['activate',B,'acme'],'config-console-key',ANY_CONFIG_MSG(e,B,'activate',why)); + await refused(e,['rollback','acme'],'config-console-key',ANY_CONFIG_MSG(e,B,'rollback',why)); + } + }finally{undo();writeFileSync(e.cfg,original);} + r=await run(e,'rollback','acme');assert.equal(r.code,0,r.out+r.err);assert.equal(pointer(e).copy,B); +}); test('config-any-key: accepting loaders run validate with the selected config on activate and rollback',async t=>{ const e=await mk(t);await seed(e);const witness=join(e.root,'loader-calls.jsonl'),canary='resolved-config-must-not-leak'; const undos=[B,C1].map(id=>anyConfigLoader(join(COPIES,id,'scripts/mosaic-config.mjs'),`import fs from 'node:fs';fs.appendFileSync(${JSON.stringify(witness)},JSON.stringify({copy:${JSON.stringify(id)},args:process.argv.slice(2),config:process.env.MOSAIC_CONFIG})+'\\n');console.log(${JSON.stringify(canary)});\n`)); @@ -2643,11 +2665,17 @@ function isolatedConfigChecks(e){ test('config-any-key: missing-loader copies accept only the four oldest keys',async t=>{ const e=await mk(t);let r=isolatedConfigChecks(e);assert.equal(r.status,0,r.stderr); const original=JSON.parse(readFileSync(e.cfg,'utf8')); - for(const [key,value] of [['compactionReadBytes',1048576],['compactionStreams',['messages']]]){ + for(const [key,value] of [['compactionReadBytes',1048576],['compactionStreams',1024]]){ writeFileSync(e.cfg,JSON.stringify({...original,[key]:value}));r=isolatedConfigChecks(e); assert.equal(r.status,3,r.stderr);assert.match(r.stderr,new RegExp(`configuration key: "${key}"`)); assert.ok(r.stderr.includes('restore the config.json backup from the window\'s step 1')); } + // Row 203's text for a console or federation key stays word for word with no loader too, even + // when a later key comes first in the file. + for(const [key,value] of [['console',{}],['federation',{self:'hub',peers:[]}]]){ + writeFileSync(e.cfg,JSON.stringify({...original,compactionStreams:1024,[key]:value}));r=isolatedConfigChecks(e); + assert.equal(r.status,3,r.stderr);assert.equal(r.stderr,`config-console-key: ${KEY_MSG(e,'old','activate','it has no scripts/mosaic-config.mjs')}\n`); + } }); test('config-any-key: missing config passes but malformed JSON refuses without calling the loader',async t=>{ const e=await mk(t),file=join(e.root,'target-copies/old/scripts/mosaic-config.mjs'),witness=join(e.root,'validate-called');