@lssm/example.policy-safe-knowledge-assistant 0.0.0-canary-20251213172311

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.
@@ -0,0 +1,19 @@
1
+ $ bun build:bundle && bun build:types
2
+ $ tsdown
3
+ ℹ tsdown v0.17.0 powered by rolldown v1.0.0-beta.53
4
+ ℹ config file: /home/runner/work/contractspec/contractspec/packages/examples/policy-safe-knowledge-assistant/tsdown.config.js
5
+ ℹ entry: src/example.ts, src/feature.ts, src/index.ts, src/docs/index.ts, src/docs/policy-safe-knowledge-assistant.docblock.ts, src/orchestrator/buildAnswer.ts, src/seed/fixtures.ts, src/seed/index.ts
6
+ ℹ target: esnext
7
+ ℹ tsconfig: tsconfig.json
8
+ ℹ Build start
9
+ ℹ dist/orchestrator/buildAnswer.js 1.65 kB │ gzip: 0.71 kB
10
+ ℹ dist/docs/policy-safe-knowledge-assistant.docblock.js 1.62 kB │ gzip: 0.83 kB
11
+ ℹ dist/feature.js 1.57 kB │ gzip: 0.57 kB
12
+ ℹ dist/example.js 0.72 kB │ gzip: 0.41 kB
13
+ ℹ dist/seed/fixtures.js 0.56 kB │ gzip: 0.31 kB
14
+ ℹ dist/index.js 0.35 kB │ gzip: 0.20 kB
15
+ ℹ dist/seed/index.js 0.07 kB │ gzip: 0.08 kB
16
+ ℹ dist/docs/index.js 0.05 kB │ gzip: 0.07 kB
17
+ ℹ 8 files, total: 6.60 kB
18
+ ✔ Build complete in 57ms
19
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # @lssm/example.policy-safe-knowledge-assistant
2
+
3
+ ## 0.0.0-canary-20251213172311
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [3086383]
8
+ - @lssm/lib.contracts@0.0.0-canary-20251213172311
9
+ - @lssm/lib.jobs@0.0.0-canary-20251213172311
10
+ - @lssm/example.kb-update-pipeline@0.0.0-canary-20251213172311
11
+ - @lssm/example.locale-jurisdiction-gate@0.0.0-canary-20251213172311
12
+ - @lssm/example.versioned-knowledge-base@0.0.0-canary-20251213172311
13
+ - @lssm/lib.feature-flags@0.0.0-canary-20251213172311
14
+ - @lssm/lib.files@0.0.0-canary-20251213172311
15
+ - @lssm/lib.identity-rbac@0.0.0-canary-20251213172311
16
+ - @lssm/lib.metering@0.0.0-canary-20251213172311
17
+ - @lssm/module.audit-trail@0.0.0-canary-20251213172311
18
+ - @lssm/module.learning-journey@0.0.0-canary-20251213172311
19
+ - @lssm/module.notifications@0.0.0-canary-20251213172311
20
+ - @lssm/example.learning-patterns@0.0.0-canary-20251213172311
@@ -0,0 +1,32 @@
1
+ ## Implementation sketch — Policy-safe Knowledge Assistant (example)
2
+
3
+ This example package is **spec-first** and demonstrates how to compose:
4
+
5
+ - `@lssm/example.locale-jurisdiction-gate`
6
+ - `@lssm/example.versioned-knowledge-base`
7
+ - `@lssm/example.kb-update-pipeline`
8
+ - `@lssm/example.learning-patterns`
9
+
10
+ The runnable **sandbox UI/runtime** lives in `@lssm/bundle.contractspec-studio` (wired later).
11
+
12
+ ### Package boundaries
13
+
14
+ - **Gate**: validates `locale`, `jurisdiction`, `kbSnapshotId`, `allowedScope`. Refuses if citations are missing or scope is violated.
15
+ - **Versioned KB**: stores immutable sources and versioned rules; publishes snapshots containing only approved rule versions.
16
+ - **Update Pipeline (HITL)**: detects changes, creates review tasks, gates approvals by risk level; publishing remains blocked until all proposed rule versions are approved.
17
+ - **Learning patterns**: drills/coach/quests as event-driven Learning Journey tracks.
18
+
19
+ ### Key invariants (fail-closed)
20
+
21
+ - No assistant call without explicit `locale` and `jurisdiction`.
22
+ - No assistant answer without at least one citation referencing a **KB snapshot**.
23
+ - Snapshot publishing includes only approved rule versions and is jurisdiction-scoped.
24
+ - High-risk KB changes require expert approval.
25
+
26
+ ### What is stubbed vs real
27
+
28
+ - **LLM**: stubbed/deterministic (no external calls). Answers are derived from KB search results.
29
+ - **Source fetching**: stubbed/offline fixtures (no web truth).
30
+ - **Storage**: in tests, in-memory stores; in sandbox, browser SQLite + IndexedDB.
31
+
32
+
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # `@lssm/example.policy-safe-knowledge-assistant`
2
+
3
+ All-in-one template example demonstrating a **policy-safe knowledge assistant** end-to-end:
4
+
5
+ - Locale + jurisdiction gating (fail-closed)
6
+ - Versioned curated KB + published snapshots
7
+ - Automated KB update pipeline with HITL review + traceability
8
+ - Learning hub (drills, ambient coach, quests)
9
+ - Cross-cutting modules wired: identity/RBAC, audit trail, notifications, jobs, feature flags, files, metering, learning journey
10
+
11
+ This package is the **spec-first** source of truth. The sandbox UI/runtime integration lives in `@lssm/bundle.contractspec-studio`.\n+\n+## Seed scenario\n+\n+See `src/seed/fixtures.ts` for deterministic offline fixtures (no web dependencies).\n+\n+## Running tests\n+\n+```bash\n+bun test\n+```\n+
12
+
@@ -0,0 +1 @@
1
+ import"./policy-safe-knowledge-assistant.docblock.js";
@@ -0,0 +1,14 @@
1
+ import{registerDocBlocks as e}from"@lssm/lib.contracts/docs";e([{id:`docs.examples.policy-safe-knowledge-assistant.goal`,title:`Policy-safe Knowledge Assistant — Goal`,summary:`End-to-end example: versioned KB snapshots + locale/jurisdiction gating + HITL pipeline + learning hub.`,kind:`goal`,visibility:`public`,route:`/docs/examples/policy-safe-knowledge-assistant/goal`,tags:[`assistant`,`knowledge`,`policy`,`hitl`,`learning`],body:`## What this template proves
2
+ - Assistant answers are structured and must cite a KB snapshot (or refuse).
3
+ - Locale + jurisdiction are mandatory inputs for every assistant call.
4
+ - Automation proposes KB patches; humans verify; publishing stays blocked until approvals.
5
+ - Learning hub demonstrates drills + coaching + quests without spam.
6
+
7
+ ## Offline-first
8
+ - Seeded fixtures are deterministic and require no external services.
9
+ - Optional non-authoritative fallback can be added behind a single feature flag (disabled by default).`},{id:`docs.examples.policy-safe-knowledge-assistant.usage`,title:`Policy-safe Knowledge Assistant — Usage`,summary:`5–10 minute sandbox walkthrough for developers.`,kind:`usage`,visibility:`public`,route:`/docs/examples/policy-safe-knowledge-assistant/usage`,tags:[`assistant`,`knowledge`,`usage`],body:`## Demo walkthrough (high level)
10
+ 1) Onboard: set locale + jurisdiction.
11
+ 2) Publish snapshot: ingest source -> propose rule -> approve -> publish.
12
+ 3) Ask assistant: must pass gate and cite snapshot.
13
+ 4) Simulate change: watcher -> review -> publish new snapshot.
14
+ 5) Learning hub: drills session, ambient tip, quest start + step completion.`}]);
@@ -0,0 +1 @@
1
+ var e={id:`policy-safe-knowledge-assistant`,title:`Policy-safe Knowledge Assistant`,summary:`All-in-one template: locale/jurisdiction gating + versioned KB snapshots + HITL update pipeline + learning hub.`,tags:[`assistant`,`knowledge`,`policy`,`hitl`,`learning`],kind:`template`,visibility:`public`,docs:{goalDocId:`docs.examples.policy-safe-knowledge-assistant.goal`,usageDocId:`docs.examples.policy-safe-knowledge-assistant.usage`},entrypoints:{packageName:`@lssm/example.policy-safe-knowledge-assistant`,feature:`./feature`,docs:`./docs`},surfaces:{templates:!0,sandbox:{enabled:!0,modes:[`playground`,`specs`,`builder`,`markdown`,`evolution`]},studio:{enabled:!0,installable:!0},mcp:{enabled:!0}}};export{e as default};
@@ -0,0 +1 @@
1
+ const e={meta:{key:`policy-safe-knowledge-assistant`,title:`Policy-safe Knowledge Assistant`,description:`All-in-one example composing locale/jurisdiction gate + versioned KB + HITL pipeline + learning hub.`,domain:`knowledge`,owners:[`examples`],tags:[`assistant`,`knowledge`,`policy`,`hitl`,`learning`],stability:`experimental`},operations:[{name:`assistant.answer`,version:1},{name:`assistant.explainConcept`,version:1},{name:`kb.ingestSource`,version:1},{name:`kb.upsertRuleVersion`,version:1},{name:`kb.approveRuleVersion`,version:1},{name:`kb.publishSnapshot`,version:1},{name:`kb.search`,version:1},{name:`kbPipeline.runWatch`,version:1},{name:`kbPipeline.createReviewTask`,version:1},{name:`kbPipeline.submitDecision`,version:1},{name:`kbPipeline.publishIfReady`,version:1}],events:[{name:`assistant.answer.requested`,version:1},{name:`assistant.answer.blocked`,version:1},{name:`assistant.answer.delivered`,version:1},{name:`kb.source.ingested`,version:1},{name:`kb.ruleVersion.created`,version:1},{name:`kb.ruleVersion.approved`,version:1},{name:`kb.snapshot.published`,version:1},{name:`kb.change.detected`,version:1},{name:`kb.review.requested`,version:1},{name:`kb.review.decided`,version:1}],presentations:[],opToPresentation:[],presentationsTargets:[],capabilities:{requires:[{key:`identity`,version:1},{key:`audit-trail`,version:1},{key:`notifications`,version:1},{key:`jobs`,version:1},{key:`feature-flags`,version:1},{key:`files`,version:1},{key:`metering`,version:1},{key:`learning-journey`,version:1}]}};export{e as PolicySafeKnowledgeAssistantFeature};
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import e from"./example.js";import{PolicySafeKnowledgeAssistantFeature as t}from"./feature.js";import{DEMO_FIXTURES as n}from"./seed/fixtures.js";import{buildPolicySafeAnswer as r}from"./orchestrator/buildAnswer.js";import"./docs/index.js";export{n as DEMO_FIXTURES,t as PolicySafeKnowledgeAssistantFeature,r as buildPolicySafeAnswer,e as example};
@@ -0,0 +1 @@
1
+ import{enforceAllowedScope as e,enforceCitations as t,validateEnvelope as n}from"@lssm/example.locale-jurisdiction-gate/policy/guard";async function r(r){let i=n(r.envelope);if(!i.ok)return{locale:r.envelope.locale??`en-GB`,jurisdiction:r.envelope.regulatoryContext?.jurisdiction??`UNKNOWN`,allowedScope:r.envelope.allowedScope??`education_only`,sections:[{heading:`Request blocked`,body:i.error.message}],citations:[],disclaimers:[`This system refuses to answer without a valid envelope.`],riskFlags:[i.error.code],refused:!0,refusalReason:i.error.code};let a=await r.kbSearch({snapshotId:i.value.kbSnapshotId,jurisdiction:i.value.regulatoryContext.jurisdiction,query:r.question}),o=a.items.map(e=>({kbSnapshotId:i.value.kbSnapshotId,sourceType:`ruleVersion`,sourceId:e.ruleVersionId,title:`Curated rule version`,excerpt:e.excerpt})),s={locale:i.value.locale,jurisdiction:i.value.regulatoryContext.jurisdiction,allowedScope:i.value.allowedScope,sections:[{heading:`Answer (KB-derived)`,body:a.items.length>0?`This answer is derived from ${a.items.length} curated rule version(s) in the referenced snapshot.`:`No curated knowledge found in the referenced snapshot.`}],citations:o,disclaimers:[`Educational demo only.`],riskFlags:[]},c=e(i.value.allowedScope,s);if(!c.ok)return{...s,sections:[{heading:`Escalation required`,body:c.error.message}],refused:!0,refusalReason:c.error.code,riskFlags:[...s.riskFlags??[],c.error.code]};let l=t(s);return l.ok?s:{...s,sections:[{heading:`Request blocked`,body:l.error.message}],citations:[],refused:!0,refusalReason:l.error.code,riskFlags:[...s.riskFlags??[],l.error.code]}}export{r as buildPolicySafeAnswer};
@@ -0,0 +1 @@
1
+ const e={jurisdictions:[`EU`,`FR`],locales:[`en-GB`,`fr-FR`],demoOrgId:`org_demo`,demoUserId:`user_demo`,sources:{EU_SOURCE_1:{jurisdiction:`EU`,authority:`DemoAuthority`,title:`EU Demo Source v1`,fetchedAt:new Date(`2026-01-01T00:00:00.000Z`),hash:`hash_eu_v1`,fileId:`file_eu_v1`},EU_SOURCE_2:{jurisdiction:`EU`,authority:`DemoAuthority`,title:`EU Demo Source v2`,fetchedAt:new Date(`2026-02-01T00:00:00.000Z`),hash:`hash_eu_v2`,fileId:`file_eu_v2`}},rules:{EU_RULE_TAX:{id:`rule_eu_tax`,jurisdiction:`EU`,topicKey:`tax_reporting`}}};export{e as DEMO_FIXTURES};
@@ -0,0 +1 @@
1
+ import{DEMO_FIXTURES as e}from"./fixtures.js";export{e as DEMO_FIXTURES};
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@lssm/example.policy-safe-knowledge-assistant",
3
+ "version": "0.0.0-canary-20251213172311",
4
+ "description": "All-in-one template example: policy-safe knowledge assistant with locale/jurisdiction gating, versioned KB snapshots, HITL update pipeline, and learning hub.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": "./src/index.ts",
11
+ "./docs": "./src/docs/index.ts",
12
+ "./docs/policy-safe-knowledge-assistant.docblock": "./src/docs/policy-safe-knowledge-assistant.docblock.ts",
13
+ "./example": "./src/example.ts",
14
+ "./feature": "./src/feature.ts",
15
+ "./orchestrator/buildAnswer": "./src/orchestrator/buildAnswer.ts",
16
+ "./seed": "./src/seed/index.ts",
17
+ "./seed/fixtures": "./src/seed/fixtures.ts",
18
+ "./*": "./*"
19
+ },
20
+ "scripts": {
21
+ "build": "bun build:bundle && bun build:types",
22
+ "build:bundle": "tsdown",
23
+ "build:types": "tsc --noEmit",
24
+ "dev": "bun build:bundle --watch",
25
+ "clean": "rimraf dist .turbo",
26
+ "lint": "bun lint:fix",
27
+ "lint:fix": "eslint src --fix",
28
+ "lint:check": "eslint src",
29
+ "test": "bun test"
30
+ },
31
+ "dependencies": {
32
+ "@lssm/example.locale-jurisdiction-gate": "workspace:*",
33
+ "@lssm/example.versioned-knowledge-base": "workspace:*",
34
+ "@lssm/example.kb-update-pipeline": "workspace:*",
35
+ "@lssm/example.learning-patterns": "workspace:*",
36
+ "@lssm/lib.contracts": "workspace:*",
37
+ "@lssm/lib.identity-rbac": "workspace:*",
38
+ "@lssm/lib.jobs": "workspace:*",
39
+ "@lssm/lib.feature-flags": "workspace:*",
40
+ "@lssm/lib.files": "workspace:*",
41
+ "@lssm/lib.metering": "workspace:*",
42
+ "@lssm/module.audit-trail": "workspace:*",
43
+ "@lssm/module.notifications": "workspace:*",
44
+ "@lssm/module.learning-journey": "workspace:*"
45
+ },
46
+ "devDependencies": {
47
+ "@lssm/tool.tsdown": "workspace:*",
48
+ "@lssm/tool.typescript": "workspace:*",
49
+ "tsdown": "^0.17.0",
50
+ "typescript": "^5.9.3"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public",
54
+ "exports": {
55
+ ".": "./dist/index.js",
56
+ "./docs": "./dist/docs/index.js",
57
+ "./docs/policy-safe-knowledge-assistant.docblock": "./dist/docs/policy-safe-knowledge-assistant.docblock.js",
58
+ "./example": "./dist/example.js",
59
+ "./feature": "./dist/feature.js",
60
+ "./orchestrator/buildAnswer": "./dist/orchestrator/buildAnswer.js",
61
+ "./seed": "./dist/seed/index.js",
62
+ "./seed/fixtures": "./dist/seed/fixtures.js",
63
+ "./*": "./*"
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,3 @@
1
+ import './policy-safe-knowledge-assistant.docblock';
2
+
3
+
@@ -0,0 +1,30 @@
1
+ import type { DocBlock } from '@lssm/lib.contracts/docs';
2
+ import { registerDocBlocks } from '@lssm/lib.contracts/docs';
3
+
4
+ const docBlocks: DocBlock[] = [
5
+ {
6
+ id: 'docs.examples.policy-safe-knowledge-assistant.goal',
7
+ title: 'Policy-safe Knowledge Assistant — Goal',
8
+ summary:
9
+ 'End-to-end example: versioned KB snapshots + locale/jurisdiction gating + HITL pipeline + learning hub.',
10
+ kind: 'goal',
11
+ visibility: 'public',
12
+ route: '/docs/examples/policy-safe-knowledge-assistant/goal',
13
+ tags: ['assistant', 'knowledge', 'policy', 'hitl', 'learning'],
14
+ body: `## What this template proves\n- Assistant answers are structured and must cite a KB snapshot (or refuse).\n- Locale + jurisdiction are mandatory inputs for every assistant call.\n- Automation proposes KB patches; humans verify; publishing stays blocked until approvals.\n- Learning hub demonstrates drills + coaching + quests without spam.\n\n## Offline-first\n- Seeded fixtures are deterministic and require no external services.\n- Optional non-authoritative fallback can be added behind a single feature flag (disabled by default).`,
15
+ },
16
+ {
17
+ id: 'docs.examples.policy-safe-knowledge-assistant.usage',
18
+ title: 'Policy-safe Knowledge Assistant — Usage',
19
+ summary: '5–10 minute sandbox walkthrough for developers.',
20
+ kind: 'usage',
21
+ visibility: 'public',
22
+ route: '/docs/examples/policy-safe-knowledge-assistant/usage',
23
+ tags: ['assistant', 'knowledge', 'usage'],
24
+ body: `## Demo walkthrough (high level)\n1) Onboard: set locale + jurisdiction.\n2) Publish snapshot: ingest source -> propose rule -> approve -> publish.\n3) Ask assistant: must pass gate and cite snapshot.\n4) Simulate change: watcher -> review -> publish new snapshot.\n5) Learning hub: drills session, ambient tip, quest start + step completion.`,
25
+ },
26
+ ];
27
+
28
+ registerDocBlocks(docBlocks);
29
+
30
+
package/src/example.ts ADDED
@@ -0,0 +1,28 @@
1
+ const example = {
2
+ id: 'policy-safe-knowledge-assistant',
3
+ title: 'Policy-safe Knowledge Assistant',
4
+ summary:
5
+ 'All-in-one template: locale/jurisdiction gating + versioned KB snapshots + HITL update pipeline + learning hub.',
6
+ tags: ['assistant', 'knowledge', 'policy', 'hitl', 'learning'],
7
+ kind: 'template',
8
+ visibility: 'public',
9
+ docs: {
10
+ goalDocId: 'docs.examples.policy-safe-knowledge-assistant.goal',
11
+ usageDocId: 'docs.examples.policy-safe-knowledge-assistant.usage',
12
+ },
13
+ entrypoints: {
14
+ packageName: '@lssm/example.policy-safe-knowledge-assistant',
15
+ feature: './feature',
16
+ docs: './docs',
17
+ },
18
+ surfaces: {
19
+ templates: true,
20
+ sandbox: { enabled: true, modes: ['playground', 'specs', 'builder', 'markdown', 'evolution'] },
21
+ studio: { enabled: true, installable: true },
22
+ mcp: { enabled: true },
23
+ },
24
+ } as const;
25
+
26
+ export default example;
27
+
28
+
package/src/feature.ts ADDED
@@ -0,0 +1,57 @@
1
+ import type { FeatureModuleSpec } from '@lssm/lib.contracts';
2
+
3
+ export const PolicySafeKnowledgeAssistantFeature: FeatureModuleSpec = {
4
+ meta: {
5
+ key: 'policy-safe-knowledge-assistant',
6
+ title: 'Policy-safe Knowledge Assistant',
7
+ description:
8
+ 'All-in-one example composing locale/jurisdiction gate + versioned KB + HITL pipeline + learning hub.',
9
+ domain: 'knowledge',
10
+ owners: ['examples'],
11
+ tags: ['assistant', 'knowledge', 'policy', 'hitl', 'learning'],
12
+ stability: 'experimental',
13
+ },
14
+ operations: [
15
+ // Gate
16
+ { name: 'assistant.answer', version: 1 },
17
+ { name: 'assistant.explainConcept', version: 1 },
18
+ // KB
19
+ { name: 'kb.ingestSource', version: 1 },
20
+ { name: 'kb.upsertRuleVersion', version: 1 },
21
+ { name: 'kb.approveRuleVersion', version: 1 },
22
+ { name: 'kb.publishSnapshot', version: 1 },
23
+ { name: 'kb.search', version: 1 },
24
+ // Pipeline
25
+ { name: 'kbPipeline.runWatch', version: 1 },
26
+ { name: 'kbPipeline.createReviewTask', version: 1 },
27
+ { name: 'kbPipeline.submitDecision', version: 1 },
28
+ { name: 'kbPipeline.publishIfReady', version: 1 },
29
+ ],
30
+ events: [
31
+ { name: 'assistant.answer.requested', version: 1 },
32
+ { name: 'assistant.answer.blocked', version: 1 },
33
+ { name: 'assistant.answer.delivered', version: 1 },
34
+ { name: 'kb.source.ingested', version: 1 },
35
+ { name: 'kb.ruleVersion.created', version: 1 },
36
+ { name: 'kb.ruleVersion.approved', version: 1 },
37
+ { name: 'kb.snapshot.published', version: 1 },
38
+ { name: 'kb.change.detected', version: 1 },
39
+ { name: 'kb.review.requested', version: 1 },
40
+ { name: 'kb.review.decided', version: 1 },
41
+ ],
42
+ presentations: [],
43
+ opToPresentation: [],
44
+ presentationsTargets: [],
45
+ capabilities: {
46
+ requires: [
47
+ { key: 'identity', version: 1 },
48
+ { key: 'audit-trail', version: 1 },
49
+ { key: 'notifications', version: 1 },
50
+ { key: 'jobs', version: 1 },
51
+ { key: 'feature-flags', version: 1 },
52
+ { key: 'files', version: 1 },
53
+ { key: 'metering', version: 1 },
54
+ { key: 'learning-journey', version: 1 },
55
+ ],
56
+ },
57
+ };
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Policy-safe Knowledge Assistant (all-in-one template) — spec-first exports.
3
+ */
4
+ export * from './feature';
5
+ export * from './seed';
6
+ export * from './orchestrator/buildAnswer';
7
+ export { default as example } from './example';
8
+
9
+ import './docs';
10
+
11
+
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+
3
+ import { createMemoryKbHandlers, createMemoryKbStore } from '@lssm/example.versioned-knowledge-base/handlers/memory.handlers';
4
+ import { createPipelineMemoryHandlers, createPipelineMemoryStore } from '@lssm/example.kb-update-pipeline/handlers/memory.handlers';
5
+
6
+ import { buildPolicySafeAnswer } from './orchestrator/buildAnswer';
7
+ import { DEMO_FIXTURES } from './seed/fixtures';
8
+
9
+ describe('@lssm/example.policy-safe-knowledge-assistant integration', () => {
10
+ it('answers cite latest snapshot; after pipeline change + publish, answers cite new snapshot', async () => {
11
+ const kbStore = createMemoryKbStore();
12
+ const kb = createMemoryKbHandlers(kbStore);
13
+
14
+ const pipelineStore = createPipelineMemoryStore();
15
+ const pipeline = createPipelineMemoryHandlers(pipelineStore);
16
+
17
+ // Seed rules
18
+ await kb.createRule(DEMO_FIXTURES.rules.EU_RULE_TAX);
19
+
20
+ // Publish initial snapshot (EU v1)
21
+ const rv1 = await kb.upsertRuleVersion({
22
+ ruleId: DEMO_FIXTURES.rules.EU_RULE_TAX.id,
23
+ content: 'EU: Reporting obligations v1',
24
+ sourceRefs: [{ sourceDocumentId: 'src_eu_v1', excerpt: 'v1 excerpt' }],
25
+ });
26
+ await kb.approveRuleVersion({ ruleVersionId: rv1.id, approver: 'expert_1' });
27
+ const snap1 = await kb.publishSnapshot({
28
+ jurisdiction: 'EU',
29
+ asOfDate: new Date('2026-01-01T00:00:00.000Z'),
30
+ });
31
+
32
+ const envelopeBase = {
33
+ traceId: 'trace_1',
34
+ locale: 'en-GB',
35
+ regulatoryContext: { jurisdiction: 'EU' },
36
+ allowedScope: 'education_only' as const,
37
+ };
38
+
39
+ const a1 = await buildPolicySafeAnswer({
40
+ envelope: { ...envelopeBase, kbSnapshotId: snap1.id },
41
+ question: 'reporting obligations',
42
+ kbSearch: kb.search,
43
+ });
44
+
45
+ expect(a1.refused).not.toBeTrue();
46
+ expect(a1.citations.length).toBeGreaterThan(0);
47
+ expect(a1.citations[0]?.kbSnapshotId).toBe(snap1.id);
48
+
49
+ // Simulate regulatory change via pipeline: create candidate, review, propose patch
50
+ pipelineStore.candidates.set('cand_1', {
51
+ id: 'cand_1',
52
+ sourceDocumentId: 'EU_src_change',
53
+ detectedAt: new Date('2026-02-01T00:00:00.000Z'),
54
+ diffSummary: 'Updated obligations',
55
+ riskLevel: 'high',
56
+ });
57
+ const review = await pipeline.createReviewTask({ changeCandidateId: 'cand_1' });
58
+ await pipeline.submitDecision({
59
+ reviewTaskId: review.id,
60
+ decision: 'approve',
61
+ decidedBy: 'expert_2',
62
+ decidedByRole: 'expert',
63
+ });
64
+
65
+ // Create + approve new KB rule version
66
+ const rv2 = await kb.upsertRuleVersion({
67
+ ruleId: DEMO_FIXTURES.rules.EU_RULE_TAX.id,
68
+ content: 'EU: Reporting obligations v2 (updated)',
69
+ sourceRefs: [{ sourceDocumentId: 'src_eu_v2', excerpt: 'v2 excerpt' }],
70
+ });
71
+ await kb.approveRuleVersion({ ruleVersionId: rv2.id, approver: 'expert_2' });
72
+
73
+ // Link pipeline proposal to the actual KB rule version id, then mark it approved
74
+ await pipeline.proposeRulePatch({
75
+ changeCandidateId: 'cand_1',
76
+ proposedRuleVersionIds: [rv2.id],
77
+ });
78
+ await pipeline.markRuleVersionApproved({ ruleVersionId: rv2.id });
79
+ await pipeline.publishIfReady({ jurisdiction: 'EU' });
80
+
81
+ const snap2 = await kb.publishSnapshot({
82
+ jurisdiction: 'EU',
83
+ asOfDate: new Date('2026-02-01T00:00:00.000Z'),
84
+ });
85
+
86
+ const a2 = await buildPolicySafeAnswer({
87
+ envelope: { ...envelopeBase, kbSnapshotId: snap2.id },
88
+ question: 'updated obligations',
89
+ kbSearch: kb.search,
90
+ });
91
+ expect(a2.refused).not.toBeTrue();
92
+ expect(a2.citations[0]?.kbSnapshotId).toBe(snap2.id);
93
+ });
94
+ });
95
+
96
+
@@ -0,0 +1,124 @@
1
+ import {
2
+ enforceAllowedScope,
3
+ enforceCitations,
4
+ validateEnvelope,
5
+ } from '@lssm/example.locale-jurisdiction-gate/policy/guard';
6
+
7
+ type AllowedScope = 'education_only' | 'generic_info' | 'escalation_required';
8
+
9
+ export type AssistantAnswerIR = {
10
+ locale: string;
11
+ jurisdiction: string;
12
+ allowedScope: AllowedScope;
13
+ sections: Array<{ heading: string; body: string }>;
14
+ citations: Array<{
15
+ kbSnapshotId: string;
16
+ sourceType: string;
17
+ sourceId: string;
18
+ title?: string;
19
+ excerpt?: string;
20
+ }>;
21
+ disclaimers?: string[];
22
+ riskFlags?: string[];
23
+ refused?: boolean;
24
+ refusalReason?: string;
25
+ };
26
+
27
+ export interface BuildAnswerInput {
28
+ envelope: {
29
+ traceId: string;
30
+ locale: string;
31
+ kbSnapshotId: string;
32
+ allowedScope: AllowedScope;
33
+ regulatoryContext: { jurisdiction: string };
34
+ };
35
+ question: string;
36
+ kbSearch: (input: {
37
+ snapshotId: string;
38
+ jurisdiction: string;
39
+ query: string;
40
+ }) => Promise<{ items: Array<{ ruleVersionId: string; excerpt?: string }> }>;
41
+ }
42
+
43
+ /**
44
+ * Build a policy-safe assistant answer derived from KB search results.
45
+ *
46
+ * Deterministic: no LLM calls; if search yields no results, it refuses.
47
+ */
48
+ export async function buildPolicySafeAnswer(
49
+ input: BuildAnswerInput
50
+ ): Promise<AssistantAnswerIR> {
51
+ const env = validateEnvelope(input.envelope);
52
+ if (!env.ok) {
53
+ return {
54
+ locale: input.envelope.locale ?? 'en-GB',
55
+ jurisdiction: input.envelope.regulatoryContext?.jurisdiction ?? 'UNKNOWN',
56
+ allowedScope: input.envelope.allowedScope ?? 'education_only',
57
+ sections: [{ heading: 'Request blocked', body: env.error.message }],
58
+ citations: [],
59
+ disclaimers: ['This system refuses to answer without a valid envelope.'],
60
+ riskFlags: [env.error.code],
61
+ refused: true,
62
+ refusalReason: env.error.code,
63
+ };
64
+ }
65
+
66
+ const results = await input.kbSearch({
67
+ snapshotId: env.value.kbSnapshotId,
68
+ jurisdiction: env.value.regulatoryContext!.jurisdiction!,
69
+ query: input.question,
70
+ });
71
+
72
+ const citations = results.items.map((item) => ({
73
+ kbSnapshotId: env.value.kbSnapshotId,
74
+ sourceType: 'ruleVersion',
75
+ sourceId: item.ruleVersionId,
76
+ title: 'Curated rule version',
77
+ excerpt: item.excerpt,
78
+ }));
79
+
80
+ const draft: AssistantAnswerIR = {
81
+ locale: env.value.locale,
82
+ jurisdiction: env.value.regulatoryContext!.jurisdiction!,
83
+ allowedScope: env.value.allowedScope,
84
+ sections: [
85
+ {
86
+ heading: 'Answer (KB-derived)',
87
+ body:
88
+ results.items.length > 0
89
+ ? `This answer is derived from ${results.items.length} curated rule version(s) in the referenced snapshot.`
90
+ : 'No curated knowledge found in the referenced snapshot.',
91
+ },
92
+ ],
93
+ citations,
94
+ disclaimers: ['Educational demo only.'],
95
+ riskFlags: [],
96
+ };
97
+
98
+ const scope = enforceAllowedScope(env.value.allowedScope, draft);
99
+ if (!scope.ok) {
100
+ return {
101
+ ...draft,
102
+ sections: [{ heading: 'Escalation required', body: scope.error.message }],
103
+ refused: true,
104
+ refusalReason: scope.error.code,
105
+ riskFlags: [...(draft.riskFlags ?? []), scope.error.code],
106
+ };
107
+ }
108
+
109
+ const cited = enforceCitations(draft);
110
+ if (!cited.ok) {
111
+ return {
112
+ ...draft,
113
+ sections: [{ heading: 'Request blocked', body: cited.error.message }],
114
+ citations: [],
115
+ refused: true,
116
+ refusalReason: cited.error.code,
117
+ riskFlags: [...(draft.riskFlags ?? []), cited.error.code],
118
+ };
119
+ }
120
+
121
+ return draft;
122
+ }
123
+
124
+
@@ -0,0 +1,33 @@
1
+ export const DEMO_FIXTURES = {
2
+ jurisdictions: ['EU', 'FR'] as const,
3
+ locales: ['en-GB', 'fr-FR'] as const,
4
+ demoOrgId: 'org_demo',
5
+ demoUserId: 'user_demo',
6
+ sources: {
7
+ EU_SOURCE_1: {
8
+ jurisdiction: 'EU',
9
+ authority: 'DemoAuthority',
10
+ title: 'EU Demo Source v1',
11
+ fetchedAt: new Date('2026-01-01T00:00:00.000Z'),
12
+ hash: 'hash_eu_v1',
13
+ fileId: 'file_eu_v1',
14
+ },
15
+ EU_SOURCE_2: {
16
+ jurisdiction: 'EU',
17
+ authority: 'DemoAuthority',
18
+ title: 'EU Demo Source v2',
19
+ fetchedAt: new Date('2026-02-01T00:00:00.000Z'),
20
+ hash: 'hash_eu_v2',
21
+ fileId: 'file_eu_v2',
22
+ },
23
+ },
24
+ rules: {
25
+ EU_RULE_TAX: {
26
+ id: 'rule_eu_tax',
27
+ jurisdiction: 'EU',
28
+ topicKey: 'tax_reporting',
29
+ },
30
+ },
31
+ } as const;
32
+
33
+
@@ -0,0 +1,3 @@
1
+ export * from './fixtures';
2
+
3
+
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "@lssm/tool.typescript/react-library.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
10
+
11
+