@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.
Files changed (83) hide show
  1. package/.turbo/turbo-build.log +72 -51
  2. package/AGENTS.md +50 -27
  3. package/CHANGELOG.md +21 -0
  4. package/README.md +87 -42
  5. package/dist/browser/entities/index.js +2 -2
  6. package/dist/browser/entities/models.js +2 -2
  7. package/dist/browser/events.js +1 -1
  8. package/dist/browser/forms/assistant-context.form.js +213 -0
  9. package/dist/browser/forms/index.js +213 -0
  10. package/dist/browser/index.js +362 -40
  11. package/dist/browser/locale-jurisdiction-gate.feature.js +66 -1
  12. package/dist/browser/operations/assistant.js +3 -3
  13. package/dist/browser/operations/index.js +3 -3
  14. package/dist/browser/policy/assistant-gate.policy.js +62 -0
  15. package/dist/browser/policy/index.js +62 -1
  16. package/dist/browser/translations/assistant-gate.en-GB.translation.js +48 -0
  17. package/dist/browser/translations/assistant-gate.en-US.translation.js +50 -0
  18. package/dist/browser/translations/assistant-gate.fr-FR.translation.js +52 -0
  19. package/dist/browser/translations/index.js +148 -0
  20. package/dist/contracts.test.d.ts +1 -0
  21. package/dist/entities/index.js +2 -2
  22. package/dist/entities/models.js +2 -2
  23. package/dist/events.js +1 -1
  24. package/dist/forms/assistant-context.form.d.ts +22 -0
  25. package/dist/forms/assistant-context.form.js +214 -0
  26. package/dist/forms/index.d.ts +1 -0
  27. package/dist/forms/index.js +214 -0
  28. package/dist/index.d.ts +5 -3
  29. package/dist/index.js +362 -40
  30. package/dist/locale-jurisdiction-gate.feature.js +66 -1
  31. package/dist/node/entities/index.js +2 -2
  32. package/dist/node/entities/models.js +2 -2
  33. package/dist/node/events.js +1 -1
  34. package/dist/node/forms/assistant-context.form.js +213 -0
  35. package/dist/node/forms/index.js +213 -0
  36. package/dist/node/index.js +362 -40
  37. package/dist/node/locale-jurisdiction-gate.feature.js +66 -1
  38. package/dist/node/operations/assistant.js +3 -3
  39. package/dist/node/operations/index.js +3 -3
  40. package/dist/node/policy/assistant-gate.policy.js +62 -0
  41. package/dist/node/policy/index.js +62 -1
  42. package/dist/node/translations/assistant-gate.en-GB.translation.js +48 -0
  43. package/dist/node/translations/assistant-gate.en-US.translation.js +50 -0
  44. package/dist/node/translations/assistant-gate.fr-FR.translation.js +52 -0
  45. package/dist/node/translations/index.js +148 -0
  46. package/dist/operations/assistant.js +3 -3
  47. package/dist/operations/index.js +3 -3
  48. package/dist/policy/assistant-gate.policy.d.ts +1 -0
  49. package/dist/policy/assistant-gate.policy.js +63 -0
  50. package/dist/policy/index.d.ts +2 -1
  51. package/dist/policy/index.js +62 -1
  52. package/dist/translations/assistant-gate.en-GB.translation.d.ts +1 -0
  53. package/dist/translations/assistant-gate.en-GB.translation.js +49 -0
  54. package/dist/translations/assistant-gate.en-US.translation.d.ts +1 -0
  55. package/dist/translations/assistant-gate.en-US.translation.js +51 -0
  56. package/dist/translations/assistant-gate.fr-FR.translation.d.ts +1 -0
  57. package/dist/translations/assistant-gate.fr-FR.translation.js +53 -0
  58. package/dist/translations/index.d.ts +3 -0
  59. package/dist/translations/index.js +149 -0
  60. package/package.json +105 -7
  61. package/src/contracts.test.ts +32 -0
  62. package/src/docs/locale-jurisdiction-gate.docblock.ts +21 -21
  63. package/src/entities/models.ts +87 -87
  64. package/src/events.ts +55 -55
  65. package/src/example.ts +28 -28
  66. package/src/forms/assistant-context.form.ts +112 -0
  67. package/src/forms/index.ts +1 -0
  68. package/src/handlers/demo.handlers.test.ts +46 -46
  69. package/src/handlers/demo.handlers.ts +133 -133
  70. package/src/index.ts +5 -3
  71. package/src/locale-jurisdiction-gate.feature.ts +40 -34
  72. package/src/operations/assistant.ts +82 -82
  73. package/src/policy/assistant-gate.policy.ts +65 -0
  74. package/src/policy/guard.test.ts +18 -18
  75. package/src/policy/guard.ts +75 -75
  76. package/src/policy/index.ts +2 -1
  77. package/src/policy/types.ts +12 -12
  78. package/src/translations/assistant-gate.en-GB.translation.ts +46 -0
  79. package/src/translations/assistant-gate.en-US.translation.ts +48 -0
  80. package/src/translations/assistant-gate.fr-FR.translation.ts +51 -0
  81. package/src/translations/index.ts +3 -0
  82. package/tsconfig.json +7 -15
  83. package/tsdown.config.js +7 -13
@@ -3,23 +3,23 @@ import { describe, expect, it } from 'bun:test';
3
3
  import { enforceCitations, validateEnvelope } from './guard';
4
4
 
5
5
  describe('locale/jurisdiction gate policy', () => {
6
- it('blocks unsupported locale', () => {
7
- const result = validateEnvelope({
8
- locale: 'es-ES',
9
- kbSnapshotId: 'snap_1',
10
- allowedScope: 'education_only',
11
- regulatoryContext: { jurisdiction: 'EU' },
12
- });
13
- expect(result.ok).toBeFalse();
14
- if (!result.ok) expect(result.error.code).toBe('LOCALE_REQUIRED');
15
- });
6
+ it('blocks unsupported locale', () => {
7
+ const result = validateEnvelope({
8
+ locale: 'es-ES',
9
+ kbSnapshotId: 'snap_1',
10
+ allowedScope: 'education_only',
11
+ regulatoryContext: { jurisdiction: 'EU' },
12
+ });
13
+ expect(result.ok).toBeFalse();
14
+ if (!result.ok) expect(result.error.code).toBe('LOCALE_REQUIRED');
15
+ });
16
16
 
17
- it('blocks answer without citations', () => {
18
- const result = enforceCitations({
19
- sections: [{ heading: 'A', body: 'B' }],
20
- citations: [],
21
- });
22
- expect(result.ok).toBeFalse();
23
- if (!result.ok) expect(result.error.code).toBe('CITATIONS_REQUIRED');
24
- });
17
+ it('blocks answer without citations', () => {
18
+ const result = enforceCitations({
19
+ sections: [{ heading: 'A', body: 'B' }],
20
+ citations: [],
21
+ });
22
+ expect(result.ok).toBeFalse();
23
+ if (!result.ok) expect(result.error.code).toBe('CITATIONS_REQUIRED');
24
+ });
25
25
  });
@@ -1,102 +1,102 @@
1
1
  import type { GateError, GateResult } from './types';
2
2
 
3
3
  interface EnvelopeLike {
4
- locale?: string;
5
- kbSnapshotId?: string;
6
- allowedScope?: 'education_only' | 'generic_info' | 'escalation_required';
7
- regulatoryContext?: { jurisdiction?: string };
4
+ locale?: string;
5
+ kbSnapshotId?: string;
6
+ allowedScope?: 'education_only' | 'generic_info' | 'escalation_required';
7
+ regulatoryContext?: { jurisdiction?: string };
8
8
  }
9
9
 
10
10
  interface AnswerLike {
11
- citations?: unknown[];
12
- sections?: { heading: string; body: string }[];
13
- refused?: boolean;
14
- refusalReason?: string;
15
- allowedScope?: 'education_only' | 'generic_info' | 'escalation_required';
11
+ citations?: unknown[];
12
+ sections?: { heading: string; body: string }[];
13
+ refused?: boolean;
14
+ refusalReason?: string;
15
+ allowedScope?: 'education_only' | 'generic_info' | 'escalation_required';
16
16
  }
17
17
 
18
18
  const SUPPORTED_LOCALES = new Set<string>(['en-US', 'en-GB', 'fr-FR']);
19
19
 
20
20
  function err(code: GateError['code'], message: string): GateError {
21
- return { code, message };
21
+ return { code, message };
22
22
  }
23
23
 
24
24
  export function validateEnvelope(
25
- envelope: EnvelopeLike
25
+ envelope: EnvelopeLike
26
26
  ): GateResult<Required<EnvelopeLike>> {
27
- if (!envelope.locale || !SUPPORTED_LOCALES.has(envelope.locale)) {
28
- return {
29
- ok: false,
30
- error: err('LOCALE_REQUIRED', 'locale is required and must be supported'),
31
- };
32
- }
33
- if (!envelope.regulatoryContext?.jurisdiction) {
34
- return {
35
- ok: false,
36
- error: err('JURISDICTION_REQUIRED', 'jurisdiction is required'),
37
- };
38
- }
39
- if (!envelope.kbSnapshotId) {
40
- return {
41
- ok: false,
42
- error: err('KB_SNAPSHOT_REQUIRED', 'kbSnapshotId is required'),
43
- };
44
- }
45
- if (!envelope.allowedScope) {
46
- return {
47
- ok: false,
48
- error: err('SCOPE_VIOLATION', 'allowedScope is required'),
49
- };
50
- }
51
- return { ok: true, value: envelope as Required<EnvelopeLike> };
27
+ if (!envelope.locale || !SUPPORTED_LOCALES.has(envelope.locale)) {
28
+ return {
29
+ ok: false,
30
+ error: err('LOCALE_REQUIRED', 'locale is required and must be supported'),
31
+ };
32
+ }
33
+ if (!envelope.regulatoryContext?.jurisdiction) {
34
+ return {
35
+ ok: false,
36
+ error: err('JURISDICTION_REQUIRED', 'jurisdiction is required'),
37
+ };
38
+ }
39
+ if (!envelope.kbSnapshotId) {
40
+ return {
41
+ ok: false,
42
+ error: err('KB_SNAPSHOT_REQUIRED', 'kbSnapshotId is required'),
43
+ };
44
+ }
45
+ if (!envelope.allowedScope) {
46
+ return {
47
+ ok: false,
48
+ error: err('SCOPE_VIOLATION', 'allowedScope is required'),
49
+ };
50
+ }
51
+ return { ok: true, value: envelope as Required<EnvelopeLike> };
52
52
  }
53
53
 
54
54
  export function enforceCitations(answer: AnswerLike): GateResult<AnswerLike> {
55
- const citations = answer.citations ?? [];
56
- if (!Array.isArray(citations) || citations.length === 0) {
57
- return {
58
- ok: false,
59
- error: err(
60
- 'CITATIONS_REQUIRED',
61
- 'answers must include at least one citation'
62
- ),
63
- };
64
- }
65
- return { ok: true, value: answer };
55
+ const citations = answer.citations ?? [];
56
+ if (!Array.isArray(citations) || citations.length === 0) {
57
+ return {
58
+ ok: false,
59
+ error: err(
60
+ 'CITATIONS_REQUIRED',
61
+ 'answers must include at least one citation'
62
+ ),
63
+ };
64
+ }
65
+ return { ok: true, value: answer };
66
66
  }
67
67
 
68
68
  const EDUCATION_ONLY_FORBIDDEN_PATTERNS: RegExp[] = [
69
- /\b(buy|sell)\b/i,
70
- /\b(should\s+buy|should\s+sell)\b/i,
71
- /\b(guarantee(d)?|promise(d)?)\b/i,
69
+ /\b(buy|sell)\b/i,
70
+ /\b(should\s+buy|should\s+sell)\b/i,
71
+ /\b(guarantee(d)?|promise(d)?)\b/i,
72
72
  ];
73
73
 
74
74
  export function enforceAllowedScope(
75
- allowedScope: EnvelopeLike['allowedScope'],
76
- answer: AnswerLike
75
+ allowedScope: EnvelopeLike['allowedScope'],
76
+ answer: AnswerLike
77
77
  ): GateResult<AnswerLike> {
78
- if (!allowedScope) {
79
- return {
80
- ok: false,
81
- error: err('SCOPE_VIOLATION', 'allowedScope is required'),
82
- };
83
- }
84
- if (allowedScope !== 'education_only') {
85
- return { ok: true, value: answer };
86
- }
78
+ if (!allowedScope) {
79
+ return {
80
+ ok: false,
81
+ error: err('SCOPE_VIOLATION', 'allowedScope is required'),
82
+ };
83
+ }
84
+ if (allowedScope !== 'education_only') {
85
+ return { ok: true, value: answer };
86
+ }
87
87
 
88
- const bodies = (answer.sections ?? []).map((s) => s.body).join('\n');
89
- const violations = EDUCATION_ONLY_FORBIDDEN_PATTERNS.some((re) =>
90
- re.test(bodies)
91
- );
92
- if (violations) {
93
- return {
94
- ok: false,
95
- error: err(
96
- 'SCOPE_VIOLATION',
97
- 'answer violates education_only scope (contains actionable or promotional language)'
98
- ),
99
- };
100
- }
101
- return { ok: true, value: answer };
88
+ const bodies = (answer.sections ?? []).map((s) => s.body).join('\n');
89
+ const violations = EDUCATION_ONLY_FORBIDDEN_PATTERNS.some((re) =>
90
+ re.test(bodies)
91
+ );
92
+ if (violations) {
93
+ return {
94
+ ok: false,
95
+ error: err(
96
+ 'SCOPE_VIOLATION',
97
+ 'answer violates education_only scope (contains actionable or promotional language)'
98
+ ),
99
+ };
100
+ }
101
+ return { ok: true, value: answer };
102
102
  }
@@ -1,2 +1,3 @@
1
- export * from './types';
1
+ export * from './assistant-gate.policy';
2
2
  export * from './guard';
3
+ export * from './types';
@@ -1,18 +1,18 @@
1
1
  export type AllowedScope =
2
- | 'education_only'
3
- | 'generic_info'
4
- | 'escalation_required';
2
+ | 'education_only'
3
+ | 'generic_info'
4
+ | 'escalation_required';
5
5
 
6
6
  export interface GateError {
7
- code:
8
- | 'LOCALE_REQUIRED'
9
- | 'JURISDICTION_REQUIRED'
10
- | 'KB_SNAPSHOT_REQUIRED'
11
- | 'CITATIONS_REQUIRED'
12
- | 'SCOPE_VIOLATION';
13
- message: string;
7
+ code:
8
+ | 'LOCALE_REQUIRED'
9
+ | 'JURISDICTION_REQUIRED'
10
+ | 'KB_SNAPSHOT_REQUIRED'
11
+ | 'CITATIONS_REQUIRED'
12
+ | 'SCOPE_VIOLATION';
13
+ message: string;
14
14
  }
15
15
 
16
16
  export type GateResult<T> =
17
- | { ok: true; value: T }
18
- | { ok: false; error: GateError };
17
+ | { ok: true; value: T }
18
+ | { ok: false; error: GateError };
@@ -0,0 +1,46 @@
1
+ import {
2
+ OwnersEnum,
3
+ StabilityEnum,
4
+ TagsEnum,
5
+ } from '@contractspec/lib.contracts-spec/ownership';
6
+ import { defineTranslation } from '@contractspec/lib.contracts-spec/translations/spec';
7
+
8
+ export const AssistantGateMessagesEnGb = defineTranslation({
9
+ meta: {
10
+ key: 'locale-jurisdiction-gate.translation.assistant-gate.en-GB',
11
+ version: '1.0.0',
12
+ domain: 'assistant',
13
+ description:
14
+ 'British English messages for the assistant locale and jurisdiction gate.',
15
+ owners: [OwnersEnum.PlatformFinance],
16
+ stability: StabilityEnum.Experimental,
17
+ tags: [TagsEnum.I18n, 'assistant', 'policy'],
18
+ },
19
+ locale: 'en-GB',
20
+ fallback: 'en-US',
21
+ messages: {
22
+ 'assistantGate.locale.label': { value: 'Locale' },
23
+ 'assistantGate.locale.description': {
24
+ value: 'Select the reviewed locale for the assistant response.',
25
+ },
26
+ 'assistantGate.jurisdiction.label': { value: 'Jurisdiction' },
27
+ 'assistantGate.jurisdiction.placeholder': { value: 'UK-FCA' },
28
+ 'assistantGate.kbSnapshotId.label': { value: 'Knowledge snapshot ID' },
29
+ 'assistantGate.kbSnapshotId.placeholder': {
30
+ value: 'kb_2026_03_20_policy_reviewed',
31
+ },
32
+ 'assistantGate.allowedScope.label': { value: 'Permitted scope' },
33
+ 'assistantGate.allowedScope.educationOnly': {
34
+ value: 'Educational content only',
35
+ },
36
+ 'assistantGate.allowedScope.genericInfo': { value: 'General information' },
37
+ 'assistantGate.allowedScope.escalationRequired': {
38
+ value: 'Escalation required',
39
+ },
40
+ 'assistantGate.question.label': { value: 'Question' },
41
+ 'assistantGate.question.placeholder': {
42
+ value: 'What may I say about this product in the selected jurisdiction?',
43
+ },
44
+ 'assistantGate.submit.label': { value: 'Generate response' },
45
+ },
46
+ });
@@ -0,0 +1,48 @@
1
+ import {
2
+ OwnersEnum,
3
+ StabilityEnum,
4
+ TagsEnum,
5
+ } from '@contractspec/lib.contracts-spec/ownership';
6
+ import { defineTranslation } from '@contractspec/lib.contracts-spec/translations/spec';
7
+
8
+ export const AssistantGateMessagesEnUs = defineTranslation({
9
+ meta: {
10
+ key: 'locale-jurisdiction-gate.translation.assistant-gate.en-US',
11
+ version: '1.0.0',
12
+ domain: 'assistant',
13
+ description:
14
+ 'US English messages for the assistant locale and jurisdiction gate.',
15
+ owners: [OwnersEnum.PlatformFinance],
16
+ stability: StabilityEnum.Experimental,
17
+ tags: [TagsEnum.I18n, 'assistant', 'policy'],
18
+ },
19
+ locale: 'en-US',
20
+ messages: {
21
+ 'assistantGate.locale.label': { value: 'Locale' },
22
+ 'assistantGate.locale.description': {
23
+ value: 'Choose the reviewed locale for the assistant answer.',
24
+ },
25
+ 'assistantGate.locale.enUs': { value: 'English (United States)' },
26
+ 'assistantGate.locale.enGb': { value: 'English (United Kingdom)' },
27
+ 'assistantGate.locale.frFr': { value: 'French (France)' },
28
+ 'assistantGate.jurisdiction.label': { value: 'Jurisdiction' },
29
+ 'assistantGate.jurisdiction.placeholder': { value: 'US-SEC' },
30
+ 'assistantGate.kbSnapshotId.label': { value: 'Knowledge snapshot ID' },
31
+ 'assistantGate.kbSnapshotId.placeholder': {
32
+ value: 'kb_2026_03_20_policy_reviewed',
33
+ },
34
+ 'assistantGate.allowedScope.label': { value: 'Allowed scope' },
35
+ 'assistantGate.allowedScope.educationOnly': {
36
+ value: 'Educational content only',
37
+ },
38
+ 'assistantGate.allowedScope.genericInfo': { value: 'Generic information' },
39
+ 'assistantGate.allowedScope.escalationRequired': {
40
+ value: 'Escalation required',
41
+ },
42
+ 'assistantGate.question.label': { value: 'Question' },
43
+ 'assistantGate.question.placeholder': {
44
+ value: 'What can I say about this product in the selected jurisdiction?',
45
+ },
46
+ 'assistantGate.submit.label': { value: 'Generate answer' },
47
+ },
48
+ });
@@ -0,0 +1,51 @@
1
+ import {
2
+ OwnersEnum,
3
+ StabilityEnum,
4
+ TagsEnum,
5
+ } from '@contractspec/lib.contracts-spec/ownership';
6
+ import { defineTranslation } from '@contractspec/lib.contracts-spec/translations/spec';
7
+
8
+ export const AssistantGateMessagesFrFr = defineTranslation({
9
+ meta: {
10
+ key: 'locale-jurisdiction-gate.translation.assistant-gate.fr-FR',
11
+ version: '1.0.0',
12
+ domain: 'assistant',
13
+ description:
14
+ 'French messages for the assistant locale and jurisdiction gate.',
15
+ owners: [OwnersEnum.PlatformFinance],
16
+ stability: StabilityEnum.Experimental,
17
+ tags: [TagsEnum.I18n, 'assistant', 'policy'],
18
+ },
19
+ locale: 'fr-FR',
20
+ fallback: 'en-US',
21
+ messages: {
22
+ 'assistantGate.locale.label': { value: 'Langue' },
23
+ 'assistantGate.locale.description': {
24
+ value: "Sélectionnez la langue validée pour la réponse de l'assistant.",
25
+ },
26
+ 'assistantGate.jurisdiction.label': { value: 'Juridiction' },
27
+ 'assistantGate.jurisdiction.placeholder': { value: 'FR-AMF' },
28
+ 'assistantGate.kbSnapshotId.label': {
29
+ value: 'Identifiant du snapshot documentaire',
30
+ },
31
+ 'assistantGate.kbSnapshotId.placeholder': {
32
+ value: 'kb_2026_03_20_policy_reviewed',
33
+ },
34
+ 'assistantGate.allowedScope.label': { value: 'Périmètre autorisé' },
35
+ 'assistantGate.allowedScope.educationOnly': {
36
+ value: 'Contenu éducatif uniquement',
37
+ },
38
+ 'assistantGate.allowedScope.genericInfo': {
39
+ value: 'Information générale',
40
+ },
41
+ 'assistantGate.allowedScope.escalationRequired': {
42
+ value: 'Escalade obligatoire',
43
+ },
44
+ 'assistantGate.question.label': { value: 'Question' },
45
+ 'assistantGate.question.placeholder': {
46
+ value:
47
+ 'Que puis-je dire sur ce produit dans la juridiction sélectionnée ?',
48
+ },
49
+ 'assistantGate.submit.label': { value: 'Générer la réponse' },
50
+ },
51
+ });
@@ -0,0 +1,3 @@
1
+ export * from './assistant-gate.en-GB.translation';
2
+ export * from './assistant-gate.en-US.translation';
3
+ export * from './assistant-gate.fr-FR.translation';
package/tsconfig.json CHANGED
@@ -1,17 +1,9 @@
1
1
  {
2
- "extends": "@contractspec/tool.typescript/react-library.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src/**/*"],
8
- "exclude": ["node_modules", "dist"]
2
+ "extends": "@contractspec/tool.typescript/react-library.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
9
  }
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
package/tsdown.config.js CHANGED
@@ -1,16 +1,10 @@
1
- import { defineConfig, moduleLibrary, withDevExports } from '@contractspec/tool.bun';
1
+ import {
2
+ defineConfig,
3
+ moduleLibrary,
4
+ withDevExports,
5
+ } from '@contractspec/tool.bun';
2
6
 
3
7
  export default defineConfig(() => ({
4
- ...moduleLibrary,
5
- ...withDevExports,
8
+ ...moduleLibrary,
9
+ ...withDevExports,
6
10
  }));
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-