@lmzhen/dsh-evolution-state-json 0.3.81 → 0.3.83
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 +2 -2
- package/lib/index.js +73 -20
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @lmzhen/dsh-evolution-state-json
|
|
2
2
|
|
|
3
3
|
JSON-file evolution state provider over the IO seam
|
|
4
4
|
|
|
@@ -9,7 +9,7 @@ JSON-file evolution state provider over the IO seam
|
|
|
9
9
|
|
|
10
10
|
#### What the model sees
|
|
11
11
|
|
|
12
|
-
`@
|
|
12
|
+
`@lmzhen/dsh-evolution-state-json` registers no direct prompt or tool schema itself. Model-visible effects are owned by the packages that consume this service.
|
|
13
13
|
|
|
14
14
|
#### Token effect
|
|
15
15
|
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { evolutionHome, makeSerialQueue, transactIo } from "@lmzhen/dsh-evolution-core";
|
|
3
|
-
import { CURATOR_STATE_FILE, CURATOR_STATE_KEY, CURATOR_STATE_TABLE, PENDING_ARCHIVE_BAK_FILE, PENDING_ARCHIVE_FILE, PENDING_LEGACY_FILE, PENDING_RESOLVED_CAP, PENDING_STATE_FILE, PENDING_TABLE, PROVIDER_JSON, REVIEW_STATE_FILE, REVIEW_STATE_SESSION_CAP, REVIEW_STATE_TABLE, assertCloneable, canClaimPending, canResolvePending, recordIssue, releasedStatus, selectPendingOverflow, selectSessionOverflow } from "@lmzhen/dsh-evolution-state-storage";
|
|
2
|
+
import { evolutionHome, makeSerialQueue, transactIo, transactTaskGuard } from "@lmzhen/dsh-evolution-core";
|
|
3
|
+
import { CURATOR_STATE_FILE, CURATOR_STATE_KEY, CURATOR_STATE_TABLE, PENDING_ARCHIVE_BAK_FILE, PENDING_ARCHIVE_FILE, PENDING_LEGACY_FILE, PENDING_RESOLVED_CAP, PENDING_STATE_FILE, PENDING_TABLE, PROVIDER_JSON, REVIEW_STATE_FILE, REVIEW_STATE_SESSION_CAP, REVIEW_STATE_TABLE, assertCloneable, canClaimPending, canResolvePending, cloneRecord, recordIssue, releasedStatus, selectPendingOverflow, selectSessionOverflow } from "@lmzhen/dsh-evolution-state-storage";
|
|
4
4
|
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
5
5
|
//#region lib/types/index.js
|
|
6
6
|
/**
|
|
@@ -24,6 +24,12 @@ const PENDING_RESOLVED_CAP$1 = PENDING_RESOLVED_CAP;
|
|
|
24
24
|
* `.bak` sidecar, so the file — and the full-array rewrite on every append —
|
|
25
25
|
* never grows without bound. */
|
|
26
26
|
const ARCHIVE_RESOLVED_CAP = 5e3;
|
|
27
|
+
/** S2-12 (FLOW5-3): the staged table's byte budget for the args it carries.
|
|
28
|
+
* Lives here rather than on the seam because it bounds THIS medium: json
|
|
29
|
+
* rewrites the whole state file per pending mutation, so bytes — not just row
|
|
30
|
+
* count — decide the cost. The domain provider writes rows individually and
|
|
31
|
+
* needs no equivalent (declared asymmetry, not an oversight). */
|
|
32
|
+
const PENDING_ARGS_BYTES_WARN = 1e6;
|
|
27
33
|
/** 0.3.27 (V4-01): an archive entry's dedupe identity. The same audit record
|
|
28
34
|
* (id + status + resolvedAt) must never appear twice; the read-only legacy
|
|
29
35
|
* `pending.json` merge used to re-introduce an evicted record on the next
|
|
@@ -223,7 +229,22 @@ function apply(ctx, rawConfig = {}) {
|
|
|
223
229
|
gateDroppedCurrent.clear();
|
|
224
230
|
for (const id of ids) gateDroppedCurrent.add(id);
|
|
225
231
|
};
|
|
232
|
+
/**
|
|
233
|
+
* v43 audit (S1-9, F-2's deeper half): every pending-state mutation goes
|
|
234
|
+
* through jsonTransact, and only the RETIREMENT path used to hand the
|
|
235
|
+
* gate-drop reporter over. The other paths read `current` with their own
|
|
236
|
+
* field-gate pass, so a record dropped there stayed invisible to
|
|
237
|
+
* `gateDroppedCurrent` — and `mergedWithFilteredLegacy` then resurrected the
|
|
238
|
+
* legacy pending twin of a record whose fields went bad (an approve replays an
|
|
239
|
+
* already-landed write). Binding the reporter to the FILE here means no call
|
|
240
|
+
* site can forget it; jsonTransact reports the drops before the task runs, so
|
|
241
|
+
* the merged basis inside the task sees them.
|
|
242
|
+
* @param task - the read-modify-write body, exactly as jsonTransact takes it.
|
|
243
|
+
* @returns jsonTransact's own result.
|
|
244
|
+
*/
|
|
245
|
+
const pendingTransact = (task) => jsonTransact(ctx, io, root, PENDING_STATE_FILE, task, { onGateDrop: noteGateDrop });
|
|
226
246
|
let warnedPendingCapacity = false;
|
|
247
|
+
let warnedPendingArgsBytes = false;
|
|
227
248
|
async function readJson(file) {
|
|
228
249
|
const raw = await io().readText(pathOf(file));
|
|
229
250
|
if (raw === null) return null;
|
|
@@ -444,6 +465,32 @@ function apply(ctx, rawConfig = {}) {
|
|
|
444
465
|
return false;
|
|
445
466
|
}
|
|
446
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* S2-12 (FLOW5-3/5-4): the staged table's two growth signals, evaluated on
|
|
470
|
+
* EVERY write path that can move it. Before this, only `savePending` warned
|
|
471
|
+
* about the record count, so a deployment whose approvals arrive through
|
|
472
|
+
* claim/resolve grew silently; and nothing measured the staged ARGS at all.
|
|
473
|
+
* Both warns flip once and re-arm when the table comes back under the bound.
|
|
474
|
+
*
|
|
475
|
+
* @param map - the post-write pending map.
|
|
476
|
+
* @param where - the path that wrote it, named in the warning.
|
|
477
|
+
*/
|
|
478
|
+
const warnPendingGrowth = (map, where) => {
|
|
479
|
+
const live = Object.values(map).filter((entry) => entry.status === "pending" || entry.status === "executing");
|
|
480
|
+
if (live.length > PENDING_RESOLVED_CAP$1) {
|
|
481
|
+
if (!warnedPendingCapacity) {
|
|
482
|
+
warnedPendingCapacity = true;
|
|
483
|
+
ctx.logger.warn(`evolution-state-json: ${live.length} pending/executing staged records exceed the resolved cap (${PENDING_RESOLVED_CAP$1}) after ${where} — they are never trimmed by design; resolve or reject them (/evolution pending) or the file keeps growing`);
|
|
484
|
+
}
|
|
485
|
+
} else warnedPendingCapacity = false;
|
|
486
|
+
const argsBytes = live.reduce((total, entry) => total + Buffer.byteLength(JSON.stringify(entry.args ?? null), "utf8"), 0);
|
|
487
|
+
if (argsBytes > PENDING_ARGS_BYTES_WARN) {
|
|
488
|
+
if (!warnedPendingArgsBytes) {
|
|
489
|
+
warnedPendingArgsBytes = true;
|
|
490
|
+
ctx.logger.warn(`evolution-state-json: ${argsBytes} bytes of staged args across ${live.length} pending/executing record(s) exceed ${PENDING_ARGS_BYTES_WARN} after ${where} — every pending mutation rewrites this file in full; resolve or drop the oversized records (/evolution pending)`);
|
|
491
|
+
}
|
|
492
|
+
} else warnedPendingArgsBytes = false;
|
|
493
|
+
};
|
|
447
494
|
const provider = {
|
|
448
495
|
name: PROVIDER_JSON,
|
|
449
496
|
async loadReviewState(sessionId) {
|
|
@@ -456,7 +503,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
456
503
|
},
|
|
457
504
|
async saveReviewState(sessionId, record) {
|
|
458
505
|
await mutate(async () => {
|
|
459
|
-
|
|
506
|
+
const guard = transactTaskGuard(`review state for session "${sessionId}" (${REVIEW_STATE_FILE})`);
|
|
507
|
+
await jsonTransact(ctx, io, root, REVIEW_STATE_FILE, guard.wrap((current) => {
|
|
460
508
|
const stamped = { ...current ?? {} };
|
|
461
509
|
stamped[sessionId] = {
|
|
462
510
|
...record,
|
|
@@ -471,7 +519,8 @@ function apply(ctx, rawConfig = {}) {
|
|
|
471
519
|
const pruned = {};
|
|
472
520
|
for (const [id, row] of Object.entries(stamped)) if (!evict.has(id)) pruned[id] = row;
|
|
473
521
|
return pruned;
|
|
474
|
-
});
|
|
522
|
+
}));
|
|
523
|
+
guard.assertInvoked();
|
|
475
524
|
});
|
|
476
525
|
},
|
|
477
526
|
async loadCuratorState() {
|
|
@@ -481,22 +530,27 @@ function apply(ctx, rawConfig = {}) {
|
|
|
481
530
|
},
|
|
482
531
|
async saveCuratorState(record) {
|
|
483
532
|
await mutate(async () => {
|
|
484
|
-
|
|
533
|
+
const guard = transactTaskGuard(`curator state (${CURATOR_STATE_FILE})`);
|
|
534
|
+
await jsonTransact(ctx, io, root, CURATOR_STATE_FILE, guard.wrap((current) => ({
|
|
485
535
|
...current ?? {},
|
|
486
536
|
[CURATOR_STATE_KEY]: record
|
|
487
|
-
}));
|
|
537
|
+
})));
|
|
538
|
+
guard.assertInvoked();
|
|
488
539
|
});
|
|
489
540
|
},
|
|
490
541
|
async transactCuratorState(task) {
|
|
491
542
|
await mutate(async () => {
|
|
492
|
-
|
|
493
|
-
|
|
543
|
+
const guard = transactTaskGuard(`curator state transact (${CURATOR_STATE_FILE})`);
|
|
544
|
+
await jsonTransact(ctx, io, root, CURATOR_STATE_FILE, guard.wrap((current) => {
|
|
545
|
+
const stored = current?.[CURATOR_STATE_KEY] ?? null;
|
|
546
|
+
const next = task(stored === null ? null : cloneRecord(stored));
|
|
494
547
|
if (next === null) return current;
|
|
495
548
|
return {
|
|
496
549
|
...current ?? {},
|
|
497
550
|
[CURATOR_STATE_KEY]: next
|
|
498
551
|
};
|
|
499
|
-
});
|
|
552
|
+
}));
|
|
553
|
+
guard.assertInvoked();
|
|
500
554
|
});
|
|
501
555
|
},
|
|
502
556
|
async listPending(status = "pending") {
|
|
@@ -507,25 +561,22 @@ function apply(ctx, rawConfig = {}) {
|
|
|
507
561
|
},
|
|
508
562
|
async savePending(record) {
|
|
509
563
|
await mutate(async () => {
|
|
510
|
-
|
|
564
|
+
const guard = transactTaskGuard(`pending record "${record.id}" (${PENDING_STATE_FILE})`);
|
|
565
|
+
await pendingTransact(guard.wrap(async (current) => {
|
|
511
566
|
const map = {
|
|
512
567
|
...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}),
|
|
513
568
|
[record.id]: record
|
|
514
569
|
};
|
|
515
|
-
|
|
516
|
-
if (liveCount > PENDING_RESOLVED_CAP$1 && !warnedPendingCapacity) {
|
|
517
|
-
warnedPendingCapacity = true;
|
|
518
|
-
ctx.logger.warn(`evolution-state-json: ${liveCount} pending/executing staged records exceed the resolved cap (${PENDING_RESOLVED_CAP$1}) — they are never trimmed by design; resolve or reject them (/evolution pending) or the file keeps growing`);
|
|
519
|
-
}
|
|
520
|
-
if (liveCount <= PENDING_RESOLVED_CAP$1) warnedPendingCapacity = false;
|
|
570
|
+
warnPendingGrowth(map, "save");
|
|
521
571
|
return map;
|
|
522
|
-
});
|
|
572
|
+
}));
|
|
573
|
+
guard.assertInvoked();
|
|
523
574
|
});
|
|
524
575
|
},
|
|
525
576
|
async claimPending(id, claimId) {
|
|
526
577
|
return await mutate(async () => {
|
|
527
578
|
const slot = { claimed: null };
|
|
528
|
-
await
|
|
579
|
+
await pendingTransact(async (current) => {
|
|
529
580
|
const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}) };
|
|
530
581
|
const record = map[id] ?? null;
|
|
531
582
|
if (record === null || !canClaimPending(record.status)) return map;
|
|
@@ -539,12 +590,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
539
590
|
map[id] = slot.claimed;
|
|
540
591
|
return map;
|
|
541
592
|
});
|
|
593
|
+
warnPendingGrowth(await loadPendingMap(), "claim");
|
|
542
594
|
return slot.claimed ? { ...slot.claimed } : null;
|
|
543
595
|
});
|
|
544
596
|
},
|
|
545
597
|
async releasePendingClaim(id, claimId) {
|
|
546
598
|
await mutate(async () => {
|
|
547
|
-
await
|
|
599
|
+
await pendingTransact(async (current) => {
|
|
548
600
|
const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}) };
|
|
549
601
|
const record = map[id];
|
|
550
602
|
if (!record || record.claimedBy !== claimId) return map;
|
|
@@ -562,7 +614,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
562
614
|
record: null,
|
|
563
615
|
applied: false
|
|
564
616
|
};
|
|
565
|
-
await
|
|
617
|
+
await pendingTransact(async (current) => {
|
|
566
618
|
const map = { ...await mergedWithFilteredLegacy(legacyMigrated ? null : await readJson(PENDING_LEGACY_FILE), current ?? {}) };
|
|
567
619
|
const record = map[id] ?? null;
|
|
568
620
|
if (record === null || !canResolvePending(record.status)) {
|
|
@@ -602,6 +654,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
602
654
|
}
|
|
603
655
|
return pruned.map;
|
|
604
656
|
});
|
|
657
|
+
warnPendingGrowth(await loadPendingMap(), "resolve");
|
|
605
658
|
return result;
|
|
606
659
|
});
|
|
607
660
|
}
|
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.
|
|
4
|
+
"version": "0.3.83",
|
|
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.3.
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.3.83"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
35
|
-
"@lmzhen/dsh-evolution-state-storage": "^0.3.
|
|
34
|
+
"@lmzhen/dsh-evolution-io": "^0.3.83",
|
|
35
|
+
"@lmzhen/dsh-evolution-state-storage": "^0.3.83"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
39
|
-
"@lmzhen/dsh-evolution-state-storage": "^0.3.
|
|
40
|
-
"@lmzhen/dsh-evolution-io-node": "^0.3.
|
|
38
|
+
"@lmzhen/dsh-evolution-io": "^0.3.83",
|
|
39
|
+
"@lmzhen/dsh-evolution-state-storage": "^0.3.83",
|
|
40
|
+
"@lmzhen/dsh-evolution-io-node": "^0.3.83"
|
|
41
41
|
}
|
|
42
42
|
}
|