@lmzhen/dsh-evolution-state-storage 0.3.67 → 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.
package/lib/index.js CHANGED
@@ -170,6 +170,52 @@ const PENDING_RESOLVED_CAP = 200;
170
170
  * interface change, no background sweeper).
171
171
  */
172
172
  const REVIEW_STATE_SESSION_CAP = 500;
173
+ /**
174
+ * V27 G2.2: WHICH pending records the audit cap evicts, as one pure rule both
175
+ * providers apply (the two implementations had drifted into separate files and
176
+ * only one of them was ever updated by a later fix).
177
+ *
178
+ * Eligible = resolved (`approved`/`rejected`) and NOT a capability approval:
179
+ * v23 (AP-1) exempts those because the Creator-mode contract reads the LIVE
180
+ * approved list and a capability cannot be re-submitted for the same package,
181
+ * so eviction would make an approved capability permanently unactivatable.
182
+ * Ordering = oldest `resolvedAt` first; a missing or unparseable timestamp sorts
183
+ * LAST (json parity, v16) — an unknown time must never make a record the victim,
184
+ * and pending/executing rows are live work that is never trimmed.
185
+ *
186
+ * @param records - every pending record currently in the live table.
187
+ * @param cap - how many resolved records may be kept.
188
+ * @returns the records to evict, oldest first (empty when within the cap).
189
+ */
190
+ function selectPendingOverflow(records, cap = 200) {
191
+ const resolved = records.filter((record) => (record.status === "approved" || record.status === "rejected") && record.kind !== "capability");
192
+ const overflow = resolved.length - cap;
193
+ if (overflow <= 0) return [];
194
+ const resolvedAtMs = (record) => {
195
+ if (!record.resolvedAt) return Number.MAX_SAFE_INTEGER;
196
+ const parsed = Date.parse(record.resolvedAt);
197
+ return Number.isNaN(parsed) ? Number.MAX_SAFE_INTEGER : parsed;
198
+ };
199
+ return [...resolved].sort((a, b) => resolvedAtMs(a) - resolvedAtMs(b)).slice(0, overflow);
200
+ }
201
+ /**
202
+ * V27 G2.2: which review-state session rows a save evicts, as one pure rule
203
+ * both providers apply. The saving session is never a candidate (it is the most
204
+ * recent write by definition); the rest are ordered by their provider stamp
205
+ * ascending, with a missing stamp read as 0 = oldest, because an active session
206
+ * re-stamps its row on its next save and an unknown stamp is stale by
207
+ * construction. Exactly one row is dropped per over-cap save, which keeps the
208
+ * table at the cap in steady state.
209
+ *
210
+ * @param rows - the other sessions' rows, with their stamps.
211
+ * @param cap - how many session rows may exist.
212
+ * @returns the keys to delete, oldest first.
213
+ */
214
+ function selectSessionOverflow(rows, options, cap = 500) {
215
+ const overflow = rows.length - cap + 1;
216
+ if (overflow <= 0) return [];
217
+ return [...rows].sort((a, b) => options.stampOf(a) - options.stampOf(b)).slice(0, overflow).map((row) => options.keyOf(row));
218
+ }
173
219
  var EvolutionStateStorageRegistry = class extends Service {
174
220
  providers = /* @__PURE__ */ new Map();
175
221
  /** C-7 (v18): per-name dispose, mirroring the evolution-io registry. */
@@ -216,4 +262,4 @@ var EvolutionStateStorageRegistry = class extends Service {
216
262
  }
217
263
  };
218
264
  //#endregion
219
- 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 };
265
+ 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 };
@@ -60,6 +60,41 @@ export declare const PENDING_RESOLVED_CAP = 200;
60
60
  * interface change, no background sweeper).
61
61
  */
62
62
  export declare const REVIEW_STATE_SESSION_CAP = 500;
63
+ /**
64
+ * V27 G2.2: WHICH pending records the audit cap evicts, as one pure rule both
65
+ * providers apply (the two implementations had drifted into separate files and
66
+ * only one of them was ever updated by a later fix).
67
+ *
68
+ * Eligible = resolved (`approved`/`rejected`) and NOT a capability approval:
69
+ * v23 (AP-1) exempts those because the Creator-mode contract reads the LIVE
70
+ * approved list and a capability cannot be re-submitted for the same package,
71
+ * so eviction would make an approved capability permanently unactivatable.
72
+ * Ordering = oldest `resolvedAt` first; a missing or unparseable timestamp sorts
73
+ * LAST (json parity, v16) — an unknown time must never make a record the victim,
74
+ * and pending/executing rows are live work that is never trimmed.
75
+ *
76
+ * @param records - every pending record currently in the live table.
77
+ * @param cap - how many resolved records may be kept.
78
+ * @returns the records to evict, oldest first (empty when within the cap).
79
+ */
80
+ export declare function selectPendingOverflow(records: readonly PendingRecord[], cap?: number): PendingRecord[];
81
+ /**
82
+ * V27 G2.2: which review-state session rows a save evicts, as one pure rule
83
+ * both providers apply. The saving session is never a candidate (it is the most
84
+ * recent write by definition); the rest are ordered by their provider stamp
85
+ * ascending, with a missing stamp read as 0 = oldest, because an active session
86
+ * re-stamps its row on its next save and an unknown stamp is stale by
87
+ * construction. Exactly one row is dropped per over-cap save, which keeps the
88
+ * table at the cap in steady state.
89
+ *
90
+ * @param rows - the other sessions' rows, with their stamps.
91
+ * @param cap - how many session rows may exist.
92
+ * @returns the keys to delete, oldest first.
93
+ */
94
+ export declare function selectSessionOverflow<T>(rows: readonly T[], options: {
95
+ keyOf(row: T): string;
96
+ stampOf(row: T): number;
97
+ }, cap?: number): string[];
63
98
  /**
64
99
  * Claim lifecycle (S3.3): pending →(claim)→ executing →(resolve)→ approved/rejected.
65
100
  * release() rolls executing back to pending (failure path). A crash between
@@ -113,6 +148,13 @@ export interface EvolutionStateStorage {
113
148
  * whole read → transform → write runs inside one provider transact, so a
114
149
  * setPaused racing the run-core bookkeeping write can never interleave a
115
150
  * stale load with a newer save.
151
+ *
152
+ * V27 S4: the record handed to `task` belongs to the task — a provider must
153
+ * NEVER pass the object it stores (both current providers hand out a copy:
154
+ * json re-parses its medium, the domain clones its record). Mutating the
155
+ * argument is not a supported way to write: a task that does so and then lets
156
+ * validation refuse the result would otherwise leave a mutated object in the
157
+ * provider's in-memory store.
116
158
  */
117
159
  transactCuratorState(task: (current: CuratorStateRecord | null) => CuratorStateRecord | null): Promise<void>;
118
160
  listPending(status?: PendingStatus): Promise<PendingRecord[]>;
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.67",
4
+ "version": "0.3.68",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },