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:
tegwick 2026-09-26 23:30:15 +02:00
parent e34ec3fafe
commit f273a190f0
13 changed files with 1385 additions and 0 deletions

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

File diff suppressed because it is too large Load diff

View 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;

View 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');

View file

@ -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.

View 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.

View file

@ -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.

View file

@ -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.