@contractspec/example.policy-safe-knowledge-assistant 1.57.0 → 1.58.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 (88) hide show
  1. package/.turbo/turbo-build.log +58 -60
  2. package/.turbo/turbo-prebuild.log +1 -0
  3. package/CHANGELOG.md +21 -0
  4. package/dist/browser/docs/index.js +38 -0
  5. package/dist/browser/docs/policy-safe-knowledge-assistant.docblock.js +38 -0
  6. package/dist/browser/example.js +37 -0
  7. package/dist/browser/handlers/index.js +334 -0
  8. package/dist/browser/handlers/policy-safe-knowledge-assistant.handlers.js +334 -0
  9. package/dist/browser/index.js +917 -0
  10. package/dist/browser/orchestrator/buildAnswer.js +73 -0
  11. package/dist/browser/policy-safe-knowledge-assistant.feature.js +57 -0
  12. package/dist/browser/seed/fixtures.js +35 -0
  13. package/dist/browser/seed/index.js +35 -0
  14. package/dist/browser/seeders/index.js +12 -0
  15. package/dist/browser/ui/PolicySafeKnowledgeAssistantDashboard.js +420 -0
  16. package/dist/browser/ui/hooks/usePolicySafeKnowledgeAssistant.js +154 -0
  17. package/dist/browser/ui/index.js +420 -0
  18. package/dist/docs/index.d.ts +2 -1
  19. package/dist/docs/index.d.ts.map +1 -0
  20. package/dist/docs/index.js +39 -1
  21. package/dist/docs/policy-safe-knowledge-assistant.docblock.d.ts +2 -1
  22. package/dist/docs/policy-safe-knowledge-assistant.docblock.d.ts.map +1 -0
  23. package/dist/docs/policy-safe-knowledge-assistant.docblock.js +37 -33
  24. package/dist/example.d.ts +2 -6
  25. package/dist/example.d.ts.map +1 -1
  26. package/dist/example.js +36 -51
  27. package/dist/handlers/index.d.ts +2 -2
  28. package/dist/handlers/index.d.ts.map +1 -0
  29. package/dist/handlers/index.js +334 -2
  30. package/dist/handlers/policy-safe-knowledge-assistant.handlers.d.ts +124 -121
  31. package/dist/handlers/policy-safe-knowledge-assistant.handlers.d.ts.map +1 -1
  32. package/dist/handlers/policy-safe-knowledge-assistant.handlers.js +325 -254
  33. package/dist/index.d.ts +11 -8
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +918 -10
  36. package/dist/integration.test.d.ts +2 -0
  37. package/dist/integration.test.d.ts.map +1 -0
  38. package/dist/node/docs/index.js +38 -0
  39. package/dist/node/docs/policy-safe-knowledge-assistant.docblock.js +38 -0
  40. package/dist/node/example.js +37 -0
  41. package/dist/node/handlers/index.js +334 -0
  42. package/dist/node/handlers/policy-safe-knowledge-assistant.handlers.js +334 -0
  43. package/dist/node/index.js +917 -0
  44. package/dist/node/orchestrator/buildAnswer.js +73 -0
  45. package/dist/node/policy-safe-knowledge-assistant.feature.js +57 -0
  46. package/dist/node/seed/fixtures.js +35 -0
  47. package/dist/node/seed/index.js +35 -0
  48. package/dist/node/seeders/index.js +12 -0
  49. package/dist/node/ui/PolicySafeKnowledgeAssistantDashboard.js +420 -0
  50. package/dist/node/ui/hooks/usePolicySafeKnowledgeAssistant.js +154 -0
  51. package/dist/node/ui/index.js +420 -0
  52. package/dist/orchestrator/buildAnswer.d.ts +40 -42
  53. package/dist/orchestrator/buildAnswer.d.ts.map +1 -1
  54. package/dist/orchestrator/buildAnswer.js +72 -75
  55. package/dist/policy-safe-knowledge-assistant.feature.d.ts +1 -6
  56. package/dist/policy-safe-knowledge-assistant.feature.d.ts.map +1 -1
  57. package/dist/policy-safe-knowledge-assistant.feature.js +56 -148
  58. package/dist/seed/fixtures.d.ts +28 -31
  59. package/dist/seed/fixtures.d.ts.map +1 -1
  60. package/dist/seed/fixtures.js +35 -33
  61. package/dist/seed/index.d.ts +2 -2
  62. package/dist/seed/index.d.ts.map +1 -0
  63. package/dist/seed/index.js +36 -3
  64. package/dist/seeders/index.d.ts +4 -8
  65. package/dist/seeders/index.d.ts.map +1 -1
  66. package/dist/seeders/index.js +11 -14
  67. package/dist/ui/PolicySafeKnowledgeAssistantDashboard.d.ts +1 -6
  68. package/dist/ui/PolicySafeKnowledgeAssistantDashboard.d.ts.map +1 -1
  69. package/dist/ui/PolicySafeKnowledgeAssistantDashboard.js +415 -225
  70. package/dist/ui/hooks/usePolicySafeKnowledgeAssistant.d.ts +49 -51
  71. package/dist/ui/hooks/usePolicySafeKnowledgeAssistant.d.ts.map +1 -1
  72. package/dist/ui/hooks/usePolicySafeKnowledgeAssistant.js +147 -185
  73. package/dist/ui/index.d.ts +2 -2
  74. package/dist/ui/index.d.ts.map +1 -0
  75. package/dist/ui/index.js +420 -2
  76. package/package.json +173 -44
  77. package/tsdown.config.js +1 -2
  78. package/.turbo/turbo-build$colon$bundle.log +0 -60
  79. package/dist/docs/policy-safe-knowledge-assistant.docblock.js.map +0 -1
  80. package/dist/example.js.map +0 -1
  81. package/dist/handlers/policy-safe-knowledge-assistant.handlers.js.map +0 -1
  82. package/dist/orchestrator/buildAnswer.js.map +0 -1
  83. package/dist/policy-safe-knowledge-assistant.feature.js.map +0 -1
  84. package/dist/seed/fixtures.js.map +0 -1
  85. package/dist/seeders/index.js.map +0 -1
  86. package/dist/ui/PolicySafeKnowledgeAssistantDashboard.js.map +0 -1
  87. package/dist/ui/hooks/usePolicySafeKnowledgeAssistant.js.map +0 -1
  88. package/tsconfig.tsbuildinfo +0 -1
@@ -1,61 +1,59 @@
1
- $ bun build:types && bun build:bundle
2
- $ tsc --noEmit
3
- $ tsdown
4
- ℹ tsdown v0.20.3 powered by rolldown v1.0.0-rc.3
5
- ℹ config file: /home/runner/work/contractspec/contractspec/packages/examples/policy-safe-knowledge-assistant/tsdown.config.js
6
- ℹ entry: src/example.ts, src/index.ts, src/policy-safe-knowledge-assistant.feature.ts, src/docs/index.ts, src/docs/policy-safe-knowledge-assistant.docblock.ts, src/handlers/index.ts, src/handlers/policy-safe-knowledge-assistant.handlers.ts, src/orchestrator/buildAnswer.ts, src/seed/fixtures.ts, src/seed/index.ts, src/seeders/index.ts, src/ui/PolicySafeKnowledgeAssistantDashboard.tsx, src/ui/index.ts, src/ui/hooks/usePolicySafeKnowledgeAssistant.ts
7
- ℹ target: esnext
8
- ℹ tsconfig: tsconfig.json
9
- ℹ Build start
10
- ℹ dist/handlers/policy-safe-knowledge-assistant.handlers.js  9.46 kB │ gzip: 2.58 kB
11
- ℹ dist/ui/PolicySafeKnowledgeAssistantDashboard.js  8.14 kB │ gzip: 2.11 kB
12
- ℹ dist/ui/hooks/usePolicySafeKnowledgeAssistant.js  4.78 kB │ gzip: 1.47 kB
13
- ℹ dist/policy-safe-knowledge-assistant.feature.js  2.55 kB │ gzip: 0.71 kB
14
- ℹ dist/orchestrator/buildAnswer.js  2.47 kB │ gzip: 0.93 kB
15
- ℹ dist/docs/policy-safe-knowledge-assistant.docblock.js  1.88 kB │ gzip: 0.90 kB
16
- ℹ dist/example.js  1.20 kB │ gzip: 0.58 kB
17
- ℹ dist/seed/fixtures.js  0.80 kB │ gzip: 0.40 kB
18
- ℹ dist/index.js  0.71 kB │ gzip: 0.25 kB
19
- ℹ dist/seeders/index.js  0.54 kB │ gzip: 0.38 kB
20
- ℹ dist/handlers/index.js  0.16 kB │ gzip: 0.11 kB
21
- ℹ dist/ui/index.js  0.15 kB │ gzip: 0.09 kB
22
- ℹ dist/seed/index.js  0.07 kB │ gzip: 0.07 kB
23
- ℹ dist/docs/index.js  0.06 kB │ gzip: 0.08 kB
24
- ℹ dist/handlers/policy-safe-knowledge-assistant.handlers.js.map 20.30 kB │ gzip: 5.31 kB
25
- ℹ dist/ui/PolicySafeKnowledgeAssistantDashboard.js.map 10.46 kB │ gzip: 3.10 kB
26
- ℹ dist/ui/hooks/usePolicySafeKnowledgeAssistant.js.map  9.84 kB │ gzip: 2.85 kB
27
- ℹ dist/orchestrator/buildAnswer.js.map  5.05 kB │ gzip: 1.68 kB
28
- ℹ dist/policy-safe-knowledge-assistant.feature.js.map  3.71 kB │ gzip: 1.05 kB
29
- ℹ dist/docs/policy-safe-knowledge-assistant.docblock.js.map  2.38 kB │ gzip: 1.08 kB
30
- ℹ dist/example.js.map  1.69 kB │ gzip: 0.80 kB
31
- ℹ dist/seed/fixtures.js.map  1.29 kB │ gzip: 0.55 kB
32
- ℹ dist/handlers/policy-safe-knowledge-assistant.handlers.d.ts.map  0.97 kB │ gzip: 0.39 kB
33
- ℹ dist/seeders/index.js.map  0.93 kB │ gzip: 0.61 kB
34
- ℹ dist/orchestrator/buildAnswer.d.ts.map  0.62 kB │ gzip: 0.31 kB
35
- ℹ dist/ui/hooks/usePolicySafeKnowledgeAssistant.d.ts.map  0.61 kB │ gzip: 0.32 kB
36
- ℹ dist/ui/PolicySafeKnowledgeAssistantDashboard.d.ts.map  0.21 kB │ gzip: 0.16 kB
37
- ℹ dist/policy-safe-knowledge-assistant.feature.d.ts.map  0.19 kB │ gzip: 0.15 kB
38
- ℹ dist/seeders/index.d.ts.map  0.17 kB │ gzip: 0.15 kB
39
- ℹ dist/seed/fixtures.d.ts.map  0.16 kB │ gzip: 0.14 kB
40
- ℹ dist/example.d.ts.map  0.13 kB │ gzip: 0.13 kB
41
- ℹ dist/handlers/policy-safe-knowledge-assistant.handlers.d.ts  3.21 kB │ gzip: 0.85 kB
42
- ℹ dist/ui/hooks/usePolicySafeKnowledgeAssistant.d.ts  1.64 kB │ gzip: 0.58 kB
43
- ℹ dist/orchestrator/buildAnswer.d.ts  1.31 kB │ gzip: 0.57 kB
44
- ℹ dist/seed/fixtures.d.ts  1.03 kB │ gzip: 0.37 kB
45
- ℹ dist/index.d.ts  0.83 kB │ gzip: 0.27 kB
46
- ℹ dist/policy-safe-knowledge-assistant.feature.d.ts  0.37 kB │ gzip: 0.20 kB
47
- ℹ dist/ui/PolicySafeKnowledgeAssistantDashboard.d.ts  0.34 kB │ gzip: 0.20 kB
48
- ℹ dist/seeders/index.d.ts  0.30 kB │ gzip: 0.23 kB
49
- ℹ dist/example.d.ts  0.25 kB │ gzip: 0.17 kB
50
- ℹ dist/handlers/index.d.ts  0.24 kB │ gzip: 0.12 kB
51
- ℹ dist/ui/index.d.ts  0.15 kB │ gzip: 0.09 kB
52
- ℹ dist/seed/index.d.ts  0.07 kB │ gzip: 0.07 kB
53
- ℹ dist/docs/index.d.ts  0.01 kB │ gzip: 0.03 kB
54
- ℹ dist/docs/policy-safe-knowledge-assistant.docblock.d.ts  0.01 kB │ gzip: 0.03 kB
55
- ℹ 45 files, total: 101.44 kB
56
- [PLUGIN_TIMINGS] Warning: Your build spent significant time in plugins. Here is a breakdown:
57
- - tsdown:external (58%)
58
- - rolldown-plugin-dts:generate (41%)
59
- See https://rolldown.rs/options/checks#plugintimings for more details.
1
+ $ contractspec-bun-build prebuild
2
+ $ bun run prebuild && bun run build:bundle && bun run build:types
3
+ $ contractspec-bun-build prebuild
4
+ $ contractspec-bun-build transpile
5
+ [contractspec-bun-build] transpile target=bun root=src entries=14
6
+ Bundled 14 modules in 20ms
60
7
 
61
- ✔ Build complete in 31440ms
8
+ docs/index.js 1.86 KB (entry point)
9
+ seeders/index.js 0.51 KB (entry point)
10
+ handlers/index.js 12.77 KB (entry point)
11
+ ./index.js 35.61 KB (entry point)
12
+ seed/index.js 0.80 KB (entry point)
13
+ seed/fixtures.js 0.80 KB (entry point)
14
+ ui/index.js 16.74 KB (entry point)
15
+ ui/PolicySafeKnowledgeAssistantDashboard.js 16.74 KB (entry point)
16
+ ui/hooks/usePolicySafeKnowledgeAssistant.js 4.92 KB (entry point)
17
+ docs/policy-safe-knowledge-assistant.docblock.js 1.86 KB (entry point)
18
+ ./example.js 1.19 KB (entry point)
19
+ handlers/policy-safe-knowledge-assistant.handlers.js 12.77 KB (entry point)
20
+ orchestrator/buildAnswer.js 2.43 KB (entry point)
21
+ ./policy-safe-knowledge-assistant.feature.js 2.32 KB (entry point)
22
+
23
+ [contractspec-bun-build] transpile target=node root=src entries=14
24
+ Bundled 14 modules in 13ms
25
+
26
+ docs/index.js 1.84 KB (entry point)
27
+ seeders/index.js 506 bytes (entry point)
28
+ handlers/index.js 12.76 KB (entry point)
29
+ ./index.js 35.57 KB (entry point)
30
+ seed/index.js 0.79 KB (entry point)
31
+ seed/fixtures.js 0.79 KB (entry point)
32
+ ui/index.js 16.71 KB (entry point)
33
+ ui/PolicySafeKnowledgeAssistantDashboard.js 16.71 KB (entry point)
34
+ ui/hooks/usePolicySafeKnowledgeAssistant.js 4.91 KB (entry point)
35
+ docs/policy-safe-knowledge-assistant.docblock.js 1.84 KB (entry point)
36
+ ./example.js 1.18 KB (entry point)
37
+ handlers/policy-safe-knowledge-assistant.handlers.js 12.76 KB (entry point)
38
+ orchestrator/buildAnswer.js 2.43 KB (entry point)
39
+ ./policy-safe-knowledge-assistant.feature.js 2.32 KB (entry point)
40
+
41
+ [contractspec-bun-build] transpile target=browser root=src entries=14
42
+ Bundled 14 modules in 28ms
43
+
44
+ docs/index.js 1.84 KB (entry point)
45
+ seeders/index.js 506 bytes (entry point)
46
+ handlers/index.js 12.76 KB (entry point)
47
+ ./index.js 35.57 KB (entry point)
48
+ seed/index.js 0.79 KB (entry point)
49
+ seed/fixtures.js 0.79 KB (entry point)
50
+ ui/index.js 16.71 KB (entry point)
51
+ ui/PolicySafeKnowledgeAssistantDashboard.js 16.71 KB (entry point)
52
+ ui/hooks/usePolicySafeKnowledgeAssistant.js 4.91 KB (entry point)
53
+ docs/policy-safe-knowledge-assistant.docblock.js 1.84 KB (entry point)
54
+ ./example.js 1.18 KB (entry point)
55
+ handlers/policy-safe-knowledge-assistant.handlers.js 12.76 KB (entry point)
56
+ orchestrator/buildAnswer.js 2.43 KB (entry point)
57
+ ./policy-safe-knowledge-assistant.feature.js 2.32 KB (entry point)
58
+
59
+ $ contractspec-bun-build types
@@ -0,0 +1 @@
1
+ $ contractspec-bun-build prebuild
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @contractspec/example.policy-safe-knowledge-assistant
2
2
 
3
+ ## 1.58.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d1f0fd0: chore: Migrate non-app package builds from tsdown to shared Bun tooling, add `@contractspec/tool.bun`, and standardize `prebuild`/`build`/`typecheck` with platform-aware exports and `tsc` declaration emission into `dist`.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [d1f0fd0]
12
+ - Updated dependencies [4355a9e]
13
+ - @contractspec/example.locale-jurisdiction-gate@1.58.0
14
+ - @contractspec/example.versioned-knowledge-base@1.58.0
15
+ - @contractspec/example.kb-update-pipeline@1.58.0
16
+ - @contractspec/example.learning-patterns@1.58.0
17
+ - @contractspec/module.learning-journey@1.58.0
18
+ - @contractspec/lib.example-shared-ui@1.12.0
19
+ - @contractspec/lib.runtime-sandbox@0.13.0
20
+ - @contractspec/lib.design-system@1.58.0
21
+ - @contractspec/lib.ui-kit-web@1.58.0
22
+ - @contractspec/lib.contracts@1.58.0
23
+
3
24
  ## 1.57.0
4
25
 
5
26
  ### Minor Changes
@@ -0,0 +1,38 @@
1
+ // src/docs/policy-safe-knowledge-assistant.docblock.ts
2
+ import { registerDocBlocks } from "@contractspec/lib.contracts/docs";
3
+ var docBlocks = [
4
+ {
5
+ id: "docs.examples.policy-safe-knowledge-assistant.goal",
6
+ title: "Policy-safe Knowledge Assistant — Goal",
7
+ summary: "End-to-end example: versioned KB snapshots + locale/jurisdiction gating + HITL pipeline + learning hub.",
8
+ kind: "goal",
9
+ visibility: "public",
10
+ route: "/docs/examples/policy-safe-knowledge-assistant/goal",
11
+ tags: ["assistant", "knowledge", "policy", "hitl", "learning"],
12
+ body: `## What this template proves
13
+ - Assistant answers are structured and must cite a KB snapshot (or refuse).
14
+ - Locale + jurisdiction are mandatory inputs for every assistant call.
15
+ - Automation proposes KB patches; humans verify; publishing stays blocked until approvals.
16
+ - Learning hub demonstrates drills + coaching + quests without spam.
17
+
18
+ ## Offline-first
19
+ - Seeded fixtures are deterministic and require no external services.
20
+ - Optional non-authoritative fallback can be added behind a single feature flag (disabled by default).`
21
+ },
22
+ {
23
+ id: "docs.examples.policy-safe-knowledge-assistant.usage",
24
+ title: "Policy-safe Knowledge Assistant — Usage",
25
+ summary: "5–10 minute sandbox walkthrough for developers.",
26
+ kind: "usage",
27
+ visibility: "public",
28
+ route: "/docs/examples/policy-safe-knowledge-assistant/usage",
29
+ tags: ["assistant", "knowledge", "usage"],
30
+ body: `## Demo walkthrough (high level)
31
+ 1) Onboard: set locale + jurisdiction.
32
+ 2) Publish snapshot: ingest source -> propose rule -> approve -> publish.
33
+ 3) Ask assistant: must pass gate and cite snapshot.
34
+ 4) Simulate change: watcher -> review -> publish new snapshot.
35
+ 5) Learning hub: drills session, ambient tip, quest start + step completion.`
36
+ }
37
+ ];
38
+ registerDocBlocks(docBlocks);
@@ -0,0 +1,38 @@
1
+ // src/docs/policy-safe-knowledge-assistant.docblock.ts
2
+ import { registerDocBlocks } from "@contractspec/lib.contracts/docs";
3
+ var docBlocks = [
4
+ {
5
+ id: "docs.examples.policy-safe-knowledge-assistant.goal",
6
+ title: "Policy-safe Knowledge Assistant — Goal",
7
+ summary: "End-to-end example: versioned KB snapshots + locale/jurisdiction gating + HITL pipeline + learning hub.",
8
+ kind: "goal",
9
+ visibility: "public",
10
+ route: "/docs/examples/policy-safe-knowledge-assistant/goal",
11
+ tags: ["assistant", "knowledge", "policy", "hitl", "learning"],
12
+ body: `## What this template proves
13
+ - Assistant answers are structured and must cite a KB snapshot (or refuse).
14
+ - Locale + jurisdiction are mandatory inputs for every assistant call.
15
+ - Automation proposes KB patches; humans verify; publishing stays blocked until approvals.
16
+ - Learning hub demonstrates drills + coaching + quests without spam.
17
+
18
+ ## Offline-first
19
+ - Seeded fixtures are deterministic and require no external services.
20
+ - Optional non-authoritative fallback can be added behind a single feature flag (disabled by default).`
21
+ },
22
+ {
23
+ id: "docs.examples.policy-safe-knowledge-assistant.usage",
24
+ title: "Policy-safe Knowledge Assistant — Usage",
25
+ summary: "5–10 minute sandbox walkthrough for developers.",
26
+ kind: "usage",
27
+ visibility: "public",
28
+ route: "/docs/examples/policy-safe-knowledge-assistant/usage",
29
+ tags: ["assistant", "knowledge", "usage"],
30
+ body: `## Demo walkthrough (high level)
31
+ 1) Onboard: set locale + jurisdiction.
32
+ 2) Publish snapshot: ingest source -> propose rule -> approve -> publish.
33
+ 3) Ask assistant: must pass gate and cite snapshot.
34
+ 4) Simulate change: watcher -> review -> publish new snapshot.
35
+ 5) Learning hub: drills session, ambient tip, quest start + step completion.`
36
+ }
37
+ ];
38
+ registerDocBlocks(docBlocks);
@@ -0,0 +1,37 @@
1
+ // src/example.ts
2
+ import { defineExample } from "@contractspec/lib.contracts";
3
+ var example = defineExample({
4
+ meta: {
5
+ key: "policy-safe-knowledge-assistant",
6
+ version: "1.0.0",
7
+ title: "Policy-safe Knowledge Assistant",
8
+ description: "All-in-one template: locale/jurisdiction gating + versioned KB snapshots + HITL update pipeline + learning hub.",
9
+ kind: "template",
10
+ visibility: "public",
11
+ stability: "experimental",
12
+ owners: ["@platform.core"],
13
+ tags: ["assistant", "knowledge", "policy", "hitl", "learning"]
14
+ },
15
+ docs: {
16
+ goalDocId: "docs.examples.policy-safe-knowledge-assistant.goal",
17
+ usageDocId: "docs.examples.policy-safe-knowledge-assistant.usage"
18
+ },
19
+ entrypoints: {
20
+ packageName: "@contractspec/example.policy-safe-knowledge-assistant",
21
+ feature: "./policy-safe-knowledge-assistant.feature",
22
+ docs: "./docs"
23
+ },
24
+ surfaces: {
25
+ templates: true,
26
+ sandbox: {
27
+ enabled: true,
28
+ modes: ["playground", "specs", "builder", "markdown", "evolution"]
29
+ },
30
+ studio: { enabled: true, installable: true },
31
+ mcp: { enabled: true }
32
+ }
33
+ });
34
+ var example_default = example;
35
+ export {
36
+ example_default as default
37
+ };
@@ -0,0 +1,334 @@
1
+ // src/orchestrator/buildAnswer.ts
2
+ import {
3
+ enforceAllowedScope,
4
+ enforceCitations,
5
+ validateEnvelope
6
+ } from "@contractspec/example.locale-jurisdiction-gate/policy/guard";
7
+ async function buildPolicySafeAnswer(input) {
8
+ const env = validateEnvelope(input.envelope);
9
+ if (!env.ok) {
10
+ return {
11
+ locale: input.envelope.locale ?? "en-GB",
12
+ jurisdiction: input.envelope.regulatoryContext?.jurisdiction ?? "UNKNOWN",
13
+ allowedScope: input.envelope.allowedScope ?? "education_only",
14
+ sections: [{ heading: "Request blocked", body: env.error.message }],
15
+ citations: [],
16
+ disclaimers: ["This system refuses to answer without a valid envelope."],
17
+ riskFlags: [env.error.code],
18
+ refused: true,
19
+ refusalReason: env.error.code
20
+ };
21
+ }
22
+ const results = await input.kbSearch({
23
+ snapshotId: env.value.kbSnapshotId,
24
+ jurisdiction: env.value.regulatoryContext?.jurisdiction ?? "UNKNOWN",
25
+ query: input.question
26
+ });
27
+ const citations = results.items.map((item) => ({
28
+ kbSnapshotId: env.value.kbSnapshotId,
29
+ sourceType: "ruleVersion",
30
+ sourceId: item.ruleVersionId,
31
+ title: "Curated rule version",
32
+ excerpt: item.excerpt
33
+ }));
34
+ const draft = {
35
+ locale: env.value.locale,
36
+ jurisdiction: env.value.regulatoryContext?.jurisdiction ?? "UNKNOWN",
37
+ allowedScope: env.value.allowedScope,
38
+ sections: [
39
+ {
40
+ heading: "Answer (KB-derived)",
41
+ body: results.items.length > 0 ? `This answer is derived from ${results.items.length} curated rule version(s) in the referenced snapshot.` : "No curated knowledge found in the referenced snapshot."
42
+ }
43
+ ],
44
+ citations,
45
+ disclaimers: ["Educational demo only."],
46
+ riskFlags: []
47
+ };
48
+ const scope = enforceAllowedScope(env.value.allowedScope, draft);
49
+ if (!scope.ok) {
50
+ return {
51
+ ...draft,
52
+ sections: [{ heading: "Escalation required", body: scope.error.message }],
53
+ refused: true,
54
+ refusalReason: scope.error.code,
55
+ riskFlags: [...draft.riskFlags ?? [], scope.error.code]
56
+ };
57
+ }
58
+ const cited = enforceCitations(draft);
59
+ if (!cited.ok) {
60
+ return {
61
+ ...draft,
62
+ sections: [{ heading: "Request blocked", body: cited.error.message }],
63
+ citations: [],
64
+ refused: true,
65
+ refusalReason: cited.error.code,
66
+ riskFlags: [...draft.riskFlags ?? [], cited.error.code]
67
+ };
68
+ }
69
+ return draft;
70
+ }
71
+
72
+ // src/handlers/policy-safe-knowledge-assistant.handlers.ts
73
+ import { web } from "@contractspec/lib.runtime-sandbox";
74
+ var { generateId } = web;
75
+ function parseJsonArray(value) {
76
+ if (!value)
77
+ return [];
78
+ try {
79
+ const parsed = JSON.parse(value);
80
+ return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string") : [];
81
+ } catch {
82
+ return [];
83
+ }
84
+ }
85
+ function nowIso() {
86
+ return new Date().toISOString();
87
+ }
88
+ function createPolicySafeKnowledgeAssistantHandlers(db) {
89
+ async function getUserContext(input) {
90
+ const result = await db.query(`SELECT * FROM psa_user_context WHERE "projectId" = $1 LIMIT 1`, [input.projectId]);
91
+ const row = result.rows[0];
92
+ if (!row) {
93
+ return {
94
+ projectId: input.projectId,
95
+ locale: "en-GB",
96
+ jurisdiction: "EU",
97
+ allowedScope: "education_only",
98
+ kbSnapshotId: null
99
+ };
100
+ }
101
+ return {
102
+ projectId: row.projectId,
103
+ locale: row.locale,
104
+ jurisdiction: row.jurisdiction,
105
+ allowedScope: row.allowedScope,
106
+ kbSnapshotId: row.kbSnapshotId
107
+ };
108
+ }
109
+ async function setUserContext(input) {
110
+ const existing = await db.query(`SELECT "projectId" FROM psa_user_context WHERE "projectId" = $1 LIMIT 1`, [input.projectId]);
111
+ if (existing.rows.length) {
112
+ await db.execute(`UPDATE psa_user_context SET locale = $1, jurisdiction = $2, "allowedScope" = $3 WHERE "projectId" = $4`, [input.locale, input.jurisdiction, input.allowedScope, input.projectId]);
113
+ } else {
114
+ await db.execute(`INSERT INTO psa_user_context ("projectId", locale, jurisdiction, "allowedScope", "kbSnapshotId") VALUES ($1, $2, $3, $4, $5)`, [
115
+ input.projectId,
116
+ input.locale,
117
+ input.jurisdiction,
118
+ input.allowedScope,
119
+ null
120
+ ]);
121
+ }
122
+ return await getUserContext({ projectId: input.projectId });
123
+ }
124
+ async function createRule(input) {
125
+ const id = generateId("psa_rule");
126
+ await db.execute(`INSERT INTO psa_rule (id, "projectId", jurisdiction, "topicKey") VALUES ($1, $2, $3, $4)`, [id, input.projectId, input.jurisdiction, input.topicKey]);
127
+ return { id, ...input };
128
+ }
129
+ async function upsertRuleVersion(input) {
130
+ if (!input.sourceRefs.length)
131
+ throw new Error("SOURCE_REFS_REQUIRED");
132
+ const rulesResult = await db.query(`SELECT * FROM psa_rule WHERE id = $1 LIMIT 1`, [input.ruleId]);
133
+ const rule = rulesResult.rows[0];
134
+ if (!rule)
135
+ throw new Error("RULE_NOT_FOUND");
136
+ const maxResult = await db.query(`SELECT MAX(version) as maxVersion FROM psa_rule_version WHERE "ruleId" = $1`, [input.ruleId]);
137
+ const maxVersion = Number(maxResult.rows[0]?.maxVersion ?? 0);
138
+ const version = maxVersion + 1;
139
+ const id = generateId("psa_rv");
140
+ const createdAt = nowIso();
141
+ await db.execute(`INSERT INTO psa_rule_version (id, "ruleId", jurisdiction, "topicKey", version, content, status, "sourceRefsJson", "approvedBy", "approvedAt", "createdAt")
142
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, [
143
+ id,
144
+ input.ruleId,
145
+ rule.jurisdiction,
146
+ rule.topicKey,
147
+ version,
148
+ input.content,
149
+ "draft",
150
+ JSON.stringify(input.sourceRefs),
151
+ null,
152
+ null,
153
+ createdAt
154
+ ]);
155
+ return {
156
+ id,
157
+ ruleId: input.ruleId,
158
+ jurisdiction: rule.jurisdiction,
159
+ topicKey: rule.topicKey,
160
+ version,
161
+ content: input.content,
162
+ status: "draft",
163
+ sourceRefs: input.sourceRefs,
164
+ createdAt: new Date(createdAt)
165
+ };
166
+ }
167
+ async function approveRuleVersion(input) {
168
+ const approvedAt = nowIso();
169
+ await db.execute(`UPDATE psa_rule_version SET status = $1, "approvedBy" = $2, "approvedAt" = $3 WHERE id = $4`, ["approved", input.approver, approvedAt, input.ruleVersionId]);
170
+ return { ruleVersionId: input.ruleVersionId, status: "approved" };
171
+ }
172
+ async function publishSnapshot(input) {
173
+ const approvedResult = await db.query(`SELECT id FROM psa_rule_version WHERE jurisdiction = $1 AND status = 'approved' ORDER BY id ASC`, [input.jurisdiction]);
174
+ const includedIds = approvedResult.rows.map((r) => r.id).filter(Boolean);
175
+ if (!includedIds.length)
176
+ throw new Error("NO_APPROVED_RULES");
177
+ const id = generateId("psa_snap");
178
+ const publishedAt = nowIso();
179
+ await db.execute(`INSERT INTO psa_snapshot (id, jurisdiction, "asOfDate", "includedRuleVersionIdsJson", "publishedAt")
180
+ VALUES ($1, $2, $3, $4, $5)`, [
181
+ id,
182
+ input.jurisdiction,
183
+ input.asOfDate.toISOString(),
184
+ JSON.stringify(includedIds),
185
+ publishedAt
186
+ ]);
187
+ await db.execute(`UPDATE psa_user_context SET "kbSnapshotId" = $1 WHERE "projectId" = $2`, [id, input.projectId]);
188
+ return {
189
+ id,
190
+ jurisdiction: input.jurisdiction,
191
+ includedRuleVersionIds: includedIds
192
+ };
193
+ }
194
+ async function searchKb(input) {
195
+ const snapResult = await db.query(`SELECT * FROM psa_snapshot WHERE id = $1 LIMIT 1`, [input.snapshotId]);
196
+ const snap = snapResult.rows[0];
197
+ if (!snap)
198
+ throw new Error("SNAPSHOT_NOT_FOUND");
199
+ if (snap.jurisdiction !== input.jurisdiction)
200
+ throw new Error("JURISDICTION_MISMATCH");
201
+ const includedIds = parseJsonArray(snap.includedRuleVersionIdsJson);
202
+ const tokens = input.query.toLowerCase().split(/\s+/).map((t) => t.trim()).filter(Boolean);
203
+ const items = [];
204
+ for (const id of includedIds) {
205
+ const rvResult = await db.query(`SELECT * FROM psa_rule_version WHERE id = $1 LIMIT 1`, [id]);
206
+ const rv = rvResult.rows[0];
207
+ if (!rv)
208
+ continue;
209
+ const hay = rv.content.toLowerCase();
210
+ const match = tokens.length ? tokens.every((t) => hay.includes(t)) : true;
211
+ if (!match)
212
+ continue;
213
+ items.push({ ruleVersionId: rv.id, excerpt: rv.content.slice(0, 140) });
214
+ }
215
+ return { items };
216
+ }
217
+ async function answer(input) {
218
+ const ctx = await getUserContext({ projectId: input.projectId });
219
+ if (!ctx.kbSnapshotId) {
220
+ const refused = await buildPolicySafeAnswer({
221
+ envelope: {
222
+ traceId: generateId("trace"),
223
+ locale: ctx.locale,
224
+ kbSnapshotId: "",
225
+ allowedScope: ctx.allowedScope,
226
+ regulatoryContext: { jurisdiction: ctx.jurisdiction }
227
+ },
228
+ question: input.question,
229
+ kbSearch: async () => ({ items: [] })
230
+ });
231
+ return refused;
232
+ }
233
+ return await buildPolicySafeAnswer({
234
+ envelope: {
235
+ traceId: generateId("trace"),
236
+ locale: ctx.locale,
237
+ kbSnapshotId: ctx.kbSnapshotId,
238
+ allowedScope: ctx.allowedScope,
239
+ regulatoryContext: { jurisdiction: ctx.jurisdiction }
240
+ },
241
+ question: input.question,
242
+ kbSearch: async (q) => await searchKb(q)
243
+ });
244
+ }
245
+ async function createChangeCandidate(input) {
246
+ const id = generateId("psa_change");
247
+ await db.execute(`INSERT INTO psa_change_candidate (id, "projectId", jurisdiction, "detectedAt", "diffSummary", "riskLevel", "proposedRuleVersionIdsJson")
248
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
249
+ id,
250
+ input.projectId,
251
+ input.jurisdiction,
252
+ nowIso(),
253
+ input.diffSummary,
254
+ input.riskLevel,
255
+ JSON.stringify(input.proposedRuleVersionIds)
256
+ ]);
257
+ return { id };
258
+ }
259
+ async function createReviewTask(input) {
260
+ const candResult = await db.query(`SELECT * FROM psa_change_candidate WHERE id = $1 LIMIT 1`, [input.changeCandidateId]);
261
+ const candidate = candResult.rows[0];
262
+ if (!candidate)
263
+ throw new Error("CHANGE_CANDIDATE_NOT_FOUND");
264
+ const assignedRole = candidate.riskLevel === "high" ? "expert" : "curator";
265
+ const id = generateId("psa_review");
266
+ await db.execute(`INSERT INTO psa_review_task (id, "changeCandidateId", status, "assignedRole", decision, "decidedAt", "decidedBy")
267
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`, [id, input.changeCandidateId, "open", assignedRole, null, null, null]);
268
+ return { id, assignedRole };
269
+ }
270
+ async function submitDecision(input) {
271
+ const reviewResult = await db.query(`SELECT * FROM psa_review_task WHERE id = $1 LIMIT 1`, [input.reviewTaskId]);
272
+ const task = reviewResult.rows[0];
273
+ if (!task)
274
+ throw new Error("REVIEW_TASK_NOT_FOUND");
275
+ const candidateResult = await db.query(`SELECT * FROM psa_change_candidate WHERE id = $1 LIMIT 1`, [task.changeCandidateId]);
276
+ const candidate = candidateResult.rows[0];
277
+ if (!candidate)
278
+ throw new Error("CHANGE_CANDIDATE_NOT_FOUND");
279
+ if (candidate.riskLevel === "high" && input.decision === "approve" && input.decidedByRole !== "expert") {
280
+ throw new Error("FORBIDDEN_ROLE");
281
+ }
282
+ const decidedAt = nowIso();
283
+ await db.execute(`UPDATE psa_review_task SET status = $1, decision = $2, "decidedAt" = $3, "decidedBy" = $4 WHERE id = $5`, [
284
+ "decided",
285
+ input.decision,
286
+ decidedAt,
287
+ input.decidedBy,
288
+ input.reviewTaskId
289
+ ]);
290
+ return { id: input.reviewTaskId, status: "decided" };
291
+ }
292
+ async function publishIfReady(input) {
293
+ const openResult = await db.query(`SELECT COUNT(*) as count FROM psa_review_task WHERE status != 'decided'`, []);
294
+ const openCount = Number(openResult.rows[0]?.count ?? 0);
295
+ if (openCount > 0)
296
+ throw new Error("NOT_READY");
297
+ const decidedResult = await db.query(`SELECT * FROM psa_review_task`, []);
298
+ for (const row of decidedResult.rows) {
299
+ if (row.decision !== "approve")
300
+ continue;
301
+ const candQueryResult = await db.query(`SELECT * FROM psa_change_candidate WHERE id = $1 LIMIT 1`, [row.changeCandidateId]);
302
+ const cand = candQueryResult.rows[0];
303
+ if (!cand)
304
+ continue;
305
+ if (cand.jurisdiction !== input.jurisdiction)
306
+ continue;
307
+ const proposedIds = parseJsonArray(cand.proposedRuleVersionIdsJson);
308
+ for (const rvId of proposedIds) {
309
+ const rvQueryResult = await db.query(`SELECT status FROM psa_rule_version WHERE id = $1 LIMIT 1`, [rvId]);
310
+ const status = String(rvQueryResult.rows[0]?.status ?? "");
311
+ if (status !== "approved")
312
+ throw new Error("NOT_READY");
313
+ }
314
+ }
315
+ return { published: true };
316
+ }
317
+ return {
318
+ getUserContext,
319
+ setUserContext,
320
+ createRule,
321
+ upsertRuleVersion,
322
+ approveRuleVersion,
323
+ publishSnapshot,
324
+ searchKb,
325
+ answer,
326
+ createChangeCandidate,
327
+ createReviewTask,
328
+ submitDecision,
329
+ publishIfReady
330
+ };
331
+ }
332
+ export {
333
+ createPolicySafeKnowledgeAssistantHandlers
334
+ };