From e34ec3fafeea23b650a011435681bf5ff7376e80 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 26 Sep 2026 23:14:34 +0200 Subject: [PATCH] Exercise feature activation rollback and old-session artifact retention Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9 --- TREE.md | 13 + research/README.md | 4 + .../candidates/IndependentFeatureBoundary.md | 13 +- research/catalog/PatternCandidateCatalog.md | 5 + research/catalog/SourceCatalog.md | 11 + .../experiments/feature-release/README.md | 50 + .../experiments/feature-release/account-a1.js | 4 + .../feature-release/experiment.mjs | 51 + research/experiments/feature-release/host.js | 29 + .../feature-release/package-lock.json | 40 + .../experiments/feature-release/package.json | 7 + .../feature-release/provenance.json | 28 + .../experiments/feature-release/reports-r1.js | 4 + .../experiments/feature-release/reports-r2.js | 4 + .../experiments/feature-release/reports-r3.js | 4 + .../experiments/feature-release/result.json | 1292 +++++++++++++++++ .../experiments/feature-release/shared.js | 1 + research/plan/OpenQuestions.md | 6 + .../reviews/FeatureDeploymentBoundaries.md | 6 + research/reviews/FeatureReleaseExperiment.md | 58 + .../walkthroughs/VC06-MicrofrontendEstate.md | 14 + workplans/FEP-WP-0003-contrasting-schools.md | 18 + 22 files changed, 1661 insertions(+), 1 deletion(-) create mode 100644 research/experiments/feature-release/README.md create mode 100644 research/experiments/feature-release/account-a1.js create mode 100644 research/experiments/feature-release/experiment.mjs create mode 100644 research/experiments/feature-release/host.js create mode 100644 research/experiments/feature-release/package-lock.json create mode 100644 research/experiments/feature-release/package.json create mode 100644 research/experiments/feature-release/provenance.json create mode 100644 research/experiments/feature-release/reports-r1.js create mode 100644 research/experiments/feature-release/reports-r2.js create mode 100644 research/experiments/feature-release/reports-r3.js create mode 100644 research/experiments/feature-release/result.json create mode 100644 research/experiments/feature-release/shared.js create mode 100644 research/reviews/FeatureReleaseExperiment.md diff --git a/TREE.md b/TREE.md index c17286b..0e5d8f1 100644 --- a/TREE.md +++ b/TREE.md @@ -368,6 +368,18 @@ research/experiments/boundary-recovery/routing-fixture.jsx research/experiments/boundary-recovery/routing-results.json research/experiments/boundary-recovery/routing-transform.mjs research/experiments/boundary-recovery/routing.mjs +research/experiments/feature-release/README.md +research/experiments/feature-release/account-a1.js +research/experiments/feature-release/experiment.mjs +research/experiments/feature-release/host.js +research/experiments/feature-release/package-lock.json +research/experiments/feature-release/package.json +research/experiments/feature-release/provenance.json +research/experiments/feature-release/reports-r1.js +research/experiments/feature-release/reports-r2.js +research/experiments/feature-release/reports-r3.js +research/experiments/feature-release/result.json +research/experiments/feature-release/shared.js research/experiments/matrix-recovery/README.md research/experiments/matrix-recovery/experiment.mjs research/experiments/matrix-recovery/package-lock.json @@ -383,6 +395,7 @@ research/reviews/BoundaryRecoveryExperiment.md research/reviews/CarniatoAdmissionRegression.json research/reviews/FeatureDeploymentBoundaries.md research/reviews/FeatureDeploymentSources.json +research/reviews/FeatureReleaseExperiment.md research/reviews/FrontendRecoveryImplementations.md research/reviews/FrontendRecoverySources.json research/reviews/JoplinConflictScreen.json diff --git a/research/README.md b/research/README.md index 409b353..ee13b77 100644 --- a/research/README.md +++ b/research/README.md @@ -65,3 +65,7 @@ Latest breadth evidence: [feature versus deployment boundaries](reviews/FeatureD compares pinned example sources and adds [provisional VC-06](walkthroughs/VC06-MicrofrontendEstate.md). Independent Feature Boundary develops an existing seed; current corpus has seventeen hypotheses and four synthetic walkthroughs. No deployment independence was runtime-verified. + +Latest execution: [feature release lifecycle](reviews/FeatureReleaseExperiment.md) +records 14/14 expected observations across two browsers. It distinguishes fresh, +already-loaded and delayed-load sessions, and includes negative failure controls. diff --git a/research/candidates/IndependentFeatureBoundary.md b/research/candidates/IndependentFeatureBoundary.md index e2509ab..753d5f0 100644 --- a/research/candidates/IndependentFeatureBoundary.md +++ b/research/candidates/IndependentFeatureBoundary.md @@ -47,4 +47,15 @@ the other. Boundaries can coincide without becoming the same pattern. Next test: [VC-06](../walkthroughs/VC06-MicrofrontendEstate.md) proposes compatible and incompatible feature releases, failed loading and binding rollback across fresh and -already-open sessions. These runtime checks have not yet been executed. +already-open sessions. A bounded native-module experiment now exercises these cases; see the update below. + +## Executed activation/session evidence — 2026-09-26 + +[Feature release experiment](../reviews/FeatureReleaseExperiment.md) meets 14/14 +expected observations across two browsers in an authored native-module fixture. +Distinguish retained artifact, per-document binding and evaluated module lifetime: +a fresh session follows activation/rollback while an existing session retains its +binding; deleting the old artifact can break its delayed first load. A post-import +API check runs after evaluation and is not a security boundary. A shared startup +failure can block otherwise separate features. These are bounded mechanism observations, +not execution of the source examples or proof of full-pattern production independence. diff --git a/research/catalog/PatternCandidateCatalog.md b/research/catalog/PatternCandidateCatalog.md index 52370bf..6981893 100644 --- a/research/catalog/PatternCandidateCatalog.md +++ b/research/catalog/PatternCandidateCatalog.md @@ -141,3 +141,8 @@ sources support partial mechanisms, not verified production release independence example and a contrast with deployment. [Evidence](../reviews/FeatureDeploymentBoundaries.md) and [provisional VC-06](../walkthroughs/VC06-MicrofrontendEstate.md) distinguish module, artifact, activation, runtime-failure and organizational boundaries. + +Independent Feature Boundary follow-up: [native-module release cases](../reviews/FeatureReleaseExperiment.md) +add bounded execution for publication, activation, session-scoped rollback and artifact +retention. Corpus remains seventeen hypotheses; synthetic host behavior is not an +independent application use or production release guarantee. diff --git a/research/catalog/SourceCatalog.md b/research/catalog/SourceCatalog.md index 94ff16c..1bfbe23 100644 --- a/research/catalog/SourceCatalog.md +++ b/research/catalog/SourceCatalog.md @@ -148,3 +148,14 @@ Accessed 2026-09-26. [17-file hash manifest](../reviews/FeatureDeploymentSources and [claims D01–D09](../reviews/FeatureDeploymentBoundaries.md). Static example-source inspection, no builds or deployments run. Documentation revisions unknown; examples and docs do not establish independent recurrence of a complete release contract. + +## Native-module release experiment — 2026-09-26 + +SRC-0036: [feature release fixture](../experiments/feature-release/README.md), authored +synthetic host/artifacts with locked Playwright and hashed browser/input provenance. +Seven cases in Chromium/Firefox, 14/14 expected observations. Distinct from the earlier +pinned source examples; no deployed release workflow was executed. + +SRC-0037: [MDN dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import#module_namespace_object), +Module namespace object; living documentation accessed 2026-09-26, exact revision +unknown. Context for evaluated module reuse versus HTTP caching. [Review](../reviews/FeatureReleaseExperiment.md). diff --git a/research/experiments/feature-release/README.md b/research/experiments/feature-release/README.md new file mode 100644 index 0000000..7cd5e66 --- /dev/null +++ b/research/experiments/feature-release/README.md @@ -0,0 +1,50 @@ +# Feature publication, activation and rollback experiment + +Executed 2026-09-26. Seven scenarios in Chromium 151.0.7922.34 and Firefox 155.0; +14/14 assertions met. [Review](../../reviews/FeatureReleaseExperiment.md), +[results](result.json), [provenance](provenance.json). + +This is an authored synthetic host using real browser-native modules/import maps, +not execution of the earlier FSD/single-spa examples or a production release system. +The host API is fixed at 1. Reports R1/R2 support 1, R3 declares 2; Account A1 stays +unchanged. Each new HTML document receives the current map. Published assets and +activation bindings are separate in-memory controls in the test server. + +## Reproduce + +From this directory: + +```bash +npm ci --ignore-scripts --cache /tmp/fep-npm-cache +PLAYWRIGHT_BROWSERS_PATH=/tmp/fep-playwright-browsers node node_modules/playwright/cli.js install firefox +PLAYWRIGHT_BROWSERS_PATH=/tmp/fep-playwright-browsers FEP_CHROMIUM_PATH=/path/to/chrome npm test +``` + +Playwright 1.63.0 is locked. Recorded runs used the same browser binaries identified +by hashes in provenance.json, with an explicit Chromium executable override. Firefox +required execution outside the workspace sandbox to access its temporary profile. +No external network is used during cases. Loopback server/browser cleanup is automatic. +Outputs default to `/tmp/fep-release-result.json` and an incomplete checkpoint at +`/tmp/fep-release-checkpoint.json`; `FEP_RESULT_PATH` overrides the final destination. +Any failed assertion produces nonzero exit. Receipts are not overwritten automatically. + +## Cases and limits + +| Case | Observed result in both browsers | +| --- | --- | +| X01 Publish R2 without changing binding | Fresh page still uses R1; no R2 request | +| X02 Activate compatible R2 | Fresh page R2; old loaded and old lazy pages R1; Account A1 unchanged | +| X03 Activate incompatible R3 | Host rejects mount, Account works; R3 module has already evaluated | +| X04 Point binding to missing artifact | Reports error, Account navigation still usable | +| X05 Roll binding back R2 → R1 | Fresh page R1; running page R2; explicit reload gets R1 | +| X06 Retire R1 after activating R2 | Old loaded page retains R1; old lazy page fails; new page uses R2 | +| X07 Fail shared startup module | Both navigation buttons remain disabled; feature loading never starts | + +Negative controls intentionally pass when the specified failure is observed. All +responses disable HTTP caching, so this is not a CDN/cache invalidation test. Existing +pages retain their document maps; dynamic map mutation/hot replacement is not tested. +Only the host's deliberately tiny API-version contract is checked; module evaluation +can already have side effects before that check. No backend writes, authentication, +API migrations, atomic/concurrent map updates, accessibility audit or safe reload of +unsaved work is modeled. The startup outage reflects this fixture's chosen dependency, +not an assertion that every independently released frontend must fail that way. diff --git a/research/experiments/feature-release/account-a1.js b/research/experiments/feature-release/account-a1.js new file mode 100644 index 0000000..bbb2ad8 --- /dev/null +++ b/research/experiments/feature-release/account-a1.js @@ -0,0 +1,4 @@ +export const version = 'A1'; +export const hostApi = 1; +window.observed.evaluations.push(version); +export function mount(node) { node.textContent = 'Account A1'; } diff --git a/research/experiments/feature-release/experiment.mjs b/research/experiments/feature-release/experiment.mjs new file mode 100644 index 0000000..b9217f0 --- /dev/null +++ b/research/experiments/feature-release/experiment.mjs @@ -0,0 +1,51 @@ +import {chromium, firefox} from 'playwright'; +import {createServer} from 'node:http'; +import {once} from 'node:events'; +import {readFile,writeFile} from 'node:fs/promises'; +import {fileURLToPath} from 'node:url'; +import {createHash} from 'node:crypto'; +import assert from 'node:assert/strict'; +const dir=fileURLToPath(new URL('.',import.meta.url)); +const fileNames=['host.js','reports-r1.js','reports-r2.js','reports-r3.js','account-a1.js','shared.js']; +const assets=new Map(await Promise.all(fileNames.map(async f=>['/'+f,await readFile(dir+f)]))); +let binding, published, sharedDown, requests; +function reset(){binding={reports:'/reports-r1.js',account:'/account-a1.js',shared:'/shared.js'};published=new Set(['/host.js','/reports-r1.js','/account-a1.js','/shared.js']);sharedDown=false;requests=[];} +reset(); +const server=createServer((req,res)=>{ + const path=new URL(req.url,'http://localhost').pathname; + res.setHeader('Cache-Control','no-store'); + if(path==='/'){ + requests.push({path,status:200,binding:{...binding}});res.setHeader('Content-Type','text/html'); + res.end(`Feature release probe

starting

`); + }else if(path==='/shared.js'&&sharedDown){requests.push({path,status:503});res.writeHead(503,{'Content-Type':'text/plain'});res.end('synthetic startup outage');} + else if(published.has(path)&&assets.has(path)){requests.push({path,status:200});res.setHeader('Content-Type','application/javascript');res.end(assets.get(path));} + else{requests.push({path,status:404});res.writeHead(404,{'Content-Type':'text/plain'});res.end('artifact absent');} +}); +server.listen(0,'127.0.0.1');await once(server,'listening'); +const url=`http://127.0.0.1:${server.address().port}`; +const results=[]; +try{ + for(const [engine,launcher]of [['chromium',chromium],['firefox',firefox]]){ + const browser=await launcher.launch({headless:true,...(engine==='chromium'&&process.env.FEP_CHROMIUM_PATH?{executablePath:process.env.FEP_CHROMIUM_PATH}:{})}); + try{ + async function scenario(id,run){reset();const pages=[];const r={id,engine,browser:browser.version(),status:'pass'}; + async function session(expected='ready'){const p=await browser.newPage();p.setDefaultTimeout(2500);pages.push(p);await p.goto(url);await p.locator('#status').filter({hasText:expected}).waitFor();return p;} + try{r.observation=await run(session);}catch(e){r.status='fail';r.error=e.message;} + r.sessions=await Promise.all(pages.map(p=>p.evaluate(()=>({text:document.body.innerText,observed:window.observed}))));r.requests=[...requests];results.push(r);for(const p of pages)await p.close(); + await writeFile('/tmp/fep-release-checkpoint.json',JSON.stringify({status:'incomplete',results},null,2)+'\n');} + const reports=p=>p.evaluate(()=>window.loadReports());const account=p=>p.evaluate(()=>window.loadAccount()); + await scenario('X01-publish-without-activation',async open=>{published.add('/reports-r2.js');const p=await open();assert.deepEqual(await reports(p),{ok:true,version:'R1'});assert(!requests.some(x=>x.path==='/reports-r2.js'));return {active:'R1',publishedButUnused:'R2'};}); + await scenario('X02-compatible-activation-old-and-lazy',async open=>{ + const loaded=await open();assert.equal((await reports(loaded)).version,'R1');const lazy=await open();published.add('/reports-r2.js');binding.reports='/reports-r2.js';const fresh=await open(); + assert.equal((await reports(fresh)).version,'R2');assert.equal((await reports(loaded)).version,'R1');assert.equal((await reports(lazy)).version,'R1');for(const p of [loaded,lazy,fresh])assert.equal((await account(p)).version,'A1'); + return {fresh:'R2',oldLoaded:'R1',oldLazy:'R1',account:'A1',hostUnchanged:true};}); + await scenario('X03-incompatible-module',async open=>{published.add('/reports-r3.js');binding.reports='/reports-r3.js';const p=await open();assert.deepEqual(await reports(p),{ok:false,message:'incompatible host API'});assert.equal((await account(p)).version,'A1');const o=await p.evaluate(()=>window.observed);assert(o.evaluations.includes('R3'));assert(!o.mounts.some(x=>x.name==='reports'));return {incompatibleMountRejected:true,moduleAlreadyEvaluated:true,accountUsable:true};}); + await scenario('X04-missing-artifact',async open=>{binding.reports='/missing.js';const p=await open();assert.equal((await reports(p)).ok,false);await p.locator('#account-nav').click();await p.locator('#account').filter({hasText:'Account A1'}).waitFor();assert(requests.some(x=>x.path==='/missing.js'&&x.status===404));return {localLoadFailure:true,navigationUsable:true};}); + await scenario('X05-rollback-fresh-versus-running',async open=>{published.add('/reports-r2.js');binding.reports='/reports-r2.js';const old=await open();assert.equal((await reports(old)).version,'R2');binding.reports='/reports-r1.js';const fresh=await open();assert.equal((await reports(fresh)).version,'R1');assert.equal((await reports(old)).version,'R2');await old.reload();await old.locator('#status').filter({hasText:'ready'}).waitFor();assert.equal((await reports(old)).version,'R1');return {freshAfterRollback:'R1',runningBeforeReload:'R2',afterExplicitReload:'R1'};}); + await scenario('X06-retired-old-artifact',async open=>{const loaded=await open();assert.equal((await reports(loaded)).version,'R1');const lazy=await open();published.add('/reports-r2.js');binding.reports='/reports-r2.js';published.delete('/reports-r1.js');assert.equal((await reports(loaded)).version,'R1');assert.equal((await reports(lazy)).ok,false);const fresh=await open();assert.equal((await reports(fresh)).version,'R2');return {oldLoadedStillWorks:true,oldLazyFails:true,newSession:'R2'};}); + await scenario('X07-shared-startup-outage',async open=>{sharedDown=true;const p=await open('startup failed');assert.equal(await p.locator('#reports-nav').isDisabled(),true);assert.equal(await p.locator('#account-nav').isDisabled(),true);assert(requests.some(x=>x.path==='/shared.js'&&x.status===503));assert(!requests.some(x=>x.path==='/reports-r1.js'));return {bothNavigationActionsBlocked:true,featureLoadingNeverStarted:true};}); + }finally{await browser.close();} + } +}finally{await new Promise(resolve=>server.close(resolve));} +const files={};for(const f of [...fileNames,'experiment.mjs','package.json','package-lock.json'])files[f]=createHash('sha256').update(await readFile(dir+f)).digest('hex'); +const output=process.env.FEP_RESULT_PATH||'/tmp/fep-release-result.json';const result={date:new Date().toISOString(),node:process.version,kind:'synthetic-native-es-module-browser-experiment',files,results,passed:results.filter(x=>x.status==='pass').length,total:results.length};await writeFile(output,JSON.stringify(result,null,2)+'\n');console.log(JSON.stringify({output,passed:result.passed,total:result.total,failures:results.filter(x=>x.status==='fail')},null,2));if(result.passed!==result.total)process.exitCode=1; diff --git a/research/experiments/feature-release/host.js b/research/experiments/feature-release/host.js new file mode 100644 index 0000000..45b911d --- /dev/null +++ b/research/experiments/feature-release/host.js @@ -0,0 +1,29 @@ +const HOST_API = 1; +window.observed = {mounts: [], errors: [], evaluations: [], hostApi: HOST_API}; +const report = document.getElementById('reports'); +const account = document.getElementById('account'); +async function load(name, target) { + try { + const feature = name === 'reports' ? await import('reports') : await import('account'); + // Compatibility gate, not a security boundary: module evaluation already happened. + if (feature.hostApi !== HOST_API) throw new Error('incompatible host API'); + feature.mount(target); + window.observed.mounts.push({name, version: feature.version}); + return {ok: true, version: feature.version}; + } catch (error) { + target.textContent = `${name} unavailable`; + window.observed.errors.push({name, message: error.message}); + return {ok: false, message: error.message}; + } +} +window.loadReports = () => load('reports', report); +window.loadAccount = () => load('account', account); +try { + await import('shared'); + for (const button of document.querySelectorAll('button')) button.disabled = false; + document.getElementById('status').textContent = 'ready'; +} catch { + document.getElementById('status').textContent = 'startup failed'; +} +document.getElementById('reports-nav').onclick = window.loadReports; +document.getElementById('account-nav').onclick = window.loadAccount; diff --git a/research/experiments/feature-release/package-lock.json b/research/experiments/feature-release/package-lock.json new file mode 100644 index 0000000..494b906 --- /dev/null +++ b/research/experiments/feature-release/package-lock.json @@ -0,0 +1,40 @@ +{ + "name": "fep-feature-release", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fep-feature-release", + "dependencies": { + "playwright": "1.63.0" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + } + } +} diff --git a/research/experiments/feature-release/package.json b/research/experiments/feature-release/package.json new file mode 100644 index 0000000..410e453 --- /dev/null +++ b/research/experiments/feature-release/package.json @@ -0,0 +1,7 @@ +{ + "name": "fep-feature-release", + "private": true, + "type": "module", + "scripts": {"test": "node experiment.mjs"}, + "dependencies": {"playwright": "1.63.0"} +} diff --git a/research/experiments/feature-release/provenance.json b/research/experiments/feature-release/provenance.json new file mode 100644 index 0000000..668d332 --- /dev/null +++ b/research/experiments/feature-release/provenance.json @@ -0,0 +1,28 @@ +{ + "date": "2026-09-26", + "packages": { + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==" + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==" + } + }, + "chromium": { + "version": "151.0.7922.34", + "executable_sha256": "0b20b130e7edd9dd51873be867761295fe0cfad490c2b9a64f95bd3cfc08fa71", + "selection": "preinstalled Chromium build 1234, explicit executable override; not claimed to be Playwright 1.63 bundled revision" + }, + "firefox": { + "playwright_build": "1543", + "executable_sha256": "062272ac4d851eade5b75db2dc7c14569d96cb3ca38f949337cdc1b74367e35b", + "version": "155.0" + }, + "execution": "Local loopback server, native ES modules and import maps; synthetic host/artifacts; no production or single-spa deployment", + "http_cache": "All responses use Cache-Control: no-store; no service worker or CDN", + "activation": "Runner changes an in-memory binding used only in newly served documents; no client hot-rebinding or concurrent deployment protocol" +} diff --git a/research/experiments/feature-release/reports-r1.js b/research/experiments/feature-release/reports-r1.js new file mode 100644 index 0000000..5cf888d --- /dev/null +++ b/research/experiments/feature-release/reports-r1.js @@ -0,0 +1,4 @@ +export const version = 'R1'; +export const hostApi = 1; +window.observed.evaluations.push(version); +export function mount(node) { node.textContent = 'Reports R1'; } diff --git a/research/experiments/feature-release/reports-r2.js b/research/experiments/feature-release/reports-r2.js new file mode 100644 index 0000000..e58432f --- /dev/null +++ b/research/experiments/feature-release/reports-r2.js @@ -0,0 +1,4 @@ +export const version = 'R2'; +export const hostApi = 1; +window.observed.evaluations.push(version); +export function mount(node) { node.textContent = 'Reports R2'; } diff --git a/research/experiments/feature-release/reports-r3.js b/research/experiments/feature-release/reports-r3.js new file mode 100644 index 0000000..2bc2ef2 --- /dev/null +++ b/research/experiments/feature-release/reports-r3.js @@ -0,0 +1,4 @@ +export const version = 'R3'; +export const hostApi = 2; +window.observed.evaluations.push(version); +export function mount(node) { node.textContent = 'Reports R3 incompatible'; } diff --git a/research/experiments/feature-release/result.json b/research/experiments/feature-release/result.json new file mode 100644 index 0000000..e6923bf --- /dev/null +++ b/research/experiments/feature-release/result.json @@ -0,0 +1,1292 @@ +{ + "date": "2026-09-26T21:12:13.993Z", + "node": "v24.11.1", + "kind": "synthetic-native-es-module-browser-experiment", + "files": { + "host.js": "f22eda8a28a57e06d1720aac3f0a794d098c8d27ff5fbcb6835f8a3a73b91939", + "reports-r1.js": "f2a39800e5dad7ba9a2b338fb62a4353094fc2156640d8674be7a15143e71206", + "reports-r2.js": "17de3351f9b4be3d2d0b517e11486cc75f6c9e4dd50b994d0a6eb7b9b5b44927", + "reports-r3.js": "63e15b4d42f865556c62c3449731ab9b59ccb73e924da8d646111be05e6ee18f", + "account-a1.js": "f55e4aa7bf1d011a738941fe830bc6481740132963facf961291380c7c7b7f56", + "shared.js": "123ed7668b15ea7a87cecfe155d98267d57880d4eab98a670ba88ef8967fa69f", + "experiment.mjs": "76847acac070c24cae4b4cc79ef4c1b21954b107265e9498be404f7baf3494cf", + "package.json": "6986c09f8fee8c8bd7005dafb1c40e09538438127ae9602fa6b0686fab4b755b", + "package-lock.json": "2e6bf0caeea9d291d83f9008ceedefc1cf71a4e0fef2ab3b01f49edbee597af8" + }, + "results": [ + { + "id": "X01-publish-without-activation", + "engine": "chromium", + "browser": "151.0.7922.34", + "status": "pass", + "observation": { + "active": "R1", + "publishedButUnused": "R2" + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r1.js", + "status": 200 + } + ] + }, + { + "id": "X02-compatible-activation-old-and-lazy", + "engine": "chromium", + "browser": "151.0.7922.34", + "status": "pass", + "observation": { + "fresh": "R2", + "oldLoaded": "R1", + "oldLazy": "R1", + "account": "A1", + "hostUnchanged": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1\nAccount A1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + }, + { + "name": "reports", + "version": "R1" + }, + { + "name": "account", + "version": "A1" + } + ], + "errors": [], + "evaluations": [ + "R1", + "A1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R1\nAccount A1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + }, + { + "name": "account", + "version": "A1" + } + ], + "errors": [], + "evaluations": [ + "R1", + "A1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R2\nAccount A1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R2" + }, + { + "name": "account", + "version": "A1" + } + ], + "errors": [], + "evaluations": [ + "R2", + "A1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r2.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r2.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + } + ] + }, + { + "id": "X03-incompatible-module", + "engine": "chromium", + "browser": "151.0.7922.34", + "status": "pass", + "observation": { + "incompatibleMountRejected": true, + "moduleAlreadyEvaluated": true, + "accountUsable": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nreports unavailable\nAccount A1", + "observed": { + "mounts": [ + { + "name": "account", + "version": "A1" + } + ], + "errors": [ + { + "name": "reports", + "message": "incompatible host API" + } + ], + "evaluations": [ + "R3", + "A1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r3.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r3.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + } + ] + }, + { + "id": "X04-missing-artifact", + "engine": "chromium", + "browser": "151.0.7922.34", + "status": "pass", + "observation": { + "localLoadFailure": true, + "navigationUsable": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nreports unavailable\nAccount A1", + "observed": { + "mounts": [ + { + "name": "account", + "version": "A1" + } + ], + "errors": [ + { + "name": "reports", + "message": "Failed to fetch dynamically imported module: http://127.0.0.1:43063/missing.js" + } + ], + "evaluations": [ + "A1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/missing.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/missing.js", + "status": 404 + }, + { + "path": "/account-a1.js", + "status": 200 + } + ] + }, + { + "id": "X05-rollback-fresh-versus-running", + "engine": "chromium", + "browser": "151.0.7922.34", + "status": "pass", + "observation": { + "freshAfterRollback": "R1", + "runningBeforeReload": "R2", + "afterExplicitReload": "R1" + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r2.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r2.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 200 + } + ] + }, + { + "id": "X06-retired-old-artifact", + "engine": "chromium", + "browser": "151.0.7922.34", + "status": "pass", + "observation": { + "oldLoadedStillWorks": true, + "oldLazyFails": true, + "newSession": "R2" + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + }, + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nreports unavailable", + "observed": { + "mounts": [], + "errors": [ + { + "name": "reports", + "message": "Failed to fetch dynamically imported module: http://127.0.0.1:43063/reports-r1.js" + } + ], + "evaluations": [], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R2", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R2" + } + ], + "errors": [], + "evaluations": [ + "R2" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 404 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r2.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r2.js", + "status": 200 + } + ] + }, + { + "id": "X07-shared-startup-outage", + "engine": "chromium", + "browser": "151.0.7922.34", + "status": "pass", + "observation": { + "bothNavigationActionsBlocked": true, + "featureLoadingNeverStarted": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nstartup failed", + "observed": { + "mounts": [], + "errors": [], + "evaluations": [], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 503 + } + ] + }, + { + "id": "X01-publish-without-activation", + "engine": "firefox", + "browser": "155.0", + "status": "pass", + "observation": { + "active": "R1", + "publishedButUnused": "R2" + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r1.js", + "status": 200 + } + ] + }, + { + "id": "X02-compatible-activation-old-and-lazy", + "engine": "firefox", + "browser": "155.0", + "status": "pass", + "observation": { + "fresh": "R2", + "oldLoaded": "R1", + "oldLazy": "R1", + "account": "A1", + "hostUnchanged": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1\nAccount A1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + }, + { + "name": "reports", + "version": "R1" + }, + { + "name": "account", + "version": "A1" + } + ], + "errors": [], + "evaluations": [ + "R1", + "A1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R1\nAccount A1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + }, + { + "name": "account", + "version": "A1" + } + ], + "errors": [], + "evaluations": [ + "R1", + "A1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R2\nAccount A1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R2" + }, + { + "name": "account", + "version": "A1" + } + ], + "errors": [], + "evaluations": [ + "R2", + "A1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r2.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r2.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + } + ] + }, + { + "id": "X03-incompatible-module", + "engine": "firefox", + "browser": "155.0", + "status": "pass", + "observation": { + "incompatibleMountRejected": true, + "moduleAlreadyEvaluated": true, + "accountUsable": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nreports unavailable\nAccount A1", + "observed": { + "mounts": [ + { + "name": "account", + "version": "A1" + } + ], + "errors": [ + { + "name": "reports", + "message": "incompatible host API" + } + ], + "evaluations": [ + "R3", + "A1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r3.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r3.js", + "status": 200 + }, + { + "path": "/account-a1.js", + "status": 200 + } + ] + }, + { + "id": "X04-missing-artifact", + "engine": "firefox", + "browser": "155.0", + "status": "pass", + "observation": { + "localLoadFailure": true, + "navigationUsable": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nreports unavailable\nAccount A1", + "observed": { + "mounts": [ + { + "name": "account", + "version": "A1" + } + ], + "errors": [ + { + "name": "reports", + "message": "error loading dynamically imported module: http://127.0.0.1:43063/missing.js" + } + ], + "evaluations": [ + "A1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/missing.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/missing.js", + "status": 404 + }, + { + "path": "/account-a1.js", + "status": 200 + } + ] + }, + { + "id": "X05-rollback-fresh-versus-running", + "engine": "firefox", + "browser": "155.0", + "status": "pass", + "observation": { + "freshAfterRollback": "R1", + "runningBeforeReload": "R2", + "afterExplicitReload": "R1" + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r2.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r2.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/reports-r1.js", + "status": 200 + } + ] + }, + { + "id": "X06-retired-old-artifact", + "engine": "firefox", + "browser": "155.0", + "status": "pass", + "observation": { + "oldLoadedStillWorks": true, + "oldLazyFails": true, + "newSession": "R2" + }, + "sessions": [ + { + "text": "ReportsAccount\n\nready\n\nReports R1", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R1" + }, + { + "name": "reports", + "version": "R1" + } + ], + "errors": [], + "evaluations": [ + "R1" + ], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nreports unavailable", + "observed": { + "mounts": [], + "errors": [ + { + "name": "reports", + "message": "error loading dynamically imported module: http://127.0.0.1:43063/reports-r1.js" + } + ], + "evaluations": [], + "hostApi": 1 + } + }, + { + "text": "ReportsAccount\n\nready\n\nReports R2", + "observed": { + "mounts": [ + { + "name": "reports", + "version": "R2" + } + ], + "errors": [], + "evaluations": [ + "R2" + ], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r1.js", + "status": 200 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r1.js", + "status": 404 + }, + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r2.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 200 + }, + { + "path": "/favicon.ico", + "status": 404 + }, + { + "path": "/reports-r2.js", + "status": 200 + } + ] + }, + { + "id": "X07-shared-startup-outage", + "engine": "firefox", + "browser": "155.0", + "status": "pass", + "observation": { + "bothNavigationActionsBlocked": true, + "featureLoadingNeverStarted": true + }, + "sessions": [ + { + "text": "ReportsAccount\n\nstartup failed", + "observed": { + "mounts": [], + "errors": [], + "evaluations": [], + "hostApi": 1 + } + } + ], + "requests": [ + { + "path": "/", + "status": 200, + "binding": { + "reports": "/reports-r1.js", + "account": "/account-a1.js", + "shared": "/shared.js" + } + }, + { + "path": "/host.js", + "status": 200 + }, + { + "path": "/shared.js", + "status": 503 + }, + { + "path": "/favicon.ico", + "status": 404 + } + ] + } + ], + "passed": 14, + "total": 14 +} diff --git a/research/experiments/feature-release/shared.js b/research/experiments/feature-release/shared.js new file mode 100644 index 0000000..79a0e68 --- /dev/null +++ b/research/experiments/feature-release/shared.js @@ -0,0 +1 @@ +export const sharedVersion = 'S1'; diff --git a/research/plan/OpenQuestions.md b/research/plan/OpenQuestions.md index cb59c40..045c658 100644 --- a/research/plan/OpenQuestions.md +++ b/research/plan/OpenQuestions.md @@ -103,3 +103,9 @@ separate repositories prove none of the latter guarantees by themselves. [VC-06](../walkthroughs/VC06-MicrofrontendEstate.md) changes only release cadence to show why source boundaries can remain while deployment units merge. Runtime version skew/rollback and independent assessment remain open; Q06 is not closed. + +Q06 lifecycle follow-up: [release experiment](../reviews/FeatureReleaseExperiment.md) +distinguishes artifact retention, document binding and module-instance lifetime. +Old delayed loads can fail after artifact retirement even while already-loaded old +sessions work. Deployment independence remains conditional on a session-support +contract; no general answer or canonical promotion follows from this fixture. diff --git a/research/reviews/FeatureDeploymentBoundaries.md b/research/reviews/FeatureDeploymentBoundaries.md index 4c21971..e31f112 100644 --- a/research/reviews/FeatureDeploymentBoundaries.md +++ b/research/reviews/FeatureDeploymentBoundaries.md @@ -70,3 +70,9 @@ activation binding with both fresh and already-running sessions. Check startup-c shared resources separately from feature-local failure. Do not use existing deployment workflows or credentials to perform the experiment; a local controlled harness suffices. Q06 is sharpened, not closed. No additional inference was needed for this review. + +## Executed synthetic follow-up + +[Feature release experiment](FeatureReleaseExperiment.md) now exercises the proposed +version/load/rollback cases in an authored native-module host. This does not change +the static-only status of the upstream repositories inspected above. diff --git a/research/reviews/FeatureReleaseExperiment.md b/research/reviews/FeatureReleaseExperiment.md new file mode 100644 index 0000000..86b3a9f --- /dev/null +++ b/research/reviews/FeatureReleaseExperiment.md @@ -0,0 +1,58 @@ +# Feature release lifecycle experiment — 2026-09-26 + +Status: executed synthetic browser mechanism, editorial interpretation by Codex. +WP-0003 T01, following [source/deployment contrast](FeatureDeploymentBoundaries.md). +The [fixture and receipt](../experiments/feature-release/README.md) record seven cases +in two browsers, 14/14 expected observations. Seventeen hypotheses remain unchanged +in maturity; no production deployment, operational team independence or recurrence +is established. No model calls were needed. + +## Evidence and provenance + +SRC-0036 is our native-module host and versioned feature fixtures, executed with +Playwright 1.63.0, Chromium 151.0.7922.34 and Firefox 155.0. Source/lock hashes are in +the result receipt; browser hashes and package integrity are in provenance.json. +Every response uses no-store, and the loopback server serves no external resources. + +SRC-0037: [MDN import()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import#module_namespace_object), +Module namespace object, accessed 2026-09-26, living revision unknown. Its description +of reuse after successful module loading provides context for distinguishing module +identity from HTTP caching. We do not generalize our document-map snapshot policy to +all loaders or dynamic import-map update strategies. + +## Observations, not deployment guarantees + +| Evidence | Result | Pattern implication | +| --- | --- | --- | +| X01 | Publishing R2 alone leaves new pages on R1 | Artifact availability and activation are separate | +| X02 | Fresh page loads R2; old loaded/lazy pages load or retain R1; A1 unchanged | Compatible feature activation can coexist with an unchanged host and neighbor in this fixture; old-session binding matters even before first feature use | +| X03 | R3 is evaluated but mounting is rejected for host API mismatch; A1 works | Post-import contract checks can prevent incompatible mounting, but cannot undo module side effects or provide security isolation | +| X04 | Missing Reports module yields local error while Account navigation works | Host-level load handling is distinct from a component rendering boundary | +| X05 | Binding rollback changes a fresh page to R1 while an open page remains R2; reload selects R1 | Rollback needs a declared session scope; binding update alone is not hot replacement | +| X06 | Removing R1 breaks the old lazy session but not one that already loaded it | Artifact retention must account for delayed first loads from supported older documents | +| X07 | Shared startup import failure keeps both actions disabled, before any feature load | Separate artifacts do not eliminate dependencies on startup-critical shared resources | + +X03, X04, X06 and X07 include deliberately observed failures. A “pass” means the +asserted behavior occurred, not that the experience is acceptable. The version gate +is our fixture code, not a universal browser, single-spa or federation guarantee. +The R3 declaration is intentionally simple; real API compatibility is not reducible +to equality of one number. Test outcomes do not prove compatibility with arbitrary +host/backend versions or that deployment changes are applied atomically. + +## Candidate and walkthrough correction + +[Independent Feature Boundary](../candidates/IndependentFeatureBoundary.md) should +name three lifetimes separately: retained artifact, binding chosen for a session, +and module instance already evaluated in that session. Retiring an artifact based +only on the current global binding can invalidate an older page's later load. + +[VC-06](../walkthroughs/VC06-MicrofrontendEstate.md) now has executable support for +its load/activation/rollback distinction in this small model. Its accessibility, +real-system compatibility, authoritative writes and independent assessment remain +open. The earlier pinned examples were not run; their source observations remain +separate. Keep the pattern a hypothesis and the walkthrough provisional. + +Next under WP-0003 T01: declare an old-session support/retention policy, then test +activation-map concurrency and safe reload with unfinished work before making a +production recommendation. Navigation/history/cache evidence remains pending. Avoid +turning this model into a release platform merely to close the research question. diff --git a/research/walkthroughs/VC06-MicrofrontendEstate.md b/research/walkthroughs/VC06-MicrofrontendEstate.md index a054087..2fa477b 100644 --- a/research/walkthroughs/VC06-MicrofrontendEstate.md +++ b/research/walkthroughs/VC06-MicrofrontendEstate.md @@ -90,3 +90,17 @@ Vertical Feature Slice need not imply Independent Feature Boundary. version-skew/load-failure/binding-rollback harness and independent walkthrough review. No cloud deployment, production artifact activation or operational team independence was verified. VC-04/05 remain planned; this draft does not complete VC-06's M2 gate. + +## Executed model follow-up — 2026-09-26 + +[Seven-scenario native-module experiment](../reviews/FeatureReleaseExperiment.md) +now supports steps 2–6's publication/activation, version mismatch, failed loading and +session-scoped rollback distinctions in a synthetic host. Both browsers meet all seven +expected observations, including deliberate failures. Old lazy sessions need retained +artifacts even when loaded old sessions still work. Post-import compatibility gates +cannot prevent evaluation side effects. Shared startup outage is a separate fault scope. + +This supersedes the lack of any load/rollback execution in the initial self-review, +but the quality criterion remains revise: browser focus/accessibility, unsaved work, +real API/data compatibility and independent review are still unverified. VC-06 stays +provisional; no production release or M2 acceptance is inferred. diff --git a/workplans/FEP-WP-0003-contrasting-schools.md b/workplans/FEP-WP-0003-contrasting-schools.md index 4b7d6a7..10ff78a 100644 --- a/workplans/FEP-WP-0003-contrasting-schools.md +++ b/workplans/FEP-WP-0003-contrasting-schools.md @@ -150,3 +150,21 @@ host, failed load, activation rollback with fresh and already-running sessions, shared startup outage. No production workflows/credentials needed. Navigation checks and broader router-lifecycle limits remain live; T03 and human-validation gate unchanged. No model calls, deployment, canonical admission or proven generativity benefit. + +## Native-module publication/activation/rollback experiment — 2026-09-26 + +T01 progress: research/experiments/feature-release executes seven scenarios in +Chromium/Firefox, 14/14 expected observations. Compatible R2 activation changes fresh +sessions, not old document bindings; incompatible R3 evaluates before mount rejection; +404 feature failure preserves neighbor navigation; rollback affects fresh/reloaded +sessions; retiring R1 breaks old lazy sessions while loaded R1 persists; shared startup +503 blocks both actions by fixture policy. No CDN, live deployment, backend writes, +model calls or execution of upstream example workflows. Inputs/packages/browser hashes +and request traces recorded; negative controls distinguished from desirable behavior. + +Independent Feature Boundary and provisional VC-06 updated, seventeen hypotheses. +Next T01 limits: old-session retention/support policy, concurrent activation updates, +safe reload with unfinished work; navigation/history/cache evidence remains pending. +Do not build a production release platform to satisfy this research model. T02/T03, +independent-human gate and M1/M2 completion remain unchanged. +See research/reviews/FeatureReleaseExperiment.md.