S00-005: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-005. This checkpoint does not mark implementation accepted. Validation: Session ended with status blocked. Blocker: Previous runner session was interrupted before submit/escalate; processes stopped. User now explicitly requests resuming development. Preserve partial artifacts, verify them in a fresh bounded remediation, and return original to a new executor only after independent review. Task: S00-005 Agent: auto-storage-88c3286c6641 Role: storage Action: claim Status: blocked Task title: Выбрать IndexedDB wrapper и migration policy
This commit is contained in:
1 parent
55d9c69a21
commit
5f4d044444
16 files changed
+363
-11
No files matched your search
@@ -0,0 +1,2 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
strict-ssl=true
|
||||
@@ -0,0 +1 @@
|
||||
24.21.0
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "0004-storage-design",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "0004-storage-design",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"fake-indexeddb": "6.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "24.21.0",
|
||||
"npm": "11.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fake-indexeddb": {
|
||||
"version": "6.2.5",
|
||||
"resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz",
|
||||
"integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "0004-storage-design",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": { "node": "24.21.0", "npm": "11.19.0" },
|
||||
"packageManager": "npm@11.19.0",
|
||||
"scripts": {
|
||||
"check:rollback": "node src/rollback.mjs",
|
||||
"check:quota": "node src/quota-blocked.mjs",
|
||||
"check": "npm run check:rollback && npm run check:quota"
|
||||
},
|
||||
"dependencies": {
|
||||
"fake-indexeddb": "6.2.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import "fake-indexeddb/auto";
|
||||
function open(name, version, upgrade) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open(name, version);
|
||||
const ev = { upgraded:false, blocked:false };
|
||||
req.onupgradeneeded = () => { ev.upgraded = true; if (upgrade) upgrade(req.result); };
|
||||
req.onsuccess = () => resolve({db:req.result, ev});
|
||||
req.onerror = () => reject(new Error(`open ${name} v${version}: ${req.error?.name}`));
|
||||
req.onblocked = () => { ev.blocked=true; console.log("BLOCKED", name); };
|
||||
});
|
||||
}
|
||||
// fresh DB clears to v2 cleanly
|
||||
const o = await open("fresh", 2, (db)=>{ const a=db.createObjectStore("accounts",{keyPath:"accountId"}); a.add({accountId:"A1",displayName:"Alice"}); });
|
||||
console.log("fresh v2 ok", o.db.version, [...o.db.objectStoreNames]);
|
||||
o.db.close();
|
||||
// not deleting cleanly - check delete
|
||||
await new Promise((res,rej)=>{ const r=indexedDB.deleteDatabase("fresh"); r.onsuccess=res; r.onerror=()=>rej(r.error); });
|
||||
console.log("after delete", await indexedDB.databases());
|
||||
@@ -0,0 +1,108 @@
|
||||
import "fake-indexeddb/auto";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
// Bounded storage experiment (S00-005, focused check "Rollback experiment").
|
||||
// Uses fake-indexeddb 6.2.5 (in-memory, pure-JS IndexedDB implementation) to exercise
|
||||
// the transaction/schema-upgrade rollback guarantees that a Web storage layer relies
|
||||
// on for STORE-001 (transactional saves or verifiable equivalent recovery).
|
||||
// This is an API-semantics probe, not a browser-durability claim.
|
||||
|
||||
const DB = "rollback-probe";
|
||||
|
||||
function open(version, upgrade) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open(DB, version);
|
||||
const events = { upgraded: false, blocked: false };
|
||||
req.onupgradeneeded = () => {
|
||||
events.upgraded = true;
|
||||
if (upgrade) upgrade(req.result);
|
||||
};
|
||||
req.onsuccess = () => resolve({ db: req.result, events });
|
||||
req.onerror = () => reject(new Error(`open(v${version}): ${req.error?.name}`));
|
||||
req.onblocked = () => { events.blocked = true; };
|
||||
});
|
||||
}
|
||||
|
||||
function getAll(db, store) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tx = db.transaction(store, "readonly");
|
||||
tx.objectStore(store).getAll().onsuccess = (e) => resolve(e.target.result);
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
}
|
||||
|
||||
async function seedV1() {
|
||||
const { db } = await open(1, (db) => {
|
||||
db.createObjectStore("accounts", { keyPath: "accountId" });
|
||||
});
|
||||
db.close();
|
||||
// write a durable per-account record (configuration, non-secret fixture)
|
||||
const w = await open(1);
|
||||
await new Promise((resolve, reject) => {
|
||||
const tx = w.db.transaction("accounts", "readwrite");
|
||||
tx.objectStore("accounts").add({ accountId: "A1", displayName: "Alice" });
|
||||
tx.oncomplete = resolve;
|
||||
tx.onerror = () => reject(tx.error);
|
||||
});
|
||||
w.db.close();
|
||||
}
|
||||
|
||||
// Scenario 1 (rollback): upgrade v1 -> v2 that fails mid-way because a data migration
|
||||
// violates a new unique index inside the versionchange transaction. The whole upgrade
|
||||
// must abort and revert: schema version stays at v1, the new index/store is absent,
|
||||
// and pre-existing v1 data is untouched.
|
||||
async function failedUpgradeRollsBack() {
|
||||
const req = indexedDB.open(DB, 2);
|
||||
let aborted = true;
|
||||
req.onupgradeneeded = () => {
|
||||
const db = req.result;
|
||||
const accounts = db.createObjectStore("accounts", { keyPath: "accountId" });
|
||||
accounts.createIndex("displayName", "displayName", { unique: true });
|
||||
// Both rows share the same unique displayName -> the second write fails and
|
||||
// aborts the versionchange transaction (organic failure, not a manual abort).
|
||||
accounts.add({ accountId: "A1", displayName: "Shared" });
|
||||
accounts.add({ accountId: "A2", displayName: "Shared" });
|
||||
};
|
||||
req.onsuccess = () => { aborted = false; req.result.close(); };
|
||||
req.onerror = () => { /* default abort */ };
|
||||
|
||||
await new Promise((resolve) => { req.onsuccess = resolve; req.onerror = resolve; });
|
||||
|
||||
assert.equal(aborted, true, "failing data migration must abort the upgrade (no fake success)");
|
||||
|
||||
// Schema version must remain at v1 after the abort.
|
||||
const meta = await indexedDB.databases().then((l) => l.find((d) => d.name === DB));
|
||||
assert.equal(meta.version, 1, "failed upgrade must keep persisted schema version at v1");
|
||||
|
||||
const { db } = await open(1);
|
||||
assert.ok(db.objectStoreNames.contains("accounts"), "v1 accounts store survives");
|
||||
const rows = await getAll(db, "accounts");
|
||||
assert.equal(rows.length, 1, "no partial migration rows persisted");
|
||||
assert.equal(rows[0].accountId, "A1", "pre-existing v1 data intact after rollback");
|
||||
db.close();
|
||||
}
|
||||
|
||||
// Scenario 2 (retry after rollback): after a clean rollback a corrected migration can be
|
||||
// retried and commits fully to v2 without a stale schema or lost data.
|
||||
async function retryAfterRollbackSucceeds() {
|
||||
const { db, events } = await open(2, (db) => {
|
||||
const accounts = db.createObjectStore("accounts", { keyPath: "accountId" });
|
||||
accounts.createIndex("displayName", "displayName", { unique: true });
|
||||
accounts.add({ accountId: "A1", displayName: "Alice" });
|
||||
accounts.add({ accountId: "A2", displayName: "Bob" });
|
||||
});
|
||||
assert.equal(events.upgraded, true, "retry upgrade runs");
|
||||
assert.equal(db.version, 2, "retry commits to v2");
|
||||
const rows = await getAll(db, "accounts");
|
||||
assert.equal(rows.length, 2, "both accounts migrated");
|
||||
db.close();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await seedV1();
|
||||
await failedUpgradeRollsBack();
|
||||
await retryAfterRollbackSucceeds();
|
||||
console.log("rollback.mjs PASS: failed v1->v2 upgrade rolled back atomically (schema v1 kept, no partial data); corrected retry committed cleanly to v2.");
|
||||
}
|
||||
|
||||
await main();
|
||||
Reference in new issue
Block a user