@contractspec/example.policy-safe-knowledge-assistant 3.7.5 → 3.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +3 -3
- package/AGENTS.md +50 -26
- package/CHANGELOG.md +17 -0
- package/README.md +68 -15
- package/dist/browser/index.js +15 -15
- package/dist/browser/ui/PolicySafeKnowledgeAssistantDashboard.js +15 -15
- package/dist/browser/ui/hooks/usePolicySafeKnowledgeAssistant.js +1 -1
- package/dist/browser/ui/index.js +15 -15
- package/dist/index.d.ts +3 -3
- package/dist/index.js +15 -15
- package/dist/node/index.js +15 -15
- package/dist/node/ui/PolicySafeKnowledgeAssistantDashboard.js +15 -15
- package/dist/node/ui/hooks/usePolicySafeKnowledgeAssistant.js +1 -1
- package/dist/node/ui/index.js +15 -15
- package/dist/ui/PolicySafeKnowledgeAssistantDashboard.js +15 -15
- package/dist/ui/hooks/usePolicySafeKnowledgeAssistant.js +1 -1
- package/dist/ui/index.js +15 -15
- package/package.json +15 -15
- package/src/docs/policy-safe-knowledge-assistant.docblock.ts +21 -21
- package/src/example.ts +30 -30
- package/src/handlers/policy-safe-knowledge-assistant.handlers.ts +426 -425
- package/src/index.ts +4 -3
- package/src/integration.test.ts +87 -88
- package/src/orchestrator/buildAnswer.ts +97 -97
- package/src/policy-safe-knowledge-assistant.feature.ts +61 -61
- package/src/seed/fixtures.ts +29 -29
- package/src/seeders/index.ts +12 -12
- package/src/ui/PolicySafeKnowledgeAssistantDashboard.tsx +191 -191
- package/src/ui/hooks/usePolicySafeKnowledgeAssistant.ts +204 -204
- package/tsconfig.json +7 -17
- package/tsdown.config.js +7 -13
package/src/seeders/index.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { DatabasePort } from '@contractspec/lib.runtime-sandbox';
|
|
2
2
|
|
|
3
3
|
export async function seedPolicyKnowledgeAssistant(params: {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
projectId: string;
|
|
5
|
+
db: DatabasePort;
|
|
6
6
|
}) {
|
|
7
|
-
|
|
7
|
+
const { projectId, db } = params;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const existing = await db.query(
|
|
10
|
+
`SELECT COUNT(*) as count FROM psa_user_context WHERE "projectId" = $1`,
|
|
11
|
+
[projectId]
|
|
12
|
+
);
|
|
13
|
+
if ((existing.rows[0]?.count as number) > 0) return;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
await db.execute(
|
|
16
|
+
`INSERT INTO psa_user_context ("projectId", locale, jurisdiction, "allowedScope")
|
|
17
17
|
VALUES ($1, $2, $3, $4)`,
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
[projectId, 'en-GB', 'EU', 'education_only']
|
|
19
|
+
);
|
|
20
20
|
}
|
|
@@ -1,206 +1,206 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
4
3
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
Button,
|
|
5
|
+
ErrorState,
|
|
6
|
+
LoaderBlock,
|
|
7
|
+
StatCard,
|
|
8
|
+
StatCardGroup,
|
|
10
9
|
} from '@contractspec/lib.design-system';
|
|
11
10
|
import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
|
|
12
11
|
import { Input } from '@contractspec/lib.ui-kit-web/ui/input';
|
|
13
|
-
import { Textarea } from '@contractspec/lib.ui-kit-web/ui/textarea';
|
|
14
12
|
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
Select,
|
|
14
|
+
SelectContent,
|
|
15
|
+
SelectItem,
|
|
16
|
+
SelectTrigger,
|
|
17
|
+
SelectValue,
|
|
20
18
|
} from '@contractspec/lib.ui-kit-web/ui/select';
|
|
19
|
+
import { Textarea } from '@contractspec/lib.ui-kit-web/ui/textarea';
|
|
20
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
21
21
|
|
|
22
22
|
import { usePolicySafeKnowledgeAssistant } from './hooks/usePolicySafeKnowledgeAssistant';
|
|
23
23
|
|
|
24
24
|
type AllowedScope = 'education_only' | 'generic_info' | 'escalation_required';
|
|
25
25
|
|
|
26
26
|
export function PolicySafeKnowledgeAssistantDashboard() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
27
|
+
const { state, actions } = usePolicySafeKnowledgeAssistant();
|
|
28
|
+
const [question, setQuestion] = useState('reporting obligations');
|
|
29
|
+
const [content, setContent] = useState(
|
|
30
|
+
'EU: Reporting obligations v2 (updated)'
|
|
31
|
+
);
|
|
32
|
+
const [locale, setLocale] = useState('en-GB');
|
|
33
|
+
const [jurisdiction, setJurisdiction] = useState('EU');
|
|
34
|
+
const [allowedScope, setAllowedScope] =
|
|
35
|
+
useState<AllowedScope>('education_only');
|
|
36
|
+
|
|
37
|
+
const snapshotId =
|
|
38
|
+
state.context?.kbSnapshotId ?? state.lastSnapshotId ?? null;
|
|
39
|
+
|
|
40
|
+
const stats = useMemo(() => {
|
|
41
|
+
return [
|
|
42
|
+
{ label: 'Locale', value: state.context?.locale ?? '—' },
|
|
43
|
+
{ label: 'Jurisdiction', value: state.context?.jurisdiction ?? '—' },
|
|
44
|
+
{ label: 'Scope', value: state.context?.allowedScope ?? '—' },
|
|
45
|
+
{ label: 'KB Snapshot', value: snapshotId ?? '—' },
|
|
46
|
+
];
|
|
47
|
+
}, [
|
|
48
|
+
snapshotId,
|
|
49
|
+
state.context?.allowedScope,
|
|
50
|
+
state.context?.jurisdiction,
|
|
51
|
+
state.context?.locale,
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const handleSetContext = useCallback(async () => {
|
|
55
|
+
await actions.setContext({ locale, jurisdiction, allowedScope });
|
|
56
|
+
}, [actions, allowedScope, jurisdiction, locale]);
|
|
57
|
+
|
|
58
|
+
const handleAsk = useCallback(async () => {
|
|
59
|
+
await actions.askAssistant(question);
|
|
60
|
+
}, [actions, question]);
|
|
61
|
+
|
|
62
|
+
const handleAdminPublishFlow = useCallback(async () => {
|
|
63
|
+
const ruleId = state.lastRuleId ?? (await actions.createDemoRule());
|
|
64
|
+
const rvId = await actions.upsertRuleVersion({ ruleId, content });
|
|
65
|
+
await actions.approveRuleVersion(rvId);
|
|
66
|
+
await actions.simulateHighRiskChangeAndApprove(rvId);
|
|
67
|
+
await actions.publishSnapshot();
|
|
68
|
+
}, [actions, content, state.lastRuleId]);
|
|
69
|
+
|
|
70
|
+
if (state.loading && !state.context) {
|
|
71
|
+
return <LoaderBlock label="Loading demo..." />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (state.error) {
|
|
75
|
+
return (
|
|
76
|
+
<ErrorState
|
|
77
|
+
title="Failed to load demo"
|
|
78
|
+
description={state.error.message}
|
|
79
|
+
onRetry={actions.refreshContext}
|
|
80
|
+
retryLabel="Retry"
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="space-y-6">
|
|
87
|
+
<StatCardGroup>
|
|
88
|
+
{stats.map((s) => (
|
|
89
|
+
<StatCard key={s.label} label={s.label} value={String(s.value)} />
|
|
90
|
+
))}
|
|
91
|
+
</StatCardGroup>
|
|
92
|
+
|
|
93
|
+
<Card className="p-4">
|
|
94
|
+
<h3 className="font-semibold text-lg">
|
|
95
|
+
1) Onboarding (explicit locale + jurisdiction)
|
|
96
|
+
</h3>
|
|
97
|
+
<div className="mt-3 grid gap-3 md:grid-cols-3">
|
|
98
|
+
<div>
|
|
99
|
+
<div className="mb-1 font-semibold text-muted-foreground text-xs uppercase tracking-wide">
|
|
100
|
+
Locale
|
|
101
|
+
</div>
|
|
102
|
+
<Input value={locale} onChange={(e) => setLocale(e.target.value)} />
|
|
103
|
+
</div>
|
|
104
|
+
<div>
|
|
105
|
+
<div className="mb-1 font-semibold text-muted-foreground text-xs uppercase tracking-wide">
|
|
106
|
+
Jurisdiction
|
|
107
|
+
</div>
|
|
108
|
+
<Input
|
|
109
|
+
value={jurisdiction}
|
|
110
|
+
onChange={(e) => setJurisdiction(e.target.value)}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
<div>
|
|
114
|
+
<div className="mb-1 font-semibold text-muted-foreground text-xs uppercase tracking-wide">
|
|
115
|
+
Allowed scope
|
|
116
|
+
</div>
|
|
117
|
+
<Select
|
|
118
|
+
value={allowedScope}
|
|
119
|
+
onValueChange={(v) => setAllowedScope(v as AllowedScope)}
|
|
120
|
+
>
|
|
121
|
+
<SelectTrigger>
|
|
122
|
+
<SelectValue placeholder="Select scope" />
|
|
123
|
+
</SelectTrigger>
|
|
124
|
+
<SelectContent>
|
|
125
|
+
<SelectItem value="education_only">education_only</SelectItem>
|
|
126
|
+
<SelectItem value="generic_info">generic_info</SelectItem>
|
|
127
|
+
<SelectItem value="escalation_required">
|
|
128
|
+
escalation_required
|
|
129
|
+
</SelectItem>
|
|
130
|
+
</SelectContent>
|
|
131
|
+
</Select>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<div className="mt-4 flex gap-2">
|
|
135
|
+
<Button onPress={handleSetContext}>Save context</Button>
|
|
136
|
+
<Button variant="outline" onPress={actions.refreshContext}>
|
|
137
|
+
Refresh
|
|
138
|
+
</Button>
|
|
139
|
+
</div>
|
|
140
|
+
</Card>
|
|
141
|
+
|
|
142
|
+
<Card className="p-4">
|
|
143
|
+
<h3 className="font-semibold text-lg">
|
|
144
|
+
2) Ask the assistant (must cite KB snapshot)
|
|
145
|
+
</h3>
|
|
146
|
+
<div className="mt-3 flex flex-col gap-3">
|
|
147
|
+
<Input
|
|
148
|
+
value={question}
|
|
149
|
+
onChange={(e) => setQuestion(e.target.value)}
|
|
150
|
+
/>
|
|
151
|
+
<div className="flex gap-2">
|
|
152
|
+
<Button onPress={handleAsk}>Ask</Button>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{state.lastAnswer ? (
|
|
157
|
+
<div className="mt-4 space-y-3">
|
|
158
|
+
{state.lastAnswer.refused ? (
|
|
159
|
+
<div className="text-red-600 text-sm">
|
|
160
|
+
Refused: {state.lastAnswer.refusalReason ?? 'UNKNOWN'}
|
|
161
|
+
</div>
|
|
162
|
+
) : null}
|
|
163
|
+
{state.lastAnswer.sections.map((s, idx) => (
|
|
164
|
+
<div key={`${s.heading}-${idx}`}>
|
|
165
|
+
<div className="font-semibold text-sm">{s.heading}</div>
|
|
166
|
+
<div className="text-muted-foreground text-sm">{s.body}</div>
|
|
167
|
+
</div>
|
|
168
|
+
))}
|
|
169
|
+
<div className="font-semibold text-sm">Citations</div>
|
|
170
|
+
<ul className="list-disc pl-5 text-muted-foreground text-sm">
|
|
171
|
+
{state.lastAnswer.citations.map((c) => (
|
|
172
|
+
<li key={`${c.kbSnapshotId}-${c.sourceId}`}>
|
|
173
|
+
{c.kbSnapshotId} — {c.sourceId}
|
|
174
|
+
</li>
|
|
175
|
+
))}
|
|
176
|
+
</ul>
|
|
177
|
+
</div>
|
|
178
|
+
) : null}
|
|
179
|
+
</Card>
|
|
180
|
+
|
|
181
|
+
<Card className="p-4">
|
|
182
|
+
<h3 className="font-semibold text-lg">
|
|
183
|
+
3) Admin: publish a new snapshot (HITL)
|
|
184
|
+
</h3>
|
|
185
|
+
<div className="mt-3 space-y-3">
|
|
186
|
+
<Textarea
|
|
187
|
+
value={content}
|
|
188
|
+
onChange={(e) => setContent(e.target.value)}
|
|
189
|
+
/>
|
|
190
|
+
<Button onPress={handleAdminPublishFlow}>
|
|
191
|
+
Simulate change → review → approve → publish snapshot
|
|
192
|
+
</Button>
|
|
193
|
+
</div>
|
|
194
|
+
</Card>
|
|
195
|
+
|
|
196
|
+
<Card className="p-4">
|
|
197
|
+
<h3 className="font-semibold text-lg">4) Learning hub (patterns)</h3>
|
|
198
|
+
<p className="mt-2 text-muted-foreground text-sm">
|
|
199
|
+
This template includes drills, ambient coach, and quests as reusable
|
|
200
|
+
Learning Journey tracks. The interactive learning UI is demonstrated
|
|
201
|
+
in dedicated Learning Journey examples.
|
|
202
|
+
</p>
|
|
203
|
+
</Card>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
206
|
}
|