@lmzhen/dsh-evolution-state-domain 0.3.66 → 0.3.68

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 (3) hide show
  1. package/README.md +1 -0
  2. package/lib/index.js +29 -12
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -24,4 +24,5 @@ Independent of request-prefix construction. This package does not alter the asse
24
24
 
25
25
  - Requires the host-plane `storage-domain` facility. The bundle row stays dormant when it is absent.
26
26
  - P2-4 (v15): the live pending table is BOUNDED — resolved (approved/rejected) records are kept to the most recent `PENDING_RESOLVED_CAP` (200, seam constant in `evolution-state-storage`); the oldest by `resolvedAt` are deleted on resolve. The audit ARCHIVE sidecar that the json provider maintains beyond the cap is json-specific (the domain seam has no sidecar facility) — resolved records past the cap are gone, not archived.
27
+ - 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`) are pruned (delete failures warn and retry on the next save). The stamp is stripped on read, so the consumer-facing record shape is unchanged.
27
28
 
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { DomainError, defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
3
- import { CURATOR_STATE_KEY, CURATOR_STATE_TABLE, PENDING_RESOLVED_CAP, PENDING_TABLE, PROVIDER_DOMAIN, REVIEW_STATE_TABLE, assertCloneable, canClaimPending, canResolvePending, cloneRecord, recordIssue, releasedStatus } from "@lmzhen/dsh-evolution-state-storage";
3
+ import { CURATOR_STATE_KEY, CURATOR_STATE_TABLE, PENDING_RESOLVED_CAP, PENDING_TABLE, PROVIDER_DOMAIN, REVIEW_STATE_SESSION_CAP, REVIEW_STATE_TABLE, assertCloneable, canClaimPending, canResolvePending, cloneRecord, recordIssue, releasedStatus, selectPendingOverflow, selectSessionOverflow } from "@lmzhen/dsh-evolution-state-storage";
4
4
  //#region lib/types/index.js
5
5
  /**
6
6
  * storage-domain provider for evolution state.
@@ -117,14 +117,36 @@ function apply(ctx) {
117
117
  name: PROVIDER_DOMAIN,
118
118
  async loadReviewState(sessionId) {
119
119
  const record = (await ensure()).table(REVIEW_STATE_TABLE).get(sessionId);
120
- return record === void 0 ? null : structuredClone(record);
120
+ if (record === void 0) return null;
121
+ const { updatedAt: _stamp, ...rest } = record;
122
+ return structuredClone(rest);
121
123
  },
122
124
  async saveReviewState(sessionId, record) {
123
125
  const issue = recordIssue(REVIEW_STATE_TABLE, record) ?? assertCloneable(record);
124
126
  if (issue !== null) throw new Error(`evolution-state-domain: refusing to persist an invalid review-state record: ${issue}`);
125
127
  const parsed = reviewStateSchema.safeParse(record);
126
128
  if (!parsed.success) throw new Error(`evolution-state-domain: refusing to persist an invalid review-state record: ${parsed.error.issues[0]?.message ?? "schema mismatch"}`);
127
- await (await ensure()).table(REVIEW_STATE_TABLE).put(sessionId, cloneRecord(parsed.data));
129
+ const table = (await ensure()).table(REVIEW_STATE_TABLE);
130
+ const stamped = {
131
+ ...cloneRecord(parsed.data),
132
+ updatedAt: Date.now()
133
+ };
134
+ await table.put(sessionId, stamped);
135
+ const entries = [...table.entries()].map(([key, row]) => ({
136
+ key,
137
+ updatedAt: row.updatedAt ?? 0
138
+ })).filter((entry) => entry.key !== sessionId);
139
+ const victims = new Set(selectSessionOverflow(entries, {
140
+ keyOf: (entry) => entry.key,
141
+ stampOf: (entry) => entry.updatedAt
142
+ }, REVIEW_STATE_SESSION_CAP));
143
+ for (const entry of entries.filter((candidate) => victims.has(candidate.key))) {
144
+ const current = table.get(entry.key);
145
+ if (current === void 0 || (current.updatedAt ?? 0) !== entry.updatedAt) continue;
146
+ await table.delete(entry.key).catch((error) => {
147
+ ctx.logger.warn(`evolution-state-domain: review-state session-cap eviction for "${entry.key}" failed (will retry on the next save): ${error instanceof Error ? error.message : String(error)}`);
148
+ });
149
+ }
128
150
  },
129
151
  async loadCuratorState() {
130
152
  const record = (await ensure()).table(CURATOR_STATE_TABLE).get(CURATOR_STATE_KEY);
@@ -140,7 +162,7 @@ function apply(ctx) {
140
162
  async transactCuratorState(task) {
141
163
  const table = (await ensure()).table(CURATOR_STATE_TABLE);
142
164
  const guarded = (current) => {
143
- const next = task(current);
165
+ const next = task(current === null ? null : cloneRecord(current));
144
166
  if (next === null) return null;
145
167
  const issue = recordIssue(CURATOR_STATE_TABLE, next) ?? assertCloneable(next);
146
168
  if (issue !== null) throw new Error(`evolution-state-domain: refusing to persist an invalid curator-state record: ${issue}`);
@@ -230,16 +252,11 @@ function apply(ctx) {
230
252
  return resolved.record;
231
253
  });
232
254
  if (resolved.record !== null) {
233
- const resolvedEntries = [...table.entries()].map(([key, record]) => ({
255
+ const evictedRecords = new Set(selectPendingOverflow([...table.entries()].map(([, record]) => record), PENDING_RESOLVED_CAP));
256
+ const evicted = [...table.entries()].filter(([, record]) => evictedRecords.has(record)).map(([key, record]) => ({
234
257
  key,
235
258
  record
236
- })).filter((entry) => (entry.record.status === "approved" || entry.record.status === "rejected") && entry.record.kind !== "capability");
237
- const resolvedAtMs = (record) => {
238
- const parsed = Date.parse(record.resolvedAt ?? "");
239
- return Number.isNaN(parsed) ? Number.MAX_SAFE_INTEGER : parsed;
240
- };
241
- resolvedEntries.sort((a, b) => resolvedAtMs(a.record) - resolvedAtMs(b.record));
242
- const evicted = resolvedEntries.slice(0, Math.max(0, resolvedEntries.length - PENDING_RESOLVED_CAP));
259
+ }));
243
260
  for (const entry of evicted) {
244
261
  const current = table.get(entry.key);
245
262
  if (!(current !== void 0 && (current.status === "approved" || current.status === "rejected") && current.resolvedAt === entry.record.resolvedAt)) continue;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-state-domain",
3
3
  "description": "storage-domain provider for evolution state (community build)",
4
- "version": "0.3.66",
4
+ "version": "0.3.68",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -36,13 +36,13 @@
36
36
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
37
37
  "@deepseek-ai/cordis": "^4.0.1",
38
38
  "@deepseek-ai/dsh-storage-domain": "^0.1.1-rc.2",
39
- "@lmzhen/dsh-evolution-state-storage": "^0.3.66"
39
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.68"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
43
43
  "@deepseek-ai/dsh-storage-domain": "^0.1.1-rc.2",
44
44
  "@deepseek-ai/dsh-storage": "^0.1.1-rc.2",
45
45
  "@deepseek-ai/dsh-storage-json": "^0.1.1-rc.2",
46
- "@lmzhen/dsh-evolution-state-storage": "^0.3.66"
46
+ "@lmzhen/dsh-evolution-state-storage": "^0.3.68"
47
47
  }
48
48
  }