@lssm/example.locale-jurisdiction-gate 0.0.0-canary-20251213172311
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 +26 -0
- package/CHANGELOG.md +9 -0
- package/README.md +31 -0
- package/dist/contracts/assistant.js +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/docs/index.js +1 -0
- package/dist/docs/locale-jurisdiction-gate.docblock.js +19 -0
- package/dist/entities/index.js +1 -0
- package/dist/entities/models.js +1 -0
- package/dist/events.js +1 -0
- package/dist/example.js +1 -0
- package/dist/feature.js +1 -0
- package/dist/handlers/demo.handlers.js +1 -0
- package/dist/handlers/index.js +1 -0
- package/dist/index.js +1 -0
- package/dist/policy/guard.js +2 -0
- package/dist/policy/index.js +1 -0
- package/dist/policy/types.js +0 -0
- package/example.ts +1 -0
- package/package.json +70 -0
- package/src/contracts/assistant.ts +96 -0
- package/src/contracts/index.ts +3 -0
- package/src/docs/index.ts +3 -0
- package/src/docs/locale-jurisdiction-gate.docblock.ts +48 -0
- package/src/entities/index.ts +3 -0
- package/src/entities/models.ts +101 -0
- package/src/events.ts +61 -0
- package/src/example.ts +29 -0
- package/src/feature.ts +31 -0
- package/src/handlers/demo.handlers.test.ts +54 -0
- package/src/handlers/demo.handlers.ts +154 -0
- package/src/handlers/index.ts +3 -0
- package/src/index.ts +17 -0
- package/src/policy/guard.test.ts +27 -0
- package/src/policy/guard.ts +96 -0
- package/src/policy/index.ts +4 -0
- package/src/policy/types.ts +17 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.js +9 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
$ bun build:bundle && bun build:types
|
|
2
|
+
$ tsdown
|
|
3
|
+
[34mℹ[39m tsdown [2mv0.17.0[22m powered by rolldown [2mv1.0.0-beta.53[22m
|
|
4
|
+
[34mℹ[39m config file: [4m/home/runner/work/contractspec/contractspec/packages/examples/locale-jurisdiction-gate/tsdown.config.js[24m
|
|
5
|
+
[34mℹ[39m entry: [34msrc/events.ts, src/example.ts, src/feature.ts, src/index.ts, src/docs/index.ts, src/docs/locale-jurisdiction-gate.docblock.ts, src/entities/index.ts, src/entities/models.ts, src/contracts/assistant.ts, src/contracts/index.ts, src/handlers/demo.handlers.ts, src/handlers/index.ts, src/policy/guard.ts, src/policy/index.ts, src/policy/types.ts[39m
|
|
6
|
+
[34mℹ[39m target: [34mesnext[39m
|
|
7
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
8
|
+
[34mℹ[39m Build start
|
|
9
|
+
[34mℹ[39m [2mdist/[22m[1mentities/models.js[22m [2m2.50 kB[22m [2m│ gzip: 0.74 kB[22m
|
|
10
|
+
[34mℹ[39m [2mdist/[22m[1mcontracts/assistant.js[22m [2m2.34 kB[22m [2m│ gzip: 0.93 kB[22m
|
|
11
|
+
[34mℹ[39m [2mdist/[22m[1mevents.js[22m [2m1.69 kB[22m [2m│ gzip: 0.48 kB[22m
|
|
12
|
+
[34mℹ[39m [2mdist/[22m[1mhandlers/demo.handlers.js[22m [2m1.55 kB[22m [2m│ gzip: 0.69 kB[22m
|
|
13
|
+
[34mℹ[39m [2mdist/[22m[1mdocs/locale-jurisdiction-gate.docblock.js[22m [2m1.40 kB[22m [2m│ gzip: 0.67 kB[22m
|
|
14
|
+
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m1.31 kB[22m [2m│ gzip: 0.46 kB[22m
|
|
15
|
+
[34mℹ[39m [2mdist/[22m[1mpolicy/guard.js[22m [2m1.18 kB[22m [2m│ gzip: 0.61 kB[22m
|
|
16
|
+
[34mℹ[39m [2mdist/[22m[1mfeature.js[22m [2m0.72 kB[22m [2m│ gzip: 0.36 kB[22m
|
|
17
|
+
[34mℹ[39m [2mdist/[22m[1mexample.js[22m [2m0.67 kB[22m [2m│ gzip: 0.40 kB[22m
|
|
18
|
+
[34mℹ[39m [2mdist/[22m[1mentities/index.js[22m [2m0.41 kB[22m [2m│ gzip: 0.19 kB[22m
|
|
19
|
+
[34mℹ[39m [2mdist/[22m[1mpolicy/index.js[22m [2m0.17 kB[22m [2m│ gzip: 0.12 kB[22m
|
|
20
|
+
[34mℹ[39m [2mdist/[22m[1mcontracts/index.js[22m [2m0.17 kB[22m [2m│ gzip: 0.11 kB[22m
|
|
21
|
+
[34mℹ[39m [2mdist/[22m[1mhandlers/index.js[22m [2m0.11 kB[22m [2m│ gzip: 0.09 kB[22m
|
|
22
|
+
[34mℹ[39m [2mdist/[22m[1mdocs/index.js[22m [2m0.05 kB[22m [2m│ gzip: 0.07 kB[22m
|
|
23
|
+
[34mℹ[39m [2mdist/[22m[1mpolicy/types.js[22m [2m0.00 kB[22m [2m│ gzip: 0.02 kB[22m
|
|
24
|
+
[34mℹ[39m 15 files, total: 14.27 kB
|
|
25
|
+
[32m✔[39m Build complete in [32m64ms[39m
|
|
26
|
+
$ tsc --noEmit
|
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# `@lssm/example.locale-jurisdiction-gate`
|
|
2
|
+
|
|
3
|
+
Spec-first example showing how to **fail-closed** on every assistant call unless:
|
|
4
|
+
|
|
5
|
+
- `locale` is provided and supported
|
|
6
|
+
- `jurisdiction` is provided
|
|
7
|
+
- `kbSnapshotId` is provided
|
|
8
|
+
- `allowedScope` is provided and respected
|
|
9
|
+
- answers include **at least one citation** (or the call is refused)
|
|
10
|
+
|
|
11
|
+
## What this example demonstrates
|
|
12
|
+
|
|
13
|
+
- **Locale + jurisdiction as explicit inputs** (no guessing)
|
|
14
|
+
- **Structured Answer IR** with citations, disclaimers, and risk flags
|
|
15
|
+
- **Scope enforcement**: blocks scope-violating content under restricted scopes
|
|
16
|
+
- **Traceability**: emits events for requested / blocked / delivered
|
|
17
|
+
|
|
18
|
+
## Key exports
|
|
19
|
+
|
|
20
|
+
- `contracts`: `assistant.answer`, `assistant.explainConcept`
|
|
21
|
+
- `entities/models`: `LLMCallEnvelope`, `AssistantAnswerIR`, `RegulatoryContext`
|
|
22
|
+
- `policy/guard`: pure gate functions used by handlers and tests
|
|
23
|
+
- `handlers/demo.handlers`: deterministic demo handlers (no LLM)
|
|
24
|
+
|
|
25
|
+
## Running tests
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bun test
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AssistantAnswerIRModel as e,LLMCallEnvelopeModel as t}from"../entities/models.js";import{defineCommand as n}from"@lssm/lib.contracts";import{ScalarTypeEnum as r,defineSchemaModel as i}from"@lssm/lib.schema";const a=i({name:`AssistantQuestionInput`,description:`Input for assistant calls with mandatory envelope.`,fields:{envelope:{type:t,isOptional:!1},question:{type:r.String_unsecure(),isOptional:!1}}}),o=i({name:`AssistantConceptInput`,description:`Input for explaining a concept with mandatory envelope.`,fields:{envelope:{type:t,isOptional:!1},conceptKey:{type:r.String_unsecure(),isOptional:!1}}}),s=n({meta:{name:`assistant.answer`,version:1,stability:`experimental`,owners:[`examples`],tags:[`assistant`,`policy`,`locale`,`jurisdiction`,`knowledge`],description:`Answer a user question using a KB snapshot with strict locale/jurisdiction gating.`,goal:`Provide policy-safe answers that cite a KB snapshot or refuse.`,context:`Called by UI or workflows; must fail-closed if envelope is invalid or citations are missing.`},io:{input:a,output:e,errors:{LOCALE_REQUIRED:{description:`Locale is required and must be supported`,http:400,gqlCode:`LOCALE_REQUIRED`,when:`locale is missing or unsupported`},JURISDICTION_REQUIRED:{description:`Jurisdiction is required`,http:400,gqlCode:`JURISDICTION_REQUIRED`,when:`jurisdiction is missing`},KB_SNAPSHOT_REQUIRED:{description:`KB snapshot id is required`,http:400,gqlCode:`KB_SNAPSHOT_REQUIRED`,when:`kbSnapshotId is missing`},CITATIONS_REQUIRED:{description:`Answers must include citations to a KB snapshot`,http:422,gqlCode:`CITATIONS_REQUIRED`,when:`answer has no citations`},SCOPE_VIOLATION:{description:`Answer violates allowed scope and must be refused/escalated`,http:403,gqlCode:`SCOPE_VIOLATION`,when:`output includes forbidden content under the given allowedScope`}}},policy:{auth:`user`}}),c=n({meta:{name:`assistant.explainConcept`,version:1,stability:`experimental`,owners:[`examples`],tags:[`assistant`,`policy`,`knowledge`,`concepts`],description:`Explain a concept using a KB snapshot with strict locale/jurisdiction gating.`,goal:`Explain concepts with citations or refuse.`,context:`Same constraints as assistant.answer.`},io:{input:o,output:e,errors:s.io.errors},policy:{auth:`user`}});export{s as AssistantAnswerContract,c as AssistantExplainConceptContract};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AssistantAnswerContract as e,AssistantExplainConceptContract as t}from"./assistant.js";export{e as AssistantAnswerContract,t as AssistantExplainConceptContract};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./locale-jurisdiction-gate.docblock.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import{registerDocBlocks as e}from"@lssm/lib.contracts/docs";e([{id:`docs.examples.locale-jurisdiction-gate.goal`,title:`Locale/Jurisdiction Gate — Goal`,summary:`Fail-closed gate that forces locale + jurisdiction + kbSnapshotId + allowedScope for assistant calls.`,kind:`goal`,visibility:`public`,route:`/docs/examples/locale-jurisdiction-gate/goal`,tags:[`assistant`,`policy`,`locale`,`jurisdiction`,`knowledge`],body:`## Why it matters
|
|
2
|
+
- Forces all assistant behavior to be bound to explicit inputs (no guessing).
|
|
3
|
+
- Requires KB snapshot citations to make answers traceable and regenerable.
|
|
4
|
+
|
|
5
|
+
## Guardrails
|
|
6
|
+
- Missing locale/jurisdiction/snapshot/scope => refuse (structured).
|
|
7
|
+
- Missing citations => refuse.
|
|
8
|
+
- Scope violations under education_only => refuse/escalate.`},{id:`docs.examples.locale-jurisdiction-gate.reference`,title:`Locale/Jurisdiction Gate — Reference`,summary:`Contracts, models, and events exposed by the gate example.`,kind:`reference`,visibility:`public`,route:`/docs/examples/locale-jurisdiction-gate`,tags:[`assistant`,`policy`,`reference`],body:`## Contracts
|
|
9
|
+
- assistant.answer (v1)
|
|
10
|
+
- assistant.explainConcept (v1)
|
|
11
|
+
|
|
12
|
+
## Models
|
|
13
|
+
- LLMCallEnvelope (locale, regulatoryContext, kbSnapshotId, allowedScope, traceId)
|
|
14
|
+
- AssistantAnswerIR (sections, citations, disclaimers, riskFlags)
|
|
15
|
+
|
|
16
|
+
## Events
|
|
17
|
+
- assistant.answer.requested
|
|
18
|
+
- assistant.answer.blocked
|
|
19
|
+
- assistant.answer.delivered`}]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AllowedScopeEnum as e,AssistantAnswerIRModel as t,AssistantAnswerSectionModel as n,AssistantCitationModel as r,LLMCallEnvelopeModel as i,RegulatoryContextModel as a,UserProfileModel as o}from"./models.js";export{e as AllowedScopeEnum,t as AssistantAnswerIRModel,n as AssistantAnswerSectionModel,r as AssistantCitationModel,i as LLMCallEnvelopeModel,a as RegulatoryContextModel,o as UserProfileModel};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{ScalarTypeEnum as e,defineEnum as t,defineSchemaModel as n}from"@lssm/lib.schema";const r=t(`AllowedScope`,[`education_only`,`generic_info`,`escalation_required`]),i=n({name:`UserProfile`,description:`User profile inputs used to derive regulatory context.`,fields:{preferredLocale:{type:e.String_unsecure(),isOptional:!0},residencyCountry:{type:e.String_unsecure(),isOptional:!0},taxResidenceCountry:{type:e.String_unsecure(),isOptional:!0},clientType:{type:e.String_unsecure(),isOptional:!0}}}),a=n({name:`RegulatoryContext`,description:`Explicit regulatory context (no guessing).`,fields:{jurisdiction:{type:e.String_unsecure(),isOptional:!1},region:{type:e.String_unsecure(),isOptional:!0},clientType:{type:e.String_unsecure(),isOptional:!0},allowedScope:{type:r,isOptional:!1}}}),o=n({name:`LLMCallEnvelope`,description:`Mandatory envelope for assistant calls. All fields are explicit and required for policy gating.`,fields:{traceId:{type:e.String_unsecure(),isOptional:!1},locale:{type:e.String_unsecure(),isOptional:!1},regulatoryContext:{type:a,isOptional:!1},kbSnapshotId:{type:e.String_unsecure(),isOptional:!1},allowedScope:{type:r,isOptional:!1}}}),s=n({name:`AssistantCitation`,description:`Citation referencing a KB snapshot + a specific item within it.`,fields:{kbSnapshotId:{type:e.String_unsecure(),isOptional:!1},sourceType:{type:e.String_unsecure(),isOptional:!1},sourceId:{type:e.String_unsecure(),isOptional:!1},title:{type:e.String_unsecure(),isOptional:!0},excerpt:{type:e.String_unsecure(),isOptional:!0}}}),c=n({name:`AssistantAnswerSection`,description:`Structured answer section.`,fields:{heading:{type:e.String_unsecure(),isOptional:!1},body:{type:e.String_unsecure(),isOptional:!1}}}),l=n({name:`AssistantAnswerIR`,description:`Structured assistant answer with mandatory citations and explicit locale/jurisdiction.`,fields:{locale:{type:e.String_unsecure(),isOptional:!1},jurisdiction:{type:e.String_unsecure(),isOptional:!1},allowedScope:{type:r,isOptional:!1},sections:{type:c,isArray:!0,isOptional:!1},citations:{type:s,isArray:!0,isOptional:!1},disclaimers:{type:e.String_unsecure(),isArray:!0,isOptional:!0},riskFlags:{type:e.String_unsecure(),isArray:!0,isOptional:!0},refused:{type:e.Boolean(),isOptional:!0},refusalReason:{type:e.String_unsecure(),isOptional:!0}}});export{r as AllowedScopeEnum,l as AssistantAnswerIRModel,c as AssistantAnswerSectionModel,s as AssistantCitationModel,o as LLMCallEnvelopeModel,a as RegulatoryContextModel,i as UserProfileModel};
|
package/dist/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineEvent as e,defineSchemaModel as t}from"@lssm/lib.contracts";import{ScalarTypeEnum as n}from"@lssm/lib.schema";const r=e({name:`assistant.answer.requested`,version:1,description:`Assistant answer requested (policy gate will run).`,payload:t({name:`AssistantAnswerRequestedPayload`,description:`Emitted when an assistant answer is requested (pre-gate).`,fields:{traceId:{type:n.String_unsecure(),isOptional:!1},locale:{type:n.String_unsecure(),isOptional:!1},jurisdiction:{type:n.String_unsecure(),isOptional:!1},kbSnapshotId:{type:n.String_unsecure(),isOptional:!1},allowedScope:{type:n.String_unsecure(),isOptional:!1}}})}),i=e({name:`assistant.answer.blocked`,version:1,description:`Assistant answer blocked (fail-closed).`,payload:t({name:`AssistantAnswerBlockedPayload`,description:`Emitted when a request is blocked by the gate.`,fields:{traceId:{type:n.String_unsecure(),isOptional:!1},reasonCode:{type:n.String_unsecure(),isOptional:!1},reason:{type:n.String_unsecure(),isOptional:!1}}})}),a=e({name:`assistant.answer.delivered`,version:1,description:`Assistant answer delivered (must include KB snapshot citations).`,payload:t({name:`AssistantAnswerDeliveredPayload`,description:`Emitted when a structured, cited answer is delivered.`,fields:{traceId:{type:n.String_unsecure(),isOptional:!1},locale:{type:n.String_unsecure(),isOptional:!1},jurisdiction:{type:n.String_unsecure(),isOptional:!1},kbSnapshotId:{type:n.String_unsecure(),isOptional:!1},allowedScope:{type:n.String_unsecure(),isOptional:!1},citationsCount:{type:n.Int_unsecure(),isOptional:!1}}})});export{i as AssistantAnswerBlockedEvent,a as AssistantAnswerDeliveredEvent,r as AssistantAnswerRequestedEvent};
|
package/dist/example.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e={id:`locale-jurisdiction-gate`,title:`Locale / Jurisdiction Gate`,summary:`Fail-closed gating for assistant calls: locale + jurisdiction + kbSnapshotId + allowedScope must be explicit, answers must cite a snapshot.`,tags:[`policy`,`locale`,`jurisdiction`,`assistant`,`gating`],kind:`knowledge`,visibility:`public`,docs:{rootDocId:`docs.examples.locale-jurisdiction-gate`},entrypoints:{packageName:`@lssm/example.locale-jurisdiction-gate`,feature:`./feature`,contracts:`./contracts`,handlers:`./handlers`,docs:`./docs`},surfaces:{templates:!0,sandbox:{enabled:!0,modes:[`markdown`,`specs`]},studio:{enabled:!0,installable:!0},mcp:{enabled:!0}}};export{e as default};
|
package/dist/feature.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={meta:{key:`locale-jurisdiction-gate`,title:`Locale + Jurisdiction Gate`,description:`Fail-closed gating for assistant calls requiring locale/jurisdiction/snapshot/scope and citations.`,domain:`knowledge`,owners:[`examples`],tags:[`assistant`,`policy`,`locale`,`jurisdiction`,`knowledge`],stability:`experimental`},operations:[{name:`assistant.answer`,version:1},{name:`assistant.explainConcept`,version:1}],events:[{name:`assistant.answer.requested`,version:1},{name:`assistant.answer.blocked`,version:1},{name:`assistant.answer.delivered`,version:1}],presentations:[],opToPresentation:[],presentationsTargets:[],capabilities:{requires:[{key:`knowledge`,version:1}]}};export{e as LocaleJurisdictionGateFeature};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{enforceAllowedScope as e,enforceCitations as t,validateEnvelope as n}from"../policy/guard.js";function r(){async function r(r){let i=n(r.envelope);if(!i.ok)return{locale:r.envelope.locale??`en-US`,jurisdiction:r.envelope.regulatoryContext?.jurisdiction??`UNKNOWN`,allowedScope:r.envelope.allowedScope??`education_only`,sections:[{heading:`Request blocked`,body:i.error.message}],citations:[],disclaimers:[`This system refuses to answer without a valid envelope.`],riskFlags:[i.error.code],refused:!0,refusalReason:i.error.code};let a={locale:i.value.locale,jurisdiction:i.value.regulatoryContext.jurisdiction,allowedScope:i.value.allowedScope,sections:[{heading:`Answer (demo)`,body:`You asked: "${r.question}". This demo answer is derived from the KB snapshot only.`}],citations:[{kbSnapshotId:i.value.kbSnapshotId,sourceType:`ruleVersion`,sourceId:`rv_demo`,title:`Demo rule version`,excerpt:`Demo excerpt`}],disclaimers:[`Educational demo only.`],riskFlags:[]},o=e(i.value.allowedScope,a);if(!o.ok)return{...a,sections:[{heading:`Escalation required`,body:o.error.message}],citations:a.citations,refused:!0,refusalReason:o.error.code,riskFlags:[...a.riskFlags??[],o.error.code]};let s=t(a);return s.ok?a:{...a,sections:[{heading:`Request blocked`,body:s.error.message}],citations:[],refused:!0,refusalReason:s.error.code,riskFlags:[...a.riskFlags??[],s.error.code]}}async function i(e){return await r({envelope:e.envelope,question:`Explain concept: ${e.conceptKey}`})}return{answer:r,explainConcept:i}}export{r as createDemoAssistantHandlers};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createDemoAssistantHandlers as e}from"./demo.handlers.js";export{e as createDemoAssistantHandlers};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AssistantAnswerBlockedEvent as e,AssistantAnswerDeliveredEvent as t,AssistantAnswerRequestedEvent as n}from"./events.js";import r from"./example.js";import{LocaleJurisdictionGateFeature as i}from"./feature.js";import{AllowedScopeEnum as a,AssistantAnswerIRModel as o,AssistantAnswerSectionModel as s,AssistantCitationModel as c,LLMCallEnvelopeModel as l,RegulatoryContextModel as u,UserProfileModel as d}from"./entities/models.js";import"./entities/index.js";import{AssistantAnswerContract as f,AssistantExplainConceptContract as p}from"./contracts/assistant.js";import"./contracts/index.js";import{enforceAllowedScope as m,enforceCitations as h,validateEnvelope as g}from"./policy/guard.js";import{createDemoAssistantHandlers as _}from"./handlers/demo.handlers.js";import"./docs/index.js";export{a as AllowedScopeEnum,e as AssistantAnswerBlockedEvent,f as AssistantAnswerContract,t as AssistantAnswerDeliveredEvent,o as AssistantAnswerIRModel,n as AssistantAnswerRequestedEvent,s as AssistantAnswerSectionModel,c as AssistantCitationModel,p as AssistantExplainConceptContract,l as LLMCallEnvelopeModel,i as LocaleJurisdictionGateFeature,u as RegulatoryContextModel,d as UserProfileModel,_ as createDemoAssistantHandlers,m as enforceAllowedScope,h as enforceCitations,r as example,g as validateEnvelope};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=new Set([`en-US`,`en-GB`,`fr-FR`]);function t(e,t){return{code:e,message:t}}function n(n){return!n.locale||!e.has(n.locale)?{ok:!1,error:t(`LOCALE_REQUIRED`,`locale is required and must be supported`)}:n.regulatoryContext?.jurisdiction?n.kbSnapshotId?n.allowedScope?{ok:!0,value:n}:{ok:!1,error:t(`SCOPE_VIOLATION`,`allowedScope is required`)}:{ok:!1,error:t(`KB_SNAPSHOT_REQUIRED`,`kbSnapshotId is required`)}:{ok:!1,error:t(`JURISDICTION_REQUIRED`,`jurisdiction is required`)}}function r(e){let n=e.citations??[];return!Array.isArray(n)||n.length===0?{ok:!1,error:t(`CITATIONS_REQUIRED`,`answers must include at least one citation`)}:{ok:!0,value:e}}const i=[/\b(buy|sell)\b/i,/\b(should\s+buy|should\s+sell)\b/i,/\b(guarantee(d)?|promise(d)?)\b/i];function a(e,n){if(!e)return{ok:!1,error:t(`SCOPE_VIOLATION`,`allowedScope is required`)};if(e!==`education_only`)return{ok:!0,value:n};let r=(n.sections??[]).map(e=>e.body).join(`
|
|
2
|
+
`);return i.some(e=>e.test(r))?{ok:!1,error:t(`SCOPE_VIOLATION`,`answer violates education_only scope (contains actionable or promotional language)`)}:{ok:!0,value:n}}export{a as enforceAllowedScope,r as enforceCitations,n as validateEnvelope};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{enforceAllowedScope as e,enforceCitations as t,validateEnvelope as n}from"./guard.js";export{e as enforceAllowedScope,t as enforceCitations,n as validateEnvelope};
|
|
File without changes
|
package/example.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './src/example';
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lssm/example.locale-jurisdiction-gate",
|
|
3
|
+
"version": "0.0.0-canary-20251213172311",
|
|
4
|
+
"description": "Example: enforce locale + jurisdiction + kbSnapshotId + allowed scope for assistant calls (fail-closed).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.ts",
|
|
11
|
+
"./contracts": "./src/contracts/index.ts",
|
|
12
|
+
"./contracts/assistant": "./src/contracts/assistant.ts",
|
|
13
|
+
"./docs": "./src/docs/index.ts",
|
|
14
|
+
"./docs/locale-jurisdiction-gate.docblock": "./src/docs/locale-jurisdiction-gate.docblock.ts",
|
|
15
|
+
"./entities": "./src/entities/index.ts",
|
|
16
|
+
"./entities/models": "./src/entities/models.ts",
|
|
17
|
+
"./events": "./src/events.ts",
|
|
18
|
+
"./example": "./src/example.ts",
|
|
19
|
+
"./feature": "./src/feature.ts",
|
|
20
|
+
"./handlers": "./src/handlers/index.ts",
|
|
21
|
+
"./handlers/demo.handlers": "./src/handlers/demo.handlers.ts",
|
|
22
|
+
"./policy": "./src/policy/index.ts",
|
|
23
|
+
"./policy/guard": "./src/policy/guard.ts",
|
|
24
|
+
"./policy/types": "./src/policy/types.ts",
|
|
25
|
+
"./*": "./*"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "bun build:bundle && bun build:types",
|
|
29
|
+
"build:bundle": "tsdown",
|
|
30
|
+
"build:types": "tsc --noEmit",
|
|
31
|
+
"dev": "bun build:bundle --watch",
|
|
32
|
+
"clean": "rimraf dist .turbo",
|
|
33
|
+
"lint": "bun lint:fix",
|
|
34
|
+
"lint:fix": "eslint src --fix",
|
|
35
|
+
"lint:check": "eslint src",
|
|
36
|
+
"test": "bun test"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@lssm/lib.contracts": "workspace:*",
|
|
40
|
+
"@lssm/lib.schema": "workspace:*",
|
|
41
|
+
"zod": "^4.1.13"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@lssm/tool.tsdown": "workspace:*",
|
|
45
|
+
"@lssm/tool.typescript": "workspace:*",
|
|
46
|
+
"tsdown": "^0.17.0",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public",
|
|
51
|
+
"exports": {
|
|
52
|
+
".": "./dist/index.js",
|
|
53
|
+
"./contracts": "./dist/contracts/index.js",
|
|
54
|
+
"./contracts/assistant": "./dist/contracts/assistant.js",
|
|
55
|
+
"./docs": "./dist/docs/index.js",
|
|
56
|
+
"./docs/locale-jurisdiction-gate.docblock": "./dist/docs/locale-jurisdiction-gate.docblock.js",
|
|
57
|
+
"./entities": "./dist/entities/index.js",
|
|
58
|
+
"./entities/models": "./dist/entities/models.js",
|
|
59
|
+
"./events": "./dist/events.js",
|
|
60
|
+
"./example": "./dist/example.js",
|
|
61
|
+
"./feature": "./dist/feature.js",
|
|
62
|
+
"./handlers": "./dist/handlers/index.js",
|
|
63
|
+
"./handlers/demo.handlers": "./dist/handlers/demo.handlers.js",
|
|
64
|
+
"./policy": "./dist/policy/index.js",
|
|
65
|
+
"./policy/guard": "./dist/policy/guard.js",
|
|
66
|
+
"./policy/types": "./dist/policy/types.js",
|
|
67
|
+
"./*": "./*"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { defineCommand } from '@lssm/lib.contracts';
|
|
2
|
+
import { ScalarTypeEnum, defineSchemaModel } from '@lssm/lib.schema';
|
|
3
|
+
|
|
4
|
+
import { AssistantAnswerIRModel, LLMCallEnvelopeModel } from '../entities/models';
|
|
5
|
+
|
|
6
|
+
const AssistantQuestionInput = defineSchemaModel({
|
|
7
|
+
name: 'AssistantQuestionInput',
|
|
8
|
+
description: 'Input for assistant calls with mandatory envelope.',
|
|
9
|
+
fields: {
|
|
10
|
+
envelope: { type: LLMCallEnvelopeModel, isOptional: false },
|
|
11
|
+
question: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const AssistantConceptInput = defineSchemaModel({
|
|
16
|
+
name: 'AssistantConceptInput',
|
|
17
|
+
description: 'Input for explaining a concept with mandatory envelope.',
|
|
18
|
+
fields: {
|
|
19
|
+
envelope: { type: LLMCallEnvelopeModel, isOptional: false },
|
|
20
|
+
conceptKey: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const AssistantAnswerContract = defineCommand({
|
|
25
|
+
meta: {
|
|
26
|
+
name: 'assistant.answer',
|
|
27
|
+
version: 1,
|
|
28
|
+
stability: 'experimental',
|
|
29
|
+
owners: ['examples'],
|
|
30
|
+
tags: ['assistant', 'policy', 'locale', 'jurisdiction', 'knowledge'],
|
|
31
|
+
description:
|
|
32
|
+
'Answer a user question using a KB snapshot with strict locale/jurisdiction gating.',
|
|
33
|
+
goal: 'Provide policy-safe answers that cite a KB snapshot or refuse.',
|
|
34
|
+
context:
|
|
35
|
+
'Called by UI or workflows; must fail-closed if envelope is invalid or citations are missing.',
|
|
36
|
+
},
|
|
37
|
+
io: {
|
|
38
|
+
input: AssistantQuestionInput,
|
|
39
|
+
output: AssistantAnswerIRModel,
|
|
40
|
+
errors: {
|
|
41
|
+
LOCALE_REQUIRED: {
|
|
42
|
+
description: 'Locale is required and must be supported',
|
|
43
|
+
http: 400,
|
|
44
|
+
gqlCode: 'LOCALE_REQUIRED',
|
|
45
|
+
when: 'locale is missing or unsupported',
|
|
46
|
+
},
|
|
47
|
+
JURISDICTION_REQUIRED: {
|
|
48
|
+
description: 'Jurisdiction is required',
|
|
49
|
+
http: 400,
|
|
50
|
+
gqlCode: 'JURISDICTION_REQUIRED',
|
|
51
|
+
when: 'jurisdiction is missing',
|
|
52
|
+
},
|
|
53
|
+
KB_SNAPSHOT_REQUIRED: {
|
|
54
|
+
description: 'KB snapshot id is required',
|
|
55
|
+
http: 400,
|
|
56
|
+
gqlCode: 'KB_SNAPSHOT_REQUIRED',
|
|
57
|
+
when: 'kbSnapshotId is missing',
|
|
58
|
+
},
|
|
59
|
+
CITATIONS_REQUIRED: {
|
|
60
|
+
description: 'Answers must include citations to a KB snapshot',
|
|
61
|
+
http: 422,
|
|
62
|
+
gqlCode: 'CITATIONS_REQUIRED',
|
|
63
|
+
when: 'answer has no citations',
|
|
64
|
+
},
|
|
65
|
+
SCOPE_VIOLATION: {
|
|
66
|
+
description: 'Answer violates allowed scope and must be refused/escalated',
|
|
67
|
+
http: 403,
|
|
68
|
+
gqlCode: 'SCOPE_VIOLATION',
|
|
69
|
+
when: 'output includes forbidden content under the given allowedScope',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
policy: { auth: 'user' },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const AssistantExplainConceptContract = defineCommand({
|
|
77
|
+
meta: {
|
|
78
|
+
name: 'assistant.explainConcept',
|
|
79
|
+
version: 1,
|
|
80
|
+
stability: 'experimental',
|
|
81
|
+
owners: ['examples'],
|
|
82
|
+
tags: ['assistant', 'policy', 'knowledge', 'concepts'],
|
|
83
|
+
description:
|
|
84
|
+
'Explain a concept using a KB snapshot with strict locale/jurisdiction gating.',
|
|
85
|
+
goal: 'Explain concepts with citations or refuse.',
|
|
86
|
+
context: 'Same constraints as assistant.answer.',
|
|
87
|
+
},
|
|
88
|
+
io: {
|
|
89
|
+
input: AssistantConceptInput,
|
|
90
|
+
output: AssistantAnswerIRModel,
|
|
91
|
+
errors: AssistantAnswerContract.io.errors,
|
|
92
|
+
},
|
|
93
|
+
policy: { auth: 'user' },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DocBlock } from '@lssm/lib.contracts/docs';
|
|
2
|
+
import { registerDocBlocks } from '@lssm/lib.contracts/docs';
|
|
3
|
+
|
|
4
|
+
const docBlocks: DocBlock[] = [
|
|
5
|
+
{
|
|
6
|
+
id: 'docs.examples.locale-jurisdiction-gate.goal',
|
|
7
|
+
title: 'Locale/Jurisdiction Gate — Goal',
|
|
8
|
+
summary:
|
|
9
|
+
'Fail-closed gate that forces locale + jurisdiction + kbSnapshotId + allowedScope for assistant calls.',
|
|
10
|
+
kind: 'goal',
|
|
11
|
+
visibility: 'public',
|
|
12
|
+
route: '/docs/examples/locale-jurisdiction-gate/goal',
|
|
13
|
+
tags: ['assistant', 'policy', 'locale', 'jurisdiction', 'knowledge'],
|
|
14
|
+
body: `## Why it matters
|
|
15
|
+
- Forces all assistant behavior to be bound to explicit inputs (no guessing).
|
|
16
|
+
- Requires KB snapshot citations to make answers traceable and regenerable.
|
|
17
|
+
|
|
18
|
+
## Guardrails
|
|
19
|
+
- Missing locale/jurisdiction/snapshot/scope => refuse (structured).
|
|
20
|
+
- Missing citations => refuse.
|
|
21
|
+
- Scope violations under education_only => refuse/escalate.`,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'docs.examples.locale-jurisdiction-gate.reference',
|
|
25
|
+
title: 'Locale/Jurisdiction Gate — Reference',
|
|
26
|
+
summary: 'Contracts, models, and events exposed by the gate example.',
|
|
27
|
+
kind: 'reference',
|
|
28
|
+
visibility: 'public',
|
|
29
|
+
route: '/docs/examples/locale-jurisdiction-gate',
|
|
30
|
+
tags: ['assistant', 'policy', 'reference'],
|
|
31
|
+
body: `## Contracts
|
|
32
|
+
- assistant.answer (v1)
|
|
33
|
+
- assistant.explainConcept (v1)
|
|
34
|
+
|
|
35
|
+
## Models
|
|
36
|
+
- LLMCallEnvelope (locale, regulatoryContext, kbSnapshotId, allowedScope, traceId)
|
|
37
|
+
- AssistantAnswerIR (sections, citations, disclaimers, riskFlags)
|
|
38
|
+
|
|
39
|
+
## Events
|
|
40
|
+
- assistant.answer.requested
|
|
41
|
+
- assistant.answer.blocked
|
|
42
|
+
- assistant.answer.delivered`,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
registerDocBlocks(docBlocks);
|
|
47
|
+
|
|
48
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ScalarTypeEnum, defineEnum, defineSchemaModel } from '@lssm/lib.schema';
|
|
2
|
+
|
|
3
|
+
export const AllowedScopeEnum = defineEnum('AllowedScope', [
|
|
4
|
+
'education_only',
|
|
5
|
+
'generic_info',
|
|
6
|
+
'escalation_required',
|
|
7
|
+
]);
|
|
8
|
+
|
|
9
|
+
export const UserProfileModel = defineSchemaModel({
|
|
10
|
+
name: 'UserProfile',
|
|
11
|
+
description: 'User profile inputs used to derive regulatory context.',
|
|
12
|
+
fields: {
|
|
13
|
+
preferredLocale: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
14
|
+
residencyCountry: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
15
|
+
taxResidenceCountry: {
|
|
16
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
17
|
+
isOptional: true,
|
|
18
|
+
},
|
|
19
|
+
clientType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const RegulatoryContextModel = defineSchemaModel({
|
|
24
|
+
name: 'RegulatoryContext',
|
|
25
|
+
description: 'Explicit regulatory context (no guessing).',
|
|
26
|
+
fields: {
|
|
27
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
28
|
+
region: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
29
|
+
clientType: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
30
|
+
allowedScope: { type: AllowedScopeEnum, isOptional: false },
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const LLMCallEnvelopeModel = defineSchemaModel({
|
|
35
|
+
name: 'LLMCallEnvelope',
|
|
36
|
+
description:
|
|
37
|
+
'Mandatory envelope for assistant calls. All fields are explicit and required for policy gating.',
|
|
38
|
+
fields: {
|
|
39
|
+
traceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
40
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
41
|
+
regulatoryContext: { type: RegulatoryContextModel, isOptional: false },
|
|
42
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
43
|
+
allowedScope: { type: AllowedScopeEnum, isOptional: false },
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const AssistantCitationModel = defineSchemaModel({
|
|
48
|
+
name: 'AssistantCitation',
|
|
49
|
+
description: 'Citation referencing a KB snapshot + a specific item within it.',
|
|
50
|
+
fields: {
|
|
51
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
52
|
+
sourceType: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
53
|
+
sourceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
54
|
+
title: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
55
|
+
excerpt: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const AssistantAnswerSectionModel = defineSchemaModel({
|
|
60
|
+
name: 'AssistantAnswerSection',
|
|
61
|
+
description: 'Structured answer section.',
|
|
62
|
+
fields: {
|
|
63
|
+
heading: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
64
|
+
body: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const AssistantAnswerIRModel = defineSchemaModel({
|
|
69
|
+
name: 'AssistantAnswerIR',
|
|
70
|
+
description:
|
|
71
|
+
'Structured assistant answer with mandatory citations and explicit locale/jurisdiction.',
|
|
72
|
+
fields: {
|
|
73
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
74
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
75
|
+
allowedScope: { type: AllowedScopeEnum, isOptional: false },
|
|
76
|
+
sections: {
|
|
77
|
+
type: AssistantAnswerSectionModel,
|
|
78
|
+
isArray: true,
|
|
79
|
+
isOptional: false,
|
|
80
|
+
},
|
|
81
|
+
citations: {
|
|
82
|
+
type: AssistantCitationModel,
|
|
83
|
+
isArray: true,
|
|
84
|
+
isOptional: false,
|
|
85
|
+
},
|
|
86
|
+
disclaimers: {
|
|
87
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
88
|
+
isArray: true,
|
|
89
|
+
isOptional: true,
|
|
90
|
+
},
|
|
91
|
+
riskFlags: {
|
|
92
|
+
type: ScalarTypeEnum.String_unsecure(),
|
|
93
|
+
isArray: true,
|
|
94
|
+
isOptional: true,
|
|
95
|
+
},
|
|
96
|
+
refused: { type: ScalarTypeEnum.Boolean(), isOptional: true },
|
|
97
|
+
refusalReason: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { defineEvent, defineSchemaModel } from '@lssm/lib.contracts';
|
|
2
|
+
import { ScalarTypeEnum } from '@lssm/lib.schema';
|
|
3
|
+
|
|
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
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const AssistantAnswerRequestedEvent = defineEvent({
|
|
17
|
+
name: 'assistant.answer.requested',
|
|
18
|
+
version: 1,
|
|
19
|
+
description: 'Assistant answer requested (policy gate will run).',
|
|
20
|
+
payload: AssistantAnswerRequestedPayload,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const AssistantAnswerBlockedPayload = defineSchemaModel({
|
|
24
|
+
name: 'AssistantAnswerBlockedPayload',
|
|
25
|
+
description: 'Emitted when a request is blocked by the gate.',
|
|
26
|
+
fields: {
|
|
27
|
+
traceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
28
|
+
reasonCode: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
29
|
+
reason: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const AssistantAnswerBlockedEvent = defineEvent({
|
|
34
|
+
name: 'assistant.answer.blocked',
|
|
35
|
+
version: 1,
|
|
36
|
+
description: 'Assistant answer blocked (fail-closed).',
|
|
37
|
+
payload: AssistantAnswerBlockedPayload,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const AssistantAnswerDeliveredPayload = defineSchemaModel({
|
|
41
|
+
name: 'AssistantAnswerDeliveredPayload',
|
|
42
|
+
description: 'Emitted when a structured, cited answer is delivered.',
|
|
43
|
+
fields: {
|
|
44
|
+
traceId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
45
|
+
locale: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
46
|
+
jurisdiction: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
47
|
+
kbSnapshotId: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
48
|
+
allowedScope: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
49
|
+
citationsCount: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const AssistantAnswerDeliveredEvent = defineEvent({
|
|
54
|
+
name: 'assistant.answer.delivered',
|
|
55
|
+
version: 1,
|
|
56
|
+
description:
|
|
57
|
+
'Assistant answer delivered (must include KB snapshot citations).',
|
|
58
|
+
payload: AssistantAnswerDeliveredPayload,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
|
package/src/example.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const example = {
|
|
2
|
+
id: 'locale-jurisdiction-gate',
|
|
3
|
+
title: 'Locale / Jurisdiction Gate',
|
|
4
|
+
summary:
|
|
5
|
+
'Fail-closed gating for assistant calls: locale + jurisdiction + kbSnapshotId + allowedScope must be explicit, answers must cite a snapshot.',
|
|
6
|
+
tags: ['policy', 'locale', 'jurisdiction', 'assistant', 'gating'],
|
|
7
|
+
kind: 'knowledge',
|
|
8
|
+
visibility: 'public',
|
|
9
|
+
docs: {
|
|
10
|
+
rootDocId: 'docs.examples.locale-jurisdiction-gate',
|
|
11
|
+
},
|
|
12
|
+
entrypoints: {
|
|
13
|
+
packageName: '@lssm/example.locale-jurisdiction-gate',
|
|
14
|
+
feature: './feature',
|
|
15
|
+
contracts: './contracts',
|
|
16
|
+
handlers: './handlers',
|
|
17
|
+
docs: './docs',
|
|
18
|
+
},
|
|
19
|
+
surfaces: {
|
|
20
|
+
templates: true,
|
|
21
|
+
sandbox: { enabled: true, modes: ['markdown', 'specs'] },
|
|
22
|
+
studio: { enabled: true, installable: true },
|
|
23
|
+
mcp: { enabled: true },
|
|
24
|
+
},
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export default example;
|
|
28
|
+
|
|
29
|
+
|
package/src/feature.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FeatureModuleSpec } from '@lssm/lib.contracts';
|
|
2
|
+
|
|
3
|
+
export const LocaleJurisdictionGateFeature: FeatureModuleSpec = {
|
|
4
|
+
meta: {
|
|
5
|
+
key: 'locale-jurisdiction-gate',
|
|
6
|
+
title: 'Locale + Jurisdiction Gate',
|
|
7
|
+
description:
|
|
8
|
+
'Fail-closed gating for assistant calls requiring locale/jurisdiction/snapshot/scope and citations.',
|
|
9
|
+
domain: 'knowledge',
|
|
10
|
+
owners: ['examples'],
|
|
11
|
+
tags: ['assistant', 'policy', 'locale', 'jurisdiction', 'knowledge'],
|
|
12
|
+
stability: 'experimental',
|
|
13
|
+
},
|
|
14
|
+
operations: [
|
|
15
|
+
{ name: 'assistant.answer', version: 1 },
|
|
16
|
+
{ name: 'assistant.explainConcept', version: 1 },
|
|
17
|
+
],
|
|
18
|
+
events: [
|
|
19
|
+
{ name: 'assistant.answer.requested', version: 1 },
|
|
20
|
+
{ name: 'assistant.answer.blocked', version: 1 },
|
|
21
|
+
{ name: 'assistant.answer.delivered', version: 1 },
|
|
22
|
+
],
|
|
23
|
+
presentations: [],
|
|
24
|
+
opToPresentation: [],
|
|
25
|
+
presentationsTargets: [],
|
|
26
|
+
capabilities: {
|
|
27
|
+
requires: [{ key: 'knowledge', version: 1 }],
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
|