Exercise feature activation rollback and old-session artifact retention
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
This commit is contained in:
parent
b8eec87f08
commit
e34ec3fafe
22 changed files with 1661 additions and 1 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
50
research/experiments/feature-release/README.md
Normal file
50
research/experiments/feature-release/README.md
Normal file
|
|
@ -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.
|
||||
4
research/experiments/feature-release/account-a1.js
Normal file
4
research/experiments/feature-release/account-a1.js
Normal file
|
|
@ -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'; }
|
||||
51
research/experiments/feature-release/experiment.mjs
Normal file
51
research/experiments/feature-release/experiment.mjs
Normal file
|
|
@ -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(`<!doctype html><html lang="en"><title>Feature release probe</title><body><nav><button id="reports-nav" disabled>Reports</button><button id="account-nav" disabled>Account</button></nav><p id="status" role="status">starting</p><section id="reports"></section><section id="account"></section><script type="importmap">${JSON.stringify({imports:binding})}</script><script type="module" src="/host.js"></script></body></html>`);
|
||||
}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;
|
||||
29
research/experiments/feature-release/host.js
Normal file
29
research/experiments/feature-release/host.js
Normal file
|
|
@ -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;
|
||||
40
research/experiments/feature-release/package-lock.json
generated
Normal file
40
research/experiments/feature-release/package-lock.json
generated
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
research/experiments/feature-release/package.json
Normal file
7
research/experiments/feature-release/package.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "fep-feature-release",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {"test": "node experiment.mjs"},
|
||||
"dependencies": {"playwright": "1.63.0"}
|
||||
}
|
||||
28
research/experiments/feature-release/provenance.json
Normal file
28
research/experiments/feature-release/provenance.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
4
research/experiments/feature-release/reports-r1.js
Normal file
4
research/experiments/feature-release/reports-r1.js
Normal file
|
|
@ -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'; }
|
||||
4
research/experiments/feature-release/reports-r2.js
Normal file
4
research/experiments/feature-release/reports-r2.js
Normal file
|
|
@ -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'; }
|
||||
4
research/experiments/feature-release/reports-r3.js
Normal file
4
research/experiments/feature-release/reports-r3.js
Normal file
|
|
@ -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'; }
|
||||
1292
research/experiments/feature-release/result.json
Normal file
1292
research/experiments/feature-release/result.json
Normal file
File diff suppressed because it is too large
Load diff
1
research/experiments/feature-release/shared.js
Normal file
1
research/experiments/feature-release/shared.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const sharedVersion = 'S1';
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
58
research/reviews/FeatureReleaseExperiment.md
Normal file
58
research/reviews/FeatureReleaseExperiment.md
Normal file
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue