@lmzhen/dsh-evolution-state-json 0.4.0 → 0.5.0

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.
Files changed (2) hide show
  1. package/lib/index.js +35 -5
  2. package/package.json +7 -7
package/lib/index.js CHANGED
@@ -274,6 +274,36 @@ function apply(ctx, rawConfig = {}) {
274
274
  if (file === PENDING_STATE_FILE) gateDroppedCurrent.clear();
275
275
  return parsed;
276
276
  }
277
+ /** A5 (audit P2-9): the LEGACY pending sidecar is read LENIENTLY — the
278
+ * deliberate opposite of the live state file's fail-loud posture. This
279
+ * file's whole purpose is to be retired, but the retirement merge reads
280
+ * it first, so one truncated pre-migration `pending.json` used to make
281
+ * EVERY pending read and mutation throw `EvolutionStateCorruptFile`
282
+ * forever (a permanent operator outage no warning can repair). A
283
+ * QUARANTINE-CLASS failure (unreadable/corrupt content) is renamed aside
284
+ * so every later load sees a clean absence; any OTHER failure (EACCES,
285
+ * EIO — a permission-locked but possibly VALID file) is left in place and
286
+ * only skips this read, so a transient hold never exiles real records.
287
+ * Either way the view continues current-only with one warn per process.
288
+ * Note the moved-aside copy is NOT auto-swept (the stale-copy sweep only
289
+ * inspects entries prefixed like the file being written, and nothing
290
+ * writes a `pending.json` prefix anymore) — it persists until an operator
291
+ * deletes it after rescue, at most one per corruption episode. */
292
+ let legacyQuarantineWarned = false;
293
+ async function readLegacyPending() {
294
+ try {
295
+ return await readJson(PENDING_LEGACY_FILE);
296
+ } catch (error) {
297
+ if (error instanceof Error && error.name === QUARANTINE_ERROR_NAME) try {
298
+ await io().rename(pathOf(PENDING_LEGACY_FILE), `${pathOf(PENDING_LEGACY_FILE)}.corrupt.${Date.now()}`);
299
+ } catch {}
300
+ if (!legacyQuarantineWarned) {
301
+ legacyQuarantineWarned = true;
302
+ ctx.logger.warn(`evolution-state-json: legacy ${PENDING_LEGACY_FILE} is unreadable and was set aside (${error instanceof Error ? error.message : String(error)}); continuing WITHOUT legacy records`);
303
+ }
304
+ return null;
305
+ }
306
+ }
277
307
  const mutate = makeSerialQueue();
278
308
  let legacyMigrated = false;
279
309
  async function readArchivedIds() {
@@ -356,7 +386,7 @@ function apply(ctx, rawConfig = {}) {
356
386
  }
357
387
  }
358
388
  async function loadPendingMap() {
359
- const [current, legacy] = await Promise.all([readJson(PENDING_STATE_FILE), readJson(PENDING_LEGACY_FILE)]);
389
+ const [current, legacy] = await Promise.all([readJson(PENDING_STATE_FILE), readLegacyPending()]);
360
390
  const keyed = keyPendingById(current);
361
391
  if (legacy !== null) return {
362
392
  ...await retireLegacyOnce(legacy),
@@ -564,7 +594,7 @@ function apply(ctx, rawConfig = {}) {
564
594
  const guard = transactTaskGuard(`pending record "${record.id}" (${PENDING_STATE_FILE})`);
565
595
  await pendingTransact(guard.wrap(async (current) => {
566
596
  const map = {
567
- ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}),
597
+ ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readLegacyPending(), current ?? {}),
568
598
  [record.id]: record
569
599
  };
570
600
  warnPendingGrowth(map, "save");
@@ -577,7 +607,7 @@ function apply(ctx, rawConfig = {}) {
577
607
  return await mutate(async () => {
578
608
  const slot = { claimed: null };
579
609
  await pendingTransact(async (current) => {
580
- const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}) };
610
+ const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readLegacyPending(), current ?? {}) };
581
611
  const record = map[id] ?? null;
582
612
  if (record === null || !canClaimPending(record.status)) return map;
583
613
  const now = Date.now();
@@ -597,7 +627,7 @@ function apply(ctx, rawConfig = {}) {
597
627
  async releasePendingClaim(id, claimId) {
598
628
  await mutate(async () => {
599
629
  await pendingTransact(async (current) => {
600
- const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}) };
630
+ const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readLegacyPending(), current ?? {}) };
601
631
  const record = map[id];
602
632
  if (!record || record.claimedBy !== claimId) return map;
603
633
  if (record.status !== "pending" && record.status !== "executing") return map;
@@ -615,7 +645,7 @@ function apply(ctx, rawConfig = {}) {
615
645
  applied: false
616
646
  };
617
647
  await pendingTransact(async (current) => {
618
- const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}) };
648
+ const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readLegacyPending(), current ?? {}) };
619
649
  const record = map[id] ?? null;
620
650
  if (record === null || !canResolvePending(record.status)) {
621
651
  result = {
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.4.0",
4
+ "version": "0.5.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,16 +27,16 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.1",
30
- "@lmzhen/dsh-evolution-core": "^0.4.0"
30
+ "@lmzhen/dsh-evolution-core": "^0.5.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/cordis": "^4.0.1",
34
- "@lmzhen/dsh-evolution-io": "^0.4.0",
35
- "@lmzhen/dsh-evolution-state-storage": "^0.4.0"
34
+ "@lmzhen/dsh-evolution-io": "^0.5.0",
35
+ "@lmzhen/dsh-evolution-state-storage": "^0.5.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@lmzhen/dsh-evolution-io": "^0.4.0",
39
- "@lmzhen/dsh-evolution-state-storage": "^0.4.0",
40
- "@lmzhen/dsh-evolution-io-node": "^0.4.0"
38
+ "@lmzhen/dsh-evolution-io": "^0.5.0",
39
+ "@lmzhen/dsh-evolution-state-storage": "^0.5.0",
40
+ "@lmzhen/dsh-evolution-io-node": "^0.5.0"
41
41
  }
42
42
  }