@x12i/memorix-descriptors 0.1.0 → 1.0.0

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 (38) hide show
  1. package/README.md +390 -41
  2. package/dist/admin/create-admin.d.ts.map +1 -1
  3. package/dist/admin/create-admin.js +2 -1
  4. package/dist/admin/create-admin.js.map +1 -1
  5. package/dist/catalox/client.d.ts.map +1 -1
  6. package/dist/catalox/client.js +14 -1
  7. package/dist/catalox/client.js.map +1 -1
  8. package/dist/tests/fixtures.d.ts +54 -0
  9. package/dist/tests/fixtures.d.ts.map +1 -0
  10. package/dist/tests/fixtures.js +79 -0
  11. package/dist/tests/fixtures.js.map +1 -0
  12. package/dist/tests/helpers/live.d.ts +7 -0
  13. package/dist/tests/helpers/live.d.ts.map +1 -0
  14. package/dist/tests/helpers/live.js +31 -0
  15. package/dist/tests/helpers/live.js.map +1 -0
  16. package/dist/tests/integrity.test.d.ts +2 -0
  17. package/dist/tests/integrity.test.d.ts.map +1 -0
  18. package/dist/tests/integrity.test.js +49 -0
  19. package/dist/tests/integrity.test.js.map +1 -0
  20. package/dist/tests/live/catalox.live.test.d.ts +2 -0
  21. package/dist/tests/live/catalox.live.test.d.ts.map +1 -0
  22. package/dist/tests/live/catalox.live.test.js +110 -0
  23. package/dist/tests/live/catalox.live.test.js.map +1 -0
  24. package/dist/tests/mutations.test.d.ts +2 -0
  25. package/dist/tests/mutations.test.d.ts.map +1 -0
  26. package/dist/tests/mutations.test.js +99 -0
  27. package/dist/tests/mutations.test.js.map +1 -0
  28. package/dist/tests/seeds.test.d.ts +2 -0
  29. package/dist/tests/seeds.test.d.ts.map +1 -0
  30. package/dist/tests/seeds.test.js +57 -0
  31. package/dist/tests/seeds.test.js.map +1 -0
  32. package/dist/tests/setup.d.ts +2 -0
  33. package/dist/tests/setup.d.ts.map +1 -0
  34. package/dist/tests/setup.js +6 -0
  35. package/dist/tests/setup.js.map +1 -0
  36. package/dist/tests/validation.test.js +3 -62
  37. package/dist/tests/validation.test.js.map +1 -1
  38. package/package.json +5 -2
@@ -0,0 +1,79 @@
1
+ export const assetsEntityFixture = {
2
+ id: "assets",
3
+ entityName: "assets",
4
+ target: "entity",
5
+ defaultListDescriptorId: "assets-main-list",
6
+ defaultItemDescriptorId: "asset-detail-item",
7
+ collectionPrefix: "assets",
8
+ identity: {
9
+ allowedIdFields: ["entityId", "eventId"],
10
+ requiredExactlyOne: true,
11
+ defaultIdField: "entityId",
12
+ },
13
+ defaults: {
14
+ canonicalContentType: "snapshots",
15
+ dataRoot: "data",
16
+ effectiveDatePath: "capturedAt",
17
+ fallbackEffectiveDatePaths: [],
18
+ },
19
+ contentTypes: {
20
+ snapshots: {
21
+ postfix: "snapshots",
22
+ collection: "assets-snapshots",
23
+ isCanonical: true,
24
+ },
25
+ analysis: {
26
+ postfix: "analysis",
27
+ collection: "assets-analysis",
28
+ isCanonical: false,
29
+ },
30
+ },
31
+ properties: {
32
+ ipAddress: {
33
+ label: "IP Address",
34
+ source: { contentType: "snapshots", path: "data.ip_address" },
35
+ humanReadable: true,
36
+ },
37
+ hostName: {
38
+ label: "Host Name",
39
+ source: { contentType: "snapshots", path: "data.xdr.host_name" },
40
+ humanReadable: false,
41
+ },
42
+ },
43
+ relations: {},
44
+ };
45
+ export function sampleSnapshot() {
46
+ return {
47
+ entityDescriptors: {
48
+ assets: { ...assetsEntityFixture },
49
+ },
50
+ listDescriptors: {
51
+ "assets-main-list": {
52
+ id: "assets-main-list",
53
+ entity: "assets",
54
+ leadingContentType: "snapshots",
55
+ pagination: { enabled: true, defaultLimit: 50, maxLimit: 200 },
56
+ fields: ["ipAddress"],
57
+ },
58
+ },
59
+ itemDescriptors: {
60
+ "asset-detail-item": {
61
+ id: "asset-detail-item",
62
+ entity: "assets",
63
+ identity: { idField: "entityId" },
64
+ contentTypes: [{ contentType: "snapshots", required: true }],
65
+ sections: [{ id: "summary", title: "Summary", fields: ["ipAddress"] }],
66
+ },
67
+ },
68
+ completionMappings: {},
69
+ };
70
+ }
71
+ export function emptySnapshot() {
72
+ return {
73
+ entityDescriptors: {},
74
+ listDescriptors: {},
75
+ itemDescriptors: {},
76
+ completionMappings: {},
77
+ };
78
+ }
79
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../../src/tests/fixtures.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,EAAE,EAAE,QAAQ;IACZ,UAAU,EAAE,QAAQ;IACpB,MAAM,EAAE,QAAQ;IAChB,uBAAuB,EAAE,kBAAkB;IAC3C,uBAAuB,EAAE,mBAAmB;IAC5C,gBAAgB,EAAE,QAAQ;IAC1B,QAAQ,EAAE;QACR,eAAe,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;QACxC,kBAAkB,EAAE,IAAI;QACxB,cAAc,EAAE,UAAU;KAC3B;IACD,QAAQ,EAAE;QACR,oBAAoB,EAAE,WAAW;QACjC,QAAQ,EAAE,MAAM;QAChB,iBAAiB,EAAE,YAAY;QAC/B,0BAA0B,EAAE,EAAE;KAC/B;IACD,YAAY,EAAE;QACZ,SAAS,EAAE;YACT,OAAO,EAAE,WAAW;YACpB,UAAU,EAAE,kBAAkB;YAC9B,WAAW,EAAE,IAAI;SAClB;QACD,QAAQ,EAAE;YACR,OAAO,EAAE,UAAU;YACnB,UAAU,EAAE,iBAAiB;YAC7B,WAAW,EAAE,KAAK;SACnB;KACF;IACD,UAAU,EAAE;QACV,SAAS,EAAE;YACT,KAAK,EAAE,YAAY;YACnB,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,iBAAiB,EAAE;YAC7D,aAAa,EAAE,IAAI;SACpB;QACD,QAAQ,EAAE;YACR,KAAK,EAAE,WAAW;YAClB,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,oBAAoB,EAAE;YAChE,aAAa,EAAE,KAAK;SACrB;KACF;IACD,SAAS,EAAE,EAAE;CACL,CAAC;AAEX,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL,iBAAiB,EAAE;YACjB,MAAM,EAAE,EAAE,GAAG,mBAAmB,EAAE;SACnC;QACD,eAAe,EAAE;YACf,kBAAkB,EAAE;gBAClB,EAAE,EAAE,kBAAkB;gBACtB,MAAM,EAAE,QAAQ;gBAChB,kBAAkB,EAAE,WAAW;gBAC/B,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE;gBAC9D,MAAM,EAAE,CAAC,WAAW,CAAC;aACtB;SACF;QACD,eAAe,EAAE;YACf,mBAAmB,EAAE;gBACnB,EAAE,EAAE,mBAAmB;gBACvB,MAAM,EAAE,QAAQ;gBAChB,QAAQ,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE;gBACjC,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;gBAC5D,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC;aACvE;SACF;QACD,kBAAkB,EAAE,EAAE;KACvB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,iBAAiB,EAAE,EAAE;QACrB,eAAe,EAAE,EAAE;QACnB,eAAe,EAAE,EAAE;QACnB,kBAAkB,EAAE,EAAE;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { MemorixDescriptorAdmin } from "../../admin/create-admin.js";
2
+ export declare function isLiveTestsEnabled(env?: NodeJS.ProcessEnv): boolean;
3
+ export declare function liveSkipReason(env?: NodeJS.ProcessEnv): string | null;
4
+ export declare function assertFirestoreReachable(): Promise<void>;
5
+ export declare function createLiveAdmin(): Promise<MemorixDescriptorAdmin>;
6
+ export declare function smokeEntityName(env?: NodeJS.ProcessEnv): string;
7
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../../src/tests/helpers/live.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAKhF;AAED,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAGlF;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAK9D;AAID,wBAAsB,eAAe,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAQvE;AAED,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E"}
@@ -0,0 +1,31 @@
1
+ import { testFirestoreConnectionFromEnv } from "@x12i/catalox/firebase";
2
+ import { createMemorixDescriptorAdminFromEnv } from "../../admin/create-admin.js";
3
+ export function isLiveTestsEnabled(env = process.env) {
4
+ return (env.FIRESTORE_LIVE_TESTS === "1" ||
5
+ env.MEMORIX_DESCRIPTORS_LIVE_TESTS === "1");
6
+ }
7
+ export function liveSkipReason(env = process.env) {
8
+ if (isLiveTestsEnabled(env))
9
+ return null;
10
+ return "Set FIRESTORE_LIVE_TESTS=1 (or MEMORIX_DESCRIPTORS_LIVE_TESTS=1) in .env";
11
+ }
12
+ export async function assertFirestoreReachable() {
13
+ const probe = await testFirestoreConnectionFromEnv();
14
+ if (!probe.ok) {
15
+ throw probe.error ?? new Error("Firestore connectivity check failed");
16
+ }
17
+ }
18
+ let cachedAdmin;
19
+ export async function createLiveAdmin() {
20
+ if (!cachedAdmin) {
21
+ await assertFirestoreReachable();
22
+ cachedAdmin = await createMemorixDescriptorAdminFromEnv({
23
+ xronox: undefined,
24
+ });
25
+ }
26
+ return cachedAdmin;
27
+ }
28
+ export function smokeEntityName(env = process.env) {
29
+ return env.MEMORIX_SMOKE_ENTITY?.trim() || "assets";
30
+ }
31
+ //# sourceMappingURL=live.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.js","sourceRoot":"","sources":["../../../src/tests/helpers/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,mCAAmC,EAAE,MAAM,6BAA6B,CAAC;AAGlF,MAAM,UAAU,kBAAkB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACrE,OAAO,CACL,GAAG,CAAC,oBAAoB,KAAK,GAAG;QAChC,GAAG,CAAC,8BAA8B,KAAK,GAAG,CAC3C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAyB,OAAO,CAAC,GAAG;IACjE,IAAI,kBAAkB,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,0EAA0E,CAAC;AACpF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,KAAK,GAAG,MAAM,8BAA8B,EAAE,CAAC;IACrD,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QACd,MAAM,KAAK,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,IAAI,WAA+C,CAAC;AAEpD,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,wBAAwB,EAAE,CAAC;QACjC,WAAW,GAAG,MAAM,mCAAmC,CAAC;YACtD,MAAM,EAAE,SAAS;SAClB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,OAAO,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC;AACtD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=integrity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity.test.d.ts","sourceRoot":"","sources":["../../src/tests/integrity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { validateDescriptorSnapshot } from "../integrity/validate.js";
3
+ import { assetsEntityFixture, sampleSnapshot } from "./fixtures.js";
4
+ describe("validateDescriptorSnapshot", () => {
5
+ it("accepts a connected minimal graph", () => {
6
+ const report = validateDescriptorSnapshot(sampleSnapshot());
7
+ expect(report.valid).toBe(true);
8
+ expect(report.errors).toHaveLength(0);
9
+ });
10
+ it("flags missing default list descriptor", () => {
11
+ const snapshot = sampleSnapshot();
12
+ snapshot.entityDescriptors.assets = {
13
+ ...assetsEntityFixture,
14
+ defaultListDescriptorId: "missing-list",
15
+ };
16
+ const report = validateDescriptorSnapshot(snapshot);
17
+ expect(report.valid).toBe(false);
18
+ expect(report.errors.some((e) => e.code === "MISSING_DEFAULT_LIST")).toBe(true);
19
+ });
20
+ it("flags non-human-readable list fields", () => {
21
+ const snapshot = sampleSnapshot();
22
+ snapshot.listDescriptors["assets-main-list"] = {
23
+ ...snapshot.listDescriptors["assets-main-list"],
24
+ fields: ["hostName"],
25
+ };
26
+ const report = validateDescriptorSnapshot(snapshot);
27
+ expect(report.valid).toBe(false);
28
+ expect(report.errors.some((e) => e.code === "NON_HUMAN_READABLE_LIST_FIELD" ||
29
+ e.code === "INVALID_LIST_DESCRIPTOR")).toBe(true);
30
+ });
31
+ it("flags unknown relation target", () => {
32
+ const snapshot = sampleSnapshot();
33
+ snapshot.entityDescriptors.assets = {
34
+ ...assetsEntityFixture,
35
+ relations: {
36
+ toMissing: {
37
+ targetEntity: "not-real",
38
+ type: "oneToMany",
39
+ source: { contentType: "snapshots", path: "data.id" },
40
+ target: { contentType: "snapshots", path: "data.id" },
41
+ },
42
+ },
43
+ };
44
+ const report = validateDescriptorSnapshot(snapshot);
45
+ expect(report.valid).toBe(false);
46
+ expect(report.orphans.danglingReferences.length).toBeGreaterThan(0);
47
+ });
48
+ });
49
+ //# sourceMappingURL=integrity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integrity.test.js","sourceRoot":"","sources":["../../src/tests/integrity.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpE,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,MAAM,GAAG,0BAA0B,CAAC,cAAc,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;QAClC,QAAQ,CAAC,iBAAiB,CAAC,MAAM,GAAG;YAClC,GAAG,mBAAmB;YACtB,uBAAuB,EAAE,cAAc;SACxC,CAAC;QACF,MAAM,MAAM,GAAG,0BAA0B,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;QAClC,QAAQ,CAAC,eAAe,CAAC,kBAAkB,CAAC,GAAG;YAC7C,GAAG,QAAQ,CAAC,eAAe,CAAC,kBAAkB,CAAE;YAChD,MAAM,EAAE,CAAC,UAAU,CAAC;SACrB,CAAC;QACF,MAAM,MAAM,GAAG,0BAA0B,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CACJ,MAAM,CAAC,MAAM,CAAC,IAAI,CAChB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,KAAK,+BAA+B;YAC1C,CAAC,CAAC,IAAI,KAAK,yBAAyB,CACvC,CACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,QAAQ,GAAG,cAAc,EAAE,CAAC;QAClC,QAAQ,CAAC,iBAAiB,CAAC,MAAM,GAAG;YAClC,GAAG,mBAAmB;YACtB,SAAS,EAAE;gBACT,SAAS,EAAE;oBACT,YAAY,EAAE,UAAU;oBACxB,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE;oBACrD,MAAM,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE;iBACtD;aACF;SACF,CAAC;QACF,MAAM,MAAM,GAAG,0BAA0B,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=catalox.live.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalox.live.test.d.ts","sourceRoot":"","sources":["../../../src/tests/live/catalox.live.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,110 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { MEMORIX_ENTITY_DESCRIPTORS_CATALOG, MEMORIX_ITEM_DESCRIPTORS_CATALOG, MEMORIX_LIST_DESCRIPTORS_CATALOG, } from "../../catalog/ids.js";
3
+ import { createLiveAdmin, isLiveTestsEnabled, liveSkipReason, smokeEntityName, } from "../helpers/live.js";
4
+ import { createMemorixDescriptorAdminFromEnv } from "../../admin/create-admin.js";
5
+ const live = isLiveTestsEnabled();
6
+ const skipReason = liveSkipReason() ?? undefined;
7
+ describe.runIf(live)("live catalox / memorix-descriptors", () => {
8
+ it("loads admin and validates deployed descriptor graph", async () => {
9
+ const admin = await createLiveAdmin();
10
+ const report = await admin.validateMemorixDescriptors();
11
+ expect(report.counts.entityDescriptors).toBeGreaterThan(0);
12
+ expect(report.counts.listDescriptors).toBeGreaterThan(0);
13
+ expect(report.counts.itemDescriptors).toBeGreaterThan(0);
14
+ if (!report.valid) {
15
+ console.warn("Integrity warnings/errors (graph may have known legacy issues):", JSON.stringify({ errorCount: report.errors.length, warningCount: report.warnings.length }, null, 2));
16
+ }
17
+ expect(report.counts.entityDescriptors).toBeGreaterThanOrEqual(3);
18
+ }, 120_000);
19
+ it("loads smoke entity descriptor from Catalox", async () => {
20
+ const admin = await createLiveAdmin();
21
+ const entityName = smokeEntityName();
22
+ const snapshot = await admin.loadSnapshot();
23
+ const entity = snapshot.entityDescriptors[entityName];
24
+ expect(entity, `entity descriptor "${entityName}"`).toBeDefined();
25
+ expect(entity?.defaultListDescriptorId).toBeTruthy();
26
+ expect(entity?.defaultItemDescriptorId).toBeTruthy();
27
+ const listId = String(entity?.defaultListDescriptorId);
28
+ const itemId = String(entity?.defaultItemDescriptorId);
29
+ expect(snapshot.listDescriptors[listId]).toBeDefined();
30
+ expect(snapshot.itemDescriptors[itemId]).toBeDefined();
31
+ }, 60_000);
32
+ it("lists catalog items for entity, list, and item descriptors", async () => {
33
+ const admin = await createLiveAdmin();
34
+ const entityPage = await admin.catalox.listCatalogItems(MEMORIX_ENTITY_DESCRIPTORS_CATALOG, { limit: 50 });
35
+ expect(entityPage.listOutcome).not.toBe("mapping_blocked");
36
+ expect(entityPage.items.length).toBeGreaterThan(0);
37
+ const listPage = await admin.catalox.listCatalogItems(MEMORIX_LIST_DESCRIPTORS_CATALOG, { limit: 50 });
38
+ expect(listPage.items.length).toBeGreaterThan(0);
39
+ const itemPage = await admin.catalox.listCatalogItems(MEMORIX_ITEM_DESCRIPTORS_CATALOG, { limit: 50 });
40
+ expect(itemPage.items.length).toBeGreaterThan(0);
41
+ }, 60_000);
42
+ it("builds descriptor graph with default-list edges", async () => {
43
+ const admin = await createLiveAdmin();
44
+ const graph = await admin.getDescriptorGraph();
45
+ const entityName = smokeEntityName();
46
+ expect(graph.nodes.some((n) => n.id === entityName && n.kind === "entity")).toBe(true);
47
+ expect(graph.edges.some((e) => e.from === entityName && e.kind === "default-list")).toBe(true);
48
+ }, 60_000);
49
+ it("resolves collection name for smoke entity canonical content type", async () => {
50
+ const admin = await createLiveAdmin();
51
+ const entityName = smokeEntityName();
52
+ const snapshot = await admin.loadSnapshot();
53
+ const entity = snapshot.entityDescriptors[entityName];
54
+ const canonical = String(entity.defaults?.canonicalContentType ??
55
+ "snapshots");
56
+ const collection = await admin.resolveCollection(entityName, canonical);
57
+ expect(collection).toBeTruthy();
58
+ expect(collection).toMatch(/^[a-z0-9-]+$/);
59
+ }, 60_000);
60
+ it("dry-run addProperty on smoke entity does not write", async () => {
61
+ const admin = await createLiveAdmin();
62
+ const entityName = smokeEntityName();
63
+ const snapshot = await admin.loadSnapshot();
64
+ const entity = snapshot.entityDescriptors[entityName];
65
+ const canonical = String(entity.defaults?.canonicalContentType ??
66
+ Object.keys(entity.contentTypes)[0] ??
67
+ "snapshots");
68
+ const propKey = `_live-test-prop-${Date.now()}`;
69
+ const result = await admin.addProperty(entityName, propKey, {
70
+ label: "Live Test Property",
71
+ source: { contentType: canonical, path: "data._live_test" },
72
+ humanReadable: true,
73
+ }, { dryRun: true, addToDefaultList: false, addToDefaultItem: false });
74
+ expect(result.dryRun).toBe(true);
75
+ expect(result.cataloxWrites.length).toBeGreaterThan(0);
76
+ expect(result.errors.some((e) => ["ENTITY_NOT_FOUND", "VALIDATION_FAILED", "UNKNOWN_CONTENT_TYPE"].includes(e.code))).toBe(false);
77
+ }, 60_000);
78
+ it("reconcileDescriptorsWithMongo returns inventory when xronox is configured", async () => {
79
+ if (!process.env.MONGO_URI?.trim()) {
80
+ console.warn("Skipping reconcile — MONGO_URI not set in .env");
81
+ return;
82
+ }
83
+ const admin = await createMemorixDescriptorAdminFromEnv();
84
+ try {
85
+ const result = await admin.reconcileDescriptorsWithMongo();
86
+ expect(result.inventory.entries).toBeDefined();
87
+ expect(Array.isArray(result.suggestions)).toBe(true);
88
+ expect(result.inventory.scannedAt).toBeTruthy();
89
+ }
90
+ catch (err) {
91
+ const message = err instanceof Error ? err.message : String(err);
92
+ if (message.includes("data tier") ||
93
+ message.includes("xronox") ||
94
+ message.includes("Xronox")) {
95
+ console.warn("Skipping reconcile assertion — Xronox not configured:", message);
96
+ return;
97
+ }
98
+ throw err;
99
+ }
100
+ }, 180_000);
101
+ });
102
+ describe("live test gate", () => {
103
+ it.skipIf(live)("skipped — live tests disabled", () => {
104
+ expect(skipReason).toBeTruthy();
105
+ });
106
+ it.runIf(!live)("documents how to enable live tests", () => {
107
+ expect(liveSkipReason()).toMatch(/FIRESTORE_LIVE_TESTS/);
108
+ });
109
+ });
110
+ //# sourceMappingURL=catalox.live.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalox.live.test.js","sourceRoot":"","sources":["../../../src/tests/live/catalox.live.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mutations.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutations.test.d.ts","sourceRoot":"","sources":["../../src/tests/mutations.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,99 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { addContentType } from "../mutations/content-type.js";
3
+ import { addProperty, removeProperty } from "../mutations/property.js";
4
+ import { registerEntityDescriptor } from "../mutations/entity.js";
5
+ import { emptySnapshot, sampleSnapshot } from "./fixtures.js";
6
+ function mockCtx(snapshot = sampleSnapshot()) {
7
+ return {
8
+ catalox: {},
9
+ env: process.env,
10
+ loadSnapshot: async () => snapshot,
11
+ };
12
+ }
13
+ describe("content type mutations (dry-run)", () => {
14
+ it("rejects duplicate content type keys", async () => {
15
+ const result = await addContentType(mockCtx(), "assets", { key: "snapshots", postfix: "snapshots" }, { dryRun: true });
16
+ expect(result.ok).toBe(false);
17
+ expect(result.errors[0]?.code).toBe("CONTENT_TYPE_EXISTS");
18
+ });
19
+ it("plans add of a new content type", async () => {
20
+ const result = await addContentType(mockCtx(), "assets", { key: "enrichment", postfix: "enrichment" }, { dryRun: true, skipMongo: true });
21
+ expect(result.ok).toBe(true);
22
+ expect(result.cataloxWrites).toHaveLength(1);
23
+ expect(result.data?.key).toBe("enrichment");
24
+ });
25
+ it("blocks removal when content type is referenced by a property", async () => {
26
+ const snapshot = sampleSnapshot();
27
+ const assets = snapshot.entityDescriptors.assets;
28
+ snapshot.entityDescriptors.assets = {
29
+ ...assets,
30
+ properties: {
31
+ ...assets.properties,
32
+ enrichmentFlag: {
33
+ label: "Enrichment",
34
+ source: { contentType: "analysis", path: "data.flag" },
35
+ humanReadable: true,
36
+ },
37
+ },
38
+ };
39
+ const result = await import("../mutations/content-type.js").then((m) => m.removeContentType(mockCtx(snapshot), "assets", "analysis", { dryRun: true }));
40
+ expect(result.ok).toBe(false);
41
+ expect(result.errors[0]?.code).toBe("CONTENT_TYPE_IN_USE");
42
+ });
43
+ });
44
+ describe("property mutations (dry-run)", () => {
45
+ it("adds a property and cascades to default list/item", async () => {
46
+ const result = await addProperty(mockCtx(), "assets", "agentType", {
47
+ label: "Agent Type",
48
+ source: { contentType: "snapshots", path: "data.agent_type" },
49
+ humanReadable: true,
50
+ }, { dryRun: true, addToDefaultList: true, addToDefaultItem: true });
51
+ expect(result.ok).toBe(true);
52
+ expect(result.cataloxWrites.length).toBeGreaterThanOrEqual(1);
53
+ });
54
+ it("blocks removal when property path is used in a relation", async () => {
55
+ const snapshot = sampleSnapshot();
56
+ snapshot.entityDescriptors.assets = {
57
+ ...snapshot.entityDescriptors.assets,
58
+ relations: {
59
+ linked: {
60
+ targetEntity: "assets",
61
+ type: "oneToMany",
62
+ source: { contentType: "snapshots", path: "data.ipAddress" },
63
+ target: { contentType: "snapshots", path: "data.id" },
64
+ },
65
+ },
66
+ };
67
+ const result = await removeProperty(mockCtx(snapshot), "assets", "ipAddress", { dryRun: true });
68
+ expect(result.ok).toBe(false);
69
+ expect(result.errors[0]?.code).toBe("PROPERTY_IN_RELATION");
70
+ });
71
+ });
72
+ describe("registerEntityDescriptor (dry-run)", () => {
73
+ it("rejects duplicate entity registration", async () => {
74
+ const result = await registerEntityDescriptor(mockCtx(), {
75
+ entityName: "assets",
76
+ target: "entity",
77
+ collectionPrefix: "assets",
78
+ canonicalContentType: { key: "snapshots", postfix: "snapshots" },
79
+ }, { dryRun: true });
80
+ expect(result.ok).toBe(false);
81
+ expect(result.errors[0]?.code).toBe("ENTITY_EXISTS");
82
+ });
83
+ it("registers a new knowledge entity graph in dry-run", async () => {
84
+ const result = await registerEntityDescriptor(mockCtx(emptySnapshot()), {
85
+ entityName: "impact-stories",
86
+ target: "knowledge",
87
+ collectionPrefix: "impact-stories",
88
+ canonicalContentType: {
89
+ key: "narratives",
90
+ postfix: "narratives",
91
+ collection: "impact-stories-narratives",
92
+ },
93
+ identity: { idField: "knowledgeId" },
94
+ }, { dryRun: true });
95
+ expect(result.ok).toBe(true);
96
+ expect(result.cataloxWrites).toHaveLength(3);
97
+ });
98
+ });
99
+ //# sourceMappingURL=mutations.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutations.test.js","sourceRoot":"","sources":["../../src/tests/mutations.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=seeds.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seeds.test.d.ts","sourceRoot":"","sources":["../../src/tests/seeds.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { buildDescriptorSeedManifest } from "../seeds/index.js";
6
+ import { sampleSnapshot } from "./fixtures.js";
7
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
8
+ const retrievalInputs = resolve(packageRoot, "..", "memorix-retrieval", "catalox-seeds", "inputs");
9
+ describe("buildDescriptorSeedManifest", () => {
10
+ it("builds manifest items from an in-memory snapshot", async () => {
11
+ const manifest = await buildDescriptorSeedManifest(sampleSnapshot(), {
12
+ domains: ["network"],
13
+ agents: ["neo"],
14
+ });
15
+ expect(manifest.items.length).toBeGreaterThanOrEqual(3);
16
+ expect(manifest.items.some((i) => i.catalogId.includes("entity"))).toBe(true);
17
+ expect(manifest.catalogs.some((c) => c.catalogId.includes("entity"))).toBe(true);
18
+ });
19
+ it.skipIf(!existsSync(retrievalInputs))("round-trips memorix-retrieval catalox-seeds/inputs when sibling repo exists", async () => {
20
+ const { createSeedInputsReader, loadMemorixRetrievalSeedScopeFromInputs } = await import("@x12i/memorix-retrieval");
21
+ const readJson = createSeedInputsReader(retrievalInputs);
22
+ const scope = await loadMemorixRetrievalSeedScopeFromInputs(retrievalInputs);
23
+ const snapshot = {
24
+ entityDescriptors: {},
25
+ listDescriptors: {},
26
+ itemDescriptors: {},
27
+ completionMappings: {},
28
+ };
29
+ for (const file of [
30
+ "entity-descriptors/assets.json",
31
+ "entity-descriptors/vulnerabilities.json",
32
+ "entity-descriptors/variabilities-groups.json",
33
+ "list-descriptors/assets-main-list.json",
34
+ "item-descriptors/asset-detail-item.json",
35
+ ]) {
36
+ const data = await readJson(file);
37
+ const id = String(data.id);
38
+ if (file.startsWith("entity-descriptors/")) {
39
+ snapshot.entityDescriptors[id] = data;
40
+ }
41
+ else if (file.startsWith("list-descriptors/")) {
42
+ snapshot.listDescriptors[id] = data;
43
+ }
44
+ else {
45
+ snapshot.itemDescriptors[id] = data;
46
+ }
47
+ }
48
+ const manifest = await buildDescriptorSeedManifest(snapshot, scope);
49
+ expect(manifest.items.length).toBeGreaterThanOrEqual(5);
50
+ const entityIds = manifest.items
51
+ .filter((i) => i.catalogId.includes("entity"))
52
+ .map((i) => String(i.data.id));
53
+ expect(entityIds).toContain("assets");
54
+ expect(entityIds).toContain("vulnerabilities");
55
+ });
56
+ });
57
+ //# sourceMappingURL=seeds.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seeds.test.js","sourceRoot":"","sources":["../../src/tests/seeds.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACjF,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE,mBAAmB,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;AAEnG,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,QAAQ,GAAG,MAAM,2BAA2B,CAAC,cAAc,EAAE,EAAE;YACnE,OAAO,EAAE,CAAC,SAAS,CAAC;YACpB,MAAM,EAAE,CAAC,KAAK,CAAC;SAChB,CAAC,CAAC;QACH,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9E,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CACrC,6EAA6E,EAC7E,KAAK,IAAI,EAAE;QACT,MAAM,EAAE,sBAAsB,EAAE,uCAAuC,EAAE,GACvE,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,sBAAsB,CAAC,eAAe,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,MAAM,uCAAuC,CAAC,eAAe,CAAC,CAAC;QAE7E,MAAM,QAAQ,GAAG;YACf,iBAAiB,EAAE,EAA6C;YAChE,eAAe,EAAE,EAA6C;YAC9D,eAAe,EAAE,EAA6C;YAC9D,kBAAkB,EAAE,EAAE;SACvB,CAAC;QAEF,KAAK,MAAM,IAAI,IAAI;YACjB,gCAAgC;YAChC,yCAAyC;YACzC,8CAA8C;YAC9C,wCAAwC;YACxC,yCAAyC;SAC1C,EAAE,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,qBAAqB,CAAC,EAAE,CAAC;gBAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;YACxC,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBAChD,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;YACtC,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;YACtC,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,2BAA2B,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACpE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK;aAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;aAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACjD,CAAC,CACF,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/tests/setup.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import { config } from "dotenv";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
5
+ config({ path: resolve(packageRoot, ".env") });
6
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/tests/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACjF,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC"}
@@ -2,62 +2,8 @@ import { describe, expect, it } from "vitest";
2
2
  import { defaultItemDescriptorId, defaultListDescriptorId, isKebabCase, singularizeEntityName, validateMemorixEntityDescriptor, validateMemorixEntityListDescriptor, validateMemorixItemDescriptor, } from "../validation/descriptor-validation.js";
3
3
  import { validateDescriptorSnapshot } from "../integrity/validate.js";
4
4
  import { buildDescriptorGraph } from "../integrity/graph.js";
5
- const sampleSnapshot = () => ({
6
- entityDescriptors: {
7
- assets: {
8
- id: "assets",
9
- entityName: "assets",
10
- target: "entity",
11
- defaultListDescriptorId: "assets-main-list",
12
- defaultItemDescriptorId: "asset-detail-item",
13
- collectionPrefix: "assets",
14
- identity: {
15
- allowedIdFields: ["entityId", "eventId"],
16
- requiredExactlyOne: true,
17
- defaultIdField: "entityId",
18
- },
19
- defaults: {
20
- canonicalContentType: "snapshots",
21
- dataRoot: "data",
22
- effectiveDatePath: "capturedAt",
23
- fallbackEffectiveDatePaths: [],
24
- },
25
- contentTypes: {
26
- snapshots: {
27
- postfix: "snapshots",
28
- collection: "assets-snapshots",
29
- isCanonical: true,
30
- },
31
- },
32
- properties: {
33
- ipAddress: {
34
- label: "IP Address",
35
- source: { contentType: "snapshots", path: "data.ip_address" },
36
- humanReadable: true,
37
- },
38
- },
39
- },
40
- },
41
- listDescriptors: {
42
- "assets-main-list": {
43
- id: "assets-main-list",
44
- entity: "assets",
45
- leadingContentType: "snapshots",
46
- pagination: { enabled: true, defaultLimit: 50, maxLimit: 200 },
47
- fields: ["ipAddress"],
48
- },
49
- },
50
- itemDescriptors: {
51
- "asset-detail-item": {
52
- id: "asset-detail-item",
53
- entity: "assets",
54
- identity: { idField: "entityId" },
55
- contentTypes: [{ contentType: "snapshots", required: true }],
56
- sections: [{ id: "summary", title: "Summary", fields: ["ipAddress"] }],
57
- },
58
- },
59
- completionMappings: {},
60
- });
5
+ import { emptySnapshot, sampleSnapshot } from "./fixtures.js";
6
+ import { registerEntityDescriptor } from "../mutations/entity.js";
61
7
  describe("descriptor validation helpers", () => {
62
8
  it("validates kebab-case entity names", () => {
63
9
  expect(isKebabCase("impact-stories")).toBe(true);
@@ -86,15 +32,10 @@ describe("descriptor validation helpers", () => {
86
32
  });
87
33
  describe("register entity plan", () => {
88
34
  it("projects entity + default list/item writes", async () => {
89
- const { registerEntityDescriptor } = await import("../mutations/entity.js");
90
- const snapshot = sampleSnapshot();
91
- snapshot.entityDescriptors = {};
92
- snapshot.listDescriptors = {};
93
- snapshot.itemDescriptors = {};
94
35
  const result = await registerEntityDescriptor({
95
36
  catalox: {},
96
37
  env: process.env,
97
- loadSnapshot: async () => snapshot,
38
+ loadSnapshot: async () => emptySnapshot(),
98
39
  }, {
99
40
  entityName: "impact-stories",
100
41
  target: "knowledge",