@lmzhen/dsh-evolution-state-json 0.3.73 → 0.3.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,3 +26,5 @@ Independent of request-prefix construction. This package does not alter the asse
26
26
  - V25-11 (v25): the review-state table is likewise BOUNDED — `REVIEW_STATE_SESSION_CAP` (500, seam constant) rows keyed by session; on every save the least-recently-active sessions (provider-stamped `updatedAt`, stored on disk only) are pruned. The stamp is stripped on read, so the consumer-facing record shape is unchanged.
27
27
  - JSON provider serializes writers inside one process AND through the IO backend's cross-process transact lock (an internal transact wrapper — not public API, audit v10 S-03 — wraps every mutation, 0.3.20/0.3.27) — this provider is NOT limited to single-process safety. The caveat below is about the DSH storage-domain providers (`storage-json` documents no cross-process write locking) when the DOMAIN provider is used instead; multi-process deployments should route the evolution domain to a backend with cross-process semantics such as SQLite or remote storage.
28
28
 
29
+ **Runtime invariant:** No companion is published. The platform auto-assembles nothing and the family mounts no `<pkg>/invariant` cordis row, so a companion here would never execute (v37 S2.1 / I-3).
30
+
package/lib/index.js CHANGED
@@ -268,11 +268,9 @@ function apply(ctx, rawConfig = {}) {
268
268
  for (const entry of rawBak) if (entry && typeof entry.id === "string") ids.add(entry.id);
269
269
  }
270
270
  } catch (bakError) {
271
- if (bakError?.name === QUARANTINE_ERROR_NAME) throw bakError;
272
271
  ctx.logger.warn(`evolution-state-json: pending archive .bak unreadable (${bakError instanceof Error ? bakError.message : String(bakError)}) — archived ids that live only in the .bak do not exclude their legacy twins until the sidecar is readable again`);
273
272
  }
274
273
  } catch (error) {
275
- if (error?.name === QUARANTINE_ERROR_NAME) throw error;
276
274
  ctx.logger.warn(`evolution-state-json: pending archive sidecars unreadable (${error instanceof Error ? error.message : String(error)}) — the V5-02 legacy ghost-twin filter runs WITHOUT the archived-id exclusion until the archive is readable again`);
277
275
  }
278
276
  return ids;
@@ -287,14 +285,38 @@ function apply(ctx, rawConfig = {}) {
287
285
  }
288
286
  return retired;
289
287
  }
288
+ let pendingKeyWarned = false;
289
+ function keyPendingById(map) {
290
+ const keyed = {};
291
+ if (map === null) return keyed;
292
+ let drifted = 0;
293
+ let dropped = 0;
294
+ for (const [key, record] of Object.entries(map)) {
295
+ const id = typeof record.id === "string" ? record.id : key;
296
+ if (key !== id) {
297
+ if (keyed[id] !== void 0) {
298
+ dropped += 1;
299
+ continue;
300
+ }
301
+ drifted += 1;
302
+ }
303
+ keyed[id] = record;
304
+ }
305
+ if ((drifted > 0 || dropped > 0) && !pendingKeyWarned) {
306
+ pendingKeyWarned = true;
307
+ ctx.logger.warn(`evolution-state-json: ${drifted} pending table entr(ies) were keyed by something other than record.id (${dropped} same-id residue row(s) dropped) — re-keyed by id, so a drifted row is reachable by claim/resolve again`);
308
+ }
309
+ return keyed;
310
+ }
290
311
  async function retireLegacyOnce(legacy) {
291
312
  if (legacyMigrated) return {};
313
+ const keyedLegacy = keyPendingById(legacy);
292
314
  try {
293
315
  const retired = {};
294
316
  await jsonTransact(ctx, io, root, PENDING_STATE_FILE, async (fresh) => {
295
- const merged = { ...fresh ?? {} };
317
+ const merged = keyPendingById(fresh);
296
318
  const archivedIds = await readArchivedIds();
297
- for (const [id, record] of Object.entries(legacy)) {
319
+ for (const [id, record] of Object.entries(keyedLegacy)) {
298
320
  if (id in merged) continue;
299
321
  if (archivedIds.has(id)) continue;
300
322
  if (gateDroppedCurrent.has(id)) continue;
@@ -314,11 +336,12 @@ function apply(ctx, rawConfig = {}) {
314
336
  }
315
337
  async function loadPendingMap() {
316
338
  const [current, legacy] = await Promise.all([readJson(PENDING_STATE_FILE), readJson(PENDING_LEGACY_FILE)]);
339
+ const keyed = keyPendingById(current);
317
340
  if (legacy !== null) return {
318
341
  ...await retireLegacyOnce(legacy),
319
- ...current ?? {}
342
+ ...keyed
320
343
  };
321
- return { ...current ?? {} };
344
+ return { ...keyed };
322
345
  }
323
346
  /** V6-01 (0.3.34): single-sourced legacy merge for the four mutation paths —
324
347
  * the SAME exclusion as the retirement read path, so a mutation that runs
@@ -326,10 +349,12 @@ function apply(ctx, rawConfig = {}) {
326
349
  * (the archive id set is read fresh on every merge — V11-B2; current-wins
327
350
  * covers stale misses). */
328
351
  async function mergedWithFilteredLegacy(legacy, current) {
329
- if (legacy === null) return current;
352
+ const keyed = keyPendingById(current);
353
+ if (legacy === null) return keyed;
354
+ const archivedIds = await readArchivedIds();
330
355
  return {
331
- ...filterLegacy(legacy, current, await readArchivedIds()),
332
- ...current
356
+ ...filterLegacy(keyPendingById(legacy), keyed, archivedIds),
357
+ ...keyed
333
358
  };
334
359
  }
335
360
  /** 0.3.22 (F-336): when the live pending map holds more than
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-state-json",
3
3
  "description": "JSON-file evolution state provider over the IO seam (community build)",
4
- "version": "0.3.73",
4
+ "version": "0.3.76",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -18,10 +18,6 @@
18
18
  "types": "./lib/types/index.d.ts",
19
19
  "default": "./lib/index.js"
20
20
  },
21
- "./invariant": {
22
- "types": "./lib/types/invariant.d.ts",
23
- "default": "./lib/invariant.js"
24
- },
25
21
  "./package.json": "./package.json"
26
22
  },
27
23
  "files": [
@@ -31,18 +27,16 @@
31
27
  "license": "MIT",
32
28
  "dependencies": {
33
29
  "@deepseek-ai/schemastery": "^3.18.1",
34
- "@lmzhen/dsh-evolution-core": "^0.3.73"
30
+ "@lmzhen/dsh-evolution-core": "^0.3.76"
35
31
  },
36
32
  "peerDependencies": {
37
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
38
33
  "@deepseek-ai/cordis": "^4.0.1",
39
- "@lmzhen/dsh-evolution-io": "^0.3.73",
40
- "@lmzhen/dsh-evolution-state-storage": "^0.3.73"
34
+ "@lmzhen/dsh-evolution-io": "^0.3.76",
35
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.76"
41
36
  },
42
37
  "devDependencies": {
43
- "@deepseek-ai/dsh-invariants": "^0.1.5-rc.2",
44
- "@lmzhen/dsh-evolution-io": "^0.3.73",
45
- "@lmzhen/dsh-evolution-state-storage": "^0.3.73",
46
- "@lmzhen/dsh-evolution-io-node": "^0.3.73"
38
+ "@lmzhen/dsh-evolution-io": "^0.3.76",
39
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.76",
40
+ "@lmzhen/dsh-evolution-io-node": "^0.3.76"
47
41
  }
48
42
  }
package/lib/invariant.js DELETED
@@ -1,8 +0,0 @@
1
- //#region lib/types/invariant.js
2
- const PACKAGE_NAME = "@lmzhen/dsh-evolution-state-json";
3
- const name = "evolution-state-json-invariant";
4
- const inject = ["invariants"];
5
- const install = () => {};
6
- const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
7
- //#endregion
8
- export { apply, inject, name };
@@ -1,5 +0,0 @@
1
- import type { Context } from '@deepseek-ai/cordis';
2
- export declare const name = "evolution-state-json-invariant";
3
- export declare const inject: string[];
4
- export declare const apply: (ctx: Context) => Promise<() => void>;
5
- //# sourceMappingURL=invariant.d.ts.map