@contractspec/example.versioned-knowledge-base 1.57.0 → 1.59.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 (76) hide show
  1. package/.turbo/turbo-build.log +52 -52
  2. package/.turbo/turbo-prebuild.log +1 -0
  3. package/CHANGELOG.md +25 -0
  4. package/dist/browser/docs/index.js +44 -0
  5. package/dist/browser/docs/versioned-knowledge-base.docblock.js +44 -0
  6. package/dist/browser/entities/index.js +74 -0
  7. package/dist/browser/entities/models.js +74 -0
  8. package/dist/browser/events.js +101 -0
  9. package/dist/browser/example.js +35 -0
  10. package/dist/browser/handlers/index.js +115 -0
  11. package/dist/browser/handlers/memory.handlers.js +115 -0
  12. package/dist/browser/index.js +586 -0
  13. package/dist/browser/operations/index.js +257 -0
  14. package/dist/browser/operations/kb.js +257 -0
  15. package/dist/browser/versioned-knowledge-base.feature.js +36 -0
  16. package/dist/docs/index.d.ts +2 -1
  17. package/dist/docs/index.d.ts.map +1 -0
  18. package/dist/docs/index.js +45 -1
  19. package/dist/docs/versioned-knowledge-base.docblock.d.ts +2 -1
  20. package/dist/docs/versioned-knowledge-base.docblock.d.ts.map +1 -0
  21. package/dist/docs/versioned-knowledge-base.docblock.js +42 -28
  22. package/dist/entities/index.d.ts +2 -2
  23. package/dist/entities/index.d.ts.map +1 -0
  24. package/dist/entities/index.js +75 -3
  25. package/dist/entities/models.d.ts +127 -132
  26. package/dist/entities/models.d.ts.map +1 -1
  27. package/dist/entities/models.js +69 -145
  28. package/dist/events.d.ts +52 -58
  29. package/dist/events.d.ts.map +1 -1
  30. package/dist/events.js +92 -114
  31. package/dist/example.d.ts +2 -6
  32. package/dist/example.d.ts.map +1 -1
  33. package/dist/example.js +34 -46
  34. package/dist/handlers/index.d.ts +2 -2
  35. package/dist/handlers/index.d.ts.map +1 -0
  36. package/dist/handlers/index.js +116 -3
  37. package/dist/handlers/memory.handlers.d.ts +62 -64
  38. package/dist/handlers/memory.handlers.d.ts.map +1 -1
  39. package/dist/handlers/memory.handlers.js +110 -97
  40. package/dist/handlers/memory.handlers.test.d.ts +2 -0
  41. package/dist/handlers/memory.handlers.test.d.ts.map +1 -0
  42. package/dist/index.d.ts +13 -9
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +587 -11
  45. package/dist/node/docs/index.js +44 -0
  46. package/dist/node/docs/versioned-knowledge-base.docblock.js +44 -0
  47. package/dist/node/entities/index.js +74 -0
  48. package/dist/node/entities/models.js +74 -0
  49. package/dist/node/events.js +101 -0
  50. package/dist/node/example.js +35 -0
  51. package/dist/node/handlers/index.js +115 -0
  52. package/dist/node/handlers/memory.handlers.js +115 -0
  53. package/dist/node/index.js +586 -0
  54. package/dist/node/operations/index.js +257 -0
  55. package/dist/node/operations/kb.js +257 -0
  56. package/dist/node/versioned-knowledge-base.feature.js +36 -0
  57. package/dist/operations/index.d.ts +2 -2
  58. package/dist/operations/index.d.ts.map +1 -0
  59. package/dist/operations/index.js +257 -2
  60. package/dist/operations/kb.d.ts +252 -258
  61. package/dist/operations/kb.d.ts.map +1 -1
  62. package/dist/operations/kb.js +246 -244
  63. package/dist/versioned-knowledge-base.feature.d.ts +1 -6
  64. package/dist/versioned-knowledge-base.feature.d.ts.map +1 -1
  65. package/dist/versioned-knowledge-base.feature.js +35 -68
  66. package/package.json +141 -38
  67. package/tsdown.config.js +1 -2
  68. package/.turbo/turbo-build$colon$bundle.log +0 -52
  69. package/dist/docs/versioned-knowledge-base.docblock.js.map +0 -1
  70. package/dist/entities/models.js.map +0 -1
  71. package/dist/events.js.map +0 -1
  72. package/dist/example.js.map +0 -1
  73. package/dist/handlers/memory.handlers.js.map +0 -1
  74. package/dist/operations/kb.js.map +0 -1
  75. package/dist/versioned-knowledge-base.feature.js.map +0 -1
  76. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,74 @@
1
+ // src/entities/models.ts
2
+ import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
3
+ var SourceDocumentModel = defineSchemaModel({
4
+ name: "SourceDocument",
5
+ description: "Immutable raw source document metadata referencing a stored file.",
6
+ fields: {
7
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
8
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
9
+ authority: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
10
+ title: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
11
+ fetchedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
12
+ hash: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
13
+ fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
14
+ }
15
+ });
16
+ var SourceRefModel = defineSchemaModel({
17
+ name: "SourceRef",
18
+ description: "Reference to a source document used to justify a rule version.",
19
+ fields: {
20
+ sourceDocumentId: {
21
+ type: ScalarTypeEnum.String_unsecure(),
22
+ isOptional: false
23
+ },
24
+ excerpt: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
25
+ }
26
+ });
27
+ var RuleModel = defineSchemaModel({
28
+ name: "Rule",
29
+ description: "Curated rule (stable identity) with topic + jurisdiction scope.",
30
+ fields: {
31
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
32
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
33
+ topicKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
34
+ }
35
+ });
36
+ var RuleVersionModel = defineSchemaModel({
37
+ name: "RuleVersion",
38
+ description: "A versioned rule content with source references and approval status.",
39
+ fields: {
40
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
41
+ ruleId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
42
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
43
+ topicKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
44
+ version: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
45
+ content: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
46
+ sourceRefs: { type: SourceRefModel, isArray: true, isOptional: false },
47
+ status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
48
+ approvedBy: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
49
+ approvedAt: { type: ScalarTypeEnum.DateTime(), isOptional: true },
50
+ createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
51
+ }
52
+ });
53
+ var KBSnapshotModel = defineSchemaModel({
54
+ name: "KBSnapshot",
55
+ description: "Published KB snapshot (as-of) referencing approved rule versions.",
56
+ fields: {
57
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
58
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
59
+ asOfDate: { type: ScalarTypeEnum.DateTime(), isOptional: false },
60
+ includedRuleVersionIds: {
61
+ type: ScalarTypeEnum.String_unsecure(),
62
+ isArray: true,
63
+ isOptional: false
64
+ },
65
+ publishedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
66
+ }
67
+ });
68
+ export {
69
+ SourceRefModel,
70
+ SourceDocumentModel,
71
+ RuleVersionModel,
72
+ RuleModel,
73
+ KBSnapshotModel
74
+ };
@@ -0,0 +1,74 @@
1
+ // src/entities/models.ts
2
+ import { ScalarTypeEnum, defineSchemaModel } from "@contractspec/lib.schema";
3
+ var SourceDocumentModel = defineSchemaModel({
4
+ name: "SourceDocument",
5
+ description: "Immutable raw source document metadata referencing a stored file.",
6
+ fields: {
7
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
8
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
9
+ authority: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
10
+ title: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
11
+ fetchedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false },
12
+ hash: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
13
+ fileId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
14
+ }
15
+ });
16
+ var SourceRefModel = defineSchemaModel({
17
+ name: "SourceRef",
18
+ description: "Reference to a source document used to justify a rule version.",
19
+ fields: {
20
+ sourceDocumentId: {
21
+ type: ScalarTypeEnum.String_unsecure(),
22
+ isOptional: false
23
+ },
24
+ excerpt: { type: ScalarTypeEnum.String_unsecure(), isOptional: true }
25
+ }
26
+ });
27
+ var RuleModel = defineSchemaModel({
28
+ name: "Rule",
29
+ description: "Curated rule (stable identity) with topic + jurisdiction scope.",
30
+ fields: {
31
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
32
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
33
+ topicKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
34
+ }
35
+ });
36
+ var RuleVersionModel = defineSchemaModel({
37
+ name: "RuleVersion",
38
+ description: "A versioned rule content with source references and approval status.",
39
+ fields: {
40
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
41
+ ruleId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
42
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
43
+ topicKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
44
+ version: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
45
+ content: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
46
+ sourceRefs: { type: SourceRefModel, isArray: true, isOptional: false },
47
+ status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
48
+ approvedBy: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
49
+ approvedAt: { type: ScalarTypeEnum.DateTime(), isOptional: true },
50
+ createdAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
51
+ }
52
+ });
53
+ var KBSnapshotModel = defineSchemaModel({
54
+ name: "KBSnapshot",
55
+ description: "Published KB snapshot (as-of) referencing approved rule versions.",
56
+ fields: {
57
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
58
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
59
+ asOfDate: { type: ScalarTypeEnum.DateTime(), isOptional: false },
60
+ includedRuleVersionIds: {
61
+ type: ScalarTypeEnum.String_unsecure(),
62
+ isArray: true,
63
+ isOptional: false
64
+ },
65
+ publishedAt: { type: ScalarTypeEnum.DateTime(), isOptional: false }
66
+ }
67
+ });
68
+ export {
69
+ SourceRefModel,
70
+ SourceDocumentModel,
71
+ RuleVersionModel,
72
+ RuleModel,
73
+ KBSnapshotModel
74
+ };
@@ -0,0 +1,101 @@
1
+ // src/events.ts
2
+ import { defineEvent, defineSchemaModel } from "@contractspec/lib.contracts";
3
+ import { ScalarTypeEnum } from "@contractspec/lib.schema";
4
+ var KbSourceIngestedPayload = defineSchemaModel({
5
+ name: "KbSourceIngestedPayload",
6
+ description: "Emitted when a source document is ingested.",
7
+ fields: {
8
+ sourceDocumentId: {
9
+ type: ScalarTypeEnum.String_unsecure(),
10
+ isOptional: false
11
+ },
12
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
13
+ hash: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
14
+ }
15
+ });
16
+ var KbSourceIngestedEvent = defineEvent({
17
+ meta: {
18
+ key: "kb.source.ingested",
19
+ version: "1.0.0",
20
+ description: "Source document ingested (immutable).",
21
+ stability: "experimental",
22
+ owners: ["@examples"],
23
+ tags: ["knowledge"]
24
+ },
25
+ payload: KbSourceIngestedPayload
26
+ });
27
+ var KbRuleVersionCreatedPayload = defineSchemaModel({
28
+ name: "KbRuleVersionCreatedPayload",
29
+ description: "Emitted when a rule version draft is created.",
30
+ fields: {
31
+ ruleVersionId: {
32
+ type: ScalarTypeEnum.String_unsecure(),
33
+ isOptional: false
34
+ },
35
+ ruleId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
36
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
37
+ status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
38
+ }
39
+ });
40
+ var KbRuleVersionCreatedEvent = defineEvent({
41
+ meta: {
42
+ key: "kb.ruleVersion.created",
43
+ version: "1.0.0",
44
+ description: "Rule version created (draft).",
45
+ stability: "experimental",
46
+ owners: ["@examples"],
47
+ tags: ["knowledge"]
48
+ },
49
+ payload: KbRuleVersionCreatedPayload
50
+ });
51
+ var KbRuleVersionApprovedPayload = defineSchemaModel({
52
+ name: "KbRuleVersionApprovedPayload",
53
+ description: "Emitted when a rule version is approved.",
54
+ fields: {
55
+ ruleVersionId: {
56
+ type: ScalarTypeEnum.String_unsecure(),
57
+ isOptional: false
58
+ },
59
+ approver: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
60
+ }
61
+ });
62
+ var KbRuleVersionApprovedEvent = defineEvent({
63
+ meta: {
64
+ key: "kb.ruleVersion.approved",
65
+ version: "1.0.0",
66
+ description: "Rule version approved (human verified).",
67
+ stability: "experimental",
68
+ owners: ["@examples"],
69
+ tags: ["knowledge"]
70
+ },
71
+ payload: KbRuleVersionApprovedPayload
72
+ });
73
+ var KbSnapshotPublishedPayload = defineSchemaModel({
74
+ name: "KbSnapshotPublishedPayload",
75
+ description: "Emitted when a KB snapshot is published.",
76
+ fields: {
77
+ snapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
78
+ jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
79
+ includedRuleVersionsCount: {
80
+ type: ScalarTypeEnum.Int_unsecure(),
81
+ isOptional: false
82
+ }
83
+ }
84
+ });
85
+ var KbSnapshotPublishedEvent = defineEvent({
86
+ meta: {
87
+ key: "kb.snapshot.published",
88
+ version: "1.0.0",
89
+ description: "KB snapshot published.",
90
+ stability: "experimental",
91
+ owners: ["@examples"],
92
+ tags: ["knowledge"]
93
+ },
94
+ payload: KbSnapshotPublishedPayload
95
+ });
96
+ export {
97
+ KbSourceIngestedEvent,
98
+ KbSnapshotPublishedEvent,
99
+ KbRuleVersionCreatedEvent,
100
+ KbRuleVersionApprovedEvent
101
+ };
@@ -0,0 +1,35 @@
1
+ // src/example.ts
2
+ import { defineExample } from "@contractspec/lib.contracts";
3
+ var example = defineExample({
4
+ meta: {
5
+ key: "versioned-knowledge-base",
6
+ version: "1.0.0",
7
+ title: "Versioned Knowledge Base",
8
+ description: "Curated KB with immutable sources, reviewable rule versions, and published snapshots.",
9
+ kind: "knowledge",
10
+ visibility: "public",
11
+ stability: "experimental",
12
+ owners: ["@platform.core"],
13
+ tags: ["knowledge", "versioning", "snapshots"]
14
+ },
15
+ docs: {
16
+ rootDocId: "docs.examples.versioned-knowledge-base"
17
+ },
18
+ entrypoints: {
19
+ packageName: "@contractspec/example.versioned-knowledge-base",
20
+ feature: "./feature",
21
+ contracts: "./contracts",
22
+ handlers: "./handlers",
23
+ docs: "./docs"
24
+ },
25
+ surfaces: {
26
+ templates: true,
27
+ sandbox: { enabled: true, modes: ["markdown", "specs", "builder"] },
28
+ studio: { enabled: true, installable: true },
29
+ mcp: { enabled: true }
30
+ }
31
+ });
32
+ var example_default = example;
33
+ export {
34
+ example_default as default
35
+ };
@@ -0,0 +1,115 @@
1
+ // src/handlers/memory.handlers.ts
2
+ function createMemoryKbStore() {
3
+ return {
4
+ sources: new Map,
5
+ rules: new Map,
6
+ ruleVersions: new Map,
7
+ snapshots: new Map,
8
+ nextRuleVersionNumberByRuleId: new Map
9
+ };
10
+ }
11
+ function stableId(prefix, value) {
12
+ return `${prefix}_${value.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
13
+ }
14
+ function createMemoryKbHandlers(store) {
15
+ async function createRule(rule) {
16
+ store.rules.set(rule.id, rule);
17
+ return rule;
18
+ }
19
+ async function ingestSource(input) {
20
+ const id = stableId("src", `${input.jurisdiction}_${input.hash}`);
21
+ const doc = { id, ...input };
22
+ store.sources.set(id, doc);
23
+ return doc;
24
+ }
25
+ async function upsertRuleVersion(input) {
26
+ if (!input.sourceRefs.length) {
27
+ throw new Error("SOURCE_REFS_REQUIRED");
28
+ }
29
+ const rule = store.rules.get(input.ruleId);
30
+ if (!rule) {
31
+ throw new Error("RULE_NOT_FOUND");
32
+ }
33
+ const next = (store.nextRuleVersionNumberByRuleId.get(input.ruleId) ?? 0) + 1;
34
+ const id = stableId("rv", `${input.ruleId}_${next}`);
35
+ const ruleVersion = {
36
+ id,
37
+ ruleId: input.ruleId,
38
+ jurisdiction: rule.jurisdiction,
39
+ topicKey: rule.topicKey,
40
+ version: next.toString(),
41
+ content: input.content,
42
+ sourceRefs: input.sourceRefs,
43
+ status: "draft",
44
+ createdAt: new Date,
45
+ approvedAt: undefined,
46
+ approvedBy: undefined
47
+ };
48
+ store.ruleVersions.set(id, ruleVersion);
49
+ return ruleVersion;
50
+ }
51
+ async function approveRuleVersion(input) {
52
+ const existing = store.ruleVersions.get(input.ruleVersionId);
53
+ if (!existing) {
54
+ throw new Error("RULE_VERSION_NOT_FOUND");
55
+ }
56
+ const approved = {
57
+ ...existing,
58
+ status: "approved",
59
+ approvedBy: input.approver,
60
+ approvedAt: new Date
61
+ };
62
+ store.ruleVersions.set(approved.id, approved);
63
+ return approved;
64
+ }
65
+ async function publishSnapshot(input) {
66
+ const approved = [...store.ruleVersions.values()].filter((rv) => rv.status === "approved" && rv.jurisdiction === input.jurisdiction);
67
+ if (approved.length === 0) {
68
+ throw new Error("NO_APPROVED_RULES");
69
+ }
70
+ const includedRuleVersionIds = approved.map((rv) => rv.id).sort();
71
+ const id = stableId("snap", `${input.jurisdiction}_${input.asOfDate.toISOString().slice(0, 10)}_${includedRuleVersionIds.length}`);
72
+ const snapshot = {
73
+ id,
74
+ jurisdiction: input.jurisdiction,
75
+ asOfDate: input.asOfDate,
76
+ includedRuleVersionIds,
77
+ publishedAt: new Date
78
+ };
79
+ store.snapshots.set(id, snapshot);
80
+ return snapshot;
81
+ }
82
+ async function search(input) {
83
+ const snapshot = store.snapshots.get(input.snapshotId);
84
+ if (!snapshot) {
85
+ throw new Error("SNAPSHOT_NOT_FOUND");
86
+ }
87
+ if (snapshot.jurisdiction !== input.jurisdiction) {
88
+ throw new Error("JURISDICTION_MISMATCH");
89
+ }
90
+ const q = input.query.toLowerCase();
91
+ const tokens = q.split(/\s+/).map((t) => t.trim()).filter(Boolean);
92
+ const items = snapshot.includedRuleVersionIds.map((id) => store.ruleVersions.get(id)).filter((rv) => Boolean(rv)).filter((rv) => {
93
+ if (tokens.length === 0)
94
+ return true;
95
+ const hay = rv.content.toLowerCase();
96
+ return tokens.every((token) => hay.includes(token));
97
+ }).map((rv) => ({
98
+ ruleVersionId: rv.id,
99
+ excerpt: rv.content.slice(0, 120)
100
+ }));
101
+ return { items };
102
+ }
103
+ return {
104
+ createRule,
105
+ ingestSource,
106
+ upsertRuleVersion,
107
+ approveRuleVersion,
108
+ publishSnapshot,
109
+ search
110
+ };
111
+ }
112
+ export {
113
+ createMemoryKbStore,
114
+ createMemoryKbHandlers
115
+ };
@@ -0,0 +1,115 @@
1
+ // src/handlers/memory.handlers.ts
2
+ function createMemoryKbStore() {
3
+ return {
4
+ sources: new Map,
5
+ rules: new Map,
6
+ ruleVersions: new Map,
7
+ snapshots: new Map,
8
+ nextRuleVersionNumberByRuleId: new Map
9
+ };
10
+ }
11
+ function stableId(prefix, value) {
12
+ return `${prefix}_${value.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
13
+ }
14
+ function createMemoryKbHandlers(store) {
15
+ async function createRule(rule) {
16
+ store.rules.set(rule.id, rule);
17
+ return rule;
18
+ }
19
+ async function ingestSource(input) {
20
+ const id = stableId("src", `${input.jurisdiction}_${input.hash}`);
21
+ const doc = { id, ...input };
22
+ store.sources.set(id, doc);
23
+ return doc;
24
+ }
25
+ async function upsertRuleVersion(input) {
26
+ if (!input.sourceRefs.length) {
27
+ throw new Error("SOURCE_REFS_REQUIRED");
28
+ }
29
+ const rule = store.rules.get(input.ruleId);
30
+ if (!rule) {
31
+ throw new Error("RULE_NOT_FOUND");
32
+ }
33
+ const next = (store.nextRuleVersionNumberByRuleId.get(input.ruleId) ?? 0) + 1;
34
+ const id = stableId("rv", `${input.ruleId}_${next}`);
35
+ const ruleVersion = {
36
+ id,
37
+ ruleId: input.ruleId,
38
+ jurisdiction: rule.jurisdiction,
39
+ topicKey: rule.topicKey,
40
+ version: next.toString(),
41
+ content: input.content,
42
+ sourceRefs: input.sourceRefs,
43
+ status: "draft",
44
+ createdAt: new Date,
45
+ approvedAt: undefined,
46
+ approvedBy: undefined
47
+ };
48
+ store.ruleVersions.set(id, ruleVersion);
49
+ return ruleVersion;
50
+ }
51
+ async function approveRuleVersion(input) {
52
+ const existing = store.ruleVersions.get(input.ruleVersionId);
53
+ if (!existing) {
54
+ throw new Error("RULE_VERSION_NOT_FOUND");
55
+ }
56
+ const approved = {
57
+ ...existing,
58
+ status: "approved",
59
+ approvedBy: input.approver,
60
+ approvedAt: new Date
61
+ };
62
+ store.ruleVersions.set(approved.id, approved);
63
+ return approved;
64
+ }
65
+ async function publishSnapshot(input) {
66
+ const approved = [...store.ruleVersions.values()].filter((rv) => rv.status === "approved" && rv.jurisdiction === input.jurisdiction);
67
+ if (approved.length === 0) {
68
+ throw new Error("NO_APPROVED_RULES");
69
+ }
70
+ const includedRuleVersionIds = approved.map((rv) => rv.id).sort();
71
+ const id = stableId("snap", `${input.jurisdiction}_${input.asOfDate.toISOString().slice(0, 10)}_${includedRuleVersionIds.length}`);
72
+ const snapshot = {
73
+ id,
74
+ jurisdiction: input.jurisdiction,
75
+ asOfDate: input.asOfDate,
76
+ includedRuleVersionIds,
77
+ publishedAt: new Date
78
+ };
79
+ store.snapshots.set(id, snapshot);
80
+ return snapshot;
81
+ }
82
+ async function search(input) {
83
+ const snapshot = store.snapshots.get(input.snapshotId);
84
+ if (!snapshot) {
85
+ throw new Error("SNAPSHOT_NOT_FOUND");
86
+ }
87
+ if (snapshot.jurisdiction !== input.jurisdiction) {
88
+ throw new Error("JURISDICTION_MISMATCH");
89
+ }
90
+ const q = input.query.toLowerCase();
91
+ const tokens = q.split(/\s+/).map((t) => t.trim()).filter(Boolean);
92
+ const items = snapshot.includedRuleVersionIds.map((id) => store.ruleVersions.get(id)).filter((rv) => Boolean(rv)).filter((rv) => {
93
+ if (tokens.length === 0)
94
+ return true;
95
+ const hay = rv.content.toLowerCase();
96
+ return tokens.every((token) => hay.includes(token));
97
+ }).map((rv) => ({
98
+ ruleVersionId: rv.id,
99
+ excerpt: rv.content.slice(0, 120)
100
+ }));
101
+ return { items };
102
+ }
103
+ return {
104
+ createRule,
105
+ ingestSource,
106
+ upsertRuleVersion,
107
+ approveRuleVersion,
108
+ publishSnapshot,
109
+ search
110
+ };
111
+ }
112
+ export {
113
+ createMemoryKbStore,
114
+ createMemoryKbHandlers
115
+ };