@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
@@ -1,110 +1,110 @@
1
1
  import {
2
- ScalarTypeEnum,
3
- defineEnum,
4
- defineSchemaModel,
2
+ defineEnum,
3
+ defineSchemaModel,
4
+ ScalarTypeEnum,
5
5
  } from '@contractspec/lib.schema';
6
6
 
7
7
  export const AllowedScopeEnum = defineEnum('AllowedScope', [
8
- 'education_only',
9
- 'generic_info',
10
- 'escalation_required',
8
+ 'education_only',
9
+ 'generic_info',
10
+ 'escalation_required',
11
11
  ]);
12
12
 
13
13
  export const UserProfileModel = defineSchemaModel({
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
- },
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
- 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
- },
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
- 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
- },
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
- 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
- },
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
- 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
- },
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
- 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
- },
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 { ScalarTypeEnum, defineSchemaModel } from '@contractspec/lib.schema';
2
+ import { defineSchemaModel, ScalarTypeEnum } from '@contractspec/lib.schema';
3
3
 
4
4
  const AssistantAnswerRequestedPayload = defineSchemaModel({
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
- },
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
- 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,
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
- 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
- },
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
- 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,
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
- 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
- },
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
- 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,
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
- 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
- },
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
- 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
- });
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
- 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
- });
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
- 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
- });
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
  });