@vorionsys/atsf-core 0.2.4 → 0.3.0
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/CHANGELOG.md +1 -0
- package/LICENSE +1 -1
- package/README.md +82 -29
- package/dist/adapters/base-adapter.d.ts +94 -0
- package/dist/adapters/base-adapter.d.ts.map +1 -0
- package/dist/adapters/base-adapter.js +233 -0
- package/dist/adapters/base-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +5 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/types.d.ts +83 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +4 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/webhook-handler.d.ts +64 -0
- package/dist/adapters/webhook-handler.d.ts.map +1 -0
- package/dist/adapters/webhook-handler.js +170 -0
- package/dist/adapters/webhook-handler.js.map +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +2 -0
- package/dist/api/server.js.map +1 -1
- package/dist/arbitration/index.d.ts +0 -8
- package/dist/arbitration/index.d.ts.map +1 -1
- package/dist/arbitration/index.js +2 -0
- package/dist/arbitration/index.js.map +1 -1
- package/dist/arbitration/types.d.ts.map +1 -1
- package/dist/arbitration/types.js +2 -8
- package/dist/arbitration/types.js.map +1 -1
- package/dist/basis/evaluator.d.ts +0 -5
- package/dist/basis/evaluator.d.ts.map +1 -1
- package/dist/basis/evaluator.js +2 -0
- package/dist/basis/evaluator.js.map +1 -1
- package/dist/basis/index.d.ts.map +1 -1
- package/dist/basis/index.js +2 -0
- package/dist/basis/index.js.map +1 -1
- package/dist/basis/parser.d.ts +28 -28
- package/dist/basis/parser.d.ts.map +1 -1
- package/dist/basis/parser.js +2 -0
- package/dist/basis/parser.js.map +1 -1
- package/dist/basis/types.d.ts.map +1 -1
- package/dist/basis/types.js +2 -3
- package/dist/basis/types.js.map +1 -1
- package/dist/chain/index.d.ts +0 -8
- package/dist/chain/index.d.ts.map +1 -1
- package/dist/chain/index.js +2 -0
- package/dist/chain/index.js.map +1 -1
- package/dist/cognigate/index.d.ts +0 -8
- package/dist/cognigate/index.d.ts.map +1 -1
- package/dist/cognigate/index.js +2 -0
- package/dist/cognigate/index.js.map +1 -1
- package/dist/common/adapters.d.ts.map +1 -1
- package/dist/common/adapters.js +2 -8
- package/dist/common/adapters.js.map +1 -1
- package/dist/common/config.d.ts.map +1 -1
- package/dist/common/config.js +2 -0
- package/dist/common/config.js.map +1 -1
- package/dist/common/index.d.ts.map +1 -1
- package/dist/common/index.js +2 -0
- package/dist/common/index.js.map +1 -1
- package/dist/common/logger.d.ts.map +1 -1
- package/dist/common/logger.js +2 -0
- package/dist/common/logger.js.map +1 -1
- package/dist/common/types.d.ts +7 -7
- package/dist/common/types.d.ts.map +1 -1
- package/dist/common/types.js +2 -9
- package/dist/common/types.js.map +1 -1
- package/dist/containment/index.d.ts +0 -8
- package/dist/containment/index.d.ts.map +1 -1
- package/dist/containment/index.js +2 -0
- package/dist/containment/index.js.map +1 -1
- package/dist/containment/types.d.ts.map +1 -1
- package/dist/containment/types.js +2 -8
- package/dist/containment/types.js.map +1 -1
- package/dist/contracts/index.d.ts +0 -8
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +2 -0
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js +2 -8
- package/dist/contracts/types.js.map +1 -1
- package/dist/crewai/callback.d.ts +0 -7
- package/dist/crewai/callback.d.ts.map +1 -1
- package/dist/crewai/callback.js +2 -0
- package/dist/crewai/callback.js.map +1 -1
- package/dist/crewai/executor.d.ts +0 -7
- package/dist/crewai/executor.d.ts.map +1 -1
- package/dist/crewai/executor.js +2 -0
- package/dist/crewai/executor.js.map +1 -1
- package/dist/crewai/index.d.ts.map +1 -1
- package/dist/crewai/index.js +2 -0
- package/dist/crewai/index.js.map +1 -1
- package/dist/crewai/tools.d.ts.map +1 -1
- package/dist/crewai/tools.js +2 -7
- package/dist/crewai/tools.js.map +1 -1
- package/dist/crewai/types.d.ts.map +1 -1
- package/dist/crewai/types.js +2 -7
- package/dist/crewai/types.js.map +1 -1
- package/dist/enforce/index.d.ts +0 -15
- package/dist/enforce/index.d.ts.map +1 -1
- package/dist/enforce/index.js +3 -1
- package/dist/enforce/index.js.map +1 -1
- package/dist/enforce/trust-aware-enforcement-service.d.ts +0 -15
- package/dist/enforce/trust-aware-enforcement-service.d.ts.map +1 -1
- package/dist/enforce/trust-aware-enforcement-service.js +2 -0
- package/dist/enforce/trust-aware-enforcement-service.js.map +1 -1
- package/dist/governance/fluid-workflow.d.ts +0 -8
- package/dist/governance/fluid-workflow.d.ts.map +1 -1
- package/dist/governance/fluid-workflow.js +2 -0
- package/dist/governance/fluid-workflow.js.map +1 -1
- package/dist/governance/index.d.ts +0 -8
- package/dist/governance/index.d.ts.map +1 -1
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -1
- package/dist/governance/proof-bridge.d.ts.map +1 -1
- package/dist/governance/proof-bridge.js +2 -12
- package/dist/governance/proof-bridge.js.map +1 -1
- package/dist/governance/types.d.ts.map +1 -1
- package/dist/governance/types.js +2 -8
- package/dist/governance/types.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/intent/index.d.ts +0 -13
- package/dist/intent/index.d.ts.map +1 -1
- package/dist/intent/index.js +4 -2
- package/dist/intent/index.js.map +1 -1
- package/dist/intent/persistent-intent-service.d.ts +0 -15
- package/dist/intent/persistent-intent-service.d.ts.map +1 -1
- package/dist/intent/persistent-intent-service.js +2 -0
- package/dist/intent/persistent-intent-service.js.map +1 -1
- package/dist/intent/supabase-intent-repository.d.ts +0 -17
- package/dist/intent/supabase-intent-repository.d.ts.map +1 -1
- package/dist/intent/supabase-intent-repository.js +2 -0
- package/dist/intent/supabase-intent-repository.js.map +1 -1
- package/dist/intent-gateway/index.d.ts +499 -0
- package/dist/intent-gateway/index.d.ts.map +1 -0
- package/dist/intent-gateway/index.js +1332 -0
- package/dist/intent-gateway/index.js.map +1 -0
- package/dist/langchain/callback.d.ts +0 -7
- package/dist/langchain/callback.d.ts.map +1 -1
- package/dist/langchain/callback.js +2 -0
- package/dist/langchain/callback.js.map +1 -1
- package/dist/langchain/executor.d.ts +0 -7
- package/dist/langchain/executor.d.ts.map +1 -1
- package/dist/langchain/executor.js +2 -0
- package/dist/langchain/executor.js.map +1 -1
- package/dist/langchain/index.d.ts.map +1 -1
- package/dist/langchain/index.js +2 -0
- package/dist/langchain/index.js.map +1 -1
- package/dist/langchain/tools.d.ts.map +1 -1
- package/dist/langchain/tools.js +2 -7
- package/dist/langchain/tools.js.map +1 -1
- package/dist/langchain/types.d.ts.map +1 -1
- package/dist/langchain/types.js +2 -7
- package/dist/langchain/types.js.map +1 -1
- package/dist/layers/implementations/L0-request-format.d.ts.map +1 -1
- package/dist/layers/implementations/L0-request-format.js +2 -0
- package/dist/layers/implementations/L0-request-format.js.map +1 -1
- package/dist/layers/implementations/L1-input-size.d.ts.map +1 -1
- package/dist/layers/implementations/L1-input-size.js +2 -0
- package/dist/layers/implementations/L1-input-size.js.map +1 -1
- package/dist/layers/implementations/L2-charset-sanitizer.d.ts.map +1 -1
- package/dist/layers/implementations/L2-charset-sanitizer.js +2 -0
- package/dist/layers/implementations/L2-charset-sanitizer.js.map +1 -1
- package/dist/layers/implementations/L3-schema-conformance.d.ts.map +1 -1
- package/dist/layers/implementations/L3-schema-conformance.js +2 -0
- package/dist/layers/implementations/L3-schema-conformance.js.map +1 -1
- package/dist/layers/implementations/L4-injection-detector.d.ts.map +1 -1
- package/dist/layers/implementations/L4-injection-detector.js +2 -0
- package/dist/layers/implementations/L4-injection-detector.js.map +1 -1
- package/dist/layers/implementations/L5-rate-limiter.d.ts.map +1 -1
- package/dist/layers/implementations/L5-rate-limiter.js +2 -0
- package/dist/layers/implementations/L5-rate-limiter.js.map +1 -1
- package/dist/layers/implementations/index.d.ts.map +1 -1
- package/dist/layers/implementations/index.js +2 -0
- package/dist/layers/implementations/index.js.map +1 -1
- package/dist/layers/index.d.ts +0 -8
- package/dist/layers/index.d.ts.map +1 -1
- package/dist/layers/index.js +2 -0
- package/dist/layers/index.js.map +1 -1
- package/dist/layers/types.d.ts.map +1 -1
- package/dist/layers/types.js +2 -8
- package/dist/layers/types.js.map +1 -1
- package/dist/paramesphere/activation-collector.d.ts +128 -0
- package/dist/paramesphere/activation-collector.d.ts.map +1 -0
- package/dist/paramesphere/activation-collector.js +260 -0
- package/dist/paramesphere/activation-collector.js.map +1 -0
- package/dist/paramesphere/cognitive-envelope.d.ts +73 -0
- package/dist/paramesphere/cognitive-envelope.d.ts.map +1 -0
- package/dist/paramesphere/cognitive-envelope.js +209 -0
- package/dist/paramesphere/cognitive-envelope.js.map +1 -0
- package/dist/paramesphere/envelope-integration.d.ts +60 -0
- package/dist/paramesphere/envelope-integration.d.ts.map +1 -0
- package/dist/paramesphere/envelope-integration.js +93 -0
- package/dist/paramesphere/envelope-integration.js.map +1 -0
- package/dist/paramesphere/fingerprint-monitor.d.ts +136 -0
- package/dist/paramesphere/fingerprint-monitor.d.ts.map +1 -0
- package/dist/paramesphere/fingerprint-monitor.js +212 -0
- package/dist/paramesphere/fingerprint-monitor.js.map +1 -0
- package/dist/paramesphere/fingerprint-store.d.ts +85 -0
- package/dist/paramesphere/fingerprint-store.d.ts.map +1 -0
- package/dist/paramesphere/fingerprint-store.js +68 -0
- package/dist/paramesphere/fingerprint-store.js.map +1 -0
- package/dist/paramesphere/index.d.ts +21 -0
- package/dist/paramesphere/index.d.ts.map +1 -0
- package/dist/paramesphere/index.js +18 -0
- package/dist/paramesphere/index.js.map +1 -0
- package/dist/paramesphere/monitor-integration.d.ts +37 -0
- package/dist/paramesphere/monitor-integration.d.ts.map +1 -0
- package/dist/paramesphere/monitor-integration.js +81 -0
- package/dist/paramesphere/monitor-integration.js.map +1 -0
- package/dist/paramesphere/paramesphere-engine.d.ts +111 -0
- package/dist/paramesphere/paramesphere-engine.d.ts.map +1 -0
- package/dist/paramesphere/paramesphere-engine.js +542 -0
- package/dist/paramesphere/paramesphere-engine.js.map +1 -0
- package/dist/paramesphere/types.d.ts +142 -0
- package/dist/paramesphere/types.d.ts.map +1 -0
- package/dist/paramesphere/types.js +4 -0
- package/dist/paramesphere/types.js.map +1 -0
- package/dist/persistence/file.d.ts +0 -7
- package/dist/persistence/file.d.ts.map +1 -1
- package/dist/persistence/file.js +2 -0
- package/dist/persistence/file.js.map +1 -1
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +2 -0
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/memory.d.ts.map +1 -1
- package/dist/persistence/memory.js +2 -7
- package/dist/persistence/memory.js.map +1 -1
- package/dist/persistence/sqlite.d.ts +0 -8
- package/dist/persistence/sqlite.d.ts.map +1 -1
- package/dist/persistence/sqlite.js +3 -1
- package/dist/persistence/sqlite.js.map +1 -1
- package/dist/persistence/supabase.d.ts.map +1 -1
- package/dist/persistence/supabase.js +3 -8
- package/dist/persistence/supabase.js.map +1 -1
- package/dist/persistence/types.d.ts.map +1 -1
- package/dist/persistence/types.js +2 -7
- package/dist/persistence/types.js.map +1 -1
- package/dist/phase6/ceiling.d.ts +0 -16
- package/dist/phase6/ceiling.d.ts.map +1 -1
- package/dist/phase6/ceiling.js +2 -0
- package/dist/phase6/ceiling.js.map +1 -1
- package/dist/phase6/context.d.ts +0 -17
- package/dist/phase6/context.d.ts.map +1 -1
- package/dist/phase6/context.js +2 -0
- package/dist/phase6/context.js.map +1 -1
- package/dist/phase6/index.d.ts.map +1 -1
- package/dist/phase6/index.js +2 -0
- package/dist/phase6/index.js.map +1 -1
- package/dist/phase6/presets.d.ts +0 -16
- package/dist/phase6/presets.d.ts.map +1 -1
- package/dist/phase6/presets.js +5 -3
- package/dist/phase6/presets.js.map +1 -1
- package/dist/phase6/provenance.d.ts +0 -15
- package/dist/phase6/provenance.d.ts.map +1 -1
- package/dist/phase6/provenance.js +2 -0
- package/dist/phase6/provenance.js.map +1 -1
- package/dist/phase6/role-gates/index.d.ts.map +1 -1
- package/dist/phase6/role-gates/index.js +2 -0
- package/dist/phase6/role-gates/index.js.map +1 -1
- package/dist/phase6/role-gates/kernel.d.ts.map +1 -1
- package/dist/phase6/role-gates/kernel.js +2 -0
- package/dist/phase6/role-gates/kernel.js.map +1 -1
- package/dist/phase6/role-gates/policy.d.ts.map +1 -1
- package/dist/phase6/role-gates/policy.js +2 -11
- package/dist/phase6/role-gates/policy.js.map +1 -1
- package/dist/phase6/role-gates.d.ts +0 -16
- package/dist/phase6/role-gates.d.ts.map +1 -1
- package/dist/phase6/role-gates.js +2 -0
- package/dist/phase6/role-gates.js.map +1 -1
- package/dist/phase6/types.d.ts +45 -16
- package/dist/phase6/types.d.ts.map +1 -1
- package/dist/phase6/types.js +49 -0
- package/dist/phase6/types.js.map +1 -1
- package/dist/phase6/weight-presets/canonical.d.ts.map +1 -1
- package/dist/phase6/weight-presets/canonical.js +2 -0
- package/dist/phase6/weight-presets/canonical.js.map +1 -1
- package/dist/phase6/weight-presets/deltas.d.ts.map +1 -1
- package/dist/phase6/weight-presets/deltas.js +2 -10
- package/dist/phase6/weight-presets/deltas.js.map +1 -1
- package/dist/phase6/weight-presets/index.d.ts.map +1 -1
- package/dist/phase6/weight-presets/index.js +2 -0
- package/dist/phase6/weight-presets/index.js.map +1 -1
- package/dist/phase6/weight-presets/merger.d.ts +0 -10
- package/dist/phase6/weight-presets/merger.d.ts.map +1 -1
- package/dist/phase6/weight-presets/merger.js +2 -0
- package/dist/phase6/weight-presets/merger.js.map +1 -1
- package/dist/proof/index.d.ts +3 -10
- package/dist/proof/index.d.ts.map +1 -1
- package/dist/proof/index.js +27 -9
- package/dist/proof/index.js.map +1 -1
- package/dist/proof/merkle.d.ts +0 -16
- package/dist/proof/merkle.d.ts.map +1 -1
- package/dist/proof/merkle.js +2 -0
- package/dist/proof/merkle.js.map +1 -1
- package/dist/proof/zk-proofs.d.ts +0 -18
- package/dist/proof/zk-proofs.d.ts.map +1 -1
- package/dist/proof/zk-proofs.js +2 -0
- package/dist/proof/zk-proofs.js.map +1 -1
- package/dist/provenance/index.d.ts +0 -8
- package/dist/provenance/index.d.ts.map +1 -1
- package/dist/provenance/index.js +2 -0
- package/dist/provenance/index.js.map +1 -1
- package/dist/provenance/types.d.ts.map +1 -1
- package/dist/provenance/types.js +2 -8
- package/dist/provenance/types.js.map +1 -1
- package/dist/sandbox-training/challenges.d.ts.map +1 -1
- package/dist/sandbox-training/challenges.js +2 -8
- package/dist/sandbox-training/challenges.js.map +1 -1
- package/dist/sandbox-training/graduation.d.ts.map +1 -1
- package/dist/sandbox-training/graduation.js +2 -8
- package/dist/sandbox-training/graduation.js.map +1 -1
- package/dist/sandbox-training/index.d.ts.map +1 -1
- package/dist/sandbox-training/index.js +2 -0
- package/dist/sandbox-training/index.js.map +1 -1
- package/dist/sandbox-training/promotion-service.d.ts.map +1 -1
- package/dist/sandbox-training/promotion-service.js +2 -11
- package/dist/sandbox-training/promotion-service.js.map +1 -1
- package/dist/sandbox-training/runner.d.ts.map +1 -1
- package/dist/sandbox-training/runner.js +2 -8
- package/dist/sandbox-training/runner.js.map +1 -1
- package/dist/sandbox-training/scorer.d.ts.map +1 -1
- package/dist/sandbox-training/scorer.js +2 -8
- package/dist/sandbox-training/scorer.js.map +1 -1
- package/dist/sandbox-training/types.d.ts.map +1 -1
- package/dist/sandbox-training/types.js +2 -8
- package/dist/sandbox-training/types.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/audit.d.ts +0 -8
- package/dist/trust-engine/ceiling-enforcement/audit.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/audit.js +2 -8
- package/dist/trust-engine/ceiling-enforcement/audit.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/index.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/index.js +2 -0
- package/dist/trust-engine/ceiling-enforcement/index.js.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/kernel.d.ts.map +1 -1
- package/dist/trust-engine/ceiling-enforcement/kernel.js +2 -0
- package/dist/trust-engine/ceiling-enforcement/kernel.js.map +1 -1
- package/dist/trust-engine/context-policy/enforcement.d.ts +0 -9
- package/dist/trust-engine/context-policy/enforcement.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/enforcement.js +2 -9
- package/dist/trust-engine/context-policy/enforcement.js.map +1 -1
- package/dist/trust-engine/context-policy/factory.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/factory.js +2 -0
- package/dist/trust-engine/context-policy/factory.js.map +1 -1
- package/dist/trust-engine/context-policy/index.d.ts.map +1 -1
- package/dist/trust-engine/context-policy/index.js +2 -0
- package/dist/trust-engine/context-policy/index.js.map +1 -1
- package/dist/trust-engine/creation-modifiers/index.d.ts.map +1 -1
- package/dist/trust-engine/creation-modifiers/index.js +2 -0
- package/dist/trust-engine/creation-modifiers/index.js.map +1 -1
- package/dist/trust-engine/creation-modifiers/types.d.ts.map +1 -1
- package/dist/trust-engine/creation-modifiers/types.js +2 -0
- package/dist/trust-engine/creation-modifiers/types.js.map +1 -1
- package/dist/trust-engine/decay-profiles.d.ts.map +1 -1
- package/dist/trust-engine/decay-profiles.js +2 -14
- package/dist/trust-engine/decay-profiles.js.map +1 -1
- package/dist/trust-engine/index.d.ts +418 -80
- package/dist/trust-engine/index.d.ts.map +1 -1
- package/dist/trust-engine/index.js +1048 -186
- package/dist/trust-engine/index.js.map +1 -1
- package/dist/trust-engine/phase6-types.d.ts +3 -13
- package/dist/trust-engine/phase6-types.d.ts.map +1 -1
- package/dist/trust-engine/phase6-types.js +5 -13
- package/dist/trust-engine/phase6-types.js.map +1 -1
- package/dist/trust-engine/trust-verifier.d.ts +121 -0
- package/dist/trust-engine/trust-verifier.d.ts.map +1 -0
- package/dist/trust-engine/trust-verifier.js +226 -0
- package/dist/trust-engine/trust-verifier.js.map +1 -0
- package/package.json +140 -135
|
@@ -0,0 +1,1332 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2024-2026 Vorion LLC
|
|
3
|
+
/**
|
|
4
|
+
* INTENT GATEWAY - Jurisdictional Router & Governance Topology Selector
|
|
5
|
+
*
|
|
6
|
+
* The Intent Gateway is the policy-aware front door for all agent intents.
|
|
7
|
+
* It resolves the operational jurisdiction from tenant context, composes
|
|
8
|
+
* applicable policy bundles (with deterministic conflict resolution), selects
|
|
9
|
+
* the governance regime that will supervise execution, and enriches the
|
|
10
|
+
* submit options with the regime's minimum trust level and metadata.
|
|
11
|
+
*
|
|
12
|
+
* Pipeline: intake -> jurisdiction resolution -> policy composition
|
|
13
|
+
* -> regime selection -> enriched submit
|
|
14
|
+
*
|
|
15
|
+
* Key design decisions exposed in this open-source version:
|
|
16
|
+
* - Jurisdiction is a first-class concept, not an afterthought.
|
|
17
|
+
* - Policy conflicts are detected eagerly and resolved deterministically
|
|
18
|
+
* (strictest-wins for ordered dimensions, additive for unordered ones).
|
|
19
|
+
* - EU AI Act risk classification runs inline for EU jurisdictions.
|
|
20
|
+
* - Every regime carries a minimum trust level; the gateway enforces it.
|
|
21
|
+
* - The gateway degrades gracefully: on error it falls through to
|
|
22
|
+
* passthrough mode with a warning, never silently drops an intent.
|
|
23
|
+
*
|
|
24
|
+
* @packageDocumentation
|
|
25
|
+
*/
|
|
26
|
+
import { createLogger } from '../common/logger.js';
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Logger
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const logger = createLogger({ component: 'intent-gateway' });
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Constants & Union Types
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/** Supported jurisdictional scopes. */
|
|
35
|
+
export const JURISDICTIONS = [
|
|
36
|
+
'GLOBAL', 'EU', 'US', 'APAC', 'UK', 'CA', 'AU', 'JP', 'SG', 'CH',
|
|
37
|
+
];
|
|
38
|
+
/** Industry verticals that affect policy selection. */
|
|
39
|
+
export const INDUSTRIES = [
|
|
40
|
+
'general', 'healthcare', 'finance', 'defense', 'government',
|
|
41
|
+
'education', 'energy',
|
|
42
|
+
];
|
|
43
|
+
/** Cryptographic suite requirements, ordered by strictness. */
|
|
44
|
+
export const CRYPTO_SUITES = [
|
|
45
|
+
'standard', 'fips-140-2', 'post-quantum',
|
|
46
|
+
];
|
|
47
|
+
/** Proof anchoring methods, ordered by assurance level. */
|
|
48
|
+
export const PROOF_ANCHORING_METHODS = [
|
|
49
|
+
'database', 'merkle-tree', 'blockchain-l2', 'tsa-rfc3161',
|
|
50
|
+
];
|
|
51
|
+
/** Consent models, ordered by strictness. */
|
|
52
|
+
export const CONSENT_MODELS = [
|
|
53
|
+
'implicit', 'opt-out', 'opt-in', 'explicit-granular',
|
|
54
|
+
];
|
|
55
|
+
/** Escalation modes, ordered by severity. */
|
|
56
|
+
export const ESCALATION_MODES = [
|
|
57
|
+
'log-only', 'flag-review', 'block-escalate', 'hard-block',
|
|
58
|
+
];
|
|
59
|
+
/** EU AI Act risk classification tiers. */
|
|
60
|
+
export const AI_ACT_CLASSIFICATIONS = [
|
|
61
|
+
'unacceptable', 'high-risk', 'limited-risk', 'minimal-risk',
|
|
62
|
+
];
|
|
63
|
+
/** High-risk categories under Annex III of the EU AI Act. */
|
|
64
|
+
export const AI_ACT_HIGH_RISK_CATEGORIES = [
|
|
65
|
+
'biometric-identification',
|
|
66
|
+
'critical-infrastructure',
|
|
67
|
+
'education-vocational',
|
|
68
|
+
'employment-worker-management',
|
|
69
|
+
'essential-services',
|
|
70
|
+
'law-enforcement',
|
|
71
|
+
'migration-asylum-border',
|
|
72
|
+
'justice-democratic',
|
|
73
|
+
];
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Default Configuration
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
export const DEFAULT_GATEWAY_CONFIG = {
|
|
78
|
+
enabled: true,
|
|
79
|
+
defaultJurisdiction: 'GLOBAL',
|
|
80
|
+
defaultIndustry: 'general',
|
|
81
|
+
regimeCacheTtlMs: 5 * 60 * 1000, // 5 minutes
|
|
82
|
+
blockOnConflicts: true,
|
|
83
|
+
logRegimeDecisions: true,
|
|
84
|
+
};
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Jurisdiction Resolver
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Data residency zones mapped to each jurisdiction.
|
|
90
|
+
*/
|
|
91
|
+
export const JURISDICTION_RESIDENCY_ZONES = {
|
|
92
|
+
GLOBAL: 'global',
|
|
93
|
+
EU: 'eu-west',
|
|
94
|
+
US: 'us-east',
|
|
95
|
+
APAC: 'ap-southeast-1',
|
|
96
|
+
UK: 'uk-south',
|
|
97
|
+
CA: 'ca-central',
|
|
98
|
+
AU: 'au-southeast',
|
|
99
|
+
JP: 'ap-northeast-1',
|
|
100
|
+
SG: 'ap-southeast-1',
|
|
101
|
+
CH: 'eu-central',
|
|
102
|
+
};
|
|
103
|
+
/** EU/EEA member state ISO 3166-1 alpha-2 codes. */
|
|
104
|
+
const EU_MEMBER_STATE_CODES = new Set([
|
|
105
|
+
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR',
|
|
106
|
+
'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL',
|
|
107
|
+
'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'IS', 'LI', 'NO',
|
|
108
|
+
]);
|
|
109
|
+
/** APAC country codes for regional mapping. */
|
|
110
|
+
const APAC_COUNTRY_CODES = new Set([
|
|
111
|
+
'JP', 'KR', 'SG', 'AU', 'NZ', 'IN', 'TH', 'MY', 'ID', 'PH', 'VN', 'TW', 'HK',
|
|
112
|
+
]);
|
|
113
|
+
/** Direct country-to-jurisdiction mapping for countries with specific regimes. */
|
|
114
|
+
const COUNTRY_JURISDICTION_MAP = {
|
|
115
|
+
US: 'US',
|
|
116
|
+
GB: 'UK',
|
|
117
|
+
CA: 'CA',
|
|
118
|
+
AU: 'AU',
|
|
119
|
+
JP: 'JP',
|
|
120
|
+
SG: 'SG',
|
|
121
|
+
CH: 'CH',
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Resolves jurisdictional context from tenant configuration, intent metadata,
|
|
125
|
+
* or defaults. Uses a three-tier resolution strategy:
|
|
126
|
+
*
|
|
127
|
+
* 1. Tenant configuration (explicit, highest priority)
|
|
128
|
+
* 2. Intent metadata inference (country codes, region hints)
|
|
129
|
+
* 3. Gateway defaults (fallback)
|
|
130
|
+
*/
|
|
131
|
+
export class JurisdictionResolver {
|
|
132
|
+
tenantConfigs = new Map();
|
|
133
|
+
config;
|
|
134
|
+
constructor(config) {
|
|
135
|
+
this.config = config;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve jurisdiction context for a tenant + optional intent metadata.
|
|
139
|
+
*/
|
|
140
|
+
resolve(ctx, intentMetadata) {
|
|
141
|
+
const tenantId = ctx.tenantId;
|
|
142
|
+
// Tier 1: Explicit tenant configuration
|
|
143
|
+
const tenantResult = this.resolveFromTenantConfig(tenantId);
|
|
144
|
+
if (tenantResult) {
|
|
145
|
+
logger.debug({ tenantId, source: 'tenant-config' }, 'Jurisdiction from tenant config');
|
|
146
|
+
return tenantResult;
|
|
147
|
+
}
|
|
148
|
+
// Tier 2: Infer from intent metadata
|
|
149
|
+
const metadataResult = this.resolveFromMetadata(intentMetadata);
|
|
150
|
+
if (metadataResult) {
|
|
151
|
+
logger.debug({ tenantId, source: 'metadata' }, 'Jurisdiction from metadata');
|
|
152
|
+
return metadataResult;
|
|
153
|
+
}
|
|
154
|
+
// Tier 3: Gateway defaults
|
|
155
|
+
logger.debug({ tenantId, source: 'default' }, 'Jurisdiction from defaults');
|
|
156
|
+
return this.resolveDefault();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Register a tenant's jurisdiction configuration.
|
|
160
|
+
*/
|
|
161
|
+
registerTenantConfig(tenantId, config) {
|
|
162
|
+
this.tenantConfigs.set(tenantId, config);
|
|
163
|
+
logger.info({ tenantId, jurisdictions: config.jurisdictions }, 'Tenant config registered');
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Retrieve a tenant's jurisdiction configuration.
|
|
167
|
+
*/
|
|
168
|
+
getTenantConfig(tenantId) {
|
|
169
|
+
return this.tenantConfigs.get(tenantId);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Determine if a set of jurisdictions implies cross-border data transfer
|
|
173
|
+
* by checking whether they map to different residency zones.
|
|
174
|
+
*/
|
|
175
|
+
detectCrossBorderTransfer(jurisdictions) {
|
|
176
|
+
if (jurisdictions.length <= 1)
|
|
177
|
+
return false;
|
|
178
|
+
const zones = new Set(jurisdictions.map(j => JURISDICTION_RESIDENCY_ZONES[j]));
|
|
179
|
+
return zones.size > 1;
|
|
180
|
+
}
|
|
181
|
+
resolveFromTenantConfig(tenantId) {
|
|
182
|
+
const config = this.tenantConfigs.get(tenantId);
|
|
183
|
+
if (!config)
|
|
184
|
+
return null;
|
|
185
|
+
return {
|
|
186
|
+
primaryJurisdictions: config.jurisdictions,
|
|
187
|
+
industry: config.industry,
|
|
188
|
+
dataResidency: config.dataResidency ??
|
|
189
|
+
JURISDICTION_RESIDENCY_ZONES[config.jurisdictions[0]] ??
|
|
190
|
+
'global',
|
|
191
|
+
crossBorderTransfer: this.detectCrossBorderTransfer(config.jurisdictions),
|
|
192
|
+
source: 'tenant-config',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
resolveFromMetadata(metadata) {
|
|
196
|
+
if (!metadata)
|
|
197
|
+
return null;
|
|
198
|
+
const jurisdictions = this.extractJurisdictionsFromMetadata(metadata);
|
|
199
|
+
if (jurisdictions.length === 0)
|
|
200
|
+
return null;
|
|
201
|
+
const industry = typeof metadata.industry === 'string' && this.isValidIndustry(metadata.industry)
|
|
202
|
+
? metadata.industry
|
|
203
|
+
: this.config.defaultIndustry;
|
|
204
|
+
const dataResidency = (typeof metadata.dataResidency === 'string' ? metadata.dataResidency : undefined) ??
|
|
205
|
+
JURISDICTION_RESIDENCY_ZONES[jurisdictions[0]] ??
|
|
206
|
+
'global';
|
|
207
|
+
return {
|
|
208
|
+
primaryJurisdictions: jurisdictions,
|
|
209
|
+
industry,
|
|
210
|
+
dataResidency,
|
|
211
|
+
crossBorderTransfer: this.detectCrossBorderTransfer(jurisdictions),
|
|
212
|
+
source: 'metadata-inference',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
resolveDefault() {
|
|
216
|
+
return {
|
|
217
|
+
primaryJurisdictions: [this.config.defaultJurisdiction],
|
|
218
|
+
industry: this.config.defaultIndustry,
|
|
219
|
+
dataResidency: JURISDICTION_RESIDENCY_ZONES[this.config.defaultJurisdiction] ?? 'global',
|
|
220
|
+
crossBorderTransfer: false,
|
|
221
|
+
source: 'default',
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Extract jurisdictions from metadata fields: jurisdiction, jurisdictions,
|
|
226
|
+
* countryCode, region. EU member state codes map to "EU", APAC codes map
|
|
227
|
+
* to "APAC", and known countries map to their specific jurisdiction.
|
|
228
|
+
*/
|
|
229
|
+
extractJurisdictionsFromMetadata(metadata) {
|
|
230
|
+
const jurisdictions = [];
|
|
231
|
+
// Direct jurisdiction field
|
|
232
|
+
if (typeof metadata.jurisdiction === 'string') {
|
|
233
|
+
const j = metadata.jurisdiction.toUpperCase();
|
|
234
|
+
if (this.isValidJurisdiction(j))
|
|
235
|
+
jurisdictions.push(j);
|
|
236
|
+
}
|
|
237
|
+
// Jurisdictions array
|
|
238
|
+
if (Array.isArray(metadata.jurisdictions)) {
|
|
239
|
+
for (const item of metadata.jurisdictions) {
|
|
240
|
+
if (typeof item === 'string') {
|
|
241
|
+
const j = item.toUpperCase();
|
|
242
|
+
if (this.isValidJurisdiction(j) && !jurisdictions.includes(j)) {
|
|
243
|
+
jurisdictions.push(j);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// Country code inference
|
|
249
|
+
if (typeof metadata.countryCode === 'string') {
|
|
250
|
+
const code = metadata.countryCode.toUpperCase();
|
|
251
|
+
if (EU_MEMBER_STATE_CODES.has(code)) {
|
|
252
|
+
if (!jurisdictions.includes('EU'))
|
|
253
|
+
jurisdictions.push('EU');
|
|
254
|
+
}
|
|
255
|
+
else if (COUNTRY_JURISDICTION_MAP[code]) {
|
|
256
|
+
const j = COUNTRY_JURISDICTION_MAP[code];
|
|
257
|
+
if (!jurisdictions.includes(j))
|
|
258
|
+
jurisdictions.push(j);
|
|
259
|
+
}
|
|
260
|
+
else if (APAC_COUNTRY_CODES.has(code)) {
|
|
261
|
+
if (!jurisdictions.includes('APAC'))
|
|
262
|
+
jurisdictions.push('APAC');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Region string inference
|
|
266
|
+
if (typeof metadata.region === 'string') {
|
|
267
|
+
const region = metadata.region.toUpperCase();
|
|
268
|
+
if (['EU', 'EUROPE', 'EEA'].includes(region)) {
|
|
269
|
+
if (!jurisdictions.includes('EU'))
|
|
270
|
+
jurisdictions.push('EU');
|
|
271
|
+
}
|
|
272
|
+
else if (['US', 'UNITED STATES', 'NORTH AMERICA'].includes(region)) {
|
|
273
|
+
if (!jurisdictions.includes('US'))
|
|
274
|
+
jurisdictions.push('US');
|
|
275
|
+
}
|
|
276
|
+
else if (['APAC', 'ASIA', 'ASIA-PACIFIC'].includes(region)) {
|
|
277
|
+
if (!jurisdictions.includes('APAC'))
|
|
278
|
+
jurisdictions.push('APAC');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return jurisdictions;
|
|
282
|
+
}
|
|
283
|
+
isValidJurisdiction(v) {
|
|
284
|
+
return JURISDICTIONS.includes(v);
|
|
285
|
+
}
|
|
286
|
+
isValidIndustry(v) {
|
|
287
|
+
return INDUSTRIES.includes(v);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// Policy Composer
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
/**
|
|
294
|
+
* Strictness rankings used for deterministic conflict resolution.
|
|
295
|
+
* Higher number = stricter requirement. When policies conflict,
|
|
296
|
+
* the strictest value wins.
|
|
297
|
+
*/
|
|
298
|
+
const CRYPTO_SUITE_STRICTNESS = {
|
|
299
|
+
'standard': 0,
|
|
300
|
+
'fips-140-2': 1,
|
|
301
|
+
'post-quantum': 2,
|
|
302
|
+
};
|
|
303
|
+
const CONSENT_STRICTNESS = {
|
|
304
|
+
'implicit': 0,
|
|
305
|
+
'opt-out': 1,
|
|
306
|
+
'opt-in': 2,
|
|
307
|
+
'explicit-granular': 3,
|
|
308
|
+
};
|
|
309
|
+
const ESCALATION_STRICTNESS = {
|
|
310
|
+
'log-only': 0,
|
|
311
|
+
'flag-review': 1,
|
|
312
|
+
'block-escalate': 2,
|
|
313
|
+
'hard-block': 3,
|
|
314
|
+
};
|
|
315
|
+
const PROOF_ANCHORING_STRICTNESS = {
|
|
316
|
+
'database': 0,
|
|
317
|
+
'merkle-tree': 1,
|
|
318
|
+
'blockchain-l2': 2,
|
|
319
|
+
'tsa-rfc3161': 3,
|
|
320
|
+
};
|
|
321
|
+
const ENFORCEMENT_ORDER = {
|
|
322
|
+
'advisory': 0,
|
|
323
|
+
'required': 1,
|
|
324
|
+
'mandatory': 2,
|
|
325
|
+
'blocking': 3,
|
|
326
|
+
};
|
|
327
|
+
/**
|
|
328
|
+
* Helper to create a PolicyConstraint with less boilerplate.
|
|
329
|
+
*/
|
|
330
|
+
function constraint(id, type, rule, enforcement, bundleId, jurisdiction, value) {
|
|
331
|
+
return { id, type, rule, enforcement, sourceBundleId: bundleId, sourceJurisdiction: jurisdiction, value };
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Built-in policy bundles covering the major jurisdictional scopes.
|
|
335
|
+
* These are the baseline; consumers can register additional bundles.
|
|
336
|
+
*/
|
|
337
|
+
function createBuiltinBundles() {
|
|
338
|
+
return [
|
|
339
|
+
// -- GLOBAL DEFAULT --
|
|
340
|
+
{
|
|
341
|
+
id: 'global-default',
|
|
342
|
+
name: 'Global Default',
|
|
343
|
+
jurisdictions: ['GLOBAL'],
|
|
344
|
+
priority: 0,
|
|
345
|
+
constraints: [
|
|
346
|
+
constraint('global-retention', 'retention', 'Min 365-day audit retention', 'required', 'global-default', 'GLOBAL', 365),
|
|
347
|
+
constraint('global-crypto', 'crypto', 'Standard cryptographic suite', 'required', 'global-default', 'GLOBAL', 'standard'),
|
|
348
|
+
constraint('global-consent', 'consent', 'Implicit consent model', 'required', 'global-default', 'GLOBAL', 'implicit'),
|
|
349
|
+
constraint('global-escalation', 'escalation', 'Flag for human review', 'required', 'global-default', 'GLOBAL', 'flag-review'),
|
|
350
|
+
constraint('global-trust', 'trust-level', 'Minimum trust T2 (Provisional)', 'required', 'global-default', 'GLOBAL', 2),
|
|
351
|
+
constraint('global-proof', 'proof-anchoring', 'Database proof anchoring', 'required', 'global-default', 'GLOBAL', 'database'),
|
|
352
|
+
constraint('global-external', 'external-services', 'External services allowed', 'advisory', 'global-default', 'GLOBAL', true),
|
|
353
|
+
],
|
|
354
|
+
},
|
|
355
|
+
// -- EU (GDPR + AI Act) --
|
|
356
|
+
{
|
|
357
|
+
id: 'eu-gdpr',
|
|
358
|
+
name: 'EU GDPR',
|
|
359
|
+
jurisdictions: ['EU'],
|
|
360
|
+
priority: 10,
|
|
361
|
+
constraints: [
|
|
362
|
+
constraint('eu-retention', 'retention', 'GDPR: 5-year audit retention', 'mandatory', 'eu-gdpr', 'EU', 1825),
|
|
363
|
+
constraint('eu-consent', 'consent', 'GDPR: Explicit granular consent', 'mandatory', 'eu-gdpr', 'EU', 'explicit-granular'),
|
|
364
|
+
constraint('eu-residency', 'data-residency', 'GDPR: EU data residency', 'mandatory', 'eu-gdpr', 'EU', 'eu-west'),
|
|
365
|
+
constraint('eu-proof', 'proof-anchoring', 'GDPR: Merkle tree proof anchoring', 'required', 'eu-gdpr', 'EU', 'merkle-tree'),
|
|
366
|
+
constraint('eu-trust', 'trust-level', 'GDPR: Minimum trust T3 (Monitored)', 'required', 'eu-gdpr', 'EU', 3),
|
|
367
|
+
constraint('eu-processing', 'processing-restriction', 'GDPR: Purpose limitation', 'mandatory', 'eu-gdpr', 'EU', 'purpose-limitation'),
|
|
368
|
+
constraint('eu-audit', 'audit-requirement', 'GDPR: Full audit trail', 'mandatory', 'eu-gdpr', 'EU', 'full-audit-trail'),
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: 'eu-ai-act',
|
|
373
|
+
name: 'EU AI Act',
|
|
374
|
+
jurisdictions: ['EU'],
|
|
375
|
+
priority: 15,
|
|
376
|
+
constraints: [
|
|
377
|
+
constraint('euai-escalation', 'escalation', 'AI Act: Block and escalate on violations', 'mandatory', 'eu-ai-act', 'EU', 'block-escalate'),
|
|
378
|
+
constraint('euai-audit', 'audit-requirement', 'AI Act: AI system audit trail', 'mandatory', 'eu-ai-act', 'EU', 'ai-system-audit'),
|
|
379
|
+
constraint('euai-processing', 'processing-restriction', 'AI Act: Risk assessment required', 'mandatory', 'eu-ai-act', 'EU', 'risk-assessment-required'),
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
// -- US --
|
|
383
|
+
{
|
|
384
|
+
id: 'us-standard',
|
|
385
|
+
name: 'US Standard',
|
|
386
|
+
jurisdictions: ['US'],
|
|
387
|
+
priority: 10,
|
|
388
|
+
constraints: [
|
|
389
|
+
constraint('us-retention', 'retention', 'US: 7-year audit retention', 'mandatory', 'us-standard', 'US', 2555),
|
|
390
|
+
constraint('us-crypto', 'crypto', 'US: FIPS 140-2 cryptographic suite', 'mandatory', 'us-standard', 'US', 'fips-140-2'),
|
|
391
|
+
constraint('us-proof', 'proof-anchoring', 'US: TSA RFC 3161 proof anchoring', 'required', 'us-standard', 'US', 'tsa-rfc3161'),
|
|
392
|
+
constraint('us-trust', 'trust-level', 'US: Minimum trust T3 (Monitored)', 'required', 'us-standard', 'US', 3),
|
|
393
|
+
constraint('us-escalation', 'escalation', 'US: Block and escalate', 'mandatory', 'us-standard', 'US', 'block-escalate'),
|
|
394
|
+
constraint('us-consent', 'consent', 'US: Opt-out consent model', 'required', 'us-standard', 'US', 'opt-out'),
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
// -- APAC --
|
|
398
|
+
{
|
|
399
|
+
id: 'apac-standard',
|
|
400
|
+
name: 'APAC Standard',
|
|
401
|
+
jurisdictions: ['APAC'],
|
|
402
|
+
priority: 8,
|
|
403
|
+
constraints: [
|
|
404
|
+
constraint('apac-retention', 'retention', 'APAC: 3-year audit retention', 'required', 'apac-standard', 'APAC', 1095),
|
|
405
|
+
constraint('apac-consent', 'consent', 'APAC: Opt-in consent model', 'required', 'apac-standard', 'APAC', 'opt-in'),
|
|
406
|
+
constraint('apac-trust', 'trust-level', 'APAC: Minimum trust T2 (Provisional)', 'required', 'apac-standard', 'APAC', 2),
|
|
407
|
+
constraint('apac-proof', 'proof-anchoring', 'APAC: Merkle tree proof anchoring', 'required', 'apac-standard', 'APAC', 'merkle-tree'),
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
// -- UK (post-Brexit, GDPR-adjacent) --
|
|
411
|
+
{
|
|
412
|
+
id: 'uk-dpa',
|
|
413
|
+
name: 'UK Data Protection Act',
|
|
414
|
+
jurisdictions: ['UK'],
|
|
415
|
+
priority: 10,
|
|
416
|
+
constraints: [
|
|
417
|
+
constraint('uk-retention', 'retention', 'UK DPA: 5-year retention', 'mandatory', 'uk-dpa', 'UK', 1825),
|
|
418
|
+
constraint('uk-consent', 'consent', 'UK DPA: Explicit granular consent', 'mandatory', 'uk-dpa', 'UK', 'explicit-granular'),
|
|
419
|
+
constraint('uk-trust', 'trust-level', 'UK DPA: Minimum trust T3 (Monitored)', 'required', 'uk-dpa', 'UK', 3),
|
|
420
|
+
constraint('uk-residency', 'data-residency', 'UK DPA: UK data residency', 'mandatory', 'uk-dpa', 'UK', 'uk-south'),
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
// -- Canada --
|
|
424
|
+
{
|
|
425
|
+
id: 'ca-pipeda',
|
|
426
|
+
name: 'Canada PIPEDA',
|
|
427
|
+
jurisdictions: ['CA'],
|
|
428
|
+
priority: 10,
|
|
429
|
+
constraints: [
|
|
430
|
+
constraint('ca-consent', 'consent', 'PIPEDA: Opt-in consent', 'mandatory', 'ca-pipeda', 'CA', 'opt-in'),
|
|
431
|
+
constraint('ca-trust', 'trust-level', 'PIPEDA: Minimum trust T3', 'required', 'ca-pipeda', 'CA', 3),
|
|
432
|
+
constraint('ca-retention', 'retention', 'PIPEDA: 3-year retention', 'required', 'ca-pipeda', 'CA', 1095),
|
|
433
|
+
],
|
|
434
|
+
},
|
|
435
|
+
];
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Composes policy constraints from multiple bundles into a single resolved set.
|
|
439
|
+
*
|
|
440
|
+
* Conflict resolution strategy:
|
|
441
|
+
* - Numeric constraints (retention, trust-level): highest value wins.
|
|
442
|
+
* - Ordered enums (crypto, consent, escalation, proof-anchoring): strictest wins.
|
|
443
|
+
* - Data residency: enforcement level decides; incompatible mandatory zones = critical conflict.
|
|
444
|
+
* - External services: restrictive (false) wins over permissive (true).
|
|
445
|
+
* - Additive constraints (audit, processing): all unique values are kept.
|
|
446
|
+
*/
|
|
447
|
+
export class PolicyComposer {
|
|
448
|
+
bundles = new Map();
|
|
449
|
+
constructor() {
|
|
450
|
+
for (const bundle of createBuiltinBundles()) {
|
|
451
|
+
this.bundles.set(bundle.id, bundle);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/** Number of registered bundles. */
|
|
455
|
+
get bundleCount() {
|
|
456
|
+
return this.bundles.size;
|
|
457
|
+
}
|
|
458
|
+
/** IDs of all registered bundles. */
|
|
459
|
+
get registeredBundleIds() {
|
|
460
|
+
return Array.from(this.bundles.keys());
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Register or overwrite a policy bundle.
|
|
464
|
+
*/
|
|
465
|
+
registerBundle(bundle) {
|
|
466
|
+
if (this.bundles.has(bundle.id)) {
|
|
467
|
+
logger.warn({ bundleId: bundle.id }, 'Overwriting existing policy bundle');
|
|
468
|
+
}
|
|
469
|
+
this.bundles.set(bundle.id, bundle);
|
|
470
|
+
logger.info({ bundleId: bundle.id, priority: bundle.priority }, 'Policy bundle registered');
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Compose a policy set by selecting applicable bundles for the given
|
|
474
|
+
* jurisdiction context, merging their constraints, and resolving conflicts.
|
|
475
|
+
*/
|
|
476
|
+
compose(ctx, additionalBundleIds) {
|
|
477
|
+
const applicable = this.selectApplicable(ctx, additionalBundleIds);
|
|
478
|
+
// Always include global default as a fallback
|
|
479
|
+
if (applicable.length === 0) {
|
|
480
|
+
const global = this.bundles.get('global-default');
|
|
481
|
+
if (global)
|
|
482
|
+
applicable.push(global);
|
|
483
|
+
}
|
|
484
|
+
// Sort by priority (lower first, so higher-priority overrides later)
|
|
485
|
+
applicable.sort((a, b) => a.priority - b.priority);
|
|
486
|
+
// Collect all constraints
|
|
487
|
+
const allConstraints = [];
|
|
488
|
+
for (const bundle of applicable) {
|
|
489
|
+
allConstraints.push(...bundle.constraints);
|
|
490
|
+
}
|
|
491
|
+
// Group by constraint type
|
|
492
|
+
const grouped = new Map();
|
|
493
|
+
for (const c of allConstraints) {
|
|
494
|
+
const group = grouped.get(c.type) ?? [];
|
|
495
|
+
group.push(c);
|
|
496
|
+
grouped.set(c.type, group);
|
|
497
|
+
}
|
|
498
|
+
// Resolve each group
|
|
499
|
+
const resolved = [];
|
|
500
|
+
const resolvedConflicts = [];
|
|
501
|
+
const unresolvedConflicts = [];
|
|
502
|
+
for (const [type, constraints] of grouped.entries()) {
|
|
503
|
+
const result = this.resolveGroup(type, constraints, applicable);
|
|
504
|
+
resolved.push(...result.resolved);
|
|
505
|
+
resolvedConflicts.push(...result.resolvedConflicts);
|
|
506
|
+
unresolvedConflicts.push(...result.unresolvedConflicts);
|
|
507
|
+
}
|
|
508
|
+
const isValid = unresolvedConflicts.every(c => c.severity !== 'critical');
|
|
509
|
+
return {
|
|
510
|
+
constraints: resolved,
|
|
511
|
+
sourceBundles: applicable.map(b => b.id),
|
|
512
|
+
resolvedConflicts,
|
|
513
|
+
unresolvedConflicts,
|
|
514
|
+
isValid,
|
|
515
|
+
composedAt: Date.now(),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Select bundles that apply to the given jurisdiction context.
|
|
520
|
+
* A bundle applies if any of its jurisdictions match the context's
|
|
521
|
+
* primary jurisdictions, or if the bundle is GLOBAL.
|
|
522
|
+
*/
|
|
523
|
+
selectApplicable(ctx, additionalIds) {
|
|
524
|
+
const selected = [];
|
|
525
|
+
const used = new Set();
|
|
526
|
+
for (const [id, bundle] of this.bundles) {
|
|
527
|
+
const applies = bundle.jurisdictions.some(j => j === 'GLOBAL' || ctx.primaryJurisdictions.includes(j));
|
|
528
|
+
if (applies && !used.has(id)) {
|
|
529
|
+
selected.push(bundle);
|
|
530
|
+
used.add(id);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Include explicitly requested additional bundles
|
|
534
|
+
if (additionalIds) {
|
|
535
|
+
for (const id of additionalIds) {
|
|
536
|
+
if (!used.has(id)) {
|
|
537
|
+
const bundle = this.bundles.get(id);
|
|
538
|
+
if (bundle) {
|
|
539
|
+
selected.push(bundle);
|
|
540
|
+
used.add(id);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return selected;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Resolve conflicts within a group of same-type constraints.
|
|
549
|
+
*/
|
|
550
|
+
resolveGroup(type, constraints, bundles) {
|
|
551
|
+
// No conflict possible with a single constraint
|
|
552
|
+
if (constraints.length <= 1) {
|
|
553
|
+
return { resolved: constraints, resolvedConflicts: [], unresolvedConflicts: [] };
|
|
554
|
+
}
|
|
555
|
+
switch (type) {
|
|
556
|
+
// Numeric max-wins
|
|
557
|
+
case 'retention':
|
|
558
|
+
case 'trust-level':
|
|
559
|
+
return this.resolveByMax(type, constraints);
|
|
560
|
+
// Ordered-enum strictest-wins
|
|
561
|
+
case 'crypto':
|
|
562
|
+
return this.resolveByStrictness(type, constraints, CRYPTO_SUITE_STRICTNESS);
|
|
563
|
+
case 'consent':
|
|
564
|
+
return this.resolveByStrictness(type, constraints, CONSENT_STRICTNESS);
|
|
565
|
+
case 'escalation':
|
|
566
|
+
return this.resolveByStrictness(type, constraints, ESCALATION_STRICTNESS);
|
|
567
|
+
case 'proof-anchoring':
|
|
568
|
+
return this.resolveByStrictness(type, constraints, PROOF_ANCHORING_STRICTNESS);
|
|
569
|
+
// Special resolution
|
|
570
|
+
case 'data-residency':
|
|
571
|
+
return this.resolveDataResidency(constraints);
|
|
572
|
+
case 'external-services':
|
|
573
|
+
return this.resolveExternalServices(constraints);
|
|
574
|
+
// Additive (keep all unique values)
|
|
575
|
+
case 'audit-requirement':
|
|
576
|
+
case 'processing-restriction':
|
|
577
|
+
return this.resolveAdditive(constraints);
|
|
578
|
+
// Fallback: by bundle priority
|
|
579
|
+
default:
|
|
580
|
+
return this.resolveByPriority(type, constraints, bundles);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/** Resolve numeric constraints by taking the maximum value. */
|
|
584
|
+
resolveByMax(type, constraints) {
|
|
585
|
+
const valued = constraints.map(c => ({
|
|
586
|
+
constraint: c,
|
|
587
|
+
numericValue: typeof c.value === 'number' ? c.value : 0,
|
|
588
|
+
}));
|
|
589
|
+
const winner = valued.reduce((max, v) => v.numericValue > max.numericValue ? v : max);
|
|
590
|
+
const hasConflict = new Set(valued.map(v => v.numericValue)).size > 1;
|
|
591
|
+
return {
|
|
592
|
+
resolved: [winner.constraint],
|
|
593
|
+
resolvedConflicts: hasConflict
|
|
594
|
+
? [{
|
|
595
|
+
constraintType: type,
|
|
596
|
+
constraints,
|
|
597
|
+
description: `${type}: resolved to max value ${winner.numericValue}`,
|
|
598
|
+
severity: 'low',
|
|
599
|
+
}]
|
|
600
|
+
: [],
|
|
601
|
+
unresolvedConflicts: [],
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/** Resolve ordered-enum constraints by taking the strictest value. */
|
|
605
|
+
resolveByStrictness(type, constraints, strictnessMap) {
|
|
606
|
+
const valued = constraints.map(c => ({
|
|
607
|
+
constraint: c,
|
|
608
|
+
strictness: strictnessMap[String(c.value)] ?? 0,
|
|
609
|
+
}));
|
|
610
|
+
const winner = valued.reduce((max, v) => v.strictness > max.strictness ? v : max);
|
|
611
|
+
const hasConflict = new Set(valued.map(v => v.strictness)).size > 1;
|
|
612
|
+
return {
|
|
613
|
+
resolved: [winner.constraint],
|
|
614
|
+
resolvedConflicts: hasConflict
|
|
615
|
+
? [{
|
|
616
|
+
constraintType: type,
|
|
617
|
+
constraints,
|
|
618
|
+
description: `${type}: resolved to strictest value "${winner.constraint.value}"`,
|
|
619
|
+
severity: 'low',
|
|
620
|
+
}]
|
|
621
|
+
: [],
|
|
622
|
+
unresolvedConflicts: [],
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Data residency conflicts are potentially critical: if two mandatory
|
|
627
|
+
* constraints require different zones, the data literally cannot be in
|
|
628
|
+
* both places. This is surfaced as an unresolved critical conflict.
|
|
629
|
+
*/
|
|
630
|
+
resolveDataResidency(constraints) {
|
|
631
|
+
const zones = new Set(constraints.map(c => String(c.value)));
|
|
632
|
+
if (zones.size <= 1) {
|
|
633
|
+
return { resolved: [constraints[0]], resolvedConflicts: [], unresolvedConflicts: [] };
|
|
634
|
+
}
|
|
635
|
+
// Sort by enforcement level (strictest first)
|
|
636
|
+
const sorted = [...constraints].sort((a, b) => (ENFORCEMENT_ORDER[b.enforcement] ?? 0) - (ENFORCEMENT_ORDER[a.enforcement] ?? 0));
|
|
637
|
+
const hasMandatory = sorted.some(c => c.enforcement === 'blocking' || c.enforcement === 'mandatory');
|
|
638
|
+
if (hasMandatory) {
|
|
639
|
+
return {
|
|
640
|
+
resolved: [sorted[0]],
|
|
641
|
+
resolvedConflicts: [],
|
|
642
|
+
unresolvedConflicts: [{
|
|
643
|
+
constraintType: 'data-residency',
|
|
644
|
+
constraints,
|
|
645
|
+
description: `Incompatible data residency requirements: ${[...zones].join(' vs ')}`,
|
|
646
|
+
severity: 'critical',
|
|
647
|
+
}],
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
resolved: [sorted[0]],
|
|
652
|
+
resolvedConflicts: [{
|
|
653
|
+
constraintType: 'data-residency',
|
|
654
|
+
constraints,
|
|
655
|
+
description: `Data residency resolved to ${sorted[0].value}`,
|
|
656
|
+
severity: 'medium',
|
|
657
|
+
}],
|
|
658
|
+
unresolvedConflicts: [],
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/** External services: false (restrictive) wins over true (permissive). */
|
|
662
|
+
resolveExternalServices(constraints) {
|
|
663
|
+
const blocked = constraints.some(c => c.value === false);
|
|
664
|
+
const winner = blocked ? constraints.find(c => c.value === false) : constraints[0];
|
|
665
|
+
const hasConflict = new Set(constraints.map(c => c.value)).size > 1;
|
|
666
|
+
return {
|
|
667
|
+
resolved: [winner],
|
|
668
|
+
resolvedConflicts: hasConflict
|
|
669
|
+
? [{
|
|
670
|
+
constraintType: 'external-services',
|
|
671
|
+
constraints,
|
|
672
|
+
description: `External services: resolved to ${winner.value}`,
|
|
673
|
+
severity: 'low',
|
|
674
|
+
}]
|
|
675
|
+
: [],
|
|
676
|
+
unresolvedConflicts: [],
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/** Additive constraints keep all unique values (e.g., audit requirements). */
|
|
680
|
+
resolveAdditive(constraints) {
|
|
681
|
+
const seen = new Set();
|
|
682
|
+
const unique = [];
|
|
683
|
+
for (const c of constraints) {
|
|
684
|
+
const key = String(c.value);
|
|
685
|
+
if (!seen.has(key)) {
|
|
686
|
+
seen.add(key);
|
|
687
|
+
unique.push(c);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return { resolved: unique, resolvedConflicts: [], unresolvedConflicts: [] };
|
|
691
|
+
}
|
|
692
|
+
/** Fallback: resolve by bundle priority (highest priority wins). */
|
|
693
|
+
resolveByPriority(type, constraints, bundles) {
|
|
694
|
+
const priorityMap = new Map(bundles.map(b => [b.id, b.priority]));
|
|
695
|
+
const sorted = [...constraints].sort((a, b) => (priorityMap.get(b.sourceBundleId) ?? 0) - (priorityMap.get(a.sourceBundleId) ?? 0));
|
|
696
|
+
return {
|
|
697
|
+
resolved: [sorted[0]],
|
|
698
|
+
resolvedConflicts: constraints.length > 1
|
|
699
|
+
? [{
|
|
700
|
+
constraintType: type,
|
|
701
|
+
constraints,
|
|
702
|
+
description: `${type}: resolved by priority to bundle "${sorted[0].sourceBundleId}"`,
|
|
703
|
+
severity: 'low',
|
|
704
|
+
}]
|
|
705
|
+
: [],
|
|
706
|
+
unresolvedConflicts: [],
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// ---------------------------------------------------------------------------
|
|
711
|
+
// EU AI Act Classifier
|
|
712
|
+
// ---------------------------------------------------------------------------
|
|
713
|
+
/** Keywords that trigger "unacceptable" (prohibited) classification under Art. 5. */
|
|
714
|
+
const PROHIBITED_KEYWORDS = [
|
|
715
|
+
'social scoring', 'social credit', 'subliminal manipulation',
|
|
716
|
+
'subliminal technique', 'exploit vulnerability', 'exploit vulnerabilities',
|
|
717
|
+
'real-time biometric identification', 'real-time facial recognition',
|
|
718
|
+
'mass surveillance', 'emotion recognition workplace',
|
|
719
|
+
'emotion recognition education', 'predictive policing individual',
|
|
720
|
+
'cognitive behavioral manipulation', 'biometric categorisation sensitive',
|
|
721
|
+
'untargeted scraping facial',
|
|
722
|
+
];
|
|
723
|
+
/** Keywords mapped to Annex III high-risk categories. */
|
|
724
|
+
const HIGH_RISK_KEYWORDS = {
|
|
725
|
+
'biometric-identification': [
|
|
726
|
+
'biometric identification', 'biometric verification', 'facial recognition',
|
|
727
|
+
'fingerprint matching', 'voice identification', 'iris recognition',
|
|
728
|
+
],
|
|
729
|
+
'critical-infrastructure': [
|
|
730
|
+
'critical infrastructure', 'power grid', 'water supply',
|
|
731
|
+
'traffic management', 'electricity distribution', 'energy management',
|
|
732
|
+
],
|
|
733
|
+
'education-vocational': [
|
|
734
|
+
'student assessment', 'educational admission', 'learning evaluation',
|
|
735
|
+
'exam scoring', 'academic grading', 'educational placement',
|
|
736
|
+
],
|
|
737
|
+
'employment-worker-management': [
|
|
738
|
+
'recruitment', 'hiring decision', 'cv screening', 'resume screening',
|
|
739
|
+
'employee evaluation', 'performance monitoring', 'promotion decision',
|
|
740
|
+
'termination decision', 'worker management',
|
|
741
|
+
],
|
|
742
|
+
'essential-services': [
|
|
743
|
+
'credit scoring', 'creditworthiness', 'insurance pricing',
|
|
744
|
+
'insurance risk', 'social benefit', 'public assistance',
|
|
745
|
+
'emergency services dispatch', 'loan application', 'mortgage decision',
|
|
746
|
+
],
|
|
747
|
+
'law-enforcement': [
|
|
748
|
+
'law enforcement', 'criminal risk assessment', 'recidivism prediction',
|
|
749
|
+
'crime prediction', 'evidence analysis', 'suspect profiling',
|
|
750
|
+
],
|
|
751
|
+
'migration-asylum-border': [
|
|
752
|
+
'border control', 'immigration', 'asylum application',
|
|
753
|
+
'visa application', 'migration management', 'refugee assessment',
|
|
754
|
+
],
|
|
755
|
+
'justice-democratic': [
|
|
756
|
+
'judicial decision', 'court ruling', 'sentencing',
|
|
757
|
+
'legal outcome prediction', 'electoral', 'voting',
|
|
758
|
+
'election', 'democratic process',
|
|
759
|
+
],
|
|
760
|
+
};
|
|
761
|
+
/** Keywords that trigger "limited-risk" classification (transparency obligations). */
|
|
762
|
+
const LIMITED_RISK_KEYWORDS = [
|
|
763
|
+
'chatbot', 'conversational ai', 'virtual assistant', 'deepfake',
|
|
764
|
+
'synthetic media', 'generated content', 'ai-generated text',
|
|
765
|
+
'ai-generated image', 'ai-generated video', 'emotion detection',
|
|
766
|
+
'content generation', 'text generation', 'image generation',
|
|
767
|
+
];
|
|
768
|
+
/** Regulatory obligations per classification tier. */
|
|
769
|
+
const OBLIGATIONS_MAP = {
|
|
770
|
+
'unacceptable': [
|
|
771
|
+
'PROHIBITED - System must not be deployed in EU/EEA',
|
|
772
|
+
'Immediate cessation required for EU market',
|
|
773
|
+
'Notify national supervisory authority',
|
|
774
|
+
],
|
|
775
|
+
'high-risk': [
|
|
776
|
+
'Risk management system (Art. 9)',
|
|
777
|
+
'Data governance and management (Art. 10)',
|
|
778
|
+
'Technical documentation (Art. 11)',
|
|
779
|
+
'Record-keeping and logging (Art. 12)',
|
|
780
|
+
'Transparency and user information (Art. 13)',
|
|
781
|
+
'Human oversight measures (Art. 14)',
|
|
782
|
+
'Accuracy, robustness, cybersecurity (Art. 15)',
|
|
783
|
+
'Conformity assessment (Art. 43)',
|
|
784
|
+
'Post-market monitoring (Art. 61)',
|
|
785
|
+
'Serious incident reporting (Art. 62)',
|
|
786
|
+
],
|
|
787
|
+
'limited-risk': [
|
|
788
|
+
'Inform users of AI interaction (Art. 50)',
|
|
789
|
+
'Label AI-generated content (Art. 50)',
|
|
790
|
+
'Disclose deepfake/synthetic content (Art. 50)',
|
|
791
|
+
],
|
|
792
|
+
'minimal-risk': [
|
|
793
|
+
'Voluntary codes of conduct (Art. 95)',
|
|
794
|
+
'No mandatory obligations',
|
|
795
|
+
],
|
|
796
|
+
};
|
|
797
|
+
/**
|
|
798
|
+
* Classifies AI system usage against the EU AI Act risk framework.
|
|
799
|
+
*
|
|
800
|
+
* Uses keyword-based heuristics against the intent goal, metadata, and type
|
|
801
|
+
* to produce a risk classification. This is a simplified, open-source
|
|
802
|
+
* implementation suitable for initial screening; production deployments
|
|
803
|
+
* should supplement this with domain-expert review for high-risk and
|
|
804
|
+
* unacceptable classifications.
|
|
805
|
+
*
|
|
806
|
+
* Classification priority: unacceptable > high-risk > limited-risk > minimal-risk
|
|
807
|
+
*/
|
|
808
|
+
export class AiActClassifier {
|
|
809
|
+
/**
|
|
810
|
+
* Classify an intent's AI Act risk level.
|
|
811
|
+
*
|
|
812
|
+
* @param goal - The intent's stated goal
|
|
813
|
+
* @param context - Optional intent context/metadata
|
|
814
|
+
* @param intentType - Optional intent type string
|
|
815
|
+
* @returns Classification result with obligations
|
|
816
|
+
*/
|
|
817
|
+
classify(goal, context, intentType) {
|
|
818
|
+
const searchText = this.buildSearchText(goal, context, intentType);
|
|
819
|
+
// Check in priority order: unacceptable > high-risk > limited > minimal
|
|
820
|
+
const prohibited = this.checkProhibited(searchText);
|
|
821
|
+
if (prohibited)
|
|
822
|
+
return prohibited;
|
|
823
|
+
const highRisk = this.checkHighRisk(searchText);
|
|
824
|
+
if (highRisk)
|
|
825
|
+
return highRisk;
|
|
826
|
+
const limited = this.checkLimitedRisk(searchText);
|
|
827
|
+
if (limited)
|
|
828
|
+
return limited;
|
|
829
|
+
return {
|
|
830
|
+
classification: 'minimal-risk',
|
|
831
|
+
confidence: 0.6,
|
|
832
|
+
reasoning: 'No risk indicators detected; classified as minimal-risk',
|
|
833
|
+
obligations: OBLIGATIONS_MAP['minimal-risk'],
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
buildSearchText(goal, context, intentType) {
|
|
837
|
+
const parts = [goal.toLowerCase()];
|
|
838
|
+
if (intentType)
|
|
839
|
+
parts.push(intentType.toLowerCase());
|
|
840
|
+
if (context) {
|
|
841
|
+
for (const [key, value] of Object.entries(context)) {
|
|
842
|
+
if (typeof value === 'string') {
|
|
843
|
+
parts.push(`${key}: ${value}`.toLowerCase());
|
|
844
|
+
}
|
|
845
|
+
else if (Array.isArray(value)) {
|
|
846
|
+
for (const item of value) {
|
|
847
|
+
if (typeof item === 'string')
|
|
848
|
+
parts.push(item.toLowerCase());
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return parts.join(' ');
|
|
854
|
+
}
|
|
855
|
+
checkProhibited(text) {
|
|
856
|
+
for (const keyword of PROHIBITED_KEYWORDS) {
|
|
857
|
+
if (text.includes(keyword)) {
|
|
858
|
+
logger.warn({ keyword }, 'EU AI Act: PROHIBITED system detected');
|
|
859
|
+
return {
|
|
860
|
+
classification: 'unacceptable',
|
|
861
|
+
confidence: 0.9,
|
|
862
|
+
reasoning: `Prohibited practice detected: "${keyword}" (Art. 5)`,
|
|
863
|
+
annexReference: 'Article 5',
|
|
864
|
+
obligations: OBLIGATIONS_MAP['unacceptable'],
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
checkHighRisk(text) {
|
|
871
|
+
let bestMatch = null;
|
|
872
|
+
for (const [category, keywords] of Object.entries(HIGH_RISK_KEYWORDS)) {
|
|
873
|
+
let count = 0;
|
|
874
|
+
let firstKeyword = '';
|
|
875
|
+
for (const keyword of keywords) {
|
|
876
|
+
if (text.includes(keyword)) {
|
|
877
|
+
count++;
|
|
878
|
+
if (!firstKeyword)
|
|
879
|
+
firstKeyword = keyword;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (count > 0 && (!bestMatch || count > bestMatch.count)) {
|
|
883
|
+
bestMatch = { category, keyword: firstKeyword, count };
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (bestMatch) {
|
|
887
|
+
const confidence = Math.min(0.5 + bestMatch.count * 0.15, 0.95);
|
|
888
|
+
logger.info({ category: bestMatch.category, confidence }, 'EU AI Act: High-risk classification');
|
|
889
|
+
return {
|
|
890
|
+
classification: 'high-risk',
|
|
891
|
+
highRiskCategory: bestMatch.category,
|
|
892
|
+
confidence,
|
|
893
|
+
reasoning: `High-risk system (Annex III: ${bestMatch.category}). Matched: "${bestMatch.keyword}"`,
|
|
894
|
+
annexReference: 'Article 6, Annex III',
|
|
895
|
+
obligations: OBLIGATIONS_MAP['high-risk'],
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
checkLimitedRisk(text) {
|
|
901
|
+
for (const keyword of LIMITED_RISK_KEYWORDS) {
|
|
902
|
+
if (text.includes(keyword)) {
|
|
903
|
+
return {
|
|
904
|
+
classification: 'limited-risk',
|
|
905
|
+
confidence: 0.7,
|
|
906
|
+
reasoning: `Limited-risk transparency obligation: "${keyword}"`,
|
|
907
|
+
annexReference: 'Article 50',
|
|
908
|
+
obligations: OBLIGATIONS_MAP['limited-risk'],
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
// ---------------------------------------------------------------------------
|
|
916
|
+
// Regime Selector
|
|
917
|
+
// ---------------------------------------------------------------------------
|
|
918
|
+
/**
|
|
919
|
+
* Pick the strictest value from an ordered enum, given jurisdiction defaults
|
|
920
|
+
* and an optional policy-composed value.
|
|
921
|
+
*/
|
|
922
|
+
function pickStrictest(defaults, policyValue, strictnessMap) {
|
|
923
|
+
const all = policyValue ? [...defaults, policyValue] : defaults;
|
|
924
|
+
return all.reduce((strictest, current) => (strictnessMap[current] ?? 0) > (strictnessMap[strictest] ?? 0) ? current : strictest);
|
|
925
|
+
}
|
|
926
|
+
/** Check if a jurisdiction context includes any of the given jurisdictions. */
|
|
927
|
+
function hasJurisdiction(ctx, ...jurisdictions) {
|
|
928
|
+
return jurisdictions.some(j => ctx.primaryJurisdictions.includes(j));
|
|
929
|
+
}
|
|
930
|
+
/** Extract a typed constraint value from a composed policy set. */
|
|
931
|
+
function extractPolicyValue(policySet, type) {
|
|
932
|
+
return policySet.constraints.find(c => c.type === type)?.value;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Assembles a complete GovernanceRegime from a jurisdiction context and
|
|
936
|
+
* composed policy set. Each regime parameter is resolved independently
|
|
937
|
+
* by combining jurisdiction-based defaults with policy-composed values,
|
|
938
|
+
* always selecting the strictest option.
|
|
939
|
+
*/
|
|
940
|
+
export class RegimeSelector {
|
|
941
|
+
/**
|
|
942
|
+
* Select the governance regime for the given context and policy set.
|
|
943
|
+
*/
|
|
944
|
+
select(ctx, policySet) {
|
|
945
|
+
const cryptoSuite = this.resolveCryptoSuite(ctx, policySet);
|
|
946
|
+
const proofAnchoring = this.resolveProofAnchoring(ctx, policySet);
|
|
947
|
+
const consentModel = this.resolveConsentModel(ctx, policySet);
|
|
948
|
+
const escalationMode = this.resolveEscalationMode(ctx, policySet);
|
|
949
|
+
const auditRetentionDays = this.resolveAuditRetentionDays(ctx, policySet);
|
|
950
|
+
const externalServicesAllowed = this.resolveExternalServicesAllowed(ctx, policySet);
|
|
951
|
+
const minimumTrustLevel = this.resolveMinimumTrustLevel(ctx, policySet);
|
|
952
|
+
// Build a deterministic regime ID from the resolved parameters
|
|
953
|
+
const regimeId = this.generateRegimeId({
|
|
954
|
+
jurisdictions: ctx.primaryJurisdictions,
|
|
955
|
+
cryptoSuite,
|
|
956
|
+
proofAnchoring,
|
|
957
|
+
consentModel,
|
|
958
|
+
escalationMode,
|
|
959
|
+
auditRetentionDays,
|
|
960
|
+
dataResidency: ctx.dataResidency,
|
|
961
|
+
externalServicesAllowed,
|
|
962
|
+
minimumTrustLevel,
|
|
963
|
+
});
|
|
964
|
+
// Build human-readable name
|
|
965
|
+
const jurisdictionPart = ctx.primaryJurisdictions.length === 1
|
|
966
|
+
? ctx.primaryJurisdictions[0]
|
|
967
|
+
: `Multi(${ctx.primaryJurisdictions.join('+')})`;
|
|
968
|
+
const name = ctx.industry !== 'general'
|
|
969
|
+
? `${jurisdictionPart}-${ctx.industry}`
|
|
970
|
+
: jurisdictionPart;
|
|
971
|
+
const regime = {
|
|
972
|
+
regimeId,
|
|
973
|
+
name: name || 'default',
|
|
974
|
+
jurisdictions: ctx.primaryJurisdictions,
|
|
975
|
+
policyNamespaces: policySet.sourceBundles,
|
|
976
|
+
cryptoSuite,
|
|
977
|
+
proofAnchoring,
|
|
978
|
+
auditRetentionDays,
|
|
979
|
+
consentModel,
|
|
980
|
+
escalationMode,
|
|
981
|
+
dataResidency: ctx.dataResidency,
|
|
982
|
+
externalServicesAllowed,
|
|
983
|
+
minimumTrustLevel,
|
|
984
|
+
conformityAssessmentRequired: hasJurisdiction(ctx, 'EU'),
|
|
985
|
+
transparencyRequired: hasJurisdiction(ctx, 'EU', 'CA', 'UK'),
|
|
986
|
+
metadata: {},
|
|
987
|
+
};
|
|
988
|
+
logger.info({ regimeId, name, cryptoSuite, minimumTrustLevel }, 'Governance regime assembled');
|
|
989
|
+
return regime;
|
|
990
|
+
}
|
|
991
|
+
resolveCryptoSuite(ctx, ps) {
|
|
992
|
+
const pv = extractPolicyValue(ps, 'crypto');
|
|
993
|
+
const defaults = ['standard'];
|
|
994
|
+
if (hasJurisdiction(ctx, 'US'))
|
|
995
|
+
defaults.push('fips-140-2');
|
|
996
|
+
return pickStrictest(defaults, pv, CRYPTO_SUITE_STRICTNESS);
|
|
997
|
+
}
|
|
998
|
+
resolveProofAnchoring(ctx, ps) {
|
|
999
|
+
const pv = extractPolicyValue(ps, 'proof-anchoring');
|
|
1000
|
+
const defaults = ['database'];
|
|
1001
|
+
if (hasJurisdiction(ctx, 'US'))
|
|
1002
|
+
defaults.push('tsa-rfc3161');
|
|
1003
|
+
else if (hasJurisdiction(ctx, 'EU'))
|
|
1004
|
+
defaults.push('merkle-tree');
|
|
1005
|
+
return pickStrictest(defaults, pv, PROOF_ANCHORING_STRICTNESS);
|
|
1006
|
+
}
|
|
1007
|
+
resolveConsentModel(ctx, ps) {
|
|
1008
|
+
const pv = extractPolicyValue(ps, 'consent');
|
|
1009
|
+
const defaults = ['implicit'];
|
|
1010
|
+
if (hasJurisdiction(ctx, 'EU', 'UK'))
|
|
1011
|
+
defaults.push('explicit-granular');
|
|
1012
|
+
else if (hasJurisdiction(ctx, 'CA'))
|
|
1013
|
+
defaults.push('opt-in');
|
|
1014
|
+
else if (hasJurisdiction(ctx, 'US'))
|
|
1015
|
+
defaults.push('opt-out');
|
|
1016
|
+
return pickStrictest(defaults, pv, CONSENT_STRICTNESS);
|
|
1017
|
+
}
|
|
1018
|
+
resolveEscalationMode(ctx, ps) {
|
|
1019
|
+
const pv = extractPolicyValue(ps, 'escalation');
|
|
1020
|
+
const defaults = ['flag-review'];
|
|
1021
|
+
if (hasJurisdiction(ctx, 'US', 'EU'))
|
|
1022
|
+
defaults.push('block-escalate');
|
|
1023
|
+
return pickStrictest(defaults, pv, ESCALATION_STRICTNESS);
|
|
1024
|
+
}
|
|
1025
|
+
resolveAuditRetentionDays(ctx, ps) {
|
|
1026
|
+
const pv = extractPolicyValue(ps, 'retention');
|
|
1027
|
+
const defaults = [365];
|
|
1028
|
+
if (hasJurisdiction(ctx, 'EU', 'UK'))
|
|
1029
|
+
defaults.push(1825);
|
|
1030
|
+
if (hasJurisdiction(ctx, 'US'))
|
|
1031
|
+
defaults.push(2555);
|
|
1032
|
+
return Math.max(...(pv !== undefined ? [...defaults, pv] : defaults));
|
|
1033
|
+
}
|
|
1034
|
+
resolveExternalServicesAllowed(ctx, ps) {
|
|
1035
|
+
const pv = extractPolicyValue(ps, 'external-services');
|
|
1036
|
+
return pv !== undefined ? pv : true;
|
|
1037
|
+
}
|
|
1038
|
+
resolveMinimumTrustLevel(ctx, ps) {
|
|
1039
|
+
const pv = extractPolicyValue(ps, 'trust-level');
|
|
1040
|
+
const defaults = [2];
|
|
1041
|
+
if (hasJurisdiction(ctx, 'EU', 'US', 'UK', 'CA'))
|
|
1042
|
+
defaults.push(3);
|
|
1043
|
+
return Math.max(...(pv !== undefined ? [...defaults, pv] : defaults));
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Generate a deterministic regime ID by hashing the canonical parameter set.
|
|
1047
|
+
* Same parameters always produce the same ID.
|
|
1048
|
+
*/
|
|
1049
|
+
generateRegimeId(params) {
|
|
1050
|
+
const canonical = {
|
|
1051
|
+
auditRetentionDays: params.auditRetentionDays,
|
|
1052
|
+
consentModel: params.consentModel,
|
|
1053
|
+
cryptoSuite: params.cryptoSuite,
|
|
1054
|
+
dataResidency: params.dataResidency,
|
|
1055
|
+
escalationMode: params.escalationMode,
|
|
1056
|
+
externalServicesAllowed: params.externalServicesAllowed,
|
|
1057
|
+
jurisdictions: [...params.jurisdictions].sort(),
|
|
1058
|
+
minimumTrustLevel: params.minimumTrustLevel,
|
|
1059
|
+
proofAnchoring: params.proofAnchoring,
|
|
1060
|
+
};
|
|
1061
|
+
// Simple hash for deterministic ID generation
|
|
1062
|
+
const str = JSON.stringify(canonical);
|
|
1063
|
+
let hash = 0;
|
|
1064
|
+
for (let i = 0; i < str.length; i++) {
|
|
1065
|
+
const char = str.charCodeAt(i);
|
|
1066
|
+
hash = ((hash << 5) - hash) + char;
|
|
1067
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
1068
|
+
}
|
|
1069
|
+
return `regime-${Math.abs(hash).toString(16).padStart(8, '0')}`;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
// ---------------------------------------------------------------------------
|
|
1073
|
+
// Gateway Conflict Error
|
|
1074
|
+
// ---------------------------------------------------------------------------
|
|
1075
|
+
/**
|
|
1076
|
+
* Thrown when the gateway encounters unresolved policy conflicts that
|
|
1077
|
+
* block intent processing (only when blockOnConflicts is enabled).
|
|
1078
|
+
*/
|
|
1079
|
+
export class GatewayConflictError extends Error {
|
|
1080
|
+
conflicts;
|
|
1081
|
+
constructor(conflicts) {
|
|
1082
|
+
super('Intent blocked by unresolved policy conflicts: ' +
|
|
1083
|
+
conflicts.map(c => c.description).join('; '));
|
|
1084
|
+
this.name = 'GatewayConflictError';
|
|
1085
|
+
this.conflicts = conflicts;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
// ---------------------------------------------------------------------------
|
|
1089
|
+
// Intent Gateway (Orchestrator)
|
|
1090
|
+
// ---------------------------------------------------------------------------
|
|
1091
|
+
/** EU/EEA jurisdiction codes that trigger AI Act classification. */
|
|
1092
|
+
const EU_JURISDICTION_CODES = new Set(['EU']);
|
|
1093
|
+
/**
|
|
1094
|
+
* The Intent Gateway is the policy-aware orchestrator for all agent intents.
|
|
1095
|
+
*
|
|
1096
|
+
* It implements the full governance pipeline:
|
|
1097
|
+
* 1. **Intake** - Receives the intent submission and options
|
|
1098
|
+
* 2. **Jurisdiction Resolution** - Determines applicable jurisdictions
|
|
1099
|
+
* 3. **Policy Composition** - Merges applicable policy bundles with conflict detection
|
|
1100
|
+
* 4. **AI Act Classification** - Classifies EU-bound intents against the AI Act
|
|
1101
|
+
* 5. **Regime Selection** - Assembles the governance regime
|
|
1102
|
+
* 6. **Enriched Submit** - Submits with regime metadata and enforced trust levels
|
|
1103
|
+
*
|
|
1104
|
+
* On error, the gateway degrades to passthrough mode (never drops intents).
|
|
1105
|
+
*
|
|
1106
|
+
* @example
|
|
1107
|
+
* ```typescript
|
|
1108
|
+
* const gateway = new IntentGateway(intentService, { enabled: true });
|
|
1109
|
+
*
|
|
1110
|
+
* // Register tenant-specific jurisdiction
|
|
1111
|
+
* gateway.registerTenantConfig('tenant-acme', {
|
|
1112
|
+
* jurisdictions: ['EU'],
|
|
1113
|
+
* industry: 'finance',
|
|
1114
|
+
* });
|
|
1115
|
+
*
|
|
1116
|
+
* // Dispatch an intent through the governance pipeline
|
|
1117
|
+
* const result = await gateway.dispatch(
|
|
1118
|
+
* { goal: 'Analyze customer data', entityId: 'agent-1' },
|
|
1119
|
+
* { ctx: { tenantId: 'tenant-acme' } },
|
|
1120
|
+
* );
|
|
1121
|
+
*
|
|
1122
|
+
* console.log(result.regime.name); // "EU-finance"
|
|
1123
|
+
* console.log(result.regime.consentModel); // "explicit-granular"
|
|
1124
|
+
* console.log(result.regime.aiActClassification); // classification result
|
|
1125
|
+
* ```
|
|
1126
|
+
*/
|
|
1127
|
+
export class IntentGateway {
|
|
1128
|
+
intentService;
|
|
1129
|
+
config;
|
|
1130
|
+
jurisdictionResolver;
|
|
1131
|
+
policyComposer;
|
|
1132
|
+
regimeSelector;
|
|
1133
|
+
aiActClassifier;
|
|
1134
|
+
constructor(intentService, config) {
|
|
1135
|
+
this.intentService = intentService;
|
|
1136
|
+
this.config = { ...DEFAULT_GATEWAY_CONFIG, ...config };
|
|
1137
|
+
this.jurisdictionResolver = new JurisdictionResolver(this.config);
|
|
1138
|
+
this.policyComposer = new PolicyComposer();
|
|
1139
|
+
this.regimeSelector = new RegimeSelector();
|
|
1140
|
+
this.aiActClassifier = new AiActClassifier();
|
|
1141
|
+
logger.info({ enabled: this.config.enabled, defaultJurisdiction: this.config.defaultJurisdiction }, 'IntentGateway initialized');
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Dispatch an intent through the full governance pipeline.
|
|
1145
|
+
*
|
|
1146
|
+
* When the gateway is disabled, intents pass through directly.
|
|
1147
|
+
* When enabled, the intent is enriched with jurisdiction, policy, and
|
|
1148
|
+
* regime metadata before submission. The trust level is enforced to
|
|
1149
|
+
* meet the regime's minimum.
|
|
1150
|
+
*
|
|
1151
|
+
* On unexpected errors, the gateway falls through to passthrough mode
|
|
1152
|
+
* with a degradation warning (never silently fails).
|
|
1153
|
+
*/
|
|
1154
|
+
async dispatch(submission, options) {
|
|
1155
|
+
// Passthrough when disabled
|
|
1156
|
+
if (!this.config.enabled) {
|
|
1157
|
+
const intent = await this.intentService.submit(submission, options);
|
|
1158
|
+
return this.createPassthroughResult(intent);
|
|
1159
|
+
}
|
|
1160
|
+
try {
|
|
1161
|
+
// Step 1: Resolve jurisdiction
|
|
1162
|
+
const intentMetadata = submission.context;
|
|
1163
|
+
const jurisdictionContext = this.jurisdictionResolver.resolve(options.ctx, intentMetadata);
|
|
1164
|
+
const tenantId = options.ctx.tenantId;
|
|
1165
|
+
// Step 2: Compose policies
|
|
1166
|
+
const tenantConfig = this.jurisdictionResolver.getTenantConfig(tenantId);
|
|
1167
|
+
const policySet = this.policyComposer.compose(jurisdictionContext, tenantConfig?.customPolicyBundles);
|
|
1168
|
+
// Check for blocking conflicts
|
|
1169
|
+
const warnings = [];
|
|
1170
|
+
if (policySet.unresolvedConflicts.length > 0) {
|
|
1171
|
+
if (this.config.blockOnConflicts && !policySet.isValid) {
|
|
1172
|
+
throw new GatewayConflictError(policySet.unresolvedConflicts);
|
|
1173
|
+
}
|
|
1174
|
+
for (const conflict of policySet.unresolvedConflicts) {
|
|
1175
|
+
warnings.push(`Unresolved policy conflict: ${conflict.description}`);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
// Step 3: EU AI Act classification (when EU jurisdiction applies)
|
|
1179
|
+
let aiActResult;
|
|
1180
|
+
if (jurisdictionContext.primaryJurisdictions.some(j => EU_JURISDICTION_CODES.has(j))) {
|
|
1181
|
+
const goal = typeof submission.goal === 'string' ? submission.goal : '';
|
|
1182
|
+
const intentType = typeof submission.intentType === 'string' ? submission.intentType : undefined;
|
|
1183
|
+
aiActResult = this.aiActClassifier.classify(goal, intentMetadata, intentType);
|
|
1184
|
+
if (aiActResult.classification === 'unacceptable') {
|
|
1185
|
+
warnings.push(`EU AI Act: PROHIBITED - ${aiActResult.reasoning}`);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
// Step 4: Select governance regime
|
|
1189
|
+
const regime = this.regimeSelector.select(jurisdictionContext, policySet);
|
|
1190
|
+
// Attach AI Act classification to regime
|
|
1191
|
+
if (aiActResult) {
|
|
1192
|
+
regime.aiActClassification = aiActResult.classification;
|
|
1193
|
+
regime.aiActHighRiskCategory = aiActResult.highRiskCategory;
|
|
1194
|
+
}
|
|
1195
|
+
// Step 5: Enrich submit options with regime metadata
|
|
1196
|
+
const enrichedOptions = {
|
|
1197
|
+
...options,
|
|
1198
|
+
trustSnapshot: {
|
|
1199
|
+
...(options.trustSnapshot ?? {}),
|
|
1200
|
+
__governanceRegime: {
|
|
1201
|
+
regimeId: regime.regimeId,
|
|
1202
|
+
name: regime.name,
|
|
1203
|
+
jurisdictions: regime.jurisdictions,
|
|
1204
|
+
cryptoSuite: regime.cryptoSuite,
|
|
1205
|
+
minimumTrustLevel: regime.minimumTrustLevel,
|
|
1206
|
+
aiActClassification: regime.aiActClassification,
|
|
1207
|
+
conformityAssessmentRequired: regime.conformityAssessmentRequired,
|
|
1208
|
+
},
|
|
1209
|
+
},
|
|
1210
|
+
};
|
|
1211
|
+
// Enforce minimum trust level
|
|
1212
|
+
if (!enrichedOptions.trustLevel || enrichedOptions.trustLevel < regime.minimumTrustLevel) {
|
|
1213
|
+
enrichedOptions.trustLevel = regime.minimumTrustLevel;
|
|
1214
|
+
}
|
|
1215
|
+
// Log regime decision
|
|
1216
|
+
if (this.config.logRegimeDecisions) {
|
|
1217
|
+
logger.info({
|
|
1218
|
+
regimeId: regime.regimeId,
|
|
1219
|
+
name: regime.name,
|
|
1220
|
+
tenantId,
|
|
1221
|
+
cryptoSuite: regime.cryptoSuite,
|
|
1222
|
+
minimumTrustLevel: regime.minimumTrustLevel,
|
|
1223
|
+
aiActClassification: regime.aiActClassification,
|
|
1224
|
+
bundles: policySet.sourceBundles,
|
|
1225
|
+
}, 'Gateway regime decision');
|
|
1226
|
+
}
|
|
1227
|
+
// Submit the intent
|
|
1228
|
+
const intent = await this.intentService.submit(submission, enrichedOptions);
|
|
1229
|
+
return { intent, regime, jurisdictionContext, policySet, warnings };
|
|
1230
|
+
}
|
|
1231
|
+
catch (error) {
|
|
1232
|
+
// Re-throw conflict errors (intentional blocks)
|
|
1233
|
+
if (error instanceof GatewayConflictError)
|
|
1234
|
+
throw error;
|
|
1235
|
+
// Degrade gracefully: submit without governance enrichment
|
|
1236
|
+
logger.error({ error: error instanceof Error ? error.message : 'Unknown error' }, 'Gateway error - falling through to passthrough');
|
|
1237
|
+
const intent = await this.intentService.submit(submission, options);
|
|
1238
|
+
const result = this.createPassthroughResult(intent);
|
|
1239
|
+
result.warnings.push(`Gateway degraded: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
1240
|
+
return result;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Resolve the governance regime for a tenant context without submitting
|
|
1245
|
+
* an intent. Useful for pre-flight checks and UI display.
|
|
1246
|
+
*/
|
|
1247
|
+
resolveRegime(ctx, metadata) {
|
|
1248
|
+
const jurisdictionContext = this.jurisdictionResolver.resolve(ctx, metadata);
|
|
1249
|
+
const tenantConfig = this.jurisdictionResolver.getTenantConfig(ctx.tenantId);
|
|
1250
|
+
const policySet = this.policyComposer.compose(jurisdictionContext, tenantConfig?.customPolicyBundles);
|
|
1251
|
+
const regime = this.regimeSelector.select(jurisdictionContext, policySet);
|
|
1252
|
+
return { regime, jurisdictionContext, policySet };
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Register a tenant's jurisdiction configuration.
|
|
1256
|
+
* This is the primary way to tell the gateway where a tenant operates.
|
|
1257
|
+
*/
|
|
1258
|
+
registerTenantConfig(tenantId, config) {
|
|
1259
|
+
this.jurisdictionResolver.registerTenantConfig(tenantId, config);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Retrieve the current gateway configuration (read-only).
|
|
1263
|
+
*/
|
|
1264
|
+
getConfig() {
|
|
1265
|
+
return { ...this.config };
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Access the underlying intent service.
|
|
1269
|
+
*/
|
|
1270
|
+
getIntentService() {
|
|
1271
|
+
return this.intentService;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Create a passthrough result (used when gateway is disabled or on error).
|
|
1275
|
+
*/
|
|
1276
|
+
createPassthroughResult(intent) {
|
|
1277
|
+
return {
|
|
1278
|
+
intent,
|
|
1279
|
+
regime: {
|
|
1280
|
+
regimeId: 'regime-passthrough',
|
|
1281
|
+
name: 'passthrough',
|
|
1282
|
+
jurisdictions: [this.config.defaultJurisdiction],
|
|
1283
|
+
policyNamespaces: [],
|
|
1284
|
+
cryptoSuite: 'standard',
|
|
1285
|
+
proofAnchoring: 'database',
|
|
1286
|
+
auditRetentionDays: 365,
|
|
1287
|
+
consentModel: 'implicit',
|
|
1288
|
+
escalationMode: 'flag-review',
|
|
1289
|
+
dataResidency: 'global',
|
|
1290
|
+
externalServicesAllowed: true,
|
|
1291
|
+
minimumTrustLevel: 2,
|
|
1292
|
+
conformityAssessmentRequired: false,
|
|
1293
|
+
transparencyRequired: false,
|
|
1294
|
+
metadata: {},
|
|
1295
|
+
},
|
|
1296
|
+
jurisdictionContext: {
|
|
1297
|
+
primaryJurisdictions: [this.config.defaultJurisdiction],
|
|
1298
|
+
industry: this.config.defaultIndustry,
|
|
1299
|
+
dataResidency: 'global',
|
|
1300
|
+
crossBorderTransfer: false,
|
|
1301
|
+
source: 'default',
|
|
1302
|
+
},
|
|
1303
|
+
policySet: {
|
|
1304
|
+
constraints: [],
|
|
1305
|
+
sourceBundles: [],
|
|
1306
|
+
resolvedConflicts: [],
|
|
1307
|
+
unresolvedConflicts: [],
|
|
1308
|
+
isValid: true,
|
|
1309
|
+
composedAt: Date.now(),
|
|
1310
|
+
},
|
|
1311
|
+
warnings: [],
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
// ---------------------------------------------------------------------------
|
|
1316
|
+
// Factory
|
|
1317
|
+
// ---------------------------------------------------------------------------
|
|
1318
|
+
/**
|
|
1319
|
+
* Create an IntentGateway instance with the given service and configuration.
|
|
1320
|
+
*
|
|
1321
|
+
* @example
|
|
1322
|
+
* ```typescript
|
|
1323
|
+
* const gateway = createIntentGateway(myIntentService, {
|
|
1324
|
+
* defaultJurisdiction: 'EU',
|
|
1325
|
+
* blockOnConflicts: true,
|
|
1326
|
+
* });
|
|
1327
|
+
* ```
|
|
1328
|
+
*/
|
|
1329
|
+
export function createIntentGateway(intentService, config) {
|
|
1330
|
+
return new IntentGateway(intentService, config);
|
|
1331
|
+
}
|
|
1332
|
+
//# sourceMappingURL=index.js.map
|