Download tools/check_workbench.cjs from Kai9987kai/CROSSWIRE: direct link, hf CLI and curl.
- Browser
- Download file 33.7 kB
-
https://huggingface.co/Kai9987kai/CROSSWIRE/resolve/main/tools/check_workbench.cjs
- Command line
-
hf download hf://Kai9987kai/CROSSWIRE/tools/check_workbench.cjs
-
curl -L -o check_workbench.cjs https://huggingface.co/Kai9987kai/CROSSWIRE/resolve/main/tools/check_workbench.cjs
33.7 kB
| /* Real-browser regression check. Requires Playwright and its Chromium browser. */ | |
| ; | |
| const assert = require('node:assert/strict'); | |
| const fs = require('node:fs'); | |
| const path = require('node:path'); | |
| const {spawn} = require('node:child_process'); | |
| const root = path.resolve(__dirname, '..'); | |
| let playwright; | |
| try { playwright = require(process.env.CROSSWIRE_PLAYWRIGHT || 'playwright'); } | |
| catch { playwright = require(path.join(process.env.USERPROFILE || process.env.HOME, '.codex/skills/develop-web-game/node_modules/playwright')); } | |
| const venvPython = path.join(root, '.venv', process.platform === 'win32' ? 'Scripts/python.exe' : 'bin/python'); | |
| const python = process.env.CROSSWIRE_PYTHON || (fs.existsSync(venvPython) ? venvPython : 'python'); | |
| (async () => { | |
| const child = spawn(python, ['-m', 'crosswire', 'serve', '--port', '0'], {cwd:root, windowsHide:true, stdio:['ignore','pipe','pipe']}); | |
| let browser, page, output, base, serverErrors = ''; | |
| const errors = []; | |
| child.stderr.on('data', value => serverErrors += value); | |
| try { | |
| base = await new Promise((resolve, reject) => { | |
| const timer = setTimeout(() => reject(new Error('Workbench server did not start')), 15000); | |
| child.stdout.once('data', value => {clearTimeout(timer);resolve(String(value).trim());}); | |
| child.once('error', reject); | |
| child.once('exit', code => {clearTimeout(timer);reject(new Error(`Server exited: ${code} ${serverErrors}`));}); | |
| }); | |
| browser = await playwright.chromium.launch({headless:true}); | |
| page = await browser.newPage({viewport:{width:1440,height:1000}}); | |
| output = path.join(root,'output/playwright'); | |
| fs.mkdirSync(output,{recursive:true}); | |
| page.on('pageerror', error => errors.push(error.message)); | |
| await page.goto(base); | |
| await page.waitForFunction(() => !document.querySelector('#run-button').disabled); | |
| assert.equal(await page.inputValue('#candidate_backend'), 'flat', 'Nominal runs must retain the exhaustive backend by default'); | |
| assert.match(await page.locator('label[for="candidate_backend"]').textContent(), /backend/i); | |
| await page.selectOption('#candidate_backend', 'factored'); | |
| await page.selectOption('#policy', 'lookahead'); | |
| assert.equal(await page.evaluate(() => config().candidate_backend), 'factored'); | |
| assert.deepEqual(await page.evaluate(() => studyConfig().candidate_backends), ['factored']); | |
| assert.match(await page.textContent('#study-description'), /factored/i); | |
| assert.match(await page.textContent('#policy-help'), /model.count.*not.*calibrated/i); | |
| await page.selectOption('#candidate_backend', 'flat'); | |
| await page.selectOption('#policy', 'task'); | |
| assert.equal(await page.locator('#instance_seed').count(), 1, 'Workbench must expose a distinct structural instance seed'); | |
| assert.equal(await page.isDisabled('#instance_seed'), true, 'Legacy families cannot set a structural instance seed'); | |
| assert.match(await page.locator('label[for="seed"]').textContent(), /label/i, 'The nuisance seed must be labeled as label variation'); | |
| assert.equal(await page.inputValue('#fault_type'), 'off', 'Fault injection must default to the nominal control'); | |
| for (const id of ['fault_step', 'fault_phase', 'fault_value']) assert.equal(await page.isDisabled(`#${id}`), true); | |
| assert.equal(await page.evaluate(() => config().fault_schedule), null); | |
| assert.deepEqual(await page.evaluate(() => studyConfig().fault_schedules), [null]); | |
| assert.match(await page.textContent('#fault-onset-help'), /zero-based/i); | |
| await page.selectOption('#fault_type', 'stuck_output'); | |
| for (const id of ['fault_step', 'fault_phase', 'fault_value']) assert.equal(await page.isEnabled(`#${id}`), true); | |
| for (const value of ['-1', '1.5', '']) { | |
| await page.fill('#fault_step', value); | |
| assert.equal(await page.locator('#fault_step').evaluate(el => el.checkValidity()), false, `Onset ${value} must fail native validation`); | |
| } | |
| await page.fill('#fault_step', '0'); | |
| for (const value of ['-1', '4', '1.5', '']) { | |
| await page.fill('#fault_value', value); | |
| assert.equal(await page.locator('#fault_value').evaluate(el => el.checkValidity()), false, `Stuck output ${value} must fail native validation`); | |
| } | |
| await page.fill('#fault_value', '3'); | |
| for (const phase of ['deployment', 'calibration', 'always']) { | |
| await page.selectOption('#fault_phase', phase); | |
| assert.deepEqual(await page.evaluate(() => config().fault_schedule), {type:'stuck_output',step:0,phase,value:3}); | |
| } | |
| for (const type of ['sensor_drift', 'actuator_swap', 'delay_spike', 'semantic_swap']) { | |
| await page.selectOption('#fault_type', type); | |
| assert.equal(await page.isDisabled('#fault_value'), true, 'Only a stuck output uses a configured symbol'); | |
| assert.deepEqual(await page.evaluate(() => config().fault_schedule), {type,step:0,phase:'always'}); | |
| } | |
| await page.fill('#fault_step', '-1'); | |
| await page.selectOption('#fault_type', 'off'); | |
| assert.equal(await page.evaluate(() => config().fault_schedule), null, 'Turning a fault off must omit retained inactive settings'); | |
| assert.deepEqual(await page.evaluate(() => studyConfig().fault_schedules), [null]); | |
| assert.equal(await page.locator('#run-form').evaluate(el => el.checkValidity()), true, 'Inactive invalid fault fields must not block a nominal run'); | |
| if (process.argv.includes('--controls-only')) { | |
| await page.evaluate(() => { | |
| const fixture = document.createElement('section');fixture.id='fixture-results';document.body.append(fixture); | |
| fixture.append(renderCertificate({valid:true,kind:'robust',model_diagnostics:{inclusion_status:'UNKNOWN',enabled_classes:['static'],classes:{static:{candidate_count:2,scored_symbols:1,unscored_symbols:0,events:[{probe_index:0,step:1,action:0,predicted_output_counts:{0:2},observed_output:0,survivors_before:2,survivors_after:2,prediction_status:'SUPPORTED'}],first_refutation:null}},reset_consistency:{status:'UNKNOWN',first_conflict:null},blind_spots:['Raw-equivalent semantic corruption remains unobservable.','<img src=x onerror=alert(1)>']}})); | |
| fixture.append(renderCertificate({valid:true,kind:'unresolved',model_diagnostics:{inclusion_status:'REFUTED',enabled_classes:['static'],classes:{static:{candidate_count:0,scored_symbols:1,unscored_symbols:1,events:[{probe_index:1,step:1,action:0,predicted_output_counts:{0:2},observed_output:1,survivors_before:2,survivors_after:0,prediction_status:'UNSUPPORTED'}],first_refutation:{kind:'probe',probe_index:1,step:1,word_prefix:[0],observed_output:1,survivors_before:2}}},reset_consistency:{status:'REFUTED',first_conflict:{word_prefix:[0],previous_probe_index:0,probe_index:1}},blind_spots:[]}})); | |
| renderStudy({rows:[],summary:[],paired:[],protocol:{families:['generated_protocol'],instance_seeds:[0,1],seeds:[17,18],fault_schedules:[null,{type:'stuck_output',step:0,phase:'calibration',value:3}]},reliability:[{family:'generated_protocol',method:'task/robust',budget:0,fault_schedule:{type:'sensor_drift',step:2,phase:'always'},requests:4,parent_clusters:2,independent_parents:2,target_any_deployment_hazard:{events:0,targets:2,rate:0,interval:[0,0.84],unknown_targets:1},target_any_calibration_hazard:{events:1,targets:2,rate:0.5,interval:null,unknown_targets:0},target_average_useful_coverage:{estimate:0.5,interval:null},conditional_selective_harm:null,inference_eligible:false,inference_reason:'Insufficient independent targets',scope:'Conditional on independent synthetic structural parents.'}]},fixture); | |
| fixture.append(renderBackend({backend:'factored',cached_adapters:4,total_candidates:144,cached_predictions:12})); | |
| fixture.append(renderPlanning([{planning:{expanded_nodes:2048,node_limit:2048,depth_limit:2,search_complete:false,selection:'fallback',certificate:{valid:true,ready_models:3,candidate_count:12,worst_case_calls:5},tree:{private:'PRIVATE_TREE_SENTINEL'}}}])); | |
| }); | |
| const fixture = page.locator('#fixture-results'); | |
| assert.match(await fixture.textContent(), /Model inclusion: UNKNOWN/); | |
| assert.match(await fixture.textContent(), /Model inclusion: REFUTED/); | |
| assert.match(await fixture.textContent(), /First refutation: probe 2, step 1/); | |
| assert.match(await fixture.textContent(), /First reset-prefix conflict/); | |
| assert.match(await fixture.textContent(), /Prediction support timeline/); | |
| assert.match(await fixture.textContent(), /SUPPORTED/); | |
| assert.match(await fixture.textContent(), /Independent-target reliability/); | |
| assert.equal(await fixture.locator('.reliability-stratum').count(), 1, 'Each reliability condition must show its outcomes without scrolling a wide table'); | |
| assert.match(await fixture.textContent(), /0 \/ 2/); | |
| assert.match(await fixture.textContent(), /1 unknown/); | |
| assert.equal(await fixture.locator('img').count(), 0, 'Model diagnostic text must use safe DOM rendering'); | |
| assert.match(await fixture.textContent(), /0 to 0.84/); | |
| assert.match(await fixture.textContent(), /No injected fault/); | |
| assert.match(await fixture.textContent(), /stuck output.*zero-based step 0.*calibration.*output 3/); | |
| assert.match(await fixture.textContent(), /sensor drift.*zero-based step 2.*both phases/); | |
| assert.match(await fixture.textContent(), /backend flat/i, 'Legacy omitted backends must display the flat default'); | |
| assert.match(await fixture.textContent(), /Candidate backend/); | |
| assert.match(await fixture.textContent(), /cached adapters.*4/i); | |
| assert.match(await fixture.textContent(), /cached predictions.*12/i); | |
| assert.match(await fixture.textContent(), /Budgeted experiment planning/); | |
| assert.match(await fixture.textContent(), /3 \/ 12/); | |
| assert.match(await fixture.textContent(), /incomplete search/i); | |
| assert.match(await fixture.textContent(), /not a calibrated.*probability/i); | |
| assert.doesNotMatch(await fixture.textContent(), /PRIVATE_TREE_SENTINEL/); | |
| assert.deepEqual(errors, []); | |
| console.log('PASS: fault defaults and native validation, structural seed controls, legacy-family boundary, public diagnostics, target reliability, safe DOM'); | |
| return; | |
| } | |
| assert.equal(await page.locator('#probe_safety').count(), 1, 'Workbench must expose the calibration evidence rule'); | |
| assert.equal(await page.inputValue('#probe_safety'), 'unrestricted'); | |
| await page.selectOption('#probe_safety', 'model_safe'); | |
| await page.selectOption('#policy', 'aalpy'); | |
| assert.equal(await page.locator('#policy').evaluate(el => el.checkValidity()), false, 'AALpy must reject model-safe calibration'); | |
| await page.selectOption('#policy', 'task'); | |
| assert.equal(await page.isDisabled('#risk_tolerance'), true, 'Tolerance applies only to candidate-fraction admission'); | |
| await page.selectOption('#admission', 'risk_controlled'); | |
| assert.equal(await page.isEnabled('#risk_tolerance'), true); | |
| await page.fill('#risk_tolerance', '0.25'); | |
| assert.equal(await page.inputValue('#risk_tolerance'), '0.25'); | |
| await page.fill('#risk_tolerance', '1'); | |
| assert.equal(await page.locator('#risk_tolerance').evaluate(el => el.checkValidity()), false, 'Candidate tolerance must remain below one'); | |
| await page.fill('#risk_tolerance', '0.25'); | |
| await page.selectOption('#admission', 'robust'); | |
| assert.equal(await page.inputValue('#risk_tolerance'), '0'); | |
| await page.selectOption('#execution_mode', 'replan'); | |
| await page.selectOption('#admission', 'adaptive'); | |
| assert.equal(await page.inputValue('#execution_mode'), 'shielded', 'Adaptive selection must choose a compatible deployment mode'); | |
| assert.equal(await page.locator('#execution_mode option[value="replan"]').isDisabled(), true, 'Adaptive policy execution does not support replanning'); | |
| await page.selectOption('#execution_mode', 'recovery'); | |
| assert.equal(await page.inputValue('#execution_mode'), 'recovery', 'Adaptive mode must permit certified recovery'); | |
| await page.selectOption('#policy', 'ec2'); | |
| assert.equal(await page.inputValue('#policy'), 'ec2', 'Workbench must allow selecting EC2 active probing'); | |
| await page.selectOption('#policy', 'branching'); | |
| assert.equal(await page.inputValue('#policy'), 'branching', 'Workbench must allow selecting adaptive within-query branching calibration'); | |
| await page.selectOption('#family', 'robotics_airlock'); | |
| assert.equal(await page.inputValue('#family'), 'robotics_airlock', 'Workbench must expose realistic robotics_airlock benchmark'); | |
| await page.selectOption('#family', 'protocol_session'); | |
| assert.equal(await page.inputValue('#family'), 'protocol_session', 'Workbench must expose realistic protocol_session benchmark'); | |
| await page.selectOption('#policy', 'task'); | |
| await page.selectOption('#admission', 'risk_controlled'); | |
| assert.equal(await page.inputValue('#admission'), 'risk_controlled', 'Workbench must allow risk-controlled admission'); | |
| await page.selectOption('#admission', 'adaptive'); | |
| await page.check('#temporal'); | |
| await page.check('#escalate'); | |
| assert.equal(await page.isChecked('#temporal'), false); | |
| assert.equal(await page.isDisabled('#temporal'), true); | |
| await page.uncheck('#escalate'); | |
| assert.equal(await page.isDisabled('#temporal'), false); | |
| await page.selectOption('#family', 'routing'); | |
| await page.fill('#seed', '42'); | |
| await page.fill('#budget', '0'); | |
| await page.fill('#horizon', '3'); | |
| await page.selectOption('#split', 'evaluation'); | |
| assert.match(await page.textContent('#study-description'), /4 runs/); | |
| const submitted = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith('/api/studies')); | |
| await page.click('#study-button'); | |
| const spec = (await submitted).postDataJSON(); | |
| assert.deepEqual(spec, {families:['routing'],seeds:[42,43],budgets:[0],policies:['task'],admissions:['robust','adaptive'],implementations:['procedural'],anchors:false,split:'evaluation',horizon:3,temporal:false,escalate:false,execution_mode:'recovery',probe_safety:'model_safe',risk_tolerance:0,fault_schedules:[null],candidate_backends:['flat']}); | |
| await page.waitForFunction(() => !document.querySelector('#result-view').hidden && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| assert.match(await page.textContent('#result-content'), /parent[ _]clusters/i); | |
| assert.match(await page.textContent('#result-content'), /descriptive 95% interval/i); | |
| assert.match(await page.textContent('#result-content'), /model.safe/i); | |
| assert.match(await page.textContent('#result-content'), /Independent-target reliability/); | |
| await page.getByRole('button', {name:'Inspect run 1', exact:true}).click(); | |
| await page.waitForSelector('#study-run-detail:not([hidden])'); | |
| assert.match(await page.textContent('#study-run-detail'), /Calibration safety/); | |
| assert.match(await page.textContent('#study-run-detail'), /Public model evidence/); | |
| assert.equal(await page.textContent('#result-kind'), 'PAIRED STUDY RECEIPT', 'Inspecting a row preserves the parent receipt'); | |
| await page.setViewportSize({width:390,height:900}); | |
| assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), true, 'Study inspection must fit a mobile viewport'); | |
| await page.screenshot({path:path.join(output,'study-inspection-mobile.png'),fullPage:true}); | |
| await page.locator('#study-run-detail').evaluate(el => el.scrollIntoView({block:'start'})); | |
| await page.screenshot({path:path.join(output,'study-inspection-mobile-viewport.png')}); | |
| await page.setViewportSize({width:1440,height:1000}); | |
| const downloadPromise = page.waitForEvent('download'); | |
| await page.click('#export-button'); | |
| const download = await downloadPromise; | |
| const receipt = fs.readFileSync(await download.path(), 'utf8'); | |
| const original = JSON.parse(receipt); | |
| assert.equal(original.payload.rows.length, 4); | |
| assert.ok(original.payload.rows.every(row => row.configuration.fault_schedule === null), 'Nominal studies must retain null fault schedules'); | |
| for (const action of ['replay', 'import']) { | |
| const block = await page.request.post(base + 'api/studies', {data:{families:['latch'],seeds:[1,2,3,4,5,6,7,8],budgets:[16],policies:['fixed']}}); | |
| const blocking = await block.json(); | |
| assert.ok(blocking.id); | |
| let submittedJob; | |
| const jobResponse = page.waitForResponse(response => response.request().method() === 'POST' && (action === 'replay' ? response.url().endsWith('/replay') : response.url().endsWith('/api/import'))); | |
| if (action === 'replay') { | |
| await page.click('#replay-button'); | |
| } else { | |
| await page.setInputFiles('#receipt-file', {name:'receipt.json',mimeType:'application/json',buffer:Buffer.from(receipt)}); | |
| } | |
| submittedJob = await (await jobResponse).json(); | |
| await page.waitForSelector('#job-panel:not([hidden])'); | |
| assert.equal(await page.isEnabled('#cancel-button'), true, `${action} must expose cancellation`); | |
| const cancelRequest = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith(`/${submittedJob.id}/cancel`)); | |
| await page.click('#cancel-button'); | |
| await cancelRequest; | |
| await page.request.post(base + `api/runs/${blocking.id}/cancel`, {data:{}}); | |
| await page.waitForFunction(() => document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| assert.match(await page.textContent('#notice'), /cancelled/i); | |
| assert.equal(await page.isVisible('#result-view'), true, 'Cancellation preserves previous complete evidence'); | |
| } | |
| await page.selectOption('#family', 'generated_protocol'); | |
| assert.equal(await page.isEnabled('#instance_seed'), true); | |
| await page.fill('#instance_seed', '1'); | |
| assert.match(await page.textContent('#study-description'), /8 runs/); | |
| assert.match(await page.textContent('#study-description'), /structural seeds 1 \/ 2/); | |
| const submittedGenerated = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith('/api/studies')); | |
| await page.click('#study-button'); | |
| assert.deepEqual((await submittedGenerated).postDataJSON(), {...spec,families:['generated_protocol'],instance_seeds:[1,2]}); | |
| await page.waitForFunction(() => document.querySelector('#result-content').textContent.includes('8 run results') && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| assert.match(await page.textContent('#result-content'), /Independent-target reliability/); | |
| assert.match(await page.textContent('#result-content'), /Independent parents/); | |
| assert.match(await page.textContent('#result-content'), /independent generator draws assumed/); | |
| assert.match(await page.textContent('#result-content'), /95% pointwise intervals/); | |
| await page.screenshot({path:path.join(output,'generated-study-desktop.png'),fullPage:true}); | |
| await page.getByRole('heading', {name:'Independent-target reliability',exact:true}).evaluate(el => el.scrollIntoView({block:'start'})); | |
| await page.screenshot({path:path.join(output,'generated-reliability-desktop-viewport.png')}); | |
| await page.getByRole('button', {name:'Inspect run 1', exact:true}).click(); | |
| assert.match(await page.textContent('#study-run-detail'), /Structural instance design/); | |
| assert.match(await page.textContent('#study-run-detail'), /Model inclusion: UNKNOWN/); | |
| assert.match(await page.textContent('#study-run-detail'), /Prediction support timeline/); | |
| await page.setViewportSize({width:390,height:900}); | |
| assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), true, 'Structural diagnostics and reliability must fit a mobile viewport'); | |
| await page.screenshot({path:path.join(output,'generated-study-mobile.png'),fullPage:true}); | |
| await page.getByRole('heading', {name:'Independent-target reliability',exact:true}).evaluate(el => el.scrollIntoView({block:'start'})); | |
| await page.screenshot({path:path.join(output,'generated-reliability-mobile-viewport.png')}); | |
| await page.getByText('Model inclusion: UNKNOWN', {exact:true}).evaluate(el => el.scrollIntoView({block:'start'})); | |
| await page.screenshot({path:path.join(output,'generated-diagnostics-mobile-viewport.png')}); | |
| await page.setViewportSize({width:1440,height:1000}); | |
| await page.selectOption('#family', 'symmetric'); | |
| assert.equal(await page.isDisabled('#instance_seed'), true); | |
| await page.selectOption('#admission', 'risk_controlled'); | |
| await page.fill('#risk_tolerance', '0.25'); | |
| const submittedRisk = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith('/api/runs')); | |
| await page.click('#run-button'); | |
| assert.equal((await submittedRisk).postDataJSON().risk_tolerance, 0.25, 'The selected candidate fraction must reach the scientific worker'); | |
| await page.waitForFunction(() => document.querySelector('#result-kind').textContent === 'EXPERIMENT RECEIPT' && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| assert.match(await page.textContent('#result-content'), /no plan selected; candidate rates are undefined/i); | |
| assert.match(await page.textContent('#result-content'), /Configured tolerance 0.25/); | |
| assert.doesNotMatch(await page.textContent('#result-content'), /Uniformly weighted candidate fractions: hazardous/); | |
| await page.selectOption('#family', 'symmetric'); | |
| await page.selectOption('#admission', 'robust'); | |
| await page.click('#run-button'); | |
| await page.waitForFunction(() => document.querySelector('#result-content').textContent.includes('Admission: Robust sequence across survivors') && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| assert.match(await page.textContent('#result-content'), /Public model evidence/); | |
| assert.match(await page.textContent('#result-content'), /Calibration safety/); | |
| assert.match(await page.textContent('#result-content'), /Structural factors among surviving adapters:/); | |
| assert.match(await page.textContent('#result-content'), /does not authenticate/i); | |
| await page.selectOption('#family', 'latch'); | |
| await page.fill('#seed', '42'); | |
| await page.fill('#budget', '16'); | |
| await page.fill('#horizon', '4'); | |
| await page.selectOption('#execution_mode', 'shielded'); | |
| await page.selectOption('#probe_safety', 'unrestricted'); | |
| await page.selectOption('#fault_type', 'sensor_drift'); | |
| await page.fill('#fault_step', '1'); | |
| await page.selectOption('#fault_phase', 'deployment'); | |
| const schedule = {type:'sensor_drift',step:1,phase:'deployment'}; | |
| assert.match(await page.textContent('#study-description'), /sensor drift.*zero-based step 1.*deployment/i); | |
| const submittedFault = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith('/api/runs')); | |
| await page.click('#run-button'); | |
| assert.deepEqual((await submittedFault).postDataJSON().fault_schedule, schedule); | |
| await page.waitForFunction(() => document.querySelector('#result-content').textContent.includes('Change-point fault') && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| assert.deepEqual(await page.evaluate(() => state.completed.result.configuration.fault_schedule), schedule); | |
| assert.equal(await page.evaluate(() => state.completed.result.deployment.change_point.detected), true); | |
| assert.match(await page.textContent('#result-content'), /sensor drift.*zero-based step 1.*deployment/i); | |
| await page.click('#replay-button'); | |
| await page.waitForFunction(() => !state.active && !state.busy && document.querySelector('#notice').textContent.includes('Replay verified') && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| await page.selectOption('#family', 'routing'); | |
| await page.fill('#budget', '0'); | |
| await page.selectOption('#admission', 'adaptive'); | |
| const submittedFaultStudy = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith('/api/studies')); | |
| await page.click('#study-button'); | |
| const faultSpec = (await submittedFaultStudy).postDataJSON(); | |
| assert.deepEqual(faultSpec.fault_schedules, [schedule]); | |
| await page.waitForFunction(() => document.querySelector('#result-kind').textContent === 'PAIRED STUDY RECEIPT' && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| assert.match(await page.textContent('#result-content'), /sensor drift.*zero-based step 1.*deployment/i); | |
| const faultDownloadPromise = page.waitForEvent('download'); | |
| await page.click('#export-button'); | |
| const faultDownload = await faultDownloadPromise; | |
| const faultReceipt = JSON.parse(fs.readFileSync(await faultDownload.path(), 'utf8')); | |
| assert.equal(faultReceipt.payload.rows.length, 4); | |
| assert.deepEqual(faultReceipt.payload.protocol.fault_schedules, [schedule]); | |
| for (const row of faultReceipt.payload.rows) assert.deepEqual(row.configuration.fault_schedule, schedule); | |
| for (const row of faultReceipt.payload.summary) assert.deepEqual(row.fault_schedule, schedule); | |
| await page.getByRole('button', {name:'Inspect run 1', exact:true}).click(); | |
| assert.match(await page.textContent('#study-run-detail'), /sensor drift.*zero-based step 1.*deployment/i); | |
| await page.click('#replay-button'); | |
| await page.waitForFunction(() => !state.active && !state.busy && document.querySelector('#notice').textContent.includes('Replay verified') && document.querySelector('#job-panel').hidden, null, {timeout:60000}); | |
| await page.selectOption('#fault_type', 'off'); | |
| await page.selectOption('#candidate_backend', 'factored'); | |
| await page.selectOption('#policy', 'lookahead'); | |
| await page.selectOption('#admission', 'robust'); | |
| await page.selectOption('#family', 'latch'); | |
| await page.fill('#budget', '4'); | |
| const submittedLookahead = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith('/api/runs')); | |
| await page.click('#run-button'); | |
| const lookaheadConfiguration = (await submittedLookahead).postDataJSON(); | |
| assert.equal(lookaheadConfiguration.policy, 'lookahead'); | |
| assert.equal(lookaheadConfiguration.candidate_backend, 'factored'); | |
| assert.equal(lookaheadConfiguration.fault_schedule, null); | |
| await page.waitForFunction(() => !state.active && !state.busy && state.completed?.result.configuration?.policy === 'lookahead', null, {timeout:60000}); | |
| assert.match(await page.textContent('#result-content'), /Candidate backend/); | |
| assert.match(await page.textContent('#result-content'), /backend factored/i); | |
| assert.match(await page.textContent('#result-content'), /Budgeted experiment planning/); | |
| assert.match(await page.textContent('#result-content'), /not a calibrated.*probability/i); | |
| const lookaheadDownloadPromise = page.waitForEvent('download'); | |
| await page.click('#export-button'); | |
| const lookaheadDownload = await lookaheadDownloadPromise; | |
| const lookaheadReceipt = JSON.parse(fs.readFileSync(await lookaheadDownload.path(), 'utf8')); | |
| assert.equal(lookaheadReceipt.payload.configuration.candidate_backend, 'factored'); | |
| assert.ok(lookaheadReceipt.payload.backend); | |
| const plans = lookaheadReceipt.payload.probes.filter(probe => probe.planning); | |
| assert.ok(plans.length > 0, 'The actual lookahead run must retain its checked experiment proposal'); | |
| assert.ok(plans.every(probe => probe.planning.certificate.valid === true)); | |
| await page.click('#replay-button'); | |
| await page.waitForFunction(() => !state.active && !state.busy && document.querySelector('#notice').textContent.includes('Replay verified'), null, {timeout:60000}); | |
| await page.fill('#budget', '2'); | |
| const submittedLookaheadStudy = page.waitForRequest(req => req.method() === 'POST' && req.url().endsWith('/api/studies')); | |
| await page.click('#study-button'); | |
| const lookaheadSpec = (await submittedLookaheadStudy).postDataJSON(); | |
| assert.deepEqual(lookaheadSpec.candidate_backends, ['factored']); | |
| assert.deepEqual(lookaheadSpec.policies, ['lookahead','fixed']); | |
| await page.waitForFunction(() => !state.active && !state.busy && state.completed?.result.schema === 'crosswire-study-v1' && state.completed.result.protocol?.policies.includes('lookahead'), null, {timeout:60000}); | |
| const lookaheadStudyDownloadPromise = page.waitForEvent('download'); | |
| await page.click('#export-button'); | |
| const lookaheadStudyDownload = await lookaheadStudyDownloadPromise; | |
| const lookaheadStudy = JSON.parse(fs.readFileSync(await lookaheadStudyDownload.path(), 'utf8')).payload; | |
| assert.equal(lookaheadStudy.rows.length, 8); | |
| assert.deepEqual(lookaheadStudy.protocol.candidate_backends, ['factored']); | |
| assert.ok(lookaheadStudy.rows.every(row => row.configuration.candidate_backend === 'factored')); | |
| assert.ok(lookaheadStudy.summary.every(row => row.candidate_backend === 'factored')); | |
| const plannedRow = lookaheadStudy.rows.findIndex(row => row.probes.some(probe => probe.planning)); | |
| assert.ok(plannedRow >= 0, 'The small study must include executed lookahead proposals'); | |
| await page.getByRole('button', {name:`Inspect run ${plannedRow+1}`, exact:true}).click(); | |
| assert.match(await page.textContent('#study-run-detail'), /Budgeted experiment planning/); | |
| assert.match(await page.textContent('#study-run-detail'), /backend factored/i); | |
| await page.click('#replay-button'); | |
| await page.waitForFunction(() => !state.active && !state.busy && document.querySelector('#notice').textContent.includes('Replay verified'), null, {timeout:60000}); | |
| await page.screenshot({path:path.join(output,'evidence-workbench-desktop.png'),fullPage:true}); | |
| await page.setViewportSize({width:390,height:900}); | |
| assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth), true, 'Mobile workbench must not overflow horizontally'); | |
| await page.screenshot({path:path.join(output,'evidence-workbench-mobile.png'),fullPage:true}); | |
| assert.deepEqual(errors, []); | |
| assert.equal(serverErrors, ''); | |
| console.log('PASS: backend/lookahead controls and actual factored run/study/export/replay, checked experiment planning, fault controls, native validation, actual fault run/study/export/replay, calibration and tolerance controls, structural instance seeds, independent-target reliability, public model diagnostics, row inspection, cancellable replay/import, responsive layout'); | |
| } catch (error) { | |
| if (page && output) { | |
| await page.screenshot({path:path.join(output,'failure.png'),fullPage:true}).catch(() => {}); | |
| const context = await page.evaluate(() => ({ | |
| notice:document.querySelector('#notice')?.textContent, | |
| job:document.querySelector('#job-message')?.textContent, | |
| status:document.querySelector('#job-status')?.textContent, | |
| active:state.active, completedSchema:state.completed?.result?.schema | |
| })).catch(() => ({})); | |
| if(context.active?.id&&base){ | |
| context.serverJob=await page.request.get(base+`api/runs/${context.active.id}`).then(r=>r.json()).then(j=>({status:j.status,error:j.error,progress:j.progress})).catch(e=>({error:String(e)})); | |
| } | |
| fs.writeFileSync(path.join(output,'failure-context.json'),JSON.stringify({error:String(error),serverErrors,pageErrors:errors,...context},null,2)); | |
| console.error('Workbench failure context:',context); | |
| } | |
| throw error; | |
| } finally { | |
| if (browser) await browser.close(); | |
| child.kill(); | |
| } | |
| })().catch(error => {console.error(error);process.exitCode=1;}); | |