Test activation conflicts and draft-preserving application reload
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0cffe-206f-7323-89d3-b20ca4cf4bf9
This commit is contained in:
parent
e34ec3fafe
commit
f273a190f0
13 changed files with 1385 additions and 0 deletions
4
TREE.md
4
TREE.md
|
|
@ -370,11 +370,14 @@ 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/conflicts-reload-results.json
|
||||
research/experiments/feature-release/conflicts-reload.mjs
|
||||
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/reload-host.js
|
||||
research/experiments/feature-release/reports-r1.js
|
||||
research/experiments/feature-release/reports-r2.js
|
||||
research/experiments/feature-release/reports-r3.js
|
||||
|
|
@ -390,6 +393,7 @@ research/plan/OpenQuestions.md
|
|||
research/plan/ResearchProgram.md
|
||||
research/plan/ResearchProtocol.md
|
||||
research/plan/ValidationCases.md
|
||||
research/reviews/ActivationConflictsAndReload.md
|
||||
research/reviews/AuthorityPilot.md
|
||||
research/reviews/BoundaryRecoveryExperiment.md
|
||||
research/reviews/CarniatoAdmissionRegression.json
|
||||
|
|
|
|||
|
|
@ -69,3 +69,7 @@ hypotheses and four synthetic walkthroughs. No deployment independence was runti
|
|||
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.
|
||||
|
||||
Latest release follow-up: [activation conflicts and unfinished-work reload](reviews/ActivationConflictsAndReload.md)
|
||||
records 21/21 expected observations, including blind-update and unguarded-reload loss
|
||||
controls. Original experiments retained; production atomicity/unload limits remain open.
|
||||
|
|
|
|||
|
|
@ -59,3 +59,16 @@ binding; deleting the old artifact can break its delayed first load. A post-impo
|
|||
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.
|
||||
|
||||
## Activation conflict and reload policy — 2026-09-26
|
||||
|
||||
[Follow-up tests](../reviews/ActivationConflictsAndReload.md) demonstrate lost updates
|
||||
under blind binding replacement and explicit conflict rejection/refetch for disjoint
|
||||
intent. A same-feature stale rollback is not automatically reapplied. Atomicity is
|
||||
verified only in the single-process fixture, not a distributed deployment service.
|
||||
|
||||
For unfinished work, distinguish defer, preserve-and-reload and authorized explicit
|
||||
discard. Block unsafe progress on storage refusal or unresolved operation outcome;
|
||||
read the latest draft after asynchronous checks. Revalidate stored schema in the new
|
||||
session because activation can change after preflight. The tested per-tab preservation
|
||||
is not native-unload/crash protection or a substitute for retention policy.
|
||||
|
|
|
|||
|
|
@ -42,3 +42,12 @@ partial known uses of unsent-intent separation, not confirmation of the entire
|
|||
base/draft/current merge contract. Full-pattern recurrence remains unknown.
|
||||
A composer may hand submitted content to a separate pending record. Preserve the
|
||||
recoverable intent across that handoff; do not require the input box to stay filled.
|
||||
|
||||
## Executed application-update path — 2026-09-26
|
||||
|
||||
[Reload follow-up](../reviews/ActivationConflictsAndReload.md) recovers text and a base
|
||||
marker across a same-tab reload, supports declining an update, and refuses progress
|
||||
on injected storage failure or schema mismatch. It reads edits made during asynchronous
|
||||
preflight and rechecks schema after navigation. Explicit discard clears stale stored
|
||||
content too. These are synthetic policy mechanisms, not permission to persist restricted
|
||||
data or guarantees for native unload, device failure, multiple tabs or remote outcomes.
|
||||
|
|
|
|||
|
|
@ -159,3 +159,8 @@ 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).
|
||||
|
||||
SRC-0036 follow-up: [activation conflicts and update reload](../reviews/ActivationConflictsAndReload.md)
|
||||
adds an authored conditional-write server and reload policy with sessionStorage.
|
||||
Three HTTP cases plus nine cases per browser, 21/21 expected observations; negative
|
||||
controls included. Same synthetic evidence family, not an independently deployed use.
|
||||
|
|
|
|||
|
|
@ -48,3 +48,30 @@ can already have side effects before that check. No backend writes, authenticati
|
|||
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.
|
||||
|
||||
## Activation conflicts and safe update reload
|
||||
|
||||
[Review](../../reviews/ActivationConflictsAndReload.md) and
|
||||
[receipt](conflicts-reload-results.json): three deterministic stale-writer HTTP cases
|
||||
and nine reload scenarios in each browser, 21/21 expected observations.
|
||||
|
||||
```bash
|
||||
PLAYWRIGHT_BROWSERS_PATH=/tmp/fep-playwright-browsers FEP_CHROMIUM_PATH=/path/to/chrome node conflicts-reload.mjs
|
||||
```
|
||||
|
||||
Uses the same pinned dependencies/browsers as the original release experiment.
|
||||
Default output `/tmp/fep-conflicts-reload.json`; `FEP_RESULT_PATH` overrides it.
|
||||
Incomplete browser progress goes to `/tmp/fep-conflicts-reload-checkpoint.json`.
|
||||
The earlier host, runner and receipts remain unchanged. The new reload-host.js is
|
||||
an authored policy using sessionStorage, explicit user choices and schema checks.
|
||||
|
||||
C01 and R01 deliberately reproduce data loss from unsafe choices. C02 refetches and
|
||||
reapplies a disjoint intent; C03 rejects a conflicting stale rollback. R02–R09 exercise
|
||||
update deferral, preservation, storage refusal, incompatible schema, explicit discard,
|
||||
unknown-outcome gating, edits during preflight and activation after preflight.
|
||||
|
||||
Activation comparison/replacement is atomic only within this single-process model;
|
||||
its revision header and 409 response are not a full HTTP precondition implementation.
|
||||
Storage refusal is injected. Preservation assumes retention is authorized and protects
|
||||
only the tested same-tab reload. Native reload/tab close, crashes, real backend
|
||||
outcome recovery, multiple tabs and automatic schema migration remain out of scope.
|
||||
|
|
|
|||
1099
research/experiments/feature-release/conflicts-reload-results.json
Normal file
1099
research/experiments/feature-release/conflicts-reload-results.json
Normal file
File diff suppressed because it is too large
Load diff
63
research/experiments/feature-release/conflicts-reload.mjs
Normal file
63
research/experiments/feature-release/conflicts-reload.mjs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
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 names=['reload-host.js','reports-r1.js','reports-r2.js'];
|
||||
const assets=new Map(await Promise.all(names.map(async n=>['/'+n,await readFile(dir+n)])));
|
||||
let state,trace,held,holdNext;
|
||||
function reset(){state={revision:1,reports:'/reports-r1.js',account:'A1',draftSchema:1};trace=[];held=null;holdNext=false;}
|
||||
reset();
|
||||
const copy=()=>({...state});
|
||||
const json=(res,code,value)=>{res.writeHead(code,{'Content-Type':'application/json','Cache-Control':'no-store'});res.end(JSON.stringify(value));};
|
||||
const server=createServer(async(req,res)=>{
|
||||
const path=new URL(req.url,'http://local').pathname;res.setHeader('Cache-Control','no-store');
|
||||
if(path==='/activation'&&req.method==='GET'){
|
||||
const snapshot=copy();trace.push({event:'read',snapshot});
|
||||
if(holdNext){holdNext=false;held=()=>json(res,200,snapshot);}else json(res,200,snapshot);
|
||||
}else if((path==='/activation'||path==='/unsafe')&&req.method==='PUT'){
|
||||
let body='';for await(const chunk of req)body+=chunk;const next=JSON.parse(body);
|
||||
// Single-process model: no await between revision comparison and replacement.
|
||||
if(path==='/activation'&&req.headers['if-match']!==String(state.revision)){
|
||||
trace.push({event:'conflict',expected:req.headers['if-match'],actual:state.revision});json(res,409,copy());return;
|
||||
}
|
||||
state={...next,revision:state.revision+1};trace.push({event:'commit',path,state:copy()});json(res,200,copy());
|
||||
}else if(path==='/'){
|
||||
const snapshot=copy();trace.push({event:'document',snapshot});res.setHeader('Content-Type','text/html');res.end(`<!doctype html><html lang="en"><title>Safe reload probe</title><body><p id="version"></p><label>Draft<textarea id="draft"></textarea></label><p id="status" role="status"></p><button id="stay">Keep working</button><button id="preserve">Preserve and reload</button><button id="discard">Discard draft and reload</button><script id="startup" type="application/json">${JSON.stringify(snapshot)}</script><script type="importmap">${JSON.stringify({imports:{reports:snapshot.reports}})}</script><script>window.observed={evaluations:[]};</script><script type="module" src="/reload-host.js"></script></body></html>`);
|
||||
}else if(assets.has(path)){res.setHeader('Content-Type','application/javascript');res.end(assets.get(path));}
|
||||
else{res.writeHead(404);res.end('missing');}
|
||||
});
|
||||
server.listen(0,'127.0.0.1');await once(server,'listening');const url=`http://127.0.0.1:${server.address().port}`;
|
||||
const results=[];
|
||||
async function put(value,rev,unsafe=false){const r=await fetch(url+(unsafe?'/unsafe':'/activation'),{method:'PUT',headers:{'Content-Type':'application/json','If-Match':String(rev)},body:JSON.stringify(value)});return {status:r.status,body:await r.json()};}
|
||||
const get=()=>fetch(url+'/activation').then(r=>r.json());
|
||||
async function control(id,run){reset();const r={id,kind:'HTTP-control-plane',status:'pass'};try{r.observation=await run();}catch(e){r.status='fail';r.error=e.message;}r.trace=[...trace];results.push(r);}
|
||||
try{
|
||||
await control('C01-blind-update-loses-neighbor',async()=>{const [a,b]=await Promise.all([get(),get()]);await put({...a,reports:'/reports-r2.js'},a.revision,true);await put({...b,account:'A2'},b.revision,true);assert.equal(state.reports,'/reports-r1.js');assert.equal(state.account,'A2');return {lostReportsUpdate:true};});
|
||||
await control('C02-stale-disjoint-update-refetch',async()=>{const [a,b]=await Promise.all([get(),get()]);assert.equal((await put({...a,reports:'/reports-r2.js'},a.revision)).status,200);assert.equal((await put({...b,account:'A2'},b.revision)).status,409);const current=await get();assert.equal((await put({...current,account:'A2'},current.revision)).status,200);assert.equal(state.reports,'/reports-r2.js');assert.equal(state.account,'A2');return {bothUpdatesRetained:true,explicitRefetchAndReapply:true};});
|
||||
await control('C03-same-feature-needs-decision',async()=>{const [a,b]=await Promise.all([get(),get()]);await put({...a,reports:'/reports-r2.js'},a.revision);const conflict=await put({...b,reports:'/reports-r1.js'},b.revision);assert.equal(conflict.status,409);assert.equal(state.reports,'/reports-r2.js');return {staleRollbackRejected:true,noAutomaticOverwrite:true};});
|
||||
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 p=await browser.newPage();p.setDefaultTimeout(2500);const errors=[];p.on('pageerror',e=>errors.push(e.message));const r={id,engine,browser:browser.version(),status:'pass'};
|
||||
try{await p.goto(url);await p.waitForFunction(()=>window.ready);await p.locator('#draft').fill('unfinished proposal');await put({...state,reports:'/reports-r2.js'},state.revision);r.observation=await run(p);assert.deepEqual(errors,[]);}catch(e){r.status='fail';r.error=e.message;}
|
||||
r.snapshot=await p.evaluate(()=>({draft:document.getElementById('draft').value,status:document.getElementById('status').textContent,version:document.getElementById('version').textContent,readOnly:document.getElementById('draft').readOnly,saved:sessionStorage.getItem('fep-review-draft')}));r.errors=errors;r.trace=[...trace];results.push(r);if(held){held();held=null;}await p.close();await writeFile('/tmp/fep-conflicts-reload-checkpoint.json',JSON.stringify({status:'incomplete',results},null,2)+'\n');}
|
||||
async function reload(p,button){await Promise.all([p.waitForEvent('load'),p.locator(button).click()]);await p.waitForFunction(()=>window.ready);}
|
||||
const draft= p=>p.locator('#draft').inputValue();
|
||||
await scenario('R01-unguarded-reload-loses-draft',async p=>{await p.reload();await p.waitForFunction(()=>window.ready);assert.equal(await draft(p),'');assert.equal(await p.locator('#version').textContent(),'R2');return {draftLost:true,negativeControl:true};});
|
||||
await scenario('R02-defer-keeps-work',async p=>{await p.locator('#stay').click();assert.equal(await draft(p),'unfinished proposal');assert.equal(await p.locator('#version').textContent(),'R1');return {declinedReload:true};});
|
||||
await scenario('R03-preserve-and-restore',async p=>{await reload(p,'#preserve');assert.equal(await draft(p),'unfinished proposal');assert.equal(await p.locator('#version').textContent(),'R2');const s=await p.evaluate(()=>JSON.parse(sessionStorage.getItem('fep-review-draft')));assert.equal(s.base,'record-v1');return {restored:true,basePreserved:true};});
|
||||
await scenario('R04-storage-failure-blocks',async p=>{await p.evaluate(()=>window.failStorage=true);await p.locator('#preserve').click();await p.locator('#status').filter({hasText:'storage failed'}).waitFor();assert.equal(await draft(p),'unfinished proposal');assert.equal(await p.locator('#version').textContent(),'R1');return {reloadBlocked:true};});
|
||||
await scenario('R05-schema-mismatch-blocks',async p=>{await put({...state,draftSchema:2},state.revision);await p.locator('#preserve').click();await p.locator('#status').filter({hasText:'incompatible draft schema'}).waitFor();assert.equal(await draft(p),'unfinished proposal');assert.equal(await p.locator('#version').textContent(),'R1');return {reloadBlocked:true};});
|
||||
await scenario('R06-explicit-discard',async p=>{await p.evaluate(()=>sessionStorage.setItem('fep-review-draft',JSON.stringify({schema:1,text:'older saved draft',base:'record-v1'})));await reload(p,'#discard');assert.equal(await draft(p),'');assert.equal(await p.evaluate(()=>sessionStorage.getItem('fep-review-draft')),null);assert.equal(await p.locator('#version').textContent(),'R2');return {explicitlyDiscarded:true};});
|
||||
await scenario('R07-unknown-write-blocks',async p=>{await p.evaluate(()=>window.pendingUnknown=true);await p.locator('#preserve').click();await p.locator('#status').filter({hasText:'unknown operation'}).waitFor();assert.equal(await draft(p),'unfinished proposal');assert.equal(await p.locator('#version').textContent(),'R1');return {uncertainOutcomeNotDiscarded:true};});
|
||||
await scenario('R08-edit-during-preflight',async p=>{holdNext=true;await p.locator('#preserve').click();await untilHeld();await p.locator('#draft').fill('newer proposal while checking');const release=held;held=null;const done=p.waitForEvent('load');release();await done;await p.waitForFunction(()=>window.ready);assert.equal(await draft(p),'newer proposal while checking');return {latestEditPreserved:true};});
|
||||
await scenario('R09-activation-changes-after-check',async p=>{holdNext=true;await p.locator('#preserve').click();await untilHeld();await put({...state,draftSchema:2},state.revision);const release=held;held=null;const done=p.waitForEvent('load');release();await done;await p.waitForFunction(()=>window.ready);assert.equal(await draft(p),'unfinished proposal');assert.equal(await p.locator('#draft').getAttribute('readonly'),'');await p.locator('#status').filter({hasText:'Recovery required'}).waitFor();return {startupRevalidation:true,draftRetainedButNotEditable:true};});
|
||||
}finally{await browser.close();}
|
||||
}
|
||||
}finally{if(held)held();await new Promise(resolve=>server.close(resolve));}
|
||||
async function untilHeld(){for(let i=0;i<100;i++){if(held)return;await new Promise(r=>setTimeout(r,10));}throw new Error('preflight barrier not reached');}
|
||||
const files={};for(const f of [...names,'conflicts-reload.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-conflicts-reload.json';const result={date:new Date().toISOString(),node:process.version,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;
|
||||
44
research/experiments/feature-release/reload-host.js
Normal file
44
research/experiments/feature-release/reload-host.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
const input = document.getElementById('draft');
|
||||
const status = document.getElementById('status');
|
||||
const key = 'fep-review-draft';
|
||||
const startup = JSON.parse(document.getElementById('startup').textContent);
|
||||
window.observed = {startup, outcomes: [], evaluations: [], restored: false};
|
||||
window.pendingUnknown = false;
|
||||
window.failStorage = false;
|
||||
let blocked = false;
|
||||
const saved = sessionStorage.getItem(key);
|
||||
if (saved) {
|
||||
const record = JSON.parse(saved);
|
||||
input.value = record.text;
|
||||
if (record.schema !== startup.draftSchema) {
|
||||
blocked = true; input.readOnly = true; status.textContent = 'Recovery required: draft schema mismatch';
|
||||
} else {window.observed.restored = true; status.textContent = 'Draft restored';}
|
||||
}
|
||||
if (!saved) status.textContent = 'Ready';
|
||||
window.feature = await import('reports');
|
||||
document.getElementById('version').textContent = window.feature.version;
|
||||
window.ready = true;
|
||||
window.requestReload = async choice => {
|
||||
if (choice === 'stay') {status.textContent = 'Update deferred'; return false;}
|
||||
if (blocked) {status.textContent = 'Recovery required: draft schema mismatch'; return false;}
|
||||
if (window.pendingUnknown) {status.textContent = 'Resolve unknown operation outcome first'; return false;}
|
||||
const latest = await fetch('/activation').then(r => r.json());
|
||||
// Recheck after asynchronous work; edits made while checking must not be replaced by an old snapshot.
|
||||
if (window.pendingUnknown) {status.textContent = 'Resolve unknown operation outcome first'; return false;}
|
||||
if (choice !== 'discard' && latest.draftSchema !== 1) {
|
||||
status.textContent = 'Update deferred: incompatible draft schema'; return false;
|
||||
}
|
||||
try {
|
||||
if (window.failStorage) throw new Error('injected storage refusal');
|
||||
if (choice === 'discard') sessionStorage.removeItem(key);
|
||||
else {
|
||||
const record = {schema: 1, text: input.value, base: 'record-v1'};
|
||||
sessionStorage.setItem(key, JSON.stringify(record));
|
||||
if (sessionStorage.getItem(key) !== JSON.stringify(record)) throw new Error('write verification failed');
|
||||
}
|
||||
} catch {status.textContent = 'Update deferred: draft storage failed'; return false;}
|
||||
location.reload();return true;
|
||||
};
|
||||
document.getElementById('stay').onclick = () => window.requestReload('stay');
|
||||
document.getElementById('preserve').onclick = () => window.requestReload('preserve');
|
||||
document.getElementById('discard').onclick = () => window.requestReload('discard');
|
||||
|
|
@ -109,3 +109,8 @@ 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.
|
||||
|
||||
Q06 activation/reload follow-up: [conditional update and draft tests](../reviews/ActivationConflictsAndReload.md)
|
||||
show why disjoint-intent retry differs from competing rollback, and why preflight
|
||||
compatibility must be rechecked after reload. Same-tab draft preservation is narrower
|
||||
than durability or remote-outcome recovery. No question or maturity gate is closed.
|
||||
|
|
|
|||
84
research/reviews/ActivationConflictsAndReload.md
Normal file
84
research/reviews/ActivationConflictsAndReload.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Activation conflicts and unfinished-work reload — 2026-09-26
|
||||
|
||||
Status: executed synthetic HTTP/browser experiment, editorial analysis by Codex.
|
||||
WP-0003 T01, continuation of [feature release lifecycle](FeatureReleaseExperiment.md).
|
||||
[Runner](../experiments/feature-release/conflicts-reload.mjs),
|
||||
[reload host](../experiments/feature-release/reload-host.js) and
|
||||
[receipt](../experiments/feature-release/conflicts-reload-results.json) record 21/21
|
||||
expected observations: three HTTP update cases plus nine reload cases in Chromium
|
||||
151.0.7922.34 and Firefox 155.0. No live deployment, backend domain write or model call.
|
||||
|
||||
## Activation-update cases
|
||||
|
||||
Two clients read the same revision before either submits. The runner then forces a
|
||||
known write ordering over real loopback HTTP; this is a reproducible stale-writer
|
||||
interleaving, not an uncontrolled concurrency load test.
|
||||
|
||||
| Case | Expected and observed |
|
||||
| --- | --- |
|
||||
| C01 Blind whole-map replacement | Reports update succeeds, then a stale Account replacement silently restores the old Reports binding: negative lost-update control |
|
||||
| C02 Conditional replacement, disjoint intent | Stale Account replacement rejected; explicit refetch and reapplication of only Account intent preserves both updates |
|
||||
| C03 Competing intent for one feature | Stale Reports rollback rejected; active newer Reports binding retained; no automatic overwrite |
|
||||
|
||||
The fixture uses an integer revision carried in If-Match and a synthetic 409 conflict
|
||||
response, not a full HTTP ETag/precondition implementation. Comparison and replacement
|
||||
have no asynchronous gap in one server process. A distributed store still needs its
|
||||
own atomic conditional-write primitive. Refetch alone does not justify retry: C02
|
||||
reapplies a disjoint intent; C03 deliberately leaves conflicting intent unresolved.
|
||||
These tests do not execute import-map-deployer or verify its implementation.
|
||||
|
||||
## Reload cases
|
||||
|
||||
The user-controlled update path offers keep-working, preserve-and-reload, and explicit
|
||||
discard-and-reload. Persistence uses sessionStorage with synthetic non-sensitive text,
|
||||
a draft schema and a base-record marker. No server save or remote acceptance is inferred.
|
||||
|
||||
| Case | Expected and observed in both browsers |
|
||||
| --- | --- |
|
||||
| R01 Unguarded reload | Unsaved input disappears and R2 loads: negative control |
|
||||
| R02 Defer update | Draft stays editable in the old R1 session |
|
||||
| R03 Preserve and reload | R2 loads with exact draft text and original base marker recovered |
|
||||
| R04 Inject storage refusal | Reload blocked; original draft and R1 remain |
|
||||
| R05 Incompatible target draft schema | Update deferred before reload; original input retained |
|
||||
| R06 Explicit discard | Both current input and an older stored draft cleared; R2 starts empty |
|
||||
| R07 Unknown write outcome | Reload blocked while the synthetic pending-outcome flag is set |
|
||||
| R08 Edit during asynchronous preflight | Latest edited text, not an earlier snapshot, persists and returns |
|
||||
| R09 Activation changes after preflight snapshot | New document detects schema mismatch again; stored text retained in read-only recovery state |
|
||||
|
||||
R08/R09 hold a server response at an explicit barrier, make the intervening edit or
|
||||
activation change, then release it. No arbitrary settling delay or automatic retry
|
||||
is used. The poll waits for the barrier itself. R09 demonstrates why a successful
|
||||
preflight cannot promise that the next document has the same compatibility contract.
|
||||
The fixture deliberately changes its declared draft schema; it does not implement
|
||||
an actual schema migration or prove compatibility of a real feature's draft format.
|
||||
|
||||
## Limits and interpretation
|
||||
|
||||
“Pass” includes intentionally observed failures in C01 and R01. The preservation
|
||||
path is an authored policy, not an automatic browser or framework guarantee. Explicit
|
||||
recovery retains bytes; it does not resolve a stale base, merge edits or establish
|
||||
permission to retain/export them. The unknown-write flag tests a UI gate only; no
|
||||
real operation lookup or idempotent replay occurs. Discard does not reverse a remote
|
||||
operation and is blocked by that same unknown-outcome gate.
|
||||
|
||||
sessionStorage covers this tab's same-origin reload, not device loss, guaranteed
|
||||
browser restart or independent backup. The injected write refusal tests the failure
|
||||
branch, not browser quota exhaustion. Other tabs, login/access revocation, storage
|
||||
schema migration, interruptions during navigation, native reload/tab close and browser
|
||||
crashes are not covered. An application-level update button is not a universal unload
|
||||
interceptor. Persisted drafts remain until explicit discard in this fixture; a real
|
||||
product needs an authorized retention/cleanup policy and safe handling of stale drafts.
|
||||
|
||||
## Candidate disposition
|
||||
|
||||
[Independent Feature Boundary](../candidates/IndependentFeatureBoundary.md) gains
|
||||
conditional activation and session-transition obligations. [Local Draft State](../candidates/LocalDraftState.md)
|
||||
gains a partial implementation of explicit preservation/refusal, with post-navigation
|
||||
schema revalidation. [VC-06](../walkthroughs/VC06-MicrofrontendEstate.md) remains
|
||||
provisional: this supports its release/reload trace, not production rollback or a
|
||||
completed accessibility/security review. Corpus remains seventeen hypotheses.
|
||||
|
||||
Next WP-0003 T01 evidence: navigation/history/full-versus-fragment caching, which is
|
||||
still pending from VC-03. Retain distributed activation atomicity, authorized draft
|
||||
retention and native-unload/crash behavior as limits requiring evidence before a
|
||||
production recommendation; do not grow this fixture into a release platform.
|
||||
|
|
@ -104,3 +104,14 @@ This supersedes the lack of any load/rollback execution in the initial self-revi
|
|||
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.
|
||||
|
||||
## Activation/update-reload follow-up — 2026-09-26
|
||||
|
||||
[21 expected observations](../reviews/ActivationConflictsAndReload.md) now cover
|
||||
stale binding writers and the application-controlled reload path with unfinished work.
|
||||
Conditional replacement rejects stale maps; disjoint intent is explicitly reapplied,
|
||||
while conflicting rollback awaits a decision. Preserve the latest authorized draft
|
||||
before reload and revalidate its schema on the next document; a preflight check can
|
||||
be invalidated by a later activation. Storage failure and unknown write outcome block
|
||||
the fixture's reload. This does not establish distributed-map atomicity, native-unload
|
||||
protection, backend acceptance, authorized retention or complete VC-06 validation.
|
||||
|
|
|
|||
|
|
@ -168,3 +168,20 @@ safe reload with unfinished work; navigation/history/cache evidence remains pend
|
|||
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.
|
||||
|
||||
## Requested activation-update conflicts and safe reload — 2026-09-26
|
||||
|
||||
T01 progress: conflicts-reload.mjs adds three HTTP stale-writer cases and nine reload
|
||||
cases in Chromium/Firefox, 21/21 expected observations. Blind overwrite and unguarded
|
||||
reload reproduce loss as negative controls. Conditional replacement rejects stale maps;
|
||||
disjoint intent is explicitly reapplied, same-feature rollback stays unresolved.
|
||||
Draft deferral, preserve/restore, injected storage failure, schema mismatch, explicit
|
||||
discard, unknown-write gating, edit-during-preflight and post-check activation covered.
|
||||
New-page schema revalidation retains incompatible text read-only. See
|
||||
research/reviews/ActivationConflictsAndReload.md. Original release artifacts unchanged.
|
||||
|
||||
Requested bounded tests complete; no distributed-store atomicity, native-unload/crash,
|
||||
real backend outcome or retention authorization claim. These remain T01 limits for
|
||||
any production recommendation. Next breadth task: pending VC-03 navigation/history/
|
||||
full-versus-fragment cache evidence. Seventeen hypotheses; T02/T03 and M1/M2 gates
|
||||
unchanged. No model inference or production actions.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue