@contractspec/example.locale-jurisdiction-gate 3.7.6 → 3.7.10
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 +72 -51
- package/AGENTS.md +50 -27
- package/CHANGELOG.md +21 -0
- package/README.md +87 -42
- package/dist/browser/entities/index.js +2 -2
- package/dist/browser/entities/models.js +2 -2
- package/dist/browser/events.js +1 -1
- package/dist/browser/forms/assistant-context.form.js +213 -0
- package/dist/browser/forms/index.js +213 -0
- package/dist/browser/index.js +362 -40
- package/dist/browser/locale-jurisdiction-gate.feature.js +66 -1
- package/dist/browser/operations/assistant.js +3 -3
- package/dist/browser/operations/index.js +3 -3
- package/dist/browser/policy/assistant-gate.policy.js +62 -0
- package/dist/browser/policy/index.js +62 -1
- package/dist/browser/translations/assistant-gate.en-GB.translation.js +48 -0
- package/dist/browser/translations/assistant-gate.en-US.translation.js +50 -0
- package/dist/browser/translations/assistant-gate.fr-FR.translation.js +52 -0
- package/dist/browser/translations/index.js +148 -0
- package/dist/contracts.test.d.ts +1 -0
- package/dist/entities/index.js +2 -2
- package/dist/entities/models.js +2 -2
- package/dist/events.js +1 -1
- package/dist/forms/assistant-context.form.d.ts +22 -0
- package/dist/forms/assistant-context.form.js +214 -0
- package/dist/forms/index.d.ts +1 -0
- package/dist/forms/index.js +214 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +362 -40
- package/dist/locale-jurisdiction-gate.feature.js +66 -1
- package/dist/node/entities/index.js +2 -2
- package/dist/node/entities/models.js +2 -2
- package/dist/node/events.js +1 -1
- package/dist/node/forms/assistant-context.form.js +213 -0
- package/dist/node/forms/index.js +213 -0
- package/dist/node/index.js +362 -40
- package/dist/node/locale-jurisdiction-gate.feature.js +66 -1
- package/dist/node/operations/assistant.js +3 -3
- package/dist/node/operations/index.js +3 -3
- package/dist/node/policy/assistant-gate.policy.js +62 -0
- package/dist/node/policy/index.js +62 -1
- package/dist/node/translations/assistant-gate.en-GB.translation.js +48 -0
- package/dist/node/translations/assistant-gate.en-US.translation.js +50 -0
- package/dist/node/translations/assistant-gate.fr-FR.translation.js +52 -0
- package/dist/node/translations/index.js +148 -0
- package/dist/operations/assistant.js +3 -3
- package/dist/operations/index.js +3 -3
- package/dist/policy/assistant-gate.policy.d.ts +1 -0
- package/dist/policy/assistant-gate.policy.js +63 -0
- package/dist/policy/index.d.ts +2 -1
- package/dist/policy/index.js +62 -1
- package/dist/translations/assistant-gate.en-GB.translation.d.ts +1 -0
- package/dist/translations/assistant-gate.en-GB.translation.js +49 -0
- package/dist/translations/assistant-gate.en-US.translation.d.ts +1 -0
- package/dist/translations/assistant-gate.en-US.translation.js +51 -0
- package/dist/translations/assistant-gate.fr-FR.translation.d.ts +1 -0
- package/dist/translations/assistant-gate.fr-FR.translation.js +53 -0
- package/dist/translations/index.d.ts +3 -0
- package/dist/translations/index.js +149 -0
- package/package.json +105 -7
- package/src/contracts.test.ts +32 -0
- package/src/docs/locale-jurisdiction-gate.docblock.ts +21 -21
- package/src/entities/models.ts +87 -87
- package/src/events.ts +55 -55
- package/src/example.ts +28 -28
- package/src/forms/assistant-context.form.ts +112 -0
- package/src/forms/index.ts +1 -0
- package/src/handlers/demo.handlers.test.ts +46 -46
- package/src/handlers/demo.handlers.ts +133 -133
- package/src/index.ts +5 -3
- package/src/locale-jurisdiction-gate.feature.ts +40 -34
- package/src/operations/assistant.ts +82 -82
- package/src/policy/assistant-gate.policy.ts +65 -0
- package/src/policy/guard.test.ts +18 -18
- package/src/policy/guard.ts +75 -75
- package/src/policy/index.ts +2 -1
- package/src/policy/types.ts +12 -12
- package/src/translations/assistant-gate.en-GB.translation.ts +46 -0
- package/src/translations/assistant-gate.en-US.translation.ts +48 -0
- package/src/translations/assistant-gate.fr-FR.translation.ts +51 -0
- package/src/translations/index.ts +3 -0
- package/tsconfig.json +7 -15
- package/tsdown.config.js +7 -13
package/src/entities/models.ts
CHANGED
|
@@ -1,110 +1,110 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
defineEnum,
|
|
3
|
+
defineSchemaModel,
|
|
4
|
+
ScalarTypeEnum,
|
|
5
5
|
} from '@contractspec/lib.schema';
|
|
6
6
|
|
|
7
7
|
export const AllowedScopeEnum = defineEnum('AllowedScope', [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
'education_only',
|
|
9
|
+
'generic_info',
|
|
10
|
+
'escalation_required',
|
|
11
11
|
]);
|
|
12
12
|
|
|
13
13
|
export const UserProfileModel = defineSchemaModel({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
name: 'UserProfile',
|
|
15
|
+
description: 'User profile inputs used to derive regulatory context.',
|
|
16
|
+
fields: {
|
|
17
|
+
preferredLocale: {
|
|
18
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
19
|
+
isOptional: true,
|
|
20
|
+
},
|
|
21
|
+
residencyCountry: {
|
|
22
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
23
|
+
isOptional: true,
|
|
24
|
+
},
|
|
25
|
+
taxResidenceCountry: {
|
|
26
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
27
|
+
isOptional: true,
|
|
28
|
+
},
|
|
29
|
+
clientType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
30
|
+
},
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
export const RegulatoryContextModel = defineSchemaModel({
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
name: 'RegulatoryContext',
|
|
35
|
+
description: 'Explicit regulatory context (no guessing).',
|
|
36
|
+
fields: {
|
|
37
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
38
|
+
region: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
39
|
+
clientType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
40
|
+
allowedScope: { type: AllowedScopeEnum, isOptional: false },
|
|
41
|
+
},
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
export const LLMCallEnvelopeModel = defineSchemaModel({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
name: 'LLMCallEnvelope',
|
|
46
|
+
description:
|
|
47
|
+
'Mandatory envelope for assistant calls. All fields are explicit and required for policy gating.',
|
|
48
|
+
fields: {
|
|
49
|
+
traceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
50
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
51
|
+
regulatoryContext: { type: RegulatoryContextModel, isOptional: false },
|
|
52
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
53
|
+
allowedScope: { type: AllowedScopeEnum, isOptional: false },
|
|
54
|
+
},
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
export const AssistantCitationModel = defineSchemaModel({
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
name: 'AssistantCitation',
|
|
59
|
+
description:
|
|
60
|
+
'Citation referencing a KB snapshot + a specific item within it.',
|
|
61
|
+
fields: {
|
|
62
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
63
|
+
sourceType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
64
|
+
sourceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
65
|
+
title: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
66
|
+
excerpt: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
67
|
+
},
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
export const AssistantAnswerSectionModel = defineSchemaModel({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
name: 'AssistantAnswerSection',
|
|
72
|
+
description: 'Structured answer section.',
|
|
73
|
+
fields: {
|
|
74
|
+
heading: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
75
|
+
body: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
76
|
+
},
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
export const AssistantAnswerIRModel = defineSchemaModel({
|
|
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
|
-
|
|
80
|
+
name: 'AssistantAnswerIR',
|
|
81
|
+
description:
|
|
82
|
+
'Structured assistant answer with mandatory citations and explicit locale/jurisdiction.',
|
|
83
|
+
fields: {
|
|
84
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
85
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
86
|
+
allowedScope: { type: AllowedScopeEnum, isOptional: false },
|
|
87
|
+
sections: {
|
|
88
|
+
type: AssistantAnswerSectionModel,
|
|
89
|
+
isArray: true,
|
|
90
|
+
isOptional: false,
|
|
91
|
+
},
|
|
92
|
+
citations: {
|
|
93
|
+
type: AssistantCitationModel,
|
|
94
|
+
isArray: true,
|
|
95
|
+
isOptional: false,
|
|
96
|
+
},
|
|
97
|
+
disclaimers: {
|
|
98
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
99
|
+
isArray: true,
|
|
100
|
+
isOptional: true,
|
|
101
|
+
},
|
|
102
|
+
riskFlags: {
|
|
103
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
104
|
+
isArray: true,
|
|
105
|
+
isOptional: true,
|
|
106
|
+
},
|
|
107
|
+
refused: { type: ScalarTypeEnum.Boolean(), isOptional: true },
|
|
108
|
+
refusalReason: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
109
|
+
},
|
|
110
110
|
});
|
package/src/events.ts
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
1
|
import { defineEvent } from '@contractspec/lib.contracts-spec';
|
|
2
|
-
import {
|
|
2
|
+
import { defineSchemaModel, ScalarTypeEnum } from '@contractspec/lib.schema';
|
|
3
3
|
|
|
4
4
|
const AssistantAnswerRequestedPayload = defineSchemaModel({
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
name: 'AssistantAnswerRequestedPayload',
|
|
6
|
+
description: 'Emitted when an assistant answer is requested (pre-gate).',
|
|
7
|
+
fields: {
|
|
8
|
+
traceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
9
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
10
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
11
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
12
|
+
allowedScope: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
13
|
+
},
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
export const AssistantAnswerRequestedEvent = defineEvent({
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
meta: {
|
|
18
|
+
key: 'assistant.answer.requested',
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
description: 'Assistant answer requested (policy gate will run).',
|
|
21
|
+
stability: 'experimental',
|
|
22
|
+
owners: ['@examples'],
|
|
23
|
+
tags: ['assistant', 'policy'],
|
|
24
|
+
},
|
|
25
|
+
payload: AssistantAnswerRequestedPayload,
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const AssistantAnswerBlockedPayload = defineSchemaModel({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
name: 'AssistantAnswerBlockedPayload',
|
|
30
|
+
description: 'Emitted when a request is blocked by the gate.',
|
|
31
|
+
fields: {
|
|
32
|
+
traceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
33
|
+
reasonCode: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
34
|
+
reason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
35
|
+
},
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
export const AssistantAnswerBlockedEvent = defineEvent({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
meta: {
|
|
40
|
+
key: 'assistant.answer.blocked',
|
|
41
|
+
version: '1.0.0',
|
|
42
|
+
description: 'Assistant answer blocked (fail-closed).',
|
|
43
|
+
stability: 'experimental',
|
|
44
|
+
owners: ['@examples'],
|
|
45
|
+
tags: ['assistant', 'policy', 'blocked'],
|
|
46
|
+
},
|
|
47
|
+
payload: AssistantAnswerBlockedPayload,
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
const AssistantAnswerDeliveredPayload = defineSchemaModel({
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
name: 'AssistantAnswerDeliveredPayload',
|
|
52
|
+
description: 'Emitted when a structured, cited answer is delivered.',
|
|
53
|
+
fields: {
|
|
54
|
+
traceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
55
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
56
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
57
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
58
|
+
allowedScope: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
59
|
+
citationsCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
60
|
+
},
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
export const AssistantAnswerDeliveredEvent = defineEvent({
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
meta: {
|
|
65
|
+
key: 'assistant.answer.delivered',
|
|
66
|
+
version: '1.0.0',
|
|
67
|
+
description:
|
|
68
|
+
'Assistant answer delivered (must include KB snapshot citations).',
|
|
69
|
+
stability: 'experimental',
|
|
70
|
+
owners: ['@examples'],
|
|
71
|
+
tags: ['assistant', 'policy', 'delivered'],
|
|
72
|
+
},
|
|
73
|
+
payload: AssistantAnswerDeliveredPayload,
|
|
74
74
|
});
|
package/src/example.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import { defineExample } from '@contractspec/lib.contracts-spec';
|
|
2
2
|
|
|
3
3
|
const example = defineExample({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
4
|
+
meta: {
|
|
5
|
+
key: 'locale-jurisdiction-gate',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
title: 'Locale / Jurisdiction Gate',
|
|
8
|
+
description:
|
|
9
|
+
'Fail-closed gating for assistant calls: locale + jurisdiction + kbSnapshotId + allowedScope must be explicit, answers must cite a snapshot.',
|
|
10
|
+
kind: 'knowledge',
|
|
11
|
+
visibility: 'public',
|
|
12
|
+
stability: 'experimental',
|
|
13
|
+
owners: ['@platform.core'],
|
|
14
|
+
tags: ['policy', 'locale', 'jurisdiction', 'assistant', 'gating'],
|
|
15
|
+
},
|
|
16
|
+
docs: {
|
|
17
|
+
rootDocId: 'docs.examples.locale-jurisdiction-gate',
|
|
18
|
+
},
|
|
19
|
+
entrypoints: {
|
|
20
|
+
packageName: '@contractspec/example.locale-jurisdiction-gate',
|
|
21
|
+
feature: './feature',
|
|
22
|
+
contracts: './contracts',
|
|
23
|
+
handlers: './handlers',
|
|
24
|
+
docs: './docs',
|
|
25
|
+
},
|
|
26
|
+
surfaces: {
|
|
27
|
+
templates: true,
|
|
28
|
+
sandbox: { enabled: true, modes: ['markdown', 'specs'] },
|
|
29
|
+
studio: { enabled: true, installable: true },
|
|
30
|
+
mcp: { enabled: true },
|
|
31
|
+
},
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
export default example;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { defineFormSpec } from '@contractspec/lib.contracts-spec/forms';
|
|
2
|
+
import {
|
|
3
|
+
OwnersEnum,
|
|
4
|
+
StabilityEnum,
|
|
5
|
+
TagsEnum,
|
|
6
|
+
} from '@contractspec/lib.contracts-spec/ownership';
|
|
7
|
+
import { defineSchemaModel, ScalarTypeEnum } from '@contractspec/lib.schema';
|
|
8
|
+
import { AllowedScopeEnum } from '../entities/models';
|
|
9
|
+
|
|
10
|
+
const AssistantContextFormModel = defineSchemaModel({
|
|
11
|
+
name: 'AssistantContextFormModel',
|
|
12
|
+
description:
|
|
13
|
+
'Form values required before a policy-gated assistant request can be executed.',
|
|
14
|
+
fields: {
|
|
15
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
16
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
17
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
18
|
+
allowedScope: { type: AllowedScopeEnum, isOptional: false },
|
|
19
|
+
question: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const AssistantContextForm = defineFormSpec({
|
|
24
|
+
meta: {
|
|
25
|
+
key: 'locale-jurisdiction-gate.form.assistant-context',
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
title: 'Assistant Context Gate',
|
|
28
|
+
description:
|
|
29
|
+
'Collects the explicit locale, jurisdiction, scope, and knowledge snapshot required by the assistant gate.',
|
|
30
|
+
domain: 'assistant',
|
|
31
|
+
owners: [OwnersEnum.PlatformFinance],
|
|
32
|
+
tags: [TagsEnum.I18n, 'assistant', 'form', 'policy'],
|
|
33
|
+
stability: StabilityEnum.Experimental,
|
|
34
|
+
},
|
|
35
|
+
model: AssistantContextFormModel,
|
|
36
|
+
fields: [
|
|
37
|
+
{
|
|
38
|
+
kind: 'select',
|
|
39
|
+
name: 'locale',
|
|
40
|
+
labelI18n: 'assistantGate.locale.label',
|
|
41
|
+
descriptionI18n: 'assistantGate.locale.description',
|
|
42
|
+
options: {
|
|
43
|
+
kind: 'static',
|
|
44
|
+
options: [
|
|
45
|
+
{ labelI18n: 'assistantGate.locale.enUs', value: 'en-US' },
|
|
46
|
+
{ labelI18n: 'assistantGate.locale.enGb', value: 'en-GB' },
|
|
47
|
+
{ labelI18n: 'assistantGate.locale.frFr', value: 'fr-FR' },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
required: true,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
kind: 'text',
|
|
54
|
+
name: 'jurisdiction',
|
|
55
|
+
labelI18n: 'assistantGate.jurisdiction.label',
|
|
56
|
+
placeholderI18n: 'assistantGate.jurisdiction.placeholder',
|
|
57
|
+
required: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
kind: 'text',
|
|
61
|
+
name: 'kbSnapshotId',
|
|
62
|
+
labelI18n: 'assistantGate.kbSnapshotId.label',
|
|
63
|
+
placeholderI18n: 'assistantGate.kbSnapshotId.placeholder',
|
|
64
|
+
required: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
kind: 'radio',
|
|
68
|
+
name: 'allowedScope',
|
|
69
|
+
labelI18n: 'assistantGate.allowedScope.label',
|
|
70
|
+
options: {
|
|
71
|
+
kind: 'static',
|
|
72
|
+
options: [
|
|
73
|
+
{
|
|
74
|
+
labelI18n: 'assistantGate.allowedScope.educationOnly',
|
|
75
|
+
value: 'education_only',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
labelI18n: 'assistantGate.allowedScope.genericInfo',
|
|
79
|
+
value: 'generic_info',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
labelI18n: 'assistantGate.allowedScope.escalationRequired',
|
|
83
|
+
value: 'escalation_required',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
required: true,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
kind: 'textarea',
|
|
91
|
+
name: 'question',
|
|
92
|
+
labelI18n: 'assistantGate.question.label',
|
|
93
|
+
placeholderI18n: 'assistantGate.question.placeholder',
|
|
94
|
+
required: true,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
actions: [
|
|
98
|
+
{
|
|
99
|
+
key: 'submit',
|
|
100
|
+
labelI18n: 'assistantGate.submit.label',
|
|
101
|
+
op: { name: 'assistant.answer', version: '1.0.0' },
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
policy: {
|
|
105
|
+
flags: [],
|
|
106
|
+
pii: ['kbSnapshotId', 'question'],
|
|
107
|
+
},
|
|
108
|
+
renderHints: {
|
|
109
|
+
ui: 'custom',
|
|
110
|
+
form: 'react-hook-form',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './assistant-context.form';
|
|
@@ -3,52 +3,52 @@ import { describe, expect, it } from 'bun:test';
|
|
|
3
3
|
import { createDemoAssistantHandlers } from './demo.handlers';
|
|
4
4
|
|
|
5
5
|
describe('@contractspec/example.locale-jurisdiction-gate demo handlers', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
it('blocks when locale is missing', async () => {
|
|
7
|
+
const handlers = createDemoAssistantHandlers();
|
|
8
|
+
const result = await handlers.answer({
|
|
9
|
+
envelope: {
|
|
10
|
+
traceId: 't1',
|
|
11
|
+
locale: '',
|
|
12
|
+
kbSnapshotId: 'snap_1',
|
|
13
|
+
allowedScope: 'education_only',
|
|
14
|
+
regulatoryContext: { jurisdiction: 'EU' },
|
|
15
|
+
},
|
|
16
|
+
question: 'What is a snapshot?',
|
|
17
|
+
});
|
|
18
|
+
expect(result.refused).toBeTrue();
|
|
19
|
+
expect(result.refusalReason).toBe('LOCALE_REQUIRED');
|
|
20
|
+
});
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
it('blocks when kbSnapshotId is missing', async () => {
|
|
23
|
+
const handlers = createDemoAssistantHandlers();
|
|
24
|
+
const result = await handlers.answer({
|
|
25
|
+
envelope: {
|
|
26
|
+
traceId: 't2',
|
|
27
|
+
locale: 'en-GB',
|
|
28
|
+
kbSnapshotId: '',
|
|
29
|
+
allowedScope: 'education_only',
|
|
30
|
+
regulatoryContext: { jurisdiction: 'EU' },
|
|
31
|
+
},
|
|
32
|
+
question: 'What is a snapshot?',
|
|
33
|
+
});
|
|
34
|
+
expect(result.refused).toBeTrue();
|
|
35
|
+
expect(result.refusalReason).toBe('KB_SNAPSHOT_REQUIRED');
|
|
36
|
+
});
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
it('blocks education_only answers that include buy/sell language', async () => {
|
|
39
|
+
const handlers = createDemoAssistantHandlers();
|
|
40
|
+
const result = await handlers.answer({
|
|
41
|
+
envelope: {
|
|
42
|
+
traceId: 't3',
|
|
43
|
+
locale: 'en-GB',
|
|
44
|
+
kbSnapshotId: 'snap_1',
|
|
45
|
+
allowedScope: 'education_only',
|
|
46
|
+
regulatoryContext: { jurisdiction: 'EU' },
|
|
47
|
+
},
|
|
48
|
+
question: 'Should I buy now?',
|
|
49
|
+
});
|
|
50
|
+
// demo handler echoes question; question includes forbidden phrase \"buy\"
|
|
51
|
+
expect(result.refused).toBeTrue();
|
|
52
|
+
expect(result.refusalReason).toBe('SCOPE_VIOLATION');
|
|
53
|
+
});
|
|
54
54
|
});
|