Initial commit: AS4/411 directory and discovery service for Sankofa Marketplace
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
0
docs/architecture/.gitkeep
Normal file
0
docs/architecture/.gitkeep
Normal file
54
docs/architecture/cbdc-settlement-adapter.md
Normal file
54
docs/architecture/cbdc-settlement-adapter.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# CBDC settlement adapter (design)
|
||||
|
||||
ISO 20022 remains the **instruction layer**; token/CBDC rails provide the **settlement layer**. as4-411 does not perform settlement; it may expose routing and settlement-rail metadata so that a **settlement adapter** (outside the core directory) can choose and invoke the correct settlement channel.
|
||||
|
||||
---
|
||||
|
||||
## Model
|
||||
|
||||
- **Instruction layer:** ISO 20022 messages (e.g. pacs.008, pacs.009) over AS4; unchanged.
|
||||
- **Settlement layer:** One of RTGS | CBDC ledger | tokenized deposit. The directory can store a **settlement rail** capability per participant or endpoint (or per routing artifact).
|
||||
- **Settlement adapter:** A component (gateway-side or separate service) that receives the resolved directive plus an instruction reference, and performs or triggers settlement on the appropriate rail. It is **outside** as4-411 core.
|
||||
|
||||
---
|
||||
|
||||
## Directory extensions
|
||||
|
||||
- **Optional capability or metadata:** e.g. `settlement_rail` = `RTGS` | `CBDC` | `tokenized_deposit`.
|
||||
- **Optional wallet/DLT endpoint:** For CBDC, the directory may store a wallet or DLT endpoint (or reference) per participant; as4-411 resolves PartyId → AS4 endpoint (unchanged) and may optionally return `settlement_rail` and `wallet_endpoint` (or equivalent) in the directive or in extended metadata for the settlement adapter to use.
|
||||
- **RouteDirective extension:** See [route-directive.md](route-directive.md). Optional fields: `settlement_rail`, `wallet_endpoint` (or `settlement_endpoint`). Not required for MVP; add when CBDC/tokenized flows are in scope.
|
||||
|
||||
---
|
||||
|
||||
## Dual-track processing
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
ISO["ISO 20022 instruction"]
|
||||
AS4["AS4 transport"]
|
||||
Dir["as4-411 directory"]
|
||||
Adapter["Settlement adapter"]
|
||||
RTGS["RTGS"]
|
||||
CBDC["CBDC ledger"]
|
||||
ISO --> AS4
|
||||
Dir -->|"endpoint + settlement_rail"| Adapter
|
||||
AS4 --> Adapter
|
||||
Adapter --> RTGS
|
||||
Adapter --> CBDC
|
||||
```
|
||||
|
||||
1. Sender resolves PartyId via as4-411 → gets AS4 endpoint and optionally settlement_rail (and wallet/DLT endpoint if stored).
|
||||
2. Sender sends ISO 20022 over AS4 to receiver.
|
||||
3. Receiver (or a settlement adapter) uses the instruction plus optional settlement_rail / wallet_endpoint from directory to choose: settle via RTGS or via CBDC/tokenized ledger.
|
||||
|
||||
---
|
||||
|
||||
## Settlement adapter contract (minimal)
|
||||
|
||||
A **settlement adapter** is a component that:
|
||||
|
||||
- **Input:** Resolved RouteDirective (or equivalent), instruction reference (e.g. message id, business id), and optionally payload or reference to the ISO 20022 instruction.
|
||||
- **Output:** Settlement result or callback (e.g. accepted, rejected, pending). Format is out of scope of as4-411; defined by the gateway or scheme.
|
||||
- **Responsibility:** Map directive + instruction to the correct rail (RTGS, CBDC, tokenized deposit) and invoke the appropriate settlement API or ledger.
|
||||
|
||||
as4-411 does **not** implement this interface; it only provides routing directives and, when extended, optional settlement_rail and wallet_endpoint so that an external adapter can be implemented. No full implementation of a CBDC settlement adapter is required in this add-on; a stub or placeholder may be added in packages/connectors or packages/core for tests if desired.
|
||||
40
docs/architecture/connectors.md
Normal file
40
docs/architecture/connectors.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Connector Specifications
|
||||
|
||||
This document describes ingest formats and behaviors for directory connectors. Each connector pulls or receives data from an external source and maps it into the core directory model (participants, identifiers, endpoints, capabilities, credentials, policies). **Trust, caching, and resilience:** see [ADR-005](../adr/005-connector-trust-and-caching.md). Each connector must define: trust anchors and signature validation; cache TTL and refresh (with jitter); timeouts, retries, circuit-breaker; and data provenance tagging (source, last_verified, confidence).
|
||||
|
||||
## SMP/SML (PEPPOL)
|
||||
|
||||
- **Source:** SML (Service Metadata Locator) for participant ID → SMP URL; SMP (Service Metadata Publisher) for document/process and endpoint + certificate.
|
||||
- **Ingest:** Resolve participant ID via SML, fetch SMP metadata, map to:
|
||||
- One participant per PEPPOL participant ID.
|
||||
- Identifiers: `peppol.participantId`, optional `peppol.documentTypeId` / `peppol.processId`.
|
||||
- Endpoints: HTTPS URL + transport profile (e.g. AS4).
|
||||
- Credentials: certificate reference (fingerprint, validity); store only ref or fingerprint, not private key.
|
||||
- **Refresh:** On-demand or periodic TTL; cache in directory for resilience. Evidence fields: `source: "smp"`, `lastVerified`, `confidenceScore`.
|
||||
- **Trust (SMP/SML):** TLS and optional payload signing; document which CAs or pins are accepted. On SMP/SML failure, fall back to cached data only; do not serve stale beyond a configured max stale window.
|
||||
|
||||
## SS7 (GTT / Point Code)
|
||||
|
||||
- **Source:** GTT (Global Title Translation) tables, point code routing tables, optional number portability/range feeds.
|
||||
- **Ingest:** Map E.164/GT → PC/SSN (and translation type) into directory or into **routing artifacts** (see data model and resolution algorithm). Participants may represent nodes or ranges; endpoints carry `protocol: ss7` and address as PC/SSN or route set reference.
|
||||
- **Format:** Vendor-specific (CSV, JSON, or proprietary); connector normalizes to internal graph edges and artifact payloads. Tag all edges with provenance and validity; SS7 mapping is only as good as ingested sources (no implied authority).
|
||||
|
||||
## File / GitOps
|
||||
|
||||
- **Source:** File system or Git repo (YAML/JSON). Used for BIN tables, participant maps, and signed routing artifact bundles.
|
||||
- **Ingest:**
|
||||
- **BIN tables:** CSV or JSON with BIN range, brand, region, routing target, optional tenant override; stored as `routing_artifacts` with `artifact_type: bin_table`.
|
||||
- **Participant/endpoint config:** YAML or JSON matching directory schema; validate and apply via Admin API or direct store writes.
|
||||
- **Signed artifacts:** Payload + signature/fingerprint, `effective_from`/`effective_to`; validate and persist as routing artifacts.
|
||||
- **Refresh:** Watch file or webhook; re-ingest on change. Optional version tags for rollback.
|
||||
|
||||
## KTT (Placeholder)
|
||||
|
||||
- **Source:** TBD per sector. Placeholder connector supports file + API ingest stubs.
|
||||
- **Identifier types:** `ktt.*`; see [protocols/ktt.md](../protocols/ktt.md) when defined.
|
||||
|
||||
## Common Requirements
|
||||
|
||||
- All connectors must map into the same core entities; no rail-specific tables for “directory” data beyond optional routing_artifacts.
|
||||
- Credentials: only references (vault_ref, fingerprint); never private keys.
|
||||
- Audit: log ingest runs and failures; optional hash-chain for artifact integrity.
|
||||
222
docs/architecture/data-model.md
Normal file
222
docs/architecture/data-model.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Data Model
|
||||
|
||||
Canonical persistence model for the as4-411 directory. Aligned with [OpenAPI schemas](../api/openapi.yaml) and the [resolution algorithm](resolution-algorithm.md).
|
||||
|
||||
## Tables (relational baseline)
|
||||
|
||||
### tenants
|
||||
|
||||
Multi-tenant isolation. All participant data is scoped by tenant.
|
||||
|
||||
| Column | Type | Description |
|
||||
| ---------- | --------- | ----------------- |
|
||||
| id | PK | Tenant identifier |
|
||||
| name | string | Display name |
|
||||
| created_at | timestamp | |
|
||||
| updated_at | timestamp | |
|
||||
|
||||
### participants
|
||||
|
||||
Logical entity capable of sending/receiving messages (organization, node, service).
|
||||
|
||||
| Column | Type | Description |
|
||||
| ---------- | --------- | ---------------------- |
|
||||
| id | PK | Participant identifier |
|
||||
| tenant_id | FK | References tenants |
|
||||
| name | string | Display name |
|
||||
| created_at | timestamp | |
|
||||
| updated_at | timestamp | |
|
||||
|
||||
### identifiers
|
||||
|
||||
Typed identifier bound to a participant. Used for resolution lookup and cross-mapping.
|
||||
|
||||
| Column | Type | Description |
|
||||
| --------------- | --------- | --------------------------------------------- |
|
||||
| id | PK | |
|
||||
| participant_id | FK | References participants |
|
||||
| identifier_type | string | See [Identifier types](#identifier-types) |
|
||||
| value | string | Identifier value |
|
||||
| scope | string | Optional scope (e.g. scheme) |
|
||||
| priority | integer | Resolution priority (higher = preferred) |
|
||||
| verified_at | timestamp | When value was last verified (e.g. SMP fetch) |
|
||||
|
||||
### endpoints
|
||||
|
||||
Routable address for a protocol domain (HTTPS URL, MLLP, MQ queue, SS7 point code, etc.).
|
||||
|
||||
| Column | Type | Description |
|
||||
| -------------- | ------- | --------------------------------------------------- |
|
||||
| id | PK | |
|
||||
| participant_id | FK | References participants |
|
||||
| protocol | string | as4, ss7, smp, http, mq, etc. |
|
||||
| address | string | Protocol-specific address (URL, PC/SSN, queue name) |
|
||||
| profile | string | Transport profile (e.g. AS4 profile name) |
|
||||
| priority | integer | Ranking for resolution |
|
||||
| status | string | active, inactive, draining |
|
||||
|
||||
### capabilities
|
||||
|
||||
Supported services/actions/processes/document types and constraints.
|
||||
|
||||
| Column | Type | Description |
|
||||
| ---------------- | ------ | ----------------------- |
|
||||
| id | PK | |
|
||||
| participant_id | FK | References participants |
|
||||
| service | string | e.g. AS4 service value |
|
||||
| action | string | e.g. AS4 action |
|
||||
| process | string | e.g. PEPPOL process ID |
|
||||
| document_type | string | e.g. document type ID |
|
||||
| constraints_json | JSON | Additional constraints |
|
||||
|
||||
### credentials
|
||||
|
||||
References to key material in vault/KMS. No private keys stored in DB.
|
||||
|
||||
| Column | Type | Description |
|
||||
| --------------- | --------- | --------------------------- |
|
||||
| id | PK | |
|
||||
| participant_id | FK | References participants |
|
||||
| credential_type | string | tls, sign, encrypt |
|
||||
| vault_ref | string | URI to vault/KMS |
|
||||
| fingerprint | string | Certificate/key fingerprint |
|
||||
| valid_from | timestamp | |
|
||||
| valid_to | timestamp | |
|
||||
|
||||
### policies
|
||||
|
||||
Rules controlling resolution (tenant scope, trust domains, allow/deny, priority).
|
||||
|
||||
| Column | Type | Description |
|
||||
| --------- | ------- | -------------------- |
|
||||
| id | PK | |
|
||||
| tenant_id | FK | References tenants |
|
||||
| rule_json | JSON | ABAC rule definition |
|
||||
| effect | string | allow, deny |
|
||||
| priority | integer | Evaluation order |
|
||||
|
||||
### audit_log
|
||||
|
||||
Append-only audit trail for all modifications. Optional hash-chain for tamper-evidence.
|
||||
|
||||
| Column | Type | Description |
|
||||
| ----------- | --------- | ------------------------------------- |
|
||||
| id | PK | |
|
||||
| at | timestamp | |
|
||||
| actor | string | Who made the change |
|
||||
| action | string | create, update, delete |
|
||||
| resource | string | tenants, participants, etc. |
|
||||
| resource_id | string | |
|
||||
| payload | JSON | Before/after or diff |
|
||||
| hash_prev | string | Optional: previous row hash for chain |
|
||||
|
||||
## Relationships
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
tenants ||--o{ participants : "has"
|
||||
participants ||--o{ identifiers : "has"
|
||||
participants ||--o{ endpoints : "has"
|
||||
participants ||--o{ capabilities : "has"
|
||||
participants ||--o{ credentials : "has"
|
||||
tenants ||--o{ policies : "has"
|
||||
participants {
|
||||
string id PK
|
||||
string tenant_id FK
|
||||
string name
|
||||
}
|
||||
identifiers {
|
||||
string id PK
|
||||
string participant_id FK
|
||||
string identifier_type
|
||||
string value
|
||||
int priority
|
||||
}
|
||||
endpoints {
|
||||
string id PK
|
||||
string participant_id FK
|
||||
string protocol
|
||||
string address
|
||||
string status
|
||||
}
|
||||
```
|
||||
|
||||
- **Tenant** scopes all participants and policies.
|
||||
- **Participant** has many identifiers, endpoints, capabilities, and credential refs.
|
||||
- Resolution uses identifiers to find participants, then endpoints + capabilities + policies to produce directives.
|
||||
|
||||
## Graph layer (edges)
|
||||
|
||||
Cross-mapping and provenance are modeled as an explicit graph. An **edges** table (or equivalent) represents relationships with provenance and validity.
|
||||
|
||||
### edges
|
||||
|
||||
| Column | Type | Description |
|
||||
| ----------- | --------- | ------------------------------------------ |
|
||||
| id | PK | |
|
||||
| from_type | string | Entity type (identifier, participant, etc.) |
|
||||
| from_id | string | Source entity id |
|
||||
| to_type | string | Target entity type |
|
||||
| to_id | string | Target entity id |
|
||||
| relation | string | Relation kind (e.g. resolves_to, has_endpoint) |
|
||||
| confidence | number | 0–1 confidence score |
|
||||
| source | string | Provenance (internal, smp, gtt_feed, etc.) |
|
||||
| valid_from | timestamp | |
|
||||
| valid_to | timestamp | Optional |
|
||||
|
||||
Relationship types: identifier → participant (resolves_to), participant → endpoint (has_endpoint), participant → capability (has_capability). When two sources give different data for the same logical edge, conflict resolution applies.
|
||||
|
||||
### Conflict resolution (deterministic)
|
||||
|
||||
When multiple candidates or edges exist, apply in order:
|
||||
|
||||
1. **Explicit priority** (from endpoint/identifier priority column)
|
||||
2. **Policy** (allow/deny and policy priority)
|
||||
3. **Freshness** (updated_at / verified_at / valid_from)
|
||||
4. **Confidence** (edge or evidence confidence score)
|
||||
5. **Lexical** (stable sort by id)
|
||||
|
||||
Documented in [resolution-algorithm.md](resolution-algorithm.md).
|
||||
|
||||
## RouteDirective (output schema)
|
||||
|
||||
Normalized object returned by the resolver. Must match [OpenAPI RouteDirective](../api/openapi.yaml#components/schemas/RouteDirective).
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----------------- | ------ | -------------------------------------------------- |
|
||||
| target_protocol | string | e.g. as4, ss7 |
|
||||
| target_address | string | Endpoint address (URL, PC/SSN, etc.) |
|
||||
| transport_profile | string | Profile name |
|
||||
| security | object | signRequired, encryptRequired, keyRefs, algorithms |
|
||||
| service_context | object | service, action, or SS7 service indicator |
|
||||
| qos | object | retries, receiptsRequired, ordering |
|
||||
| ttl_seconds | int | Cache TTL for this directive |
|
||||
| evidence | object | source, lastVerified, confidenceScore |
|
||||
|
||||
## Identifier types
|
||||
|
||||
Reference values for `identifier_type` and resolution input. See protocol domains in the master plan.
|
||||
|
||||
### AS4 domain
|
||||
|
||||
- `as4.partyId` (with optional partyIdType in scope)
|
||||
- `as4.role` (initiator/responder)
|
||||
- `as4.service`, `as4.action`, `as4.mpc`
|
||||
|
||||
For FI-to-FI, PartyId type is often BIC or LEI (see [iso20022-over-as4](../protocols/iso20022-over-as4.md)).
|
||||
|
||||
### PEPPOL / SMP
|
||||
|
||||
- `peppol.participantId`
|
||||
- `peppol.documentTypeId`
|
||||
- `peppol.processId`
|
||||
|
||||
### SS7 domain
|
||||
|
||||
- `e164` (MSISDN, E.164 format)
|
||||
- `gt` (Global Title)
|
||||
- `pc` (Point Code)
|
||||
- `ssn` (Subsystem Number)
|
||||
- `mccmnc` (mobile network identifiers where relevant)
|
||||
|
||||
Cross-mapping examples: `as4.partyId: "0088:123456789"` ↔ `peppol.participantId: "0088:123456789"`; `e164` ↔ `gt` ↔ `pc/ssn` via GTT.
|
||||
77
docs/architecture/resolution-algorithm.md
Normal file
77
docs/architecture/resolution-algorithm.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Resolution Algorithm
|
||||
|
||||
Deterministic resolution pipeline that produces ordered routing directives. Input/output contracts are defined in the [OpenAPI spec](../api/openapi.yaml); persistence shape is in the [data model](data-model.md).
|
||||
|
||||
## Precedence ladder (per rail)
|
||||
|
||||
When multiple sources can contribute a directive, apply this order. The first successful source wins unless overridden by tenant/contract config:
|
||||
|
||||
1. **Tenant override** — Tenant-specific routing artifact or endpoint override.
|
||||
2. **Contract-specific config** — Contract or connectivity-group mapping.
|
||||
3. **Internal curated directory** — Participants/endpoints stored in the directory (admin or connector).
|
||||
4. **External authoritative directory** — SMP/SML, GTT feed, or other external source (cached).
|
||||
5. **Fallback heuristics** — Optional, disabled by default (e.g. default route).
|
||||
|
||||
Log and expose **resolution_trace** in the response so callers see which source(s) contributed (e.g. "tenant override", "internal directory", "SMP cache"). See [route-directive.md](route-directive.md).
|
||||
|
||||
**Source-driven mappings (e.g. SS7):** Data from connectors (GTT, NP/range feeds) is only as good as the ingested sources. Expose confidence and `last_verified` in directives; tag edges with provenance. No implied authority—see [connectors.md](connectors.md).
|
||||
|
||||
## Pipeline (steps 1–9)
|
||||
|
||||
1. **Normalize input**
|
||||
Parse and validate all identifiers in the request. Validate formats per type (E.164, PartyId, PC/SSN, etc.). Reject invalid or unsupported types early.
|
||||
|
||||
2. **Expand context**
|
||||
Infer candidate identifier equivalences using the mapping graph (same participant: multiple identifier types pointing to the same participant). Build a set of "equivalent" identifiers for lookup.
|
||||
|
||||
3. **Candidate retrieval**
|
||||
Query the directory store for participants and endpoints matching any of the normalized/expanded identifiers, within the requested tenant and constraints.
|
||||
|
||||
4. **Capability filter**
|
||||
Retain only participants/endpoints whose capabilities match the requested service context (service, action, process, document type) and any constraints in the request. Constraints may include `requiredCapability`, `messageType` (e.g. ISO8583 MTI), and `networkBrand` for card rails.
|
||||
|
||||
5. **Policy filter**
|
||||
Apply tenant-scoped policies (ABAC). Enforce trust domain, geo, compliance, and allow/deny rules. Remove any candidate that is denied or out of scope.
|
||||
|
||||
6. **Score and rank**
|
||||
Score remaining candidates (see [Scoring](#scoring)). Sort by score descending; apply [tie-break rules](#determinism-and-tie-break) for stable ordering.
|
||||
|
||||
7. **Assemble directives**
|
||||
For each ranked candidate, build a `RouteDirective`: map endpoint + participant to `target_protocol`, `target_address`, `transport_profile`, attach security refs (from credentials), `service_context`, `qos`, `ttl_seconds`, and `evidence` (source, lastVerified, confidenceScore).
|
||||
|
||||
8. **Sign response (optional)**
|
||||
In multi-party setups, optionally sign the response for non-repudiation. Not required for MVP.
|
||||
|
||||
9. **Cache**
|
||||
Store result in positive cache (keyed by normalized request + tenant) with TTL. On cache hit, return cached directives and skip steps 2–7. Negative results (no candidates after filters) may be cached with shorter TTL and invalidation hooks.
|
||||
|
||||
## Determinism and tie-break
|
||||
|
||||
- **Invariant:** Same inputs + same store state ⇒ same ordered list of directives.
|
||||
- **Tie-break order** when scores are equal (aligned with [data-model conflict resolution](data-model.md#conflict-resolution-deterministic)):
|
||||
1. **Explicit priority** (endpoint/identifier priority from store) — higher first.
|
||||
2. **Policy** (allow/deny and policy priority).
|
||||
3. **Freshness** (updated_at / verified_at / valid_from).
|
||||
4. **Confidence** (edge or evidence confidence score).
|
||||
5. **Lexical** — stable sort by deterministic key (e.g. participant id + endpoint id).
|
||||
|
||||
Implementation must use a fixed ordering (e.g. sort by `(score DESC, priority DESC, updated_at DESC, id ASC)`).
|
||||
|
||||
## Scoring
|
||||
|
||||
Factors that contribute to the score (combined by weighted sum or ordered rules; exact weights are implementation/config):
|
||||
|
||||
| Factor | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------- |
|
||||
| Explicit priority | From `identifiers.priority` / `endpoints.priority` in the store. |
|
||||
| Endpoint health/status | Prefer `active` over `draining` over `inactive`. |
|
||||
| Freshness/verification | Higher score when `identifiers.verified_at` or evidence is recent. |
|
||||
| Trust domain affinity | Match between requested trust domain and endpoint/participant metadata. |
|
||||
|
||||
Scoring must be deterministic: same inputs and same data ⇒ same scores and thus same order after tie-break.
|
||||
|
||||
## Caching
|
||||
|
||||
- **Positive cache:** Key = hash or canonical form of (normalized identifiers, serviceContext, constraints, tenant). Value = ordered list of directives + TTL. Reuse until TTL expires or explicit invalidation.
|
||||
- **Negative cache:** When no candidates survive filters, cache "no result" with a shorter TTL to avoid thundering herd on missing keys. Invalidation: on participant/identifier/endpoint/policy change for that tenant or key scope.
|
||||
- **Invalidation hooks:** Connectors or admin updates that change participants/endpoints/policies should invalidate affected cache keys (by tenant, participant id, or key prefix). Optional: publish events for external caches.
|
||||
34
docs/architecture/route-directive.md
Normal file
34
docs/architecture/route-directive.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# RouteDirective Contract
|
||||
|
||||
Schema: [../api/route-directive.schema.json](../api/route-directive.schema.json). OpenAPI: [../api/openapi.yaml](../api/openapi.yaml).
|
||||
|
||||
## Response Shape
|
||||
|
||||
- **primary:** One directive (best match). **alternates:** ordered fallback list with optional **reason** per entry.
|
||||
- **directives:** Backward compat: `[primary, ...alternates]`.
|
||||
- **failure_policy:** Optional retry, backoff, circuitBreak.
|
||||
- **evidence[]:** source, freshness, confidence, optional signature (array for multiple sources).
|
||||
- **negative_cache_ttl:** TTL for negative (no-match) cache.
|
||||
- **resolution_trace:** Which source(s) contributed (tenant override, internal directory, SMP cache, etc.).
|
||||
- **Idempotency:** Same request + same store ⇒ same ordering. Optional correlationId.
|
||||
|
||||
## Multi-Hop
|
||||
|
||||
Multi-hop (intermediary) routing is out of scope for MVP.
|
||||
|
||||
## Failover
|
||||
|
||||
Gateway uses primary first; on failure may try alternates in order. failure_policy is advisory.
|
||||
|
||||
## Optional extensions (settlement)
|
||||
|
||||
For CBDC/tokenized settlement overlays, a directive may include optional metadata for a settlement adapter (see [cbdc-settlement-adapter.md](cbdc-settlement-adapter.md)):
|
||||
|
||||
- **settlement_rail:** One of `RTGS` | `CBDC` | `tokenized_deposit` (when stored per participant/endpoint).
|
||||
- **wallet_endpoint** (or **settlement_endpoint**): Optional URL or reference for wallet/DLT when settlement_rail is CBDC or tokenized. Not required for MVP; schema and OpenAPI may be extended when in scope.
|
||||
|
||||
## Invariants
|
||||
|
||||
1. Match: at least one of primary or directives present. No match: empty and negative_cache_ttl set.
|
||||
2. When primary present, directives[0] equals primary.
|
||||
3. evidence and resolution_trace must not contain sensitive data.
|
||||
32
docs/architecture/tenant-model.md
Normal file
32
docs/architecture/tenant-model.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Tenant Model and Row-Level Security
|
||||
|
||||
## Global vs Tenant-Private
|
||||
|
||||
- **Global objects:** Public or shared data that can be read across tenants (e.g. BIC, LEI, BIN range metadata). Stored with `tenant_id` null or a dedicated "global" tenant. Used for cross-tenant lookup when the rail has a public identifier scheme.
|
||||
- **Tenant-private objects:** Participant-specific data, merchant IDs, terminal IDs, contractual endpoints. Always scoped by `tenant_id`. Queries must supply tenant (from auth or request) so that only that tenant's rows are visible.
|
||||
|
||||
## Enforcement
|
||||
|
||||
- **Postgres Row Level Security (RLS):** Enable RLS on `participants`, `identifiers`, `endpoints`, `capabilities`, `credentials`, `policies`, and optionally `routing_artifacts`. Policies: `tenant_id = current_setting('app.current_tenant_id')` or equivalent. Global rows: allow when `tenant_id IS NULL` or when reading public identifiers.
|
||||
- **Application layer:** Resolver and Admin API must set tenant context (e.g. from JWT or request parameter) before querying. Never return rows from another tenant.
|
||||
- **Per-tenant encryption:** For Tier 2+ data (see [data-classification](../security/data-classification.md)), use per-tenant encryption keys so that key compromise affects only one tenant.
|
||||
|
||||
## Caching
|
||||
|
||||
- Cache key **includes tenant**: same request for different tenants must not share a cache entry.
|
||||
- Optional per-tenant TTL or invalidation rules (e.g. shorter TTL for high-churn tenants).
|
||||
- Negative cache: key includes tenant; invalidate on any change for that tenant.
|
||||
|
||||
## RLS Policy Summary
|
||||
|
||||
| Table | Policy (conceptual) |
|
||||
| ---------------- | -------------------------------------------------------- |
|
||||
| participants | WHERE tenant_id = current_tenant OR tenant_id IS NULL |
|
||||
| identifiers | JOIN participants; same tenant or global |
|
||||
| endpoints | JOIN participants; same tenant |
|
||||
| capabilities | JOIN participants; same tenant |
|
||||
| credentials | JOIN participants; same tenant |
|
||||
| policies | WHERE tenant_id = current_tenant |
|
||||
| routing_artifacts| WHERE tenant_id = current_tenant OR tenant_id IS NULL |
|
||||
|
||||
Apply `current_tenant` from connection/session (e.g. set by API after auth).
|
||||
26
docs/architecture/testing-strategy.md
Normal file
26
docs/architecture/testing-strategy.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Testing Strategy
|
||||
|
||||
Testing approach for the as4-411 directory and resolver. Ensures determinism, protocol correctness, integration, and resilience.
|
||||
|
||||
## Property-based tests for determinism
|
||||
|
||||
- **Invariant:** Same normalized request + same store state ⇒ same ordered list of directives (see [resolution-algorithm](resolution-algorithm.md) and [ADR-002](../adr/002-resolution-scoring-determinism.md)).
|
||||
- Use property-based testing (e.g. fast-check, Hypothesis) to generate many (request, store snapshot) pairs and assert that repeated resolution runs produce identical outputs. Vary identifiers, tenant, constraints, and store contents within valid ranges.
|
||||
- Tie-break and scoring must be deterministic; tests should catch any dependence on iteration order or non-deterministic randomness.
|
||||
|
||||
## Golden test vectors per rail
|
||||
|
||||
- Each rail (or protocol adapter) should have **golden test vectors** derived from the [\_rail-template](../protocols/_rail-template.md) “Sample payloads and test vectors” section.
|
||||
- Tests: given a fixed request and a small, fixed store (or artifact set), the resolver output must match the golden directive list (primary + alternates, protocol, address, evidence). Update goldens only when the spec or algorithm intentionally changes; review changes.
|
||||
|
||||
## Integration harness
|
||||
|
||||
- **Stack:** Postgres (migrations applied) + resolver service + sample gateway client (or mock that calls resolve and validates response shape).
|
||||
- **Scenarios:** Create participants, identifiers, endpoints, and routing artifacts via Admin API or store; call resolve with various identifiers and constraints; assert directives and resolution_trace. Include multi-tenant isolation: data for tenant A must not appear in resolve for tenant B.
|
||||
- Run in CI; use container or test DB so that migrations and seed data are reproducible.
|
||||
|
||||
## Chaos tests for connectors
|
||||
|
||||
- **Timeouts and retries:** Simulate connector backends (SMP, file, GTT) that delay or fail. Assert timeout and retry behavior per [ADR-005](../adr/005-connector-trust-and-caching.md) and [connectors](connectors.md).
|
||||
- **Circuit-breaker:** After N failures, connector should open circuit and (per policy) fall back to cache-only or fail closed. Tests should verify circuit state and that no unbounded retries occur.
|
||||
- **Fallback to cache:** When external source is unavailable, resolver should use cached data only within max stale window; tests assert no stale data beyond that and correct resolution_trace (e.g. “SMP cache” when SMP is down).
|
||||
Reference in New Issue
Block a user