@lmzhen/dsh-evolution-state-storage 0.17.1 → 0.18.1
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 +3 -1
- package/lib/index.js +100 -67
- package/lib/types/conformance.d.ts +21 -1
- package/lib/types/index.d.ts +1 -17
- package/lib/types/record-contract.d.ts +23 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,9 @@ import { expect } from 'vitest' // or any runner wrapped into `ConformanceAssert
|
|
|
20
20
|
await runStateProviderConsistency(myProvider, expect)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Every value the vectors use is PARAMETERIZABLE (U-2): `runStateProviderConsistency(provider, assert, options)` takes `idPrefix` / `claimPrefix` / `createdAt` / `sessionId` / `attributionSessionId` / `reviewSessionPrefix`, so a medium that already holds records in the default `c-…` namespace — or whose schema insists on a parseable stamp — runs the very same vectors under its own values. Every field defaults to the in-tree literals, so a two-argument call is unchanged. Each vector carries a `since <version>` note saying which release it became binding in.
|
|
24
|
+
|
|
25
|
+
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`) — the json one also runs it under a caller-supplied namespace — and `consistency-forge.spec.ts` proves the suite fails when a single field is forged.
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
## Known limitations
|
package/lib/index.js
CHANGED
|
@@ -82,17 +82,38 @@ const REVIEW_STATE_SESSION_CAP = 500;
|
|
|
82
82
|
const UNKNOWN_FIELD_POLICY = "preserve";
|
|
83
83
|
const isNonNegInt = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
84
84
|
const optionalString = (value) => value === void 0 || typeof value === "string";
|
|
85
|
-
|
|
85
|
+
/** 0.3.17 (S3.5, D-4): 'skill_batch' is gone — nothing ever created one (a dead enum member). A
|
|
86
|
+
* historic value, if it ever reached disk, is read as an unknown kind by consumers rather than
|
|
87
|
+
* minted here.
|
|
88
|
+
*
|
|
89
|
+
* 0.3.66: 'capability' stays with NO producer — the evolution-capability adapter was removed. It is
|
|
90
|
+
* a read-compatibility member: state written by an install that used that adapter (≤0.3.65) still
|
|
91
|
+
* holds such records, and they must keep loading, listing in `/evolution pending`, and answering
|
|
92
|
+
* approve or reject. Dropping it would strand them two ways: json quarantines the row to
|
|
93
|
+
* `<file>.corrupt` and refuses the resolving write, while the domain provider validates every
|
|
94
|
+
* stored record at mount, so one such row fails the whole domain with `invalid-record`.
|
|
95
|
+
*
|
|
96
|
+
* S2.9/1-10: this array is the ONE source of the vocabulary — the types, the write gate below and
|
|
97
|
+
* the domain provider's schema all derive from it. A second literal copy is the E-10 drift class
|
|
98
|
+
* this module exists to end. */
|
|
99
|
+
const PENDING_KINDS = [
|
|
86
100
|
"memory",
|
|
87
101
|
"skill",
|
|
88
102
|
"capability"
|
|
89
|
-
]
|
|
90
|
-
|
|
103
|
+
];
|
|
104
|
+
/** 0.3.17 (S3.3, E-24): 'executing' = claimed, runner in flight — a fresh claim only takes
|
|
105
|
+
* 'pending', and resolve accepts 'pending'/'executing', so a crash mid-approve can never
|
|
106
|
+
* double-execute the runner. Same one-source rule as PENDING_KINDS. */
|
|
107
|
+
const PENDING_STATUSES = [
|
|
91
108
|
"pending",
|
|
92
109
|
"executing",
|
|
93
110
|
"approved",
|
|
94
111
|
"rejected"
|
|
95
|
-
]
|
|
112
|
+
];
|
|
113
|
+
/** Membership test for the write gate (per record per write, so the Set is worth it). */
|
|
114
|
+
const PENDING_KIND_SET = new Set(PENDING_KINDS);
|
|
115
|
+
/** Membership test for the write gate. */
|
|
116
|
+
const PENDING_STATUS_SET = new Set(PENDING_STATUSES);
|
|
96
117
|
/**
|
|
97
118
|
* The write gate for one record. Both providers call this before persisting,
|
|
98
119
|
* so a record the other provider would refuse can never land.
|
|
@@ -118,12 +139,12 @@ function recordIssue(table, record) {
|
|
|
118
139
|
return null;
|
|
119
140
|
}
|
|
120
141
|
if (typeof value.id !== "string") return "id must be a string";
|
|
121
|
-
if (typeof value.kind !== "string" || !
|
|
142
|
+
if (typeof value.kind !== "string" || !PENDING_KIND_SET.has(value.kind)) return `kind must be ${PENDING_KINDS.join("|")}`;
|
|
122
143
|
if (typeof value.summary !== "string") return "summary must be a string";
|
|
123
144
|
if (!Object.prototype.hasOwnProperty.call(value, "args")) return "args key is required (may be any cloneable value)";
|
|
124
145
|
if (value.args === void 0) return "args must be a cloneable value — `undefined` is dropped by the json medium and the record would be unreadable after a restart (pass {} instead)";
|
|
125
146
|
if (typeof value.createdAt !== "string") return "createdAt must be a string";
|
|
126
|
-
if (typeof value.status !== "string" || !
|
|
147
|
+
if (typeof value.status !== "string" || !PENDING_STATUS_SET.has(value.status)) return `status must be ${PENDING_STATUSES.join("|")}`;
|
|
127
148
|
for (const field of [
|
|
128
149
|
"resolvedAt",
|
|
129
150
|
"claimedBy",
|
|
@@ -185,8 +206,9 @@ function cloneRecord(record) {
|
|
|
185
206
|
* the same public operations on both providers, so neither medium can keep the
|
|
186
207
|
* bound to itself.
|
|
187
208
|
*/
|
|
188
|
-
/** Build a fixture pending record, optionally carrying origin/sessionId.
|
|
189
|
-
|
|
209
|
+
/** Build a fixture pending record, optionally carrying origin/sessionId. The suite's own stamp and
|
|
210
|
+
* namespace come from {@link ConformanceOptions} — see `basePendingOf`'s call sites. */
|
|
211
|
+
const basePendingOf = (id, kind = "memory", attribution = {}) => ({
|
|
190
212
|
id,
|
|
191
213
|
kind,
|
|
192
214
|
summary: `${kind}:${id}`,
|
|
@@ -213,13 +235,24 @@ const findRecord = (records, id) => {
|
|
|
213
235
|
if (found === void 0) throw new Error(`conformance: pending record "${id}" missing from the listing`);
|
|
214
236
|
return found;
|
|
215
237
|
};
|
|
216
|
-
async function runStateProviderConsistency(provider, assert) {
|
|
217
|
-
|
|
238
|
+
async function runStateProviderConsistency(provider, assert, options = {}) {
|
|
239
|
+
const idPrefix = options.idPrefix ?? "c-";
|
|
240
|
+
const claimPrefix = options.claimPrefix ?? "claim-";
|
|
241
|
+
const stamp = options.createdAt ?? "now";
|
|
242
|
+
const sessionId = options.sessionId ?? "s-consistent";
|
|
243
|
+
const attributionSessionId = options.attributionSessionId ?? "sess-attrib";
|
|
244
|
+
const reviewSessionPrefix = options.reviewSessionPrefix ?? "s-cap-";
|
|
245
|
+
/** This run's fixture record: the module helper's fields under THIS run's namespace and stamp. */
|
|
246
|
+
const pending = (id, kind = "memory", attribution = {}) => ({
|
|
247
|
+
...basePendingOf(id, kind, attribution),
|
|
248
|
+
createdAt: stamp
|
|
249
|
+
});
|
|
250
|
+
await provider.saveReviewState(sessionId, {
|
|
218
251
|
turnsSinceMemory: 1,
|
|
219
252
|
turnsSinceSkill: 2,
|
|
220
253
|
lastTurn: 3
|
|
221
254
|
});
|
|
222
|
-
assert(await provider.loadReviewState(
|
|
255
|
+
assert(await provider.loadReviewState(sessionId)).toEqual({
|
|
223
256
|
turnsSinceMemory: 1,
|
|
224
257
|
turnsSinceSkill: 2,
|
|
225
258
|
lastTurn: 3
|
|
@@ -267,92 +300,92 @@ async function runStateProviderConsistency(provider, assert) {
|
|
|
267
300
|
lastSummary: "updated",
|
|
268
301
|
paused: false
|
|
269
302
|
});
|
|
270
|
-
const live =
|
|
303
|
+
const live = pending(`${idPrefix}live`);
|
|
271
304
|
await provider.savePending(live);
|
|
272
|
-
assert((await provider.listPending("pending")).map((record) => record.id)).toContain(
|
|
273
|
-
const claimed = await provider.claimPending(
|
|
305
|
+
assert((await provider.listPending("pending")).map((record) => record.id)).toContain(`${idPrefix}live`);
|
|
306
|
+
const claimed = await provider.claimPending(`${idPrefix}live`, `${claimPrefix}a`);
|
|
274
307
|
assert(claimed?.status).toBe("executing");
|
|
275
|
-
assert(claimed?.claimedBy).toBe(
|
|
308
|
+
assert(claimed?.claimedBy).toBe(`${claimPrefix}a`);
|
|
276
309
|
expectPendingCarried(assert, claimed, live);
|
|
277
310
|
assert(typeof claimed?.claimedAt).toBe("string");
|
|
278
|
-
const resolved = await provider.tryResolvePending(
|
|
311
|
+
const resolved = await provider.tryResolvePending(`${idPrefix}live`, "approved");
|
|
279
312
|
assert(resolved.applied).toBe(true);
|
|
280
|
-
assert(resolved.record?.id).toBe(
|
|
313
|
+
assert(resolved.record?.id).toBe(`${idPrefix}live`);
|
|
281
314
|
assert(resolved.record?.status).toBe("approved");
|
|
282
315
|
expectPendingCarried(assert, resolved.record, live);
|
|
283
|
-
assert(resolved.record?.claimedBy).toBe(
|
|
316
|
+
assert(resolved.record?.claimedBy).toBe(`${claimPrefix}a`);
|
|
284
317
|
assert(typeof resolved.record?.claimedAt).toBe("string");
|
|
285
318
|
assert(typeof resolved.record?.resolvedAt).toBe("string");
|
|
286
|
-
assert((await provider.listPending("approved")).map((record) => record.id)).toContain(
|
|
287
|
-
assert((await provider.listPending("pending")).map((record) => record.id)).not.toContain(
|
|
288
|
-
const attrib =
|
|
319
|
+
assert((await provider.listPending("approved")).map((record) => record.id)).toContain(`${idPrefix}live`);
|
|
320
|
+
assert((await provider.listPending("pending")).map((record) => record.id)).not.toContain(`${idPrefix}live`);
|
|
321
|
+
const attrib = pending(`${idPrefix}attrib`, "memory", {
|
|
289
322
|
origin: "background_review",
|
|
290
|
-
sessionId:
|
|
323
|
+
sessionId: attributionSessionId
|
|
291
324
|
});
|
|
292
325
|
await provider.savePending(attrib);
|
|
293
|
-
expectPendingCarried(assert, await provider.claimPending(
|
|
294
|
-
const attribResolved = await provider.tryResolvePending(
|
|
326
|
+
expectPendingCarried(assert, await provider.claimPending(`${idPrefix}attrib`, `${claimPrefix}a`), attrib);
|
|
327
|
+
const attribResolved = await provider.tryResolvePending(`${idPrefix}attrib`, "approved");
|
|
295
328
|
expectPendingCarried(assert, attribResolved.record, attrib);
|
|
296
329
|
assert(attribResolved.record?.origin).toBe("background_review");
|
|
297
|
-
assert(attribResolved.record?.sessionId).toBe(
|
|
330
|
+
assert(attribResolved.record?.sessionId).toBe(attributionSessionId);
|
|
298
331
|
assert(typeof attribResolved.record?.resolvedAt).toBe("string");
|
|
299
|
-
const rel =
|
|
332
|
+
const rel = pending(`${idPrefix}release`, "skill");
|
|
300
333
|
await provider.savePending(rel);
|
|
301
|
-
assert((await provider.claimPending(
|
|
302
|
-
await provider.releasePendingClaim(
|
|
303
|
-
const released = (await provider.listPending("pending")).find((record) => record.id ===
|
|
334
|
+
assert((await provider.claimPending(`${idPrefix}release`, `${claimPrefix}b`))?.status).toBe("executing");
|
|
335
|
+
await provider.releasePendingClaim(`${idPrefix}release`, `${claimPrefix}b`);
|
|
336
|
+
const released = (await provider.listPending("pending")).find((record) => record.id === `${idPrefix}release`);
|
|
304
337
|
assert(released?.status).toBe("pending");
|
|
305
338
|
expectPendingCarried(assert, released, rel);
|
|
306
339
|
assert(released?.claimedBy).toBeUndefined();
|
|
307
340
|
assert(released?.claimedAt).toBeUndefined();
|
|
308
|
-
const scoped =
|
|
341
|
+
const scoped = pending(`${idPrefix}scoped`);
|
|
309
342
|
await provider.savePending(scoped);
|
|
310
|
-
await provider.claimPending(
|
|
311
|
-
assert((await provider.tryResolvePending(
|
|
312
|
-
assert((await provider.listPending("executing")).map((record) => record.id)).toContain(
|
|
313
|
-
const ownerScope = await provider.tryResolvePending(
|
|
343
|
+
await provider.claimPending(`${idPrefix}scoped`, `${claimPrefix}owner`);
|
|
344
|
+
assert((await provider.tryResolvePending(`${idPrefix}scoped`, "approved", `${claimPrefix}foreign`)).applied).toBe(false);
|
|
345
|
+
assert((await provider.listPending("executing")).map((record) => record.id)).toContain(`${idPrefix}scoped`);
|
|
346
|
+
const ownerScope = await provider.tryResolvePending(`${idPrefix}scoped`, "approved", `${claimPrefix}owner`);
|
|
314
347
|
assert(ownerScope.applied).toBe(true);
|
|
315
348
|
assert(ownerScope.record?.status).toBe("approved");
|
|
316
|
-
const filt =
|
|
349
|
+
const filt = pending(`${idPrefix}filter`);
|
|
317
350
|
await provider.savePending(filt);
|
|
318
|
-
assert((await provider.listPending("pending")).map((record) => record.id)).toContain(
|
|
319
|
-
assert((await provider.listPending("approved")).map((record) => record.id)).not.toContain(
|
|
320
|
-
const post =
|
|
351
|
+
assert((await provider.listPending("pending")).map((record) => record.id)).toContain(`${idPrefix}filter`);
|
|
352
|
+
assert((await provider.listPending("approved")).map((record) => record.id)).not.toContain(`${idPrefix}filter`);
|
|
353
|
+
const post = pending(`${idPrefix}post-resolve`, "memory");
|
|
321
354
|
await provider.savePending(post);
|
|
322
|
-
await provider.claimPending(
|
|
323
|
-
await provider.tryResolvePending(
|
|
324
|
-
await provider.releasePendingClaim(
|
|
325
|
-
const postResolved = (await provider.listPending("rejected")).find((record) => record.id ===
|
|
355
|
+
await provider.claimPending(`${idPrefix}post-resolve`, `${claimPrefix}c`);
|
|
356
|
+
await provider.tryResolvePending(`${idPrefix}post-resolve`, "rejected");
|
|
357
|
+
await provider.releasePendingClaim(`${idPrefix}post-resolve`, `${claimPrefix}c`);
|
|
358
|
+
const postResolved = (await provider.listPending("rejected")).find((record) => record.id === `${idPrefix}post-resolve`);
|
|
326
359
|
assert(postResolved?.status).toBe("rejected");
|
|
327
|
-
assert(postResolved?.claimedBy).toBe(
|
|
360
|
+
assert(postResolved?.claimedBy).toBe(`${claimPrefix}c`);
|
|
328
361
|
assert(typeof postResolved?.claimedAt).toBe("string");
|
|
329
362
|
assert(typeof postResolved?.resolvedAt).toBe("string");
|
|
330
363
|
await assert(provider.savePending({
|
|
331
|
-
id:
|
|
364
|
+
id: `${idPrefix}bad`,
|
|
332
365
|
kind: "memory",
|
|
333
366
|
summary: "bad",
|
|
334
|
-
createdAt:
|
|
367
|
+
createdAt: stamp,
|
|
335
368
|
status: "pending"
|
|
336
369
|
})).rejects.toThrow();
|
|
337
|
-
const poison =
|
|
370
|
+
const poison = pending(`${idPrefix}poison`);
|
|
338
371
|
poison.args = { nested: { value: 1 } };
|
|
339
372
|
await provider.savePending(poison);
|
|
340
|
-
const returned = findRecord(await provider.listPending("pending"),
|
|
373
|
+
const returned = findRecord(await provider.listPending("pending"), `${idPrefix}poison`);
|
|
341
374
|
returned.summary = "poisoned";
|
|
342
375
|
returned.args.nested.value = 99;
|
|
343
|
-
const reread = findRecord(await provider.listPending("pending"),
|
|
344
|
-
assert(reread.summary).toBe(
|
|
376
|
+
const reread = findRecord(await provider.listPending("pending"), `${idPrefix}poison`);
|
|
377
|
+
assert(reread.summary).toBe(`memory:${idPrefix}poison`);
|
|
345
378
|
assert(reread.args.nested.value).toBe(1);
|
|
346
379
|
const uncloneable = {
|
|
347
|
-
...
|
|
380
|
+
...pending(`${idPrefix}uncloneable`),
|
|
348
381
|
args: { fn: () => {} }
|
|
349
382
|
};
|
|
350
383
|
await assert(provider.savePending(uncloneable)).rejects.toThrow();
|
|
351
|
-
const aliased =
|
|
384
|
+
const aliased = pending(`${idPrefix}alias`);
|
|
352
385
|
aliased.args = { nested: { value: 1 } };
|
|
353
386
|
await provider.savePending(aliased);
|
|
354
387
|
aliased.args.nested.value = 99;
|
|
355
|
-
assert(findRecord(await provider.listPending("pending"),
|
|
388
|
+
assert(findRecord(await provider.listPending("pending"), `${idPrefix}alias`).args.nested.value).toBe(1);
|
|
356
389
|
await assert(provider.saveCuratorState({
|
|
357
390
|
lastRunAt: 1,
|
|
358
391
|
runCount: 0,
|
|
@@ -361,37 +394,37 @@ async function runStateProviderConsistency(provider, assert) {
|
|
|
361
394
|
schemaVersion: -1
|
|
362
395
|
})).rejects.toThrow();
|
|
363
396
|
await provider.savePending({
|
|
364
|
-
...
|
|
397
|
+
...pending(`${idPrefix}extra`),
|
|
365
398
|
extraField: "kept"
|
|
366
399
|
});
|
|
367
|
-
assert((await provider.listPending("pending")).find((record) => record.id ===
|
|
368
|
-
const capPrefix =
|
|
369
|
-
await provider.savePending(
|
|
400
|
+
assert((await provider.listPending("pending")).find((record) => record.id === `${idPrefix}extra`).extraField).toBe("kept");
|
|
401
|
+
const capPrefix = `${idPrefix}cap-`;
|
|
402
|
+
await provider.savePending(pending(`${idPrefix}cap-live`, "skill"));
|
|
370
403
|
for (let index = 0; index <= 201; index += 1) {
|
|
371
404
|
const id = capPrefix + String(index).padStart(3, "0");
|
|
372
|
-
await provider.savePending(
|
|
373
|
-
await provider.claimPending(id,
|
|
405
|
+
await provider.savePending(pending(id));
|
|
406
|
+
await provider.claimPending(id, `${claimPrefix}cap`);
|
|
374
407
|
await provider.tryResolvePending(id, "approved");
|
|
375
408
|
}
|
|
376
409
|
const resolvedNow = (await provider.listPending("approved")).concat(await provider.listPending("rejected")).filter((record) => record.kind !== "capability");
|
|
377
410
|
assert(resolvedNow).toHaveLength(200);
|
|
378
411
|
const resolvedIds = resolvedNow.map((record) => record.id);
|
|
379
|
-
assert(resolvedIds).not.toContain("
|
|
380
|
-
assert(resolvedIds).not.toContain("
|
|
412
|
+
assert(resolvedIds).not.toContain(capPrefix + "000");
|
|
413
|
+
assert(resolvedIds).not.toContain(capPrefix + "001");
|
|
381
414
|
assert(resolvedIds).toContain(capPrefix + String(201).padStart(3, "0"));
|
|
382
|
-
assert((await provider.listPending("pending")).map((record) => record.id)).toContain(
|
|
383
|
-
const sessionPrefix =
|
|
415
|
+
assert((await provider.listPending("pending")).map((record) => record.id)).toContain(`${idPrefix}cap-live`);
|
|
416
|
+
const sessionPrefix = reviewSessionPrefix;
|
|
384
417
|
const reviewStateOf = (turns) => ({
|
|
385
418
|
turnsSinceMemory: turns,
|
|
386
419
|
turnsSinceSkill: 0,
|
|
387
420
|
lastTurn: turns
|
|
388
421
|
});
|
|
389
422
|
for (let index = 0; index <= 500; index += 1) await provider.saveReviewState(sessionPrefix + String(index), reviewStateOf(index));
|
|
390
|
-
assert(await provider.loadReviewState("
|
|
391
|
-
assert(await provider.loadReviewState("
|
|
423
|
+
assert(await provider.loadReviewState(sessionPrefix + "0")).toBeNull();
|
|
424
|
+
assert(await provider.loadReviewState(sessionPrefix + "1")).toEqual(reviewStateOf(1));
|
|
392
425
|
assert(await provider.loadReviewState(sessionPrefix + String(500))).toEqual(reviewStateOf(500));
|
|
393
|
-
await provider.saveReviewState("
|
|
394
|
-
assert(await provider.loadReviewState("
|
|
426
|
+
await provider.saveReviewState(sessionPrefix + "0", reviewStateOf(999));
|
|
427
|
+
assert(await provider.loadReviewState(sessionPrefix + "0")).toEqual(reviewStateOf(999));
|
|
395
428
|
}
|
|
396
429
|
//#endregion
|
|
397
430
|
//#region lib/types/index.js
|
|
@@ -520,4 +553,4 @@ var EvolutionStateStorageRegistry = class extends Service {
|
|
|
520
553
|
}
|
|
521
554
|
};
|
|
522
555
|
//#endregion
|
|
523
|
-
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 };
|
|
556
|
+
export { CURATOR_STATE_FILE, CURATOR_STATE_KEY, CURATOR_STATE_TABLE, EvolutionStateStorageRegistry, EvolutionStateStorageRegistry as default, PENDING_ARCHIVE_BAK_FILE, PENDING_ARCHIVE_FILE, PENDING_KINDS, PENDING_LEGACY_FILE, PENDING_RESOLVED_CAP, PENDING_STATE_FILE, PENDING_STATUSES, 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 };
|
|
@@ -23,5 +23,25 @@ export interface ConformanceAssert {
|
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The values a PROVIDER UNDER TEST may need to vary (U-2): a third-party medium can already hold
|
|
28
|
+
* records in the `c-…` namespace, and a strict schema may insist on a parseable `createdAt`. Every
|
|
29
|
+
* field defaults to the in-tree literals, so an existing caller's two-argument call is unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export interface ConformanceOptions {
|
|
32
|
+
/** Namespace for every pending record this run creates (default `'c-'`). */
|
|
33
|
+
idPrefix?: string;
|
|
34
|
+
/** Namespace for every claim id (default `'claim-'`). */
|
|
35
|
+
claimPrefix?: string;
|
|
36
|
+
/** The `createdAt` stamp every fixture record carries (default `'now'`). A provider whose
|
|
37
|
+
* schema requires a parseable time passes a real stamp here. */
|
|
38
|
+
createdAt?: string;
|
|
39
|
+
/** The review-state session the round-trip vector writes (default `'s-consistent'`). */
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
/** The review-state session the ATTRIBUTION vector pins (default `attributionSessionId`). */
|
|
42
|
+
attributionSessionId?: string;
|
|
43
|
+
/** Namespace for the per-session cap vector's sessions (default `'s-cap-'`). */
|
|
44
|
+
reviewSessionPrefix?: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function runStateProviderConsistency(provider: EvolutionStateStorage, assert: ConformanceAssert, options?: ConformanceOptions): Promise<void>;
|
|
27
47
|
//# sourceMappingURL=conformance.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,26 +7,10 @@
|
|
|
7
7
|
* @module @lmzhen/dsh-evolution-state-storage
|
|
8
8
|
*/
|
|
9
9
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { PendingKind, PendingStatus } from './record-contract.ts';
|
|
10
11
|
export * from './constants.ts';
|
|
11
12
|
export * from './record-contract.ts';
|
|
12
13
|
export * from './conformance.ts';
|
|
13
|
-
/** 0.3.17 (S3.5, D-4): 'skill_batch' removed — nothing ever created one
|
|
14
|
-
* (dead enum member); the historic value, if it ever reached disk, is read as
|
|
15
|
-
* an unknown kind by consumers rather than minted here.
|
|
16
|
-
*
|
|
17
|
-
* 0.3.66: 'capability' is retained with NO producer — the evolution-capability
|
|
18
|
-
* adapter was removed. It is a read-compatibility member: state written by an
|
|
19
|
-
* install that used that adapter (≤0.3.65) still holds such records, and they
|
|
20
|
-
* must keep loading, listing in `/evolution pending`, and answering approve or
|
|
21
|
-
* reject. Dropping it would strand them two ways: json quarantines the row to
|
|
22
|
-
* `<file>.corrupt` and refuses the resolving write, while the domain provider
|
|
23
|
-
* validates every stored record at mount, so one such row fails the whole domain
|
|
24
|
-
* with `invalid-record`. */
|
|
25
|
-
export type PendingKind = 'memory' | 'skill' | 'capability';
|
|
26
|
-
/** 0.3.17 (S3.3, E-24): 'executing' = claimed, runner in flight — a fresh
|
|
27
|
-
* claim only takes 'pending', and resolve accepts 'pending'/'executing', so a
|
|
28
|
-
* crash mid-approve can never double-execute the runner. */
|
|
29
|
-
export type PendingStatus = 'pending' | 'executing' | 'approved' | 'rejected';
|
|
30
14
|
/** 0.3.17 (S3.3): the claim lifecycle as ONE transition table — BOTH
|
|
31
15
|
* providers (json/domain) must use these, never a hand-written copy (a second
|
|
32
16
|
* copy is exactly the E-10 drift class). */
|
|
@@ -21,6 +21,29 @@ export type SeamRecordTable = typeof REVIEW_STATE_TABLE | typeof CURATOR_STATE_T
|
|
|
21
21
|
/** Unknown fields survive a provider round-trip (json preserves by
|
|
22
22
|
* construction; the domain schemas are `.loose()`). */
|
|
23
23
|
export declare const UNKNOWN_FIELD_POLICY: "preserve";
|
|
24
|
+
/** 0.3.17 (S3.5, D-4): 'skill_batch' is gone — nothing ever created one (a dead enum member). A
|
|
25
|
+
* historic value, if it ever reached disk, is read as an unknown kind by consumers rather than
|
|
26
|
+
* minted here.
|
|
27
|
+
*
|
|
28
|
+
* 0.3.66: 'capability' stays with NO producer — the evolution-capability adapter was removed. It is
|
|
29
|
+
* a read-compatibility member: state written by an install that used that adapter (≤0.3.65) still
|
|
30
|
+
* holds such records, and they must keep loading, listing in `/evolution pending`, and answering
|
|
31
|
+
* approve or reject. Dropping it would strand them two ways: json quarantines the row to
|
|
32
|
+
* `<file>.corrupt` and refuses the resolving write, while the domain provider validates every
|
|
33
|
+
* stored record at mount, so one such row fails the whole domain with `invalid-record`.
|
|
34
|
+
*
|
|
35
|
+
* S2.9/1-10: this array is the ONE source of the vocabulary — the types, the write gate below and
|
|
36
|
+
* the domain provider's schema all derive from it. A second literal copy is the E-10 drift class
|
|
37
|
+
* this module exists to end. */
|
|
38
|
+
export declare const PENDING_KINDS: readonly ["memory", "skill", "capability"];
|
|
39
|
+
/** 0.3.17 (S3.3, E-24): 'executing' = claimed, runner in flight — a fresh claim only takes
|
|
40
|
+
* 'pending', and resolve accepts 'pending'/'executing', so a crash mid-approve can never
|
|
41
|
+
* double-execute the runner. Same one-source rule as PENDING_KINDS. */
|
|
42
|
+
export declare const PENDING_STATUSES: readonly ["pending", "executing", "approved", "rejected"];
|
|
43
|
+
/** The legal kind of one pending record. */
|
|
44
|
+
export type PendingKind = (typeof PENDING_KINDS)[number];
|
|
45
|
+
/** The legal status of one pending record. */
|
|
46
|
+
export type PendingStatus = (typeof PENDING_STATUSES)[number];
|
|
24
47
|
/**
|
|
25
48
|
* The write gate for one record. Both providers call this before persisting,
|
|
26
49
|
* so a record the other provider would refuse can never land.
|