@lmzhen/dsh-evolution-state-storage 0.3.80 → 0.3.82

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
@@ -19,6 +19,19 @@ Zero direct token effect from this package; consumers add any model-visible toke
19
19
 
20
20
  Independent of request-prefix construction. This package does not alter the assembled prompt or tool list.
21
21
 
22
+ ## Conformance suite (S3-2 / J-5)
23
+
24
+ The package publishes `runStateProviderConsistency(provider, assert)` — the whole seam contract as one call: field-complete pending round-trips, claim/resolve rollback, refusals at the write boundary, clone/alias independence, unknown-field preservation, and the two caps (resolved pending tail, review-state session rows). A third-party provider checks itself by running the suite against its own instance:
25
+
26
+ ```ts
27
+ import { runStateProviderConsistency } from '@deepseek-ai/dsh-evolution-state-storage'
28
+ import { expect } from 'vitest' // or any runner wrapped into `ConformanceAssert`
29
+
30
+ await runStateProviderConsistency(myProvider, expect)
31
+ ```
32
+
33
+ The assertion surface is INJECTED rather than imported, which is what lets this module ship: it carries no test-runner dependency, so loading the package never loads vitest. Both shipped providers run the same suite (`evolution-state-json` / `evolution-state-domain` `tests/provider-consistency.spec.ts`), and `consistency-forge.spec.ts` proves the suite fails when a single field is forged.
34
+
22
35
  ## Known Limitations and Deferred Work
23
36
 
24
37
 
package/lib/index.js CHANGED
@@ -32,6 +32,32 @@ const PROVIDER_DOMAIN = "domain";
32
32
  const REVIEW_STATE_TABLE = "review_state";
33
33
  const CURATOR_STATE_TABLE = "curator_state";
34
34
  const PENDING_TABLE = "pending";
35
+ /** P2-4 (v15): the live pending map/table is BOUNDED on the RESOLVE path —
36
+ * `tryResolvePending` drops the oldest resolved (approved/rejected) records by
37
+ * `resolvedAt` once more than this many exist. Single source (the v15 audit
38
+ * found the bound was json-only, so domain deployments grew the table without
39
+ * bound).
40
+ *
41
+ * C-6 (v18) contract precision: a direct `savePending` of an already-resolved
42
+ * record does NOT trigger eviction (the cap is maintained by the resolve
43
+ * operation, not by the writer), and pending/executing records are never
44
+ * trimmed. Callers that write resolved audit records themselves own that
45
+ * growth; the seam's resolve path is what keeps the table bounded.
46
+ * The audit ARCHIVE sidecar that json maintains beyond the cap stays
47
+ * json-specific (domain has no sidecar facility) — declared in both READMEs. */
48
+ const PENDING_RESOLVED_CAP = 200;
49
+ /**
50
+ * V24-08 (v24): session rows in the review-state table, per session id. The
51
+ * review pipeline saves on EVERY turn/end of EVERY session and nothing ever
52
+ * deleted rows, so the file grew (and was fully rewritten) with the deploy's
53
+ * whole session history — the same unbounded-growth class the pending cap
54
+ * above already fixed for approvals. A review row is advisory cadence state:
55
+ * evicting the least-recently-active session merely lets that session's next
56
+ * review fire from a fresh counter, so a generous cap is loss-less in
57
+ * practice. Enforced by BOTH providers inside their save path (no seam
58
+ * interface change, no background sweeper).
59
+ */
60
+ const REVIEW_STATE_SESSION_CAP = 500;
35
61
  //#endregion
36
62
  //#region lib/types/record-contract.js
37
63
  /**
@@ -127,6 +153,234 @@ function cloneRecord(record) {
127
153
  return structuredClone(record);
128
154
  }
129
155
  //#endregion
156
+ //#region lib/types/conformance.js
157
+ /**
158
+ * Published conformance suite for `EvolutionStateStorage` providers (J-5 / S3-2).
159
+ *
160
+ * A third-party provider (or a new medium inside this family) runs ONE call to
161
+ * check the seam contract end to end: field-complete round-trips, claim/resolve
162
+ * rollback, refusal at the write boundary, clone/alias independence, unknown-field
163
+ * preservation, and the two seam caps (the resolved pending tail and the
164
+ * review-state session rows).
165
+ *
166
+ * The assertion surface is INJECTED ({@link ConformanceAssert}) rather than
167
+ * imported: vitest satisfies it in-tree, and a host that only has node:assert can
168
+ * wrap it in a few lines. That keeps this module free of any test-runner import,
169
+ * so it ships inside the published package.
170
+ */
171
+ /**
172
+ * Cross-provider consistency harness (G7.4, 0.3.22; V4-07 0.3.28).
173
+ *
174
+ * Runs the SAME observable operation sequence against an
175
+ * `EvolutionStateStorage` provider (json or domain) and asserts both yield the
176
+ * same field-complete result. Every pending-record field that is carried
177
+ * through a round-trip is compared field by field: id, kind, status, summary,
178
+ * args, createdAt, origin, sessionId, claimedBy. The wall-clock holder stamps
179
+ * (claimedAt, resolvedAt) are generated by each provider at its own transact
180
+ * time and legitimately differ in value between the two claims, so they are
181
+ * asserted only for presence/type (a string), never for equality — and
182
+ * claimedAt is asserted to be cleared on release. Status filtering / release
183
+ * rollback must agree. S2-4 (v29): the two seam caps — the resolved pending
184
+ * tail (P2-4) and the review-state session rows (V24-08) — are exercised through
185
+ * the same public operations on both providers, so neither medium can keep the
186
+ * bound to itself.
187
+ */
188
+ /** Build a fixture pending record, optionally carrying origin/sessionId. */
189
+ const pendingOf = (id, kind = "memory", attribution = {}) => ({
190
+ id,
191
+ kind,
192
+ summary: `${kind}:${id}`,
193
+ args: {},
194
+ createdAt: "now",
195
+ status: "pending",
196
+ ...attribution
197
+ });
198
+ /** Every non-holder pending field must equal the input, field by field. */
199
+ const expectPendingCarried = (assert, actual, input) => {
200
+ assert(actual).toBeDefined();
201
+ const carried = actual;
202
+ assert(carried.id).toBe(input.id);
203
+ assert(carried.kind).toBe(input.kind);
204
+ assert(carried.summary).toBe(input.summary);
205
+ assert(carried.args).toEqual(input.args);
206
+ assert(carried.createdAt).toBe(input.createdAt);
207
+ assert(carried.origin).toBe(input.origin);
208
+ assert(carried.sessionId).toBe(input.sessionId);
209
+ };
210
+ /** The listing must contain `id`; a missing row fails the SUITE, not the runner. */
211
+ const findRecord = (records, id) => {
212
+ const found = records.find((record) => record.id === id);
213
+ if (found === void 0) throw new Error(`conformance: pending record "${id}" missing from the listing`);
214
+ return found;
215
+ };
216
+ async function runStateProviderConsistency(provider, assert) {
217
+ await provider.saveReviewState("s-consistent", {
218
+ turnsSinceMemory: 1,
219
+ turnsSinceSkill: 2,
220
+ lastTurn: 3
221
+ });
222
+ assert(await provider.loadReviewState("s-consistent")).toEqual({
223
+ turnsSinceMemory: 1,
224
+ turnsSinceSkill: 2,
225
+ lastTurn: 3
226
+ });
227
+ assert(await provider.loadCuratorState()).toBeNull();
228
+ await provider.transactCuratorState(() => ({
229
+ lastRunAt: 10,
230
+ runCount: 0,
231
+ lastSummary: "seed",
232
+ paused: false
233
+ }));
234
+ assert(await provider.loadCuratorState()).toEqual({
235
+ lastRunAt: 10,
236
+ runCount: 0,
237
+ lastSummary: "seed",
238
+ paused: false
239
+ });
240
+ await provider.transactCuratorState(() => null);
241
+ assert(await provider.loadCuratorState()).toEqual({
242
+ lastRunAt: 10,
243
+ runCount: 0,
244
+ lastSummary: "seed",
245
+ paused: false
246
+ });
247
+ await provider.transactCuratorState((current) => ({
248
+ ...current,
249
+ lastSummary: "updated"
250
+ }));
251
+ assert(await provider.loadCuratorState()).toEqual({
252
+ lastRunAt: 10,
253
+ runCount: 0,
254
+ lastSummary: "updated",
255
+ paused: false
256
+ });
257
+ const live = pendingOf("c-live");
258
+ await provider.savePending(live);
259
+ assert((await provider.listPending("pending")).map((record) => record.id)).toContain("c-live");
260
+ const claimed = await provider.claimPending("c-live", "claim-a");
261
+ assert(claimed?.status).toBe("executing");
262
+ assert(claimed?.claimedBy).toBe("claim-a");
263
+ expectPendingCarried(assert, claimed, live);
264
+ assert(typeof claimed?.claimedAt).toBe("string");
265
+ const resolved = await provider.tryResolvePending("c-live", "approved");
266
+ assert(resolved.applied).toBe(true);
267
+ assert(resolved.record?.id).toBe("c-live");
268
+ assert(resolved.record?.status).toBe("approved");
269
+ expectPendingCarried(assert, resolved.record, live);
270
+ assert(resolved.record?.claimedBy).toBe("claim-a");
271
+ assert(typeof resolved.record?.claimedAt).toBe("string");
272
+ assert(typeof resolved.record?.resolvedAt).toBe("string");
273
+ assert((await provider.listPending("approved")).map((record) => record.id)).toContain("c-live");
274
+ assert((await provider.listPending("pending")).map((record) => record.id)).not.toContain("c-live");
275
+ const attrib = pendingOf("c-attrib", "memory", {
276
+ origin: "background_review",
277
+ sessionId: "sess-attrib"
278
+ });
279
+ await provider.savePending(attrib);
280
+ expectPendingCarried(assert, await provider.claimPending("c-attrib", "claim-a"), attrib);
281
+ const attribResolved = await provider.tryResolvePending("c-attrib", "approved");
282
+ expectPendingCarried(assert, attribResolved.record, attrib);
283
+ assert(attribResolved.record?.origin).toBe("background_review");
284
+ assert(attribResolved.record?.sessionId).toBe("sess-attrib");
285
+ assert(typeof attribResolved.record?.resolvedAt).toBe("string");
286
+ const rel = pendingOf("c-release", "skill");
287
+ await provider.savePending(rel);
288
+ assert((await provider.claimPending("c-release", "claim-b"))?.status).toBe("executing");
289
+ await provider.releasePendingClaim("c-release", "claim-b");
290
+ const released = (await provider.listPending("pending")).find((record) => record.id === "c-release");
291
+ assert(released?.status).toBe("pending");
292
+ expectPendingCarried(assert, released, rel);
293
+ assert(released?.claimedBy).toBeUndefined();
294
+ assert(released?.claimedAt).toBeUndefined();
295
+ const scoped = pendingOf("c-scoped");
296
+ await provider.savePending(scoped);
297
+ await provider.claimPending("c-scoped", "claim-owner");
298
+ assert((await provider.tryResolvePending("c-scoped", "approved", "claim-foreign")).applied).toBe(false);
299
+ assert((await provider.listPending("executing")).map((record) => record.id)).toContain("c-scoped");
300
+ const ownerScope = await provider.tryResolvePending("c-scoped", "approved", "claim-owner");
301
+ assert(ownerScope.applied).toBe(true);
302
+ assert(ownerScope.record?.status).toBe("approved");
303
+ const filt = pendingOf("c-filter");
304
+ await provider.savePending(filt);
305
+ assert((await provider.listPending("pending")).map((record) => record.id)).toContain("c-filter");
306
+ assert((await provider.listPending("approved")).map((record) => record.id)).not.toContain("c-filter");
307
+ const post = pendingOf("c-post-resolve", "memory");
308
+ await provider.savePending(post);
309
+ await provider.claimPending("c-post-resolve", "claim-c");
310
+ await provider.tryResolvePending("c-post-resolve", "rejected");
311
+ await provider.releasePendingClaim("c-post-resolve", "claim-c");
312
+ const postResolved = (await provider.listPending("rejected")).find((record) => record.id === "c-post-resolve");
313
+ assert(postResolved?.status).toBe("rejected");
314
+ assert(postResolved?.claimedBy).toBe("claim-c");
315
+ assert(typeof postResolved?.claimedAt).toBe("string");
316
+ assert(typeof postResolved?.resolvedAt).toBe("string");
317
+ await assert(provider.savePending({
318
+ id: "c-bad",
319
+ kind: "memory",
320
+ summary: "bad",
321
+ createdAt: "now",
322
+ status: "pending"
323
+ })).rejects.toThrow();
324
+ const poison = pendingOf("c-poison");
325
+ poison.args = { nested: { value: 1 } };
326
+ await provider.savePending(poison);
327
+ const returned = findRecord(await provider.listPending("pending"), "c-poison");
328
+ returned.summary = "poisoned";
329
+ returned.args.nested.value = 99;
330
+ const reread = findRecord(await provider.listPending("pending"), "c-poison");
331
+ assert(reread.summary).toBe("memory:c-poison");
332
+ assert(reread.args.nested.value).toBe(1);
333
+ const uncloneable = {
334
+ ...pendingOf("c-uncloneable"),
335
+ args: { fn: () => {} }
336
+ };
337
+ await assert(provider.savePending(uncloneable)).rejects.toThrow();
338
+ const aliased = pendingOf("c-alias");
339
+ aliased.args = { nested: { value: 1 } };
340
+ await provider.savePending(aliased);
341
+ aliased.args.nested.value = 99;
342
+ assert(findRecord(await provider.listPending("pending"), "c-alias").args.nested.value).toBe(1);
343
+ await assert(provider.saveCuratorState({
344
+ lastRunAt: 1,
345
+ runCount: 0,
346
+ lastSummary: "x",
347
+ paused: false,
348
+ schemaVersion: -1
349
+ })).rejects.toThrow();
350
+ await provider.savePending({
351
+ ...pendingOf("c-extra"),
352
+ extraField: "kept"
353
+ });
354
+ assert((await provider.listPending("pending")).find((record) => record.id === "c-extra").extraField).toBe("kept");
355
+ const capPrefix = "c-cap-";
356
+ await provider.savePending(pendingOf("c-cap-live", "skill"));
357
+ for (let index = 0; index <= 201; index += 1) {
358
+ const id = capPrefix + String(index).padStart(3, "0");
359
+ await provider.savePending(pendingOf(id));
360
+ await provider.claimPending(id, "claim-cap");
361
+ await provider.tryResolvePending(id, "approved");
362
+ }
363
+ const resolvedNow = (await provider.listPending("approved")).concat(await provider.listPending("rejected")).filter((record) => record.kind !== "capability");
364
+ assert(resolvedNow).toHaveLength(200);
365
+ const resolvedIds = resolvedNow.map((record) => record.id);
366
+ assert(resolvedIds).not.toContain("c-cap-000");
367
+ assert(resolvedIds).not.toContain("c-cap-001");
368
+ assert(resolvedIds).toContain(capPrefix + String(201).padStart(3, "0"));
369
+ assert((await provider.listPending("pending")).map((record) => record.id)).toContain("c-cap-live");
370
+ const sessionPrefix = "s-cap-";
371
+ const reviewStateOf = (turns) => ({
372
+ turnsSinceMemory: turns,
373
+ turnsSinceSkill: 0,
374
+ lastTurn: turns
375
+ });
376
+ for (let index = 0; index <= 500; index += 1) await provider.saveReviewState(sessionPrefix + String(index), reviewStateOf(index));
377
+ assert(await provider.loadReviewState("s-cap-0")).toBeNull();
378
+ assert(await provider.loadReviewState("s-cap-1")).toEqual(reviewStateOf(1));
379
+ assert(await provider.loadReviewState(sessionPrefix + String(500))).toEqual(reviewStateOf(500));
380
+ await provider.saveReviewState("s-cap-0", reviewStateOf(999));
381
+ assert(await provider.loadReviewState("s-cap-0")).toEqual(reviewStateOf(999));
382
+ }
383
+ //#endregion
130
384
  //#region lib/types/index.js
131
385
  /**
132
386
  * Provider seam for durable evolution state.
@@ -144,32 +398,6 @@ const canResolvePending = (status) => status === "pending" || status === "execut
144
398
  /** Releasing a claim on an executing record rolls it back to pending (a
145
399
  * runner FAILURE is retryable); other statuses pass through unchanged. */
146
400
  const releasedStatus = (status) => status === "executing" ? "pending" : status;
147
- /** P2-4 (v15): the live pending map/table is BOUNDED on the RESOLVE path —
148
- * `tryResolvePending` drops the oldest resolved (approved/rejected) records by
149
- * `resolvedAt` once more than this many exist. Single source (the v15 audit
150
- * found the bound was json-only, so domain deployments grew the table without
151
- * bound).
152
- *
153
- * C-6 (v18) contract precision: a direct `savePending` of an already-resolved
154
- * record does NOT trigger eviction (the cap is maintained by the resolve
155
- * operation, not by the writer), and pending/executing records are never
156
- * trimmed. Callers that write resolved audit records themselves own that
157
- * growth; the seam's resolve path is what keeps the table bounded.
158
- * The audit ARCHIVE sidecar that json maintains beyond the cap stays
159
- * json-specific (domain has no sidecar facility) — declared in both READMEs. */
160
- const PENDING_RESOLVED_CAP = 200;
161
- /**
162
- * V24-08 (v24): session rows in the review-state table, per session id. The
163
- * review pipeline saves on EVERY turn/end of EVERY session and nothing ever
164
- * deleted rows, so the file grew (and was fully rewritten) with the deploy's
165
- * whole session history — the same unbounded-growth class the pending cap
166
- * above already fixed for approvals. A review row is advisory cadence state:
167
- * evicting the least-recently-active session merely lets that session's next
168
- * review fire from a fresh counter, so a generous cap is loss-less in
169
- * practice. Enforced by BOTH providers inside their save path (no seam
170
- * interface change, no background sweeper).
171
- */
172
- const REVIEW_STATE_SESSION_CAP = 500;
173
401
  /**
174
402
  * V27 G2.2: WHICH pending records the audit cap evicts, as one pure rule both
175
403
  * providers apply (the two implementations had drifted into separate files and
@@ -279,4 +507,4 @@ var EvolutionStateStorageRegistry = class extends Service {
279
507
  }
280
508
  };
281
509
  //#endregion
282
- export { CURATOR_STATE_FILE, CURATOR_STATE_KEY, CURATOR_STATE_TABLE, EvolutionStateStorageRegistry, EvolutionStateStorageRegistry as default, PENDING_ARCHIVE_BAK_FILE, PENDING_ARCHIVE_FILE, PENDING_LEGACY_FILE, PENDING_RESOLVED_CAP, PENDING_STATE_FILE, PENDING_TABLE, PROVIDER_DOMAIN, PROVIDER_JSON, REVIEW_STATE_FILE, REVIEW_STATE_SESSION_CAP, REVIEW_STATE_TABLE, UNKNOWN_FIELD_POLICY, assertCloneable, canClaimPending, canResolvePending, cloneRecord, recordIssue, releasedStatus, selectPendingOverflow, selectSessionOverflow };
510
+ export { CURATOR_STATE_FILE, CURATOR_STATE_KEY, CURATOR_STATE_TABLE, EvolutionStateStorageRegistry, EvolutionStateStorageRegistry as default, PENDING_ARCHIVE_BAK_FILE, PENDING_ARCHIVE_FILE, PENDING_LEGACY_FILE, PENDING_RESOLVED_CAP, PENDING_STATE_FILE, PENDING_TABLE, PROVIDER_DOMAIN, PROVIDER_JSON, REVIEW_STATE_FILE, REVIEW_STATE_SESSION_CAP, REVIEW_STATE_TABLE, UNKNOWN_FIELD_POLICY, assertCloneable, canClaimPending, canResolvePending, cloneRecord, recordIssue, releasedStatus, runStateProviderConsistency, selectPendingOverflow, selectSessionOverflow };
@@ -0,0 +1,27 @@
1
+ import type { EvolutionStateStorage } from './index.ts';
2
+ /**
3
+ * The assertion surface this suite needs. Vitest's `expect` satisfies it in-tree;
4
+ * a host with any other runner wraps its own in a few lines. Deliberately
5
+ * structural and minimal — the suite must not import a test runner.
6
+ */
7
+ export interface ConformanceAssert {
8
+ (actual: unknown): {
9
+ toBe(expected: unknown): void;
10
+ toEqual(expected: unknown): void;
11
+ toBeDefined(): void;
12
+ toBeUndefined(): void;
13
+ toBeNull(): void;
14
+ toContain(expected: unknown): void;
15
+ toHaveLength(length: number): void;
16
+ toBeGreaterThan(expected: number): void;
17
+ not: {
18
+ toBe(expected: unknown): void;
19
+ toContain(expected: unknown): void;
20
+ };
21
+ rejects: {
22
+ toThrow(expected?: unknown): Promise<void>;
23
+ };
24
+ };
25
+ }
26
+ export declare function runStateProviderConsistency(provider: EvolutionStateStorage, assert: ConformanceAssert): Promise<void>;
27
+ //# sourceMappingURL=conformance.d.ts.map
@@ -30,4 +30,30 @@ export declare const PROVIDER_DOMAIN = "domain";
30
30
  export declare const REVIEW_STATE_TABLE = "review_state";
31
31
  export declare const CURATOR_STATE_TABLE = "curator_state";
32
32
  export declare const PENDING_TABLE = "pending";
33
+ /** P2-4 (v15): the live pending map/table is BOUNDED on the RESOLVE path —
34
+ * `tryResolvePending` drops the oldest resolved (approved/rejected) records by
35
+ * `resolvedAt` once more than this many exist. Single source (the v15 audit
36
+ * found the bound was json-only, so domain deployments grew the table without
37
+ * bound).
38
+ *
39
+ * C-6 (v18) contract precision: a direct `savePending` of an already-resolved
40
+ * record does NOT trigger eviction (the cap is maintained by the resolve
41
+ * operation, not by the writer), and pending/executing records are never
42
+ * trimmed. Callers that write resolved audit records themselves own that
43
+ * growth; the seam's resolve path is what keeps the table bounded.
44
+ * The audit ARCHIVE sidecar that json maintains beyond the cap stays
45
+ * json-specific (domain has no sidecar facility) — declared in both READMEs. */
46
+ export declare const PENDING_RESOLVED_CAP = 200;
47
+ /**
48
+ * V24-08 (v24): session rows in the review-state table, per session id. The
49
+ * review pipeline saves on EVERY turn/end of EVERY session and nothing ever
50
+ * deleted rows, so the file grew (and was fully rewritten) with the deploy's
51
+ * whole session history — the same unbounded-growth class the pending cap
52
+ * above already fixed for approvals. A review row is advisory cadence state:
53
+ * evicting the least-recently-active session merely lets that session's next
54
+ * review fire from a fresh counter, so a generous cap is loss-less in
55
+ * practice. Enforced by BOTH providers inside their save path (no seam
56
+ * interface change, no background sweeper).
57
+ */
58
+ export declare const REVIEW_STATE_SESSION_CAP = 500;
33
59
  //# sourceMappingURL=constants.d.ts.map
@@ -9,6 +9,7 @@
9
9
  import { Context, Service } from '@deepseek-ai/cordis';
10
10
  export * from './constants.ts';
11
11
  export * from './record-contract.ts';
12
+ export * from './conformance.ts';
12
13
  /** 0.3.17 (S3.5, D-4): 'skill_batch' removed — nothing ever created one
13
14
  * (dead enum member); the historic value, if it ever reached disk, is read as
14
15
  * an unknown kind by consumers rather than minted here.
@@ -34,32 +35,6 @@ export declare const canResolvePending: (status: PendingStatus) => boolean;
34
35
  /** Releasing a claim on an executing record rolls it back to pending (a
35
36
  * runner FAILURE is retryable); other statuses pass through unchanged. */
36
37
  export declare const releasedStatus: (status: PendingStatus) => PendingStatus;
37
- /** P2-4 (v15): the live pending map/table is BOUNDED on the RESOLVE path —
38
- * `tryResolvePending` drops the oldest resolved (approved/rejected) records by
39
- * `resolvedAt` once more than this many exist. Single source (the v15 audit
40
- * found the bound was json-only, so domain deployments grew the table without
41
- * bound).
42
- *
43
- * C-6 (v18) contract precision: a direct `savePending` of an already-resolved
44
- * record does NOT trigger eviction (the cap is maintained by the resolve
45
- * operation, not by the writer), and pending/executing records are never
46
- * trimmed. Callers that write resolved audit records themselves own that
47
- * growth; the seam's resolve path is what keeps the table bounded.
48
- * The audit ARCHIVE sidecar that json maintains beyond the cap stays
49
- * json-specific (domain has no sidecar facility) — declared in both READMEs. */
50
- export declare const PENDING_RESOLVED_CAP = 200;
51
- /**
52
- * V24-08 (v24): session rows in the review-state table, per session id. The
53
- * review pipeline saves on EVERY turn/end of EVERY session and nothing ever
54
- * deleted rows, so the file grew (and was fully rewritten) with the deploy's
55
- * whole session history — the same unbounded-growth class the pending cap
56
- * above already fixed for approvals. A review row is advisory cadence state:
57
- * evicting the least-recently-active session merely lets that session's next
58
- * review fire from a fresh counter, so a generous cap is loss-less in
59
- * practice. Enforced by BOTH providers inside their save path (no seam
60
- * interface change, no background sweeper).
61
- */
62
- export declare const REVIEW_STATE_SESSION_CAP = 500;
63
38
  /**
64
39
  * V27 G2.2: WHICH pending records the audit cap evicts, as one pure rule both
65
40
  * providers apply (the two implementations had drifted into separate files and
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-state-storage",
3
3
  "description": "Provider registry seam for durable evolution state (community build)",
4
- "version": "0.3.80",
4
+ "version": "0.3.82",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },