From 9f18692f076c0f8311def321856bb31e5f21374c Mon Sep 17 00:00:00 2001 From: Forge Date: Wed, 23 Sep 2026 04:10:07 +0000 Subject: [PATCH] docs: prepare UI-only preview rollout and shared-account budget controls [skip ci] --- CHANGELOG.md | 6 ++---- docs/extensions/marketplace-budget-and-source.md | 2 ++ marketplace/cloudflare/.gitignore | 1 + .../marketplace-source-hosted-readonly.cjs | 16 ++++++++++++++++ 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 scripts/validation/marketplace-source-hosted-readonly.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index b8f90523..7cc11c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,6 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -### New Features - -- Inspect the exact extension package and every bundled source file before installing, with verified fingerprints and clear repository provenance. - - Add a trusted, pinned Drupal import command for the Cloudflare registry, with target/plan verification, atomic owner claims, official backup preservation and local Worker/browser graph acceptance. @@ -48,6 +44,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### New Features +- Inspect the exact extension package and every bundled source file before installing, with verified fingerprints and clear repository provenance. + - Prepare a Cloudflare marketplace backend with signed publication, immutable packages, persistent metadata and isolated recovery validation. Hosted setup remains a preview prerequisite. - Marketplace operators can preserve published extensions across service restarts and restore verified backups into an isolated registry. diff --git a/docs/extensions/marketplace-budget-and-source.md b/docs/extensions/marketplace-budget-and-source.md index 2c3bba8c..0bb63673 100644 --- a/docs/extensions/marketplace-budget-and-source.md +++ b/docs/extensions/marketplace-budget-and-source.md @@ -25,6 +25,8 @@ CPU inputs: 26 ms per package fetch (largest of three previous deployed 8 MiB sa [D1 pricing](https://developers.cloudflare.com/d1/platform/pricing/) includes 25 billion read rows, 50 million write rows and 5 GB across the account on Paid. The model bounds metadata reads against both the current 17 releases and the existing 1,000-release cap. The preview databases were 94,208 bytes each at the accepted readback. **Unrelated telemetry already occupied roughly 10 GB at that readback**, so an account-total $5 promise would be unsound even if marketplace queries fit. We did not inspect or change that database or fetch an invoice. Account usage, rounding, taxes, other applications, backups and logging remain separate. Additional retention is not silently free. +The already-enabled Workers observability setting is unchanged. [Workers Logs pricing](https://developers.cloudflare.com/workers/platform/pricing/#workers-logs) includes 20 million events/month on Paid, then $0.60/million. A one-event-per-modeled-request allowance is 180,030–360,030 events (under 2% of that allowance); actual extra console/error events and unrelated services can raise it. No Logpush, retention upgrade or new logging service was enabled. + ## Controls prepared, not account changes - Keep `PUBLISHING_ENABLED=false`. Any separately authorized acceptance uses the existing absolute `PUBLISHING_UNTIL` deadline and an explicit close. Future publication also requires the reviewed source policy below. An allowlist is not a billing cap. diff --git a/marketplace/cloudflare/.gitignore b/marketplace/cloudflare/.gitignore index 6d854571..832fae37 100644 --- a/marketplace/cloudflare/.gitignore +++ b/marketplace/cloudflare/.gitignore @@ -5,3 +5,4 @@ node_modules/ wrangler.deploy.json wrangler.window.json wrangler.restore.json +wrangler.source-ui.json diff --git a/scripts/validation/marketplace-source-hosted-readonly.cjs b/scripts/validation/marketplace-source-hosted-readonly.cjs new file mode 100644 index 00000000..c32f9664 --- /dev/null +++ b/scripts/validation/marketplace-source-hosted-readonly.cjs @@ -0,0 +1,16 @@ +// Read-only public HTTPS data with tested local UI overlaid in this browser only. +// This is NOT deployed-UI proof. Does not connect a companion or write to the registry. +const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),crypto=require('node:crypto'); +const {chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright'); +const origin='https://codegraph-marketplace-preview.colby-7d9.workers.dev',restore='https://codegraph-marketplace-preview-restore-20260922.colby-7d9.workers.dev'; +const out=path.resolve(process.env.SOURCE_READONLY_OUTPUT||'.qa/source-budget/https-readonly');fs.mkdirSync(out,{recursive:true}); +(async()=>{let browser,page;try{ + const health=await(await fetch(origin+'/api/health')).json(),restoreHealth=await(await fetch(restore+'/api/health')).json();assert.equal(health.publishing,false);assert.equal(restoreHealth.publishing,false); + const catalog=await(await fetch(origin+'/api/extensions')).json(),release=catalog.find(e=>e.id==='drupal');assert.equal(release.version,'0.1.1');assert.equal(release.integrity,'e5ed73bee51524585e4f4da1d5017fdee3037a3fa71d8f08edab6c318fedc59a');assert.equal(release.sourceReview,undefined); + const remoteApp=await(await fetch(origin+'/app.js')).text(); + browser=await chromium.launch({executablePath:process.env.CHROME_PATH||'/opt/google/chrome/chrome',headless:true,args:['--no-sandbox']});page=await browser.newPage({viewport:{width:1440,height:1000}});const errors=[];page.on('pageerror',e=>errors.push(e.message)); + for(const file of ['app.js','style.css'])await page.route(origin+'/'+file,route=>route.fulfill({status:200,contentType:file.endsWith('.js')?'text/javascript':'text/css',body:fs.readFileSync(path.resolve('marketplace/public',file))})); + await page.goto(origin+'/extensions/drupal');await page.locator('[data-view-source]').click();await page.locator('.source-verified').waitFor();assert.match(await page.locator('.source-unverified').innerText(),/Repository correspondence unverified/);assert.equal(await page.evaluate(()=>state.snapshot),null);assert.equal(await page.locator('#source-content').innerText(),JSON.parse(fs.readFileSync('dist/extensions/drupal.cgext')).files[await page.locator('#source-file').inputValue()].slice(0,64000)); + await page.screenshot({path:path.join(out,'desktop-legacy-source.png'),fullPage:true});await page.setViewportSize({width:390,height:844});await page.screenshot({path:path.join(out,'mobile-legacy-source.png'),fullPage:true});assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));assert.deepEqual(errors,[]); + fs.writeFileSync(path.join(out,'summary.json'),JSON.stringify({success:true,scope:'Actual read-only HTTPS health/catalog/package; tested local UI overlaid in this browser, NOT deployed UI',origin,health,restoreHealth,release,remoteAppSHA:crypto.createHash('sha256').update(remoteApp).digest('hex'),remoteHasSourceViewer:remoteApp.includes('function verifiedSource('),files:await page.locator('#source-file option').count(),checks:['both public endpoints publishing false','immutable Drupal 0.1.1 fingerprint matches accepted artifact','all displayed packaged source matches local immutable reference','historical repository provenance explicitly unverified','no connection installation or publication','desktop/mobile inert inspection without page errors']},null,2)); +}catch(error){await page?.screenshot({path:path.join(out,'failure.png'),fullPage:true}).catch(()=>{});throw error;}finally{await browser?.close();}})().catch(e=>{console.error(e);process.exitCode=1;});