Initial commit: AS4/411 directory and discovery service for Sankofa Marketplace
Some checks failed
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-02-08 08:44:20 -08:00
commit c24ae925cf
109 changed files with 7222 additions and 0 deletions

0
docs/adr/.gitkeep Normal file
View File

View File

@@ -0,0 +1,31 @@
# ADR-000: Scope and Non-Goals
## Status
Accepted.
## Context
as4-411 must have a locked scope so that "interact" is not interpreted as brokering, orchestration, or config generation. The system boundary and trust model depend on this.
## Decision
### In Scope
- as4-411 is a **directory + discovery + routing directive generator**.
- It stores participants, identifiers, endpoints, capabilities, credentials references, and policies.
- It resolves identifiers to **routing directives** (target protocol, address, profile, security refs, QoS). Gateways **execute** these directives; as4-411 does **not** transmit messages on their behalf.
### Out of Scope (Unless Explicitly Added Later)
- **Brokering / orchestration:** Sending or relaying messages between parties is out of scope. If added in the future, it must be a **separate component** (e.g. `as4-411-broker`) with a separate trust boundary so the directory's integrity and confidentiality are not contaminated.
- **Config generation for multiple gateway stacks:** Generating full gateway configuration (e.g. PMode files, STP config) may be added as a separate tool or module; it is not part of the core directory/resolver.
### Integration Default
- Gateways may consume as4-411 as an **embedded library** (core + resolver + storage) or as a **sidecar/shared service** (REST or gRPC). The default pattern is documented in the README and deployment docs; both are supported.
## Consequences
- All feature work stays within directory, discovery, and directive generation.
- Brokering or message transmission, if ever required, is a distinct service with its own security and compliance story.

View File

@@ -0,0 +1,44 @@
# ADR-001: Adapter Interface and Semantic Versioning
## Status
Accepted.
## Context
Multi-rail support requires a strict plugin boundary so that each rail has a single adapter, version negotiation is clear, and compatibility is guaranteed. The protocol registry must define the minimum interface surface and versioning rules.
## Decision
### ProtocolAdapter Interface
Every rail adapter implements the following (see `packages/core` adapter-interface.ts):
- **validateIdentifier(type, value): boolean** — Validate format for the rail.
- **normalizeIdentifier(type, value): string | null** — Return normalized value for lookup/storage, or null if invalid.
- **resolveCandidates(ctx, request, options): Promise<AdapterCandidate[]]** — Use the supplied context (directory view) to return candidate participant+endpoint pairs.
- **evaluateCapabilities(candidate, serviceContext): boolean** — Whether the candidate matches the requested service/action/process.
- **renderRouteDirective(candidate, options): RouteDirective** — Build the canonical directive from a candidate.
- **ingestSource?(config): Promise<IngestResult>** — Optional; for connectors that pull from external directories (SMP, file, etc.).
The resolver (or a registry) supplies an **AdapterContext** to adapters; the context exposes findParticipantsByIdentifiers, getEndpointsByParticipantId, getCapabilitiesByParticipantId. The storage layer implements this context.
### Plugin Boundaries
- One adapter per rail (or per protocol family). Adapters are discovered by config or package layout (e.g. registered by protocol name or identifier type prefix).
- No adapter depends on another adapter; shared logic lives in core or a shared utility package.
### Semantic Versioning
- The **adapter interface** (ProtocolAdapter) follows semantic versioning. Backward-compatible changes only: new optional methods, new optional fields on types. Breaking changes require a new major version of the interface.
- Each **adapter implementation** has its own version (e.g. `version: "1.0.0"`). Registry can enforce minimum interface version when loading adapters.
### Compatibility Guarantees
- New optional methods or optional parameters do not break existing adapters.
- New required methods or required fields are breaking; they belong to a new major version of the interface contract.
## Consequences
- Rails can be added by implementing ProtocolAdapter and registering; the resolver delegates to the appropriate adapter by identifier type or protocol.
- Version mismatches can be detected at load time; operators can pin adapter or interface versions.

View File

@@ -0,0 +1,29 @@
# ADR-001: Persistence and Caching Strategy
## Status
Accepted.
## Context
as4-411 needs canonical persistence for directory data (tenants, participants, identifiers, endpoints, capabilities, credentials, policies) and a caching strategy for resolution results to support low-latency gateway lookups and resilience.
## Decision
### Persistence
- **Primary store: PostgreSQL.** Chosen for ACID guarantees, relational model matching the [data model](../architecture/data-model.md), and operational familiarity (replication, backups, tooling).
- **Migrations:** SQL migrations live under `packages/storage/migrations/` (e.g. `001_initial.sql`). Applied out-of-band or via a migration runner; no automatic migrate on startup by default.
- **Alternatives:** In-memory store for development and tests; SQLite for embedded/library deployments where Postgres is not available. Both implement the same `DirectoryStore`/`AdminStore` port.
### Caching
- **Resolution cache:** In-process TTL cache (e.g. `InMemoryResolveCache`) keyed by canonical `ResolveRequest` (identifiers, serviceContext, constraints, tenant). Positive and negative results are cached; negative TTL is shorter (e.g. 60s) to avoid prolonged stale “not found.”
- **Cache key:** Deterministic and stable for same inputs (see [ADR-002](002-resolution-scoring-determinism.md)).
- **Invalidation:** On directory mutation (participant/identifier/endpoint/policy change), invalidate by tenant or by cache key prefix when a proper event or hook is available; until then, rely on TTL.
- **Optional:** Redis or similar for shared cache across multiple resolver instances; same interface `ResolveCache`.
## Consequences
- Gateways can rely on Postgres for durability and use in-memory or Redis cache for latency.
- Embedded use cases can use SQLite or in-memory without Postgres dependency.

View File

@@ -0,0 +1,28 @@
# ADR-002: Resolution Scoring and Determinism
## Status
Accepted.
## Context
Resolution must return a stable, ordered list of routing directives for the same inputs and store state.
## Decision
### Determinism
- Same normalized request + same directory state implies same ordered list of RouteDirectives.
- Tie-break when scores are equal: (1) explicit priority higher first, (2) lexical by endpoint id then participant id.
### Scoring
- Factors: endpoint priority, endpoint status (active preferred over draining over inactive). No randomness; same inputs imply same scores and order.
### Cache Key
- Derived from canonical request (sorted identifiers, serialized serviceContext and constraints, tenant).
## Consequences
- Caching and retries are reproducible and safe.

View File

@@ -0,0 +1,30 @@
# ADR-003: Multi-Tenancy and RLS Strategy
## Status
Accepted.
## Context
Tenant scoping is required for isolation. Shared (global) data (e.g. BIC, LEI) and tenant-private data must be clearly separated, and access enforced at the database and application layer.
## Decision
### Model
- **Global objects:** Identifiers or metadata that are public or shared (e.g. BIC, LEI, BIN range metadata). Stored with `tenant_id` null or a dedicated global tenant. Readable by all tenants for resolution when the identifier is public.
- **Tenant-private objects:** All participant-specific data, contractual endpoints, MID/TID, and tenant-specific routing artifacts. Must be scoped by `tenant_id`; only the owning tenant can read/write.
### Enforcement
- **Postgres Row Level Security (RLS):** Enable on tenant-scoped tables. Policy: restrict to rows where `tenant_id` matches the session/connection tenant (set after auth). Allow read of global rows (`tenant_id IS NULL`) where applicable.
- **Application:** Resolver and Admin API set tenant context from JWT or request; all queries filter by tenant. No cross-tenant data in responses.
- **Per-tenant encryption:** For confidential data (Tier 2+), use per-tenant keys so compromise is isolated (see ADR-004).
### Caching
- Cache key includes tenant. Per-tenant TTL and invalidation optional.
## Consequences
- Tenants cannot see each other's private data. Global data remains available for public identifier resolution. RLS provides defense in depth alongside application checks.

View File

@@ -0,0 +1,29 @@
# ADR-003: Policy Engine Model (ABAC)
## Status
Accepted.
## Context
Resolution must respect tenant scope and allow/deny rules using an attribute-based model.
## Decision
### Model
- Policies are stored per tenant with rule_json (ABAC attributes), effect (allow/deny), and priority.
- Tenant is enforced by restricting resolution to that tenant when request.tenant is set.
### MVP Rule Shape
- Deny: rule_json.participantId or rule_json.participantIds — exclude those participants.
- Allow (restrictive): if any allow policy exists, rule_json.participantId/participantIds — only include those participants.
### Ordering
- Deny applied first; then allow restriction. Policies loaded by tenant and ordered by priority.
## Consequences
- Simple allow/deny by participant supported; ABAC can be extended via rule_json and filter logic.

View File

@@ -0,0 +1,19 @@
# ADR-004: Sensitive Data Classification and Encryption
## Status
Accepted.
## Context
The directory holds mixed sensitivity data: public identifiers (BIC, LEI), internal endpoints and participant data, and confidential or regulated data (MID/TID, contract routing, key references). We need a clear classification and enforcement policy so that storage and access controls are consistent and auditable.
## Decision
- **Four tiers:** Tier 0 (public), Tier 1 (internal), Tier 2 (confidential), Tier 3 (regulated/secrets). See [data-classification.md](../security/data-classification.md) for definitions and examples.
- **Enforcement:** Field-level encryption for Tier 2+ at rest; strict RBAC/ABAC; immutable audit logs for mutations and Tier 2+ access. Tier 3: only references (e.g. vault_ref) stored; no private keys or tokens in the directory.
- **Mapping:** All tables and fields used for directory and routing artifacts are mapped to a tier. New fields require a tier before merge. Per-tenant encryption keys for Tier 2+ are recommended (see ADR-003).
## Consequences
- Operators and developers have a single reference for how to handle each data type. Compliance and security reviews can align on tier and controls.

View File

@@ -0,0 +1,30 @@
# ADR-005: Connector Trust and Caching Strategy
## Status
Accepted.
## Context
Connectors ingest data from external or file-based sources (SMP/SML, file, SS7 feeds). Trust anchors, signature validation, caching, and resilience must be defined so that bad or stale data does not compromise resolution.
## Decision
### Per-Connector Requirements
For each connector (SMP/SML, file, SS7, etc.) the following must be defined and documented (see [connectors.md](../architecture/connectors.md)):
- **Trust anchors and signature validation:** Which certificates or keys are trusted for signed payloads; how to validate signatures on ingested bundles. Pinning and trust anchor refresh policy.
- **Caching and refresh:** TTL for cached data, jitter to avoid thundering herd, negative caching (how long to cache "not found" or fetch failure).
- **Resilience:** Timeouts, retries, circuit-breaker thresholds. Behavior on failure: fall back to cached only, fail closed, or fail open (document per connector).
- **Data provenance tagging:** Every ingested record or edge must be tagged with source (e.g. "smp", "file", "gtt_feed"), last_verified (or fetched_at), and optional confidence score. Exposed in resolution evidence and resolution_trace.
### SMP/SML Specifics
- Cache TTL policy: document default TTL for SMP metadata and SML lookups; jitter on refresh.
- Pinning and trust anchors: SML and SMP TLS and optional payload signing; which CAs or pins are accepted.
- Failure behavior: on network or SMP failure, fall back to cached data only; do not serve stale beyond max stale window (document). No silent fallback to unrelated data.
## Consequences
- Operators can configure trust and cache per connector. Provenance is always available for audit and explainability.

0
docs/api/.gitkeep Normal file
View File

18
docs/api/README.md Normal file
View File

@@ -0,0 +1,18 @@
# API definitions
- **OpenAPI:** [openapi.yaml](openapi.yaml) — REST API for resolve, bulk-resolve, admin, system.
- **Route directive schema:** [route-directive.schema.json](route-directive.schema.json) — JSON Schema for RouteDirective and ResolveResponse.
- **Protobuf:** [proto/resolver.proto](proto/resolver.proto) — Resolver service and messages (ResolveRequest, ResolveResponse, RouteDirective). Package `as411.resolver.v1`.
## Generating stubs from Proto
From the repo root, with `protoc` installed:
```bash
# Example (adjust paths for your language)
protoc -I docs/api/proto docs/api/proto/resolver.proto --go_out=paths=source_relative:.
# Or with buf (if using buf.gen.yaml):
# buf generate docs/api/proto
```
gRPC server implementation is optional; the Proto file defines the contract for clients and future gRPC support.

530
docs/api/openapi.yaml Normal file
View File

@@ -0,0 +1,530 @@
openapi: 3.0.3
info:
title: as4-411 Directory and Resolver API
description: |
Standards-aware directory and discovery service for AS4, SS7, and messaging gateways.
See [data-model](../architecture/data-model.md) and [resolution-algorithm](../architecture/resolution-algorithm.md).
version: 0.1.0
servers:
- url: /api
description: API base path
tags:
- name: Resolver
description: Gateway-facing resolution
- name: Admin
description: Directory management
- name: System
description: Health and metrics
paths:
# --- Resolver API (gateway-facing) ---
/v1/resolve:
post:
tags: [Resolver]
summary: Resolve identifiers to routing directives
description: |
For ISO 20022 FI-to-FI, use service = `iso20022.fi` and action = `credit.transfer`, `fi.credit.transfer`, `payment.status`, `payment.cancellation`, `resolution.of.investigation`, `statement`, or `notification`.
Profile returned: `as4.fifi.iso20022.v1`.
operationId: resolve
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/ResolveRequest" }
example:
identifiers:
- type: as4.partyId
value: BANKUS33XXX
scope: BIC
serviceContext:
service: iso20022.fi
action: credit.transfer
responses:
"200":
description: Resolution result
content:
application/json:
schema: { $ref: "#/components/schemas/ResolveResponse" }
example:
primary:
target_protocol: as4
target_address: https://as4.bankus.com/fi
transport_profile: as4.fifi.iso20022.v1
security:
signRequired: true
encryptRequired: true
keyRefs: [vault://certs/bankus/iso20022]
service_context:
service: iso20022.fi
action: credit.transfer
resolution_trace:
- source: internal directory
"400":
description: Invalid request
"503":
description: Resolver unavailable
/v1/bulk-resolve:
post:
tags: [Resolver]
summary: Batch resolve multiple requests
operationId: bulkResolve
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [requests]
properties:
requests:
type: array
items: { $ref: "#/components/schemas/ResolveRequest" }
responses:
"200":
description: Batch resolution results
content:
application/json:
schema:
type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/ResolveResponse" }
traceId: { type: string, format: uuid }
# --- System ---
/v1/health:
get:
tags: [System]
summary: Health check
operationId: health
responses:
"200":
description: Service healthy
content:
application/json:
schema:
type: object
properties:
status: { type: string, enum: [ok, degraded] }
version: { type: string }
checks: { type: object }
/v1/metrics:
get:
tags: [System]
summary: Prometheus metrics
operationId: metrics
responses:
"200":
description: Prometheus text format
content:
text/plain: {}
# --- Admin API (CRUD) ---
/v1/admin/tenants:
get:
tags: [Admin]
summary: List tenants
operationId: listTenants
responses:
"200": { description: List of tenants }
post:
tags: [Admin]
summary: Create tenant
operationId: createTenant
requestBody:
{ content: { "application/json": { schema: { $ref: "#/components/schemas/Tenant" } } } }
responses:
"201": { description: Created }
"400": { description: Validation error }
/v1/admin/tenants/{tenantId}:
get:
tags: [Admin]
summary: Get tenant
operationId: getTenant
parameters: [{ name: tenantId, in: path, required: true, schema: { type: string } }]
responses:
"200": { description: Tenant }
"404": { description: Not found }
put:
tags: [Admin]
summary: Update tenant
operationId: updateTenant
parameters: [{ name: tenantId, in: path, required: true, schema: { type: string } }]
requestBody:
{ content: { "application/json": { schema: { $ref: "#/components/schemas/Tenant" } } } }
responses:
"200": { description: Updated }
"404": { description: Not found }
delete:
tags: [Admin]
summary: Delete tenant
operationId: deleteTenant
parameters: [{ name: tenantId, in: path, required: true, schema: { type: string } }]
responses:
"204": { description: Deleted }
"404": { description: Not found }
/v1/admin/participants:
get:
tags: [Admin]
summary: List participants
parameters:
- name: tenantId
in: query
schema: { type: string }
responses:
"200": { description: List of participants }
post:
tags: [Admin]
summary: Create participant
operationId: createParticipant
requestBody:
{
content: { "application/json": { schema: { $ref: "#/components/schemas/Participant" } } },
}
responses:
"201": { description: Created }
"400": { description: Validation error }
/v1/admin/participants/{participantId}:
get:
tags: [Admin]
summary: Get participant
operationId: getParticipant
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
responses:
"200": { description: Participant }
"404": { description: Not found }
put:
tags: [Admin]
summary: Update participant
operationId: updateParticipant
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
requestBody:
{
content: { "application/json": { schema: { $ref: "#/components/schemas/Participant" } } },
}
responses:
"200": { description: Updated }
"404": { description: Not found }
delete:
tags: [Admin]
summary: Delete participant
operationId: deleteParticipant
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
responses:
"204": { description: Deleted }
"404": { description: Not found }
/v1/admin/participants/{participantId}/identifiers:
get:
tags: [Admin]
summary: List identifiers for participant
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
responses:
"200": { description: List of identifiers }
post:
tags: [Admin]
summary: Add identifier
operationId: createIdentifier
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
requestBody:
{ content: { "application/json": { schema: { $ref: "#/components/schemas/Identifier" } } } }
responses:
"201": { description: Created }
"400": { description: Validation error }
/v1/admin/participants/{participantId}/endpoints:
get:
tags: [Admin]
summary: List endpoints for participant
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
responses:
"200": { description: List of endpoints }
post:
tags: [Admin]
summary: Add endpoint
operationId: createEndpoint
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
requestBody:
{ content: { "application/json": { schema: { $ref: "#/components/schemas/Endpoint" } } } }
responses:
"201": { description: Created }
"400": { description: Validation error }
/v1/admin/participants/{participantId}/credentials:
get:
tags: [Admin]
summary: List credential refs for participant
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
responses:
"200": { description: List of credential refs }
post:
tags: [Admin]
summary: Add credential reference
operationId: createCredential
parameters: [{ name: participantId, in: path, required: true, schema: { type: string } }]
requestBody:
{
content:
{ "application/json": { schema: { $ref: "#/components/schemas/CredentialRef" } } },
}
responses:
"201": { description: Created }
"400": { description: Validation error }
/v1/admin/policies:
get:
tags: [Admin]
summary: List policies
parameters:
- name: tenantId
in: query
schema: { type: string }
responses:
"200": { description: List of policies }
post:
tags: [Admin]
summary: Create policy
operationId: createPolicy
requestBody:
{ content: { "application/json": { schema: { $ref: "#/components/schemas/Policy" } } } }
responses:
"201": { description: Created }
"400": { description: Validation error }
/v1/admin/policies/{policyId}:
get:
tags: [Admin]
summary: Get policy
parameters: [{ name: policyId, in: path, required: true, schema: { type: string } }]
responses:
"200": { description: Policy }
"404": { description: Not found }
put:
tags: [Admin]
summary: Update policy
operationId: updatePolicy
parameters: [{ name: policyId, in: path, required: true, schema: { type: string } }]
requestBody:
{ content: { "application/json": { schema: { $ref: "#/components/schemas/Policy" } } } }
responses:
"200": { description: Updated }
"404": { description: Not found }
delete:
tags: [Admin]
summary: Delete policy
operationId: deletePolicy
parameters: [{ name: policyId, in: path, required: true, schema: { type: string } }]
responses:
"204": { description: Deleted }
"404": { description: Not found }
components:
schemas:
# --- Resolver request/response (aligned with data-model and resolution-algorithm) ---
ResolveRequest:
type: object
required: [identifiers]
properties:
identifiers:
type: array
minItems: 1
items: { $ref: "#/components/schemas/IdentifierInput" }
serviceContext:
$ref: "#/components/schemas/ServiceContext"
constraints:
$ref: "#/components/schemas/ResolveConstraints"
tenant: { type: string, description: "Tenant scope for resolution" }
desiredProtocols:
type: array
items: { type: string }
description: "Preferred protocol domains (e.g. as4, ss7, peppol)"
IdentifierInput:
type: object
required: [type, value]
properties:
type:
{
type: string,
description: "Identifier type (e.g. as4.partyId, e164, peppol.participantId)",
}
value: { type: string }
scope: { type: string }
ServiceContext:
type: object
description: |
For ISO 20022 FI-to-FI (profile as4.fifi.iso20022.v1), service = `iso20022.fi` and action is one of
credit.transfer, fi.credit.transfer, payment.status, payment.cancellation, resolution.of.investigation, statement, notification.
properties:
service: { type: string }
action: { type: string }
process: { type: string }
documentType: { type: string }
ResolveConstraints:
type: object
properties:
trustDomain: { type: string }
region: { type: string }
jurisdiction: { type: string }
maxResults: { type: integer, minimum: 1 }
networkBrand:
type: string
description: "Card network (visa, mastercard, amex, discover, diners)"
tenantContract: { type: string, description: "Tenant contract for routing" }
connectivityGroup: { type: string }
requiredCapability: { type: string }
messageType: { type: string, description: "e.g. ISO8583 MTI or AS4 service/action" }
ResolveResponse:
type: object
required: [directives]
properties:
primary: { $ref: "#/components/schemas/RouteDirective" }
alternates:
type: array
items: { $ref: "#/components/schemas/DirectiveWithReason" }
directives:
type: array
items: { $ref: "#/components/schemas/RouteDirective" }
ttl: { type: integer, description: "Cache TTL in seconds" }
traceId: { type: string, format: uuid }
correlationId: { type: string }
failure_policy: { $ref: "#/components/schemas/FailurePolicy" }
negative_cache_ttl: { type: integer, description: "TTL for negative cache when no match" }
resolution_trace:
type: array
items: { $ref: "#/components/schemas/ResolutionTraceEntry" }
DirectiveWithReason:
type: object
required: [directive]
properties:
directive: { $ref: "#/components/schemas/RouteDirective" }
reason: { type: string }
FailurePolicy:
type: object
properties:
retry: { type: boolean }
backoff: { type: string }
circuitBreak: { type: boolean }
ResolutionTraceEntry:
type: object
properties:
source: { type: string }
directiveIndex: { type: integer }
message: { type: string }
RouteDirective:
description: "Normalized routing output; see architecture/route-directive.md and route-directive.schema.json"
type: object
required: [target_protocol, target_address]
properties:
target_protocol: { type: string }
target_address: { type: string }
transport_profile: { type: string }
security:
type: object
properties:
signRequired: { type: boolean }
encryptRequired: { type: boolean }
keyRefs: { type: array, items: { type: string } }
algorithms: { type: object }
service_context:
type: object
properties:
service: { type: string }
action: { type: string }
serviceIndicator: { type: string }
qos:
type: object
properties:
retries: { type: integer }
receiptsRequired: { type: boolean }
ordering: { type: string }
ttl_seconds: { type: integer }
evidence:
oneOf:
- type: object
properties:
source: { type: string }
lastVerified: { type: string, format: date-time }
confidenceScore: { type: number }
signature: { type: string }
- type: array
items:
type: object
properties:
source: { type: string }
freshness: { type: string, format: date-time }
confidence: { type: number }
signature: { type: string }
# --- Admin entities (aligned with data-model) ---
Tenant:
type: object
properties:
id: { type: string }
name: { type: string }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
Participant:
type: object
properties:
id: { type: string }
tenantId: { type: string }
name: { type: string }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
Identifier:
type: object
properties:
id: { type: string }
participantId: { type: string }
identifier_type: { type: string }
value: { type: string }
scope: { type: string }
priority: { type: integer }
verified_at: { type: string, format: date-time }
Endpoint:
type: object
properties:
id: { type: string }
participantId: { type: string }
protocol: { type: string }
address: { type: string }
profile: { type: string }
priority: { type: integer }
status: { type: string, enum: [active, inactive, draining] }
CredentialRef:
type: object
properties:
id: { type: string }
participantId: { type: string }
credential_type: { type: string, enum: [tls, sign, encrypt] }
vault_ref: { type: string }
fingerprint: { type: string }
valid_from: { type: string, format: date-time }
valid_to: { type: string, format: date-time }
Policy:
type: object
properties:
id: { type: string }
tenantId: { type: string }
rule_json: { type: object, description: "ABAC rule" }
effect: { type: string, enum: [allow, deny] }
priority: { type: integer }

View File

@@ -0,0 +1,120 @@
syntax = "proto3";
package as411.resolver.v1;
option go_package = "github.com/as4-411/api/proto/resolver/v1;resolverv1";
// ResolverService provides resolution of identifiers to routing directives.
// Aligned with REST /v1/resolve and /v1/bulk-resolve; see OpenAPI and route-directive.schema.json.
service ResolverService {
rpc Resolve(ResolveRequest) returns (ResolveResponse);
rpc BulkResolve(BulkResolveRequest) returns (BulkResolveResponse);
}
message ResolveRequest {
repeated IdentifierInput identifiers = 1;
ServiceContext service_context = 2;
ResolveConstraints constraints = 3;
string tenant = 4;
repeated string desired_protocols = 5;
}
message IdentifierInput {
string type = 1; // e.g. as4.partyId, e164, peppol.participantId
string value = 2;
string scope = 3; // e.g. BIC, LEI
}
message ServiceContext {
string service = 1; // e.g. iso20022.fi
string action = 2; // e.g. credit.transfer
string process = 3;
string document_type = 4;
}
message ResolveConstraints {
string trust_domain = 1;
string region = 2;
string jurisdiction = 3;
int32 max_results = 4;
string network_brand = 5;
string tenant_contract = 6;
string connectivity_group = 7;
string required_capability = 8;
string message_type = 9;
}
message ResolveResponse {
RouteDirective primary = 1;
repeated DirectiveWithReason alternates = 2;
repeated RouteDirective directives = 3;
int32 ttl = 4;
string trace_id = 5;
string correlation_id = 6;
FailurePolicy failure_policy = 7;
int32 negative_cache_ttl = 8;
repeated ResolutionTraceEntry resolution_trace = 9;
}
message RouteDirective {
string target_protocol = 1;
string target_address = 2;
string transport_profile = 3; // e.g. as4.fifi.iso20022.v1
RouteDirectiveSecurity security = 4;
RouteDirectiveServiceContext service_context = 5;
RouteDirectiveQos qos = 6;
int32 ttl_seconds = 7;
repeated EvidenceItem evidence = 8;
}
message EvidenceItem {
string source = 1;
string freshness = 2; // date-time
double confidence = 3;
string signature = 4;
}
message RouteDirectiveSecurity {
bool sign_required = 1;
bool encrypt_required = 2;
repeated string key_refs = 3;
map<string, string> algorithms = 4;
}
message RouteDirectiveServiceContext {
string service = 1;
string action = 2;
string service_indicator = 3;
}
message RouteDirectiveQos {
int32 retries = 1;
bool receipts_required = 2;
string ordering = 3;
}
message DirectiveWithReason {
RouteDirective directive = 1;
string reason = 2;
}
message FailurePolicy {
bool retry = 1;
string backoff = 2;
bool circuit_break = 3;
}
message ResolutionTraceEntry {
string source = 1;
int32 directive_index = 2;
string message = 3;
}
message BulkResolveRequest {
repeated ResolveRequest requests = 1;
}
message BulkResolveResponse {
repeated ResolveResponse results = 1;
string trace_id = 2;
}

View File

@@ -0,0 +1,110 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://as4-411.example/schemas/route-directive.json",
"title": "RouteDirective and ResolveResponse",
"description": "Formal schema for routing directive and resolve response. See docs/architecture/route-directive.md.",
"definitions": {
"RouteDirectiveSecurity": {
"type": "object",
"properties": {
"signRequired": { "type": "boolean" },
"encryptRequired": { "type": "boolean" },
"keyRefs": { "type": "array", "items": { "type": "string" } },
"algorithms": { "type": "object" }
}
},
"RouteDirectiveServiceContext": {
"type": "object",
"properties": {
"service": { "type": "string" },
"action": { "type": "string" },
"serviceIndicator": { "type": "string" }
}
},
"RouteDirectiveQos": {
"type": "object",
"properties": {
"retries": { "type": "integer" },
"receiptsRequired": { "type": "boolean" },
"ordering": { "type": "string" }
}
},
"EvidenceItem": {
"type": "object",
"properties": {
"source": { "type": "string" },
"freshness": { "type": "string", "format": "date-time" },
"confidence": { "type": "number" },
"signature": { "type": "string" }
}
},
"RouteDirective": {
"type": "object",
"required": ["target_protocol", "target_address"],
"properties": {
"target_protocol": { "type": "string" },
"target_address": { "type": "string" },
"transport_profile": { "type": "string" },
"security": { "$ref": "#/definitions/RouteDirectiveSecurity" },
"service_context": { "$ref": "#/definitions/RouteDirectiveServiceContext" },
"qos": { "$ref": "#/definitions/RouteDirectiveQos" },
"ttl_seconds": { "type": "integer" },
"evidence": {
"type": "array",
"items": { "$ref": "#/definitions/EvidenceItem" }
}
}
},
"DirectiveWithReason": {
"type": "object",
"required": ["directive"],
"properties": {
"directive": { "$ref": "#/definitions/RouteDirective" },
"reason": { "type": "string" }
}
},
"FailurePolicy": {
"type": "object",
"properties": {
"retry": { "type": "boolean" },
"backoff": { "type": "string" },
"circuitBreak": { "type": "boolean" }
}
},
"ResolutionTraceEntry": {
"type": "object",
"properties": {
"source": { "type": "string" },
"directiveIndex": { "type": "integer" },
"message": { "type": "string" }
}
},
"ResolveResponse": {
"type": "object",
"properties": {
"primary": { "$ref": "#/definitions/RouteDirective" },
"alternates": {
"type": "array",
"items": { "$ref": "#/definitions/DirectiveWithReason" }
},
"directives": {
"type": "array",
"items": { "$ref": "#/definitions/RouteDirective" }
},
"ttl": { "type": "integer" },
"traceId": { "type": "string", "format": "uuid" },
"correlationId": { "type": "string" },
"failure_policy": { "$ref": "#/definitions/FailurePolicy" },
"negative_cache_ttl": { "type": "integer" },
"resolution_trace": {
"type": "array",
"items": { "$ref": "#/definitions/ResolutionTraceEntry" }
}
}
}
},
"oneOf": [
{ "$ref": "#/definitions/RouteDirective" },
{ "$ref": "#/definitions/ResolveResponse" }
]
}

View File

View 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.

View 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.

View 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 | 01 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.

View 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 19)
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 27. 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.

View 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.

View 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).

View 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).

View File

@@ -0,0 +1,61 @@
# as4-411 resolution examples for ISO 20022 over AS4 (FI-to-FI)
# Profile: as4.fifi.iso20022.v1. Service: iso20022.fi. Actions: credit.transfer, fi.credit.transfer, etc.
# BIC/LEI Tier 0/1 per security/data-classification.md.
bic_example:
request:
identifiers:
- type: as4.partyId
value: BANKUS33XXX
scope: BIC
serviceContext:
service: iso20022.fi
action: credit.transfer
response_excerpt:
primary:
target_protocol: as4
target_address: https://as4.bankus.com/fi
transport_profile: as4.fifi.iso20022.v1
security:
signRequired: true
encryptRequired: true
keyRefs:
- vault://certs/bankus/iso20022
service_context:
service: iso20022.fi
action: credit.transfer
evidence:
source: internal directory
lastVerified: "2025-02-07T12:00:00Z"
resolution_trace:
- source: internal directory
# Output: Endpoint https://as4.bankus.com/fi, EncryptionCert vault://certs/bankus/iso20022, Profile as4.fifi.iso20022.v1, Receipts signed
lei_example:
request:
identifiers:
- type: as4.partyId
value: "5493001KJTIIGC8Y1R12"
scope: LEI
serviceContext:
service: iso20022.fi
action: fi.credit.transfer
response_excerpt:
primary:
target_protocol: as4
target_address: https://as4.bankus.com/fi
transport_profile: as4.fifi.iso20022.v1
security:
signRequired: true
encryptRequired: true
keyRefs:
- vault://certs/bankus/iso20022
service_context:
service: iso20022.fi
action: fi.credit.transfer
evidence:
source: internal directory
message: "LEI to BIC mapping applied"
resolution_trace:
- source: internal directory
# Mapping: LEI -> BIC(s) -> AS4 Endpoint. Evidence includes LEI->BIC mapping source.

0
docs/operations/.gitkeep Normal file
View File

View File

@@ -0,0 +1,22 @@
# Promotion and Sync (GitOps)
Staging to validated to production promotion for directory and routing artifacts, with signed bundles and CLI workflows.
## Model
- **Staging:** Editable branch or workspace where artifacts (participant/endpoint config, BIN tables, signed routing bundles) are authored and validated.
- **Validated:** Output of validation (schema, lint, and rail-specific checks). Artifacts are signed and ready for promotion.
- **Production:** Deployed state consumed by the resolver and gateways. Updated only via promote from validated; rollback to a previous validated bundle when needed.
Signed bundles carry payload plus signature/fingerprint and optional effective_from / effective_to. Use the existing signed-bundle and routing artifact format (see [data model](../architecture/data-model.md) and [connectors](../architecture/connectors.md)).
## CLI commands
When [packages/cli](../../packages/cli) (or equivalent) is present, support these workflows:
- **as4-411-cli diff** — Compare staging artifact set (or branch) against current production (or another ref). Output human- and machine-readable diff (participants, endpoints, routing_artifacts, policies).
- **as4-411-cli validate** — Validate staging: schema validation and linting per rail (using [\_rail-template](../protocols/_rail-template.md) and protocol validators). Exit non-zero on failure; report errors by file and rule.
- **as4-411-cli promote** — Promote validated, signed bundle to production. Verify signatures and effective dates; apply to store (or write to production artifact store). Record promotion in audit_log.
- **as4-411-cli rollback** — Rollback production to a previous validated revision (by tag or bundle id). Re-apply that revision's artifacts and invalidate affected caches.
Schema validation and linting must run per rail so that protocol-specific rules (e.g. BIN format, identifier types) are enforced before promotion.

View File

@@ -0,0 +1,49 @@
# Rail Definition Template
**Required before implementing a new rail or protocol adapter.** Copy this template, fill every section, and ensure the rail doc is complete before merge. See [catalog.md](catalog.md).
---
## 1. Owner and Authority
- **Governing body / owner:**
- **Normative specifications (links):**
- **Message families / standards:**
## 2. Identifier Scheme(s)
- **Identifier types** (e.g. partyId, BIC, BIN):
- **Validation rules** (format, length, allowed characters):
- **Uniqueness and scope** (global vs tenant-scoped):
## 3. Addressing and Endpoint Rules
- **Endpoint types** (URL, queue, point code, etc.):
- **Address format** (regex or pattern):
- **Transport profiles** (if any):
## 4. Security Model
- **Authentication** (how participants are authenticated):
- **Integrity** (signing, hashing):
- **Confidentiality** (encryption, TLS):
## 5. Discovery Model
- **Public directory?** (yes / no / partial)
- **Contractual only?** (e.g. BIN tables, member config)
- **Authoritative source** (SML/SMP, vendor API, internal only):
## 6. Compliance Constraints
- **Regulatory** (e.g. PCI-DSS, data residency):
- **Data handling** (what must not be stored, encryption requirements):
## 7. Sample Payloads and Test Vectors
- **Sample request/response or message** (anonymized):
- **Test vectors** (identifier in → expected directive or behavior):
---
*Block merge of new rails until this template is completed for the rail.*

39
docs/protocols/cards.md Normal file
View File

@@ -0,0 +1,39 @@
# Card Networks (Visa, Mastercard, Amex, Discover, Diners)
## Scope
Card rails are **private routing artifacts** (BIN tables, acquirer routing). There is **no public "discover Visa endpoint"** behavior. Ingestion is from internal systems only; strong encryption and access controls apply. The directory stores routing tables and returns directives to an ISO8583/API switch. Never store PAN; BIN ranges only. Merchant ID (MID), Terminal ID (TID), and contract identifiers are **Tier 2** (confidential)—encrypt at rest and restrict access. See [data-classification](../security/data-classification.md).
## Identifier Taxonomy
- **pan.bin** — BIN/IIN range (68 digits only); never full PAN.
- **mid**, **tid**, **caid** — Merchant/terminal/card-acceptor IDs (tenant-scoped).
- **processorId** / **acquirerId** — Tenant/contract scoped.
- **network.brand** — Constraint: visa, mastercard, amex, discover, diners.
Do not store PAN or token values in plaintext.
## Endpoints
- **iso8583.tcp** — Host:port, mTLS/VPN.
- **api.https** — Base URL + auth.
- **file.sftp** — Clearing files.
- **mq** — Internal switch.
Profile indicates channel (e.g. visa-base1, mc-mip).
## BIN-Table Model
- Artifact type: **bin_table**. Payload: versioned entries with binPrefix, binLength, brand, region, routingTarget, optional tenantId.
- Resolver matches request BIN to longest-matching prefix and returns directive with target_address = routingTarget. Per-tenant overrides supported.
## Directive Outputs
- ISO8583: target_protocol iso8583, target_address host:port.
- API: target_protocol api/https, target_address base URL.
Capabilities: auth.request/response, clearing.presentment, chargeback, reversal, advice, tokenization, 3ds.
## Security
- Store BIN ranges only; no PAN/token. Field-level encryption for merchant/terminal IDs. Strict RBAC and audit for card-related records. See security/key-reference-model.md.

23
docs/protocols/catalog.md Normal file
View File

@@ -0,0 +1,23 @@
# Protocol and Rail Catalog
Extensibility catalog for additional rails. **New rails require a completed doc derived from [_rail-template.md](_rail-template.md) before merge.**
Adapters are added incrementally; each defines identifier types, endpoint profiles, capability taxonomy, and optional connector in `packages/connectors/`.
## Implemented
- **AS4 / PEPPOL** — See data-model identifier types; SMP/SML connector spec in docs/architecture/connectors.md.
- **ISO 20022 over AS4 (FI-to-FI)** — [iso20022-over-as4.md](iso20022-over-as4.md). PartyId (BIC, LEI) → endpoint + cert + AS4 profile; Service/Action for pacs/camt; directory-only (no ISO 20022 parsing, no settlement). Scheme-specific profiles: [iso20022-scheme-profiles.md](iso20022-scheme-profiles.md) (TARGET2, Fedwire, CHAPS).
- **SS7** — e164, gt, pc, ssn; GTT via routing artifacts.
- **Card networks** — [cards.md](cards.md); pan.bin, BIN table, ISO8583.
- **DTC / Digital securities** — [dtc.md](dtc.md); lei, bic, dtc.participantId, dtc.accountId.
- **KTT** — [ktt.md](ktt.md); placeholder rail.
## Candidate Families (priority order)
- **Payments:** ISO 20022 over AS4 (FI-to-FI) documented; ISO 20022 over API, SWIFT, Fedwire-like remain candidates.
- **EDI / B2B:** AS2, SFTP EDI, RosettaNet.
- **Healthcare:** HL7 MLLP, FHIR endpoints (endpoint registry + certs).
- **Identity:** DID, DNSSEC, PKI directories.
- **Message brokers:** Kafka topics, NATS subjects, AMQP queues (logical addresses + ACL).
Integration: add identifier validators in core, register profile in protocol_registry, optional connector; document in a new docs/protocols/*.md.

44
docs/protocols/dtc.md Normal file
View File

@@ -0,0 +1,44 @@
# DTC / Digital Securities (DTCC Ecosystem)
## Overview
DTC and DTC-related messaging cover securities post-trade and custody. Participants include broker-dealers, custodian banks, and clearing members. Directory use is often **client-owned configuration**; public directory availability is limited.
## Scope
**Identity mapping** (LEI, BIC, participant IDs) plus **contractual endpoint profiles**. Optional import from customer-managed config (files or APIs). Do **not** claim automated discovery unless an authoritative or licensed directory feed exists. Prefer routing artifacts and admin API for participant/endpoint maps.
## Identifier Taxonomy
| Type | Description | Scope |
|------|-------------|--------|
| `lei` | Legal Entity Identifier | Public/registry |
| `bic` | Bank Identifier Code (SWIFT) | Market identifier |
| `dtc.participantId` | DTC/internal participant ID | Tenant/confidential |
| `dtc.accountId` | Custody/account ID (proprietary) | Tenant/confidential |
Historical: `duns` (D&B) where still in use.
## Endpoint Profiles
| Profile | Protocol | Description |
|---------|----------|-------------|
| `dtcc-mq` | MQ | DTCC connectivity / message system |
| `sftp.*` | SFTP | File-based instructions |
| `https.*` | HTTPS/AS2/AS4 | API or EDI over HTTP |
Address format is vendor- or channel-specific (queue name, path, URL).
## Capability Taxonomy
- `securities.settlement.*` — Settlement instructions and messages.
- `securities.corpactions.*` — Corporate actions.
- `securities.assetservices.*` — Asset servicing.
Used in resolution to match service context (e.g. request for settlement instructions).
## Tenancy and Confidentiality
- **Strong tenant scoping:** DTC and account identifiers are frequently confidential. Resolution must be scoped by tenant; no cross-tenant leakage.
- **Prefer integration via client-owned configuration:** Ingest from client-provided files or APIs rather than assuming a public directory. Use [routing artifacts](../architecture/data-model.md) and admin API for participant/endpoint maps.
- **Audit:** All access to DTC-related participant and endpoint data must be audited.

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Anonymized AS4/ebMS3-style sample for ISO 20022 FI-to-FI.
References: OASIS ebMS 3.0, AS4. Payload is opaque (ISO 20022 XML).
Placeholders: SENDERBICXXX, RECEIVERBICXXX; endpoint resolved via directory. -->
<Envelope xmlns="http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/">
<Header>
<Messaging>
<UserMessage>
<MessageInfo>
<MessageId>uuid-sample-message-id-001</MessageId>
<Timestamp>2025-02-07T12:00:00Z</Timestamp>
<RefToMessageId>optional-ref</RefToMessageId>
</MessageInfo>
<PartyInfo>
<From>
<PartyId>SENDERBICXXX</PartyId>
<Role>http://example.org/roles/sender</Role>
</From>
<To>
<PartyId>RECEIVERBICXXX</PartyId>
<Role>http://example.org/roles/receiver</Role>
</To>
</PartyInfo>
<CollaborationInfo>
<AgreementRef>as4.fifi.iso20022.v1</AgreementRef>
<Service type="application">iso20022.fi</Service>
<Action>credit.transfer</Action>
<ConversationId>conv-sample-001</ConversationId>
</CollaborationInfo>
<PayloadInfo>
<PartInfo href="cid:pacs008.xml">
<Schema location="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08"/>
</PartInfo>
</PayloadInfo>
</UserMessage>
</Messaging>
</Header>
<!-- Body: multipart with signed+encrypted payload (omitted here; AS4 treats as opaque) -->
</Envelope>

View File

@@ -0,0 +1,299 @@
# ISO 20022 over AS4 (FI-to-FI)
Canonical reference for ISO 20022 messages transported via AS4 between financial institutions. Used by gateway teams, auditors, and scheme designers. as4-411 provides **directory and resolution only**; it does not parse ISO 20022, perform settlement, or replace AS4 MSH (see [ADR-000](../adr/000-scope-and-non-goals.md)).
---
## 1. Overview and layer model
ISO 20022 is the **business payload** (pacs, camt); AS4 (ebMS 3.0) is the **messaging envelope**; HTTPS is the **transport**. Identity-based addressing: PartyId (BIC, LEI) is resolved by the directory to endpoint URL, certificates, and profile. No URL in the envelope—only PartyId.
```mermaid
flowchart TB
subgraph business [Business Layer]
ISO20022["ISO 20022 XML (pacs, camt)"]
end
subgraph messaging [Messaging Layer]
AS4["ebMS 3.0 / AS4 Envelope"]
AS4_detail["PartyId, Service, Action, MPC, Receipts"]
end
subgraph transport [Transport Layer]
HTTPS["HTTPS + TLS"]
end
ISO20022 --> AS4
AS4 --> AS4_detail
AS4_detail --> HTTPS
```
**Principle:** ISO 20022 never handles transport. AS4 never interprets business content.
---
## 2. Message classes (pacs, camt)
| ISO 20022 Message | Description |
| ----------------- | ----------- |
| pacs.008 | Customer Credit Transfer |
| pacs.009 | Financial Institution Credit Transfer |
| pacs.002 | Payment Status |
| camt.056 | Payment Cancellation |
| camt.029 | Resolution of Investigation |
| camt.053 | Statement |
| camt.054 | Debit/Credit Notification |
Payload: XML, UTF-8, strict XSD validation. May include BICs, LEIs, clearing member IDs. **AS4 treats payload as opaque.**
---
## 3. AS4 envelope mapping
ebMS headers: From.PartyId, To.PartyId, Service, Action, ConversationId, MessageId, MPC. **Profile ID:** `as4.fifi.iso20022.v1`. **Service:** `iso20022.fi`. **Action:** one per ISO 20022 message type (see §4).
Header skeleton (simplified):
```xml
<eb:Messaging>
<eb:UserMessage>
<eb:MessageInfo>
<eb:MessageId>uuid</eb:MessageId>
<eb:ConversationId>business-id</eb:ConversationId>
</eb:MessageInfo>
<eb:PartyInfo>
<eb:From>
<eb:PartyId type="BIC">BANKDEFFXXX</eb:PartyId>
</eb:From>
<eb:To>
<eb:PartyId type="BIC">BANKUS33XXX</eb:PartyId>
</eb:To>
</eb:PartyInfo>
<eb:CollaborationInfo>
<eb:Service>iso20022.fi</eb:Service>
<eb:Action>credit.transfer</eb:Action>
</eb:CollaborationInfo>
<eb:PayloadInfo>
<eb:PartInfo href="cid:pacs008.xml"/>
</eb:PayloadInfo>
</eb:UserMessage>
</eb:Messaging>
```
Payload: detached MIME; **signed → encrypted → attached**. Full sample: [iso20022-as4-sample-envelope.xml](iso20022-as4-sample-envelope.xml).
---
## 4. Addressing and identity
- **PartyId types:** BIC, LEI, internal.bank.code, scheme-specific (e.g. TARGET2, RTGS).
- **Directory:** maps PartyId → HTTPS endpoint URL + transport profile + receiver encryption cert + receipt requirements. **Profile:** `as4.fifi.iso20022.v1`.
- Discovery is directory-driven (contractual or internal); no public “discover any BIC” without directory data. See [data-model](../architecture/data-model.md) (`as4.partyId`, scope/partyIdType).
### AS4 FI-to-FI profile definition
| Feature | Requirement |
| ------- | ----------- |
| ebMS Version | ebMS 3.0 |
| Transport | HTTPS |
| Payload | ISO 20022 XML |
| Signing | Mandatory |
| Encryption | Mandatory |
| Receipts | Signed Receipts |
| Duplicate Detection | Enabled |
| Reliability | Exactly-once delivery |
### MPC usage
| MPC | Purpose |
| --- | ------- |
| `default` | Normal traffic |
| `urgent` | Time-critical payments |
| `bulk` | High-volume settlement batches |
### Service / Action taxonomy
**Service namespace:** `iso20022.fi`. **Rule:** one ISO 20022 message type = one AS4 Action.
| ISO 20022 Message | Action |
| ----------------- | ------ |
| pacs.008 | credit.transfer |
| pacs.009 | fi.credit.transfer |
| pacs.002 | payment.status |
| camt.056 | payment.cancellation |
| camt.029 | resolution.of.investigation |
| camt.053 | statement |
| camt.054 | notification |
---
## 5. Security model
- **Message-level:** XML Digital Signature (sender), XML Encryption (receiver); mandatory for profile `as4.fifi.iso20022.v1`. Optional compression.
- **Transport:** HTTPS, TLS; optional mTLS; network allowlisting.
- See [key-reference-model](../security/key-reference-model.md).
---
## 6. Reliability and receipts
- **Signed receipts** required (non-repudiation).
- **Duplicate detection** enabled.
- **Exactly-once delivery** per profile.
- Retries on transient failure; receipt stored by sender.
---
## 7. Processing lifecycle
```
ISO 20022 XML created
AS4 envelope built (PartyId, Service, Action)
Directory resolves PartyId → endpoint + cert
AS4 signs + encrypts
HTTPS transmission
Receiver AS4 gateway validates + decrypts
ISO 20022 payload extracted
ISO 20022 engine processes message
```
---
## 8. Error separation (transport vs business)
| Layer | Handled by | Examples |
| ----- | ---------- | -------- |
| Transport | AS4 only | Retries, receipts, duplicate suppression. |
| Business | ISO 20022 | pacs.002 (status), camt.056 (cancellation), scheme rejects. |
**Never mix transport errors with business rejects.**
---
## 9. as4-411 integration
**Provides:** PartyId → endpoint resolution; Service/Action constraints; certificate references; multi-rail identity (BIC ↔ LEI ↔ internal); deterministic, auditable routing directives.
**Does not:** Parse ISO 20022; perform settlement; replace AS4 MSH.
---
## 10. Compliance and audit notes
- Receipts and **resolution_trace** support audit (which source contributed the directive).
- BIC/LEI are Tier 0/1 per [data-classification](../security/data-classification.md).
- Who may call resolve and what they see: [trust-model](../security/trust-model.md).
---
## 11. Sample AS4 envelopes
See §3 for header skeleton. Full anonymized envelope: [iso20022-as4-sample-envelope.xml](iso20022-as4-sample-envelope.xml). Resolution examples: [../examples/iso20022-as4-resolution-examples.yaml](../examples/iso20022-as4-resolution-examples.yaml).
---
## 12. as4-411 resolution examples
### Example 1 — BIC-based resolution
**Input**
- To.PartyId = BANKUS33XXX
- PartyIdType = BIC
- Service = iso20022.fi
- Action = credit.transfer
**Resolution output**
- Endpoint: https://as4.bankus.com/fi
- EncryptionCert: vault://certs/bankus/iso20022
- Profile: as4.fifi.iso20022.v1
- Receipts: signed
### Example 2 — LEI-based resolution
**Input**
- To.PartyId = 5493001KJTIIGC8Y1R12
- PartyIdType = LEI
**Mapping**
- LEI → BIC(s) → AS4 Endpoint
**Output**
- Same directive structure as BIC.
- Evidence includes LEI→BIC mapping source.
(JSON request/response shapes in [../examples/iso20022-as4-resolution-examples.yaml](../examples/iso20022-as4-resolution-examples.yaml) and [OpenAPI](../api/openapi.yaml).)
---
## 13. RTGS-specific nuances
- **Characteristics:** Real-time settlement, tight SLA windows, liquidity constraints.
- **AS4 adjustments:**
| Aspect | RTGS requirement |
| ------ | ----------------- |
| MPC | `urgent` |
| Retry | Minimal / controlled |
| Timeouts | Aggressive |
| Receipts | Mandatory, immediate |
- **Message patterns:** pacs.008 / pacs.009; immediate pacs.002 response expected.
---
## 14. Cross-border correspondent banking
- **Topology:** Bank A → Correspondent X → Correspondent Y → Bank B.
- **as4-411 role:** Resolve **next hop**, not final destination; maintain correspondent routing tables; apply jurisdiction and currency policies.
- **Envelope:** Each hop = new AS4 envelope; business ConversationId preserved; transport MessageId regenerated.
---
## 15. CBDC / tokenized settlement overlays
- **Overlay model:** ISO 20022 remains the **instruction layer**; token/CBDC rails provide the **settlement layer**.
- **Directory extensions:** Map PartyId → wallet/DLT endpoint; store settlement rail capability (RTGS, CBDC, tokenized deposit). See [cbdc-settlement-adapter](../architecture/cbdc-settlement-adapter.md).
- **Dual-track:** ISO 20022 instruction → AS4 transport → settlement adapter (RTGS or CBDC ledger).
---
## 16. Appendix: ISO 20022 over AS4 vs over API
| Dimension | AS4 | API |
| --------- | --- | --- |
| Reliability | Guaranteed | Best-effort |
| Non-repudiation | Built-in | Custom |
| Identity | PartyId-based | URL/token-based |
| Audit | Native | Add-on |
| Regulatory fit | High | Medium |
| Latency | Higher | Lower |
**Guidance:** AS4 for interbank, regulated, cross-border. API for internal, fintech, low-latency. **Hybrid:** API inside the bank; AS4 between banks.
---
## 17. Rail-template alignment
| Section | Content |
| ------- | ------- |
| Owner/authority | ISO 20022, OASIS ebMS 3.0 / AS4; as4-411 directory only. |
| Identifier scheme | BIC, LEI; as4.partyId with scope/partyIdType. |
| Addressing | HTTPS endpoint; profile as4.fifi.iso20022.v1. |
| Security | Mandatory sign + encrypt; HTTPS; optional mTLS. |
| Discovery | Directory-driven; no public discover-any-BIC. |
| Compliance | data-classification (BIC/LEI Tier 0/1). |
| Sample payloads | §1112; test-vectors and scheme profiles. |
Scheme-specific profiles (TARGET2, Fedwire, CHAPS): [iso20022-scheme-profiles.md](iso20022-scheme-profiles.md).

View File

@@ -0,0 +1,39 @@
# Scheme-specific profiles (ISO 20022 over AS4)
Constraint layers on top of the base FI-to-FI profile **as4.fifi.iso20022.v1** (service **iso20022.fi**, same [Action taxonomy](iso20022-over-as4.md#service--action-taxonomy)). The directory may store scheme in constraints or as transport_profile variant (e.g. `as4.fifi.iso20022.v1.target2`).
---
## TARGET2
- **Base profile:** as4.fifi.iso20022.v1
- **Identifiers:** BIC; TARGET2 participant ID (scheme-specific)
- **MPC:** `urgent` for RTGS traffic
- **SLA / timeouts:** Per ECB TARGET2 documentation; tight windows for real-time settlement
- **Reference:** ECB TARGET2 documentation and rulebooks
---
## Fedwire
- **Base profile:** as4.fifi.iso20022.v1
- **Identifiers:** Fedwire-specific participant / routing identifiers; US-facing
- **MPC:** As per scheme (e.g. `default` or `urgent` for time-critical)
- **SLA / timeouts:** Per Fedwire rules
- **Reference:** Fedwire documentation and operator rules
---
## CHAPS
- **Base profile:** as4.fifi.iso20022.v1
- **Identifiers:** UK CHAPS participant identifiers; BIC where applicable
- **MPC:** As per CHAPS rules (e.g. `urgent` for same-day)
- **SLA / timeouts:** Per CHAPS rules
- **Reference:** CHAPS documentation and Bank of England rules
---
## Catalog
See [catalog.md](catalog.md). ISO 20022 over AS4 (FI-to-FI) links to this document for scheme-specific profiles.

30
docs/protocols/ktt.md Normal file
View File

@@ -0,0 +1,30 @@
# KTT Rail (Placeholder)
## Status
KTT is a **rail plugin slot** until sector-specific definition. The name is used in different sectors for different systems; this document reserves the slot and describes placeholder behavior.
## Identifier Types
- **ktt.id** — Generic KTT identifier; format: alphanumeric, optional `.` `_` `-`.
- **ktt.participantId** — Same validation as ktt.id.
Validation is in `@as4-411/core` (validation.ts). A valid `ktt.*` identifier can be stored and resolved to a RouteDirective like any other identifier once directory data exists.
## Endpoints
To be defined when the rail is specified. Use standard protocol values (https, mq, etc.) and a profile name indicating the KTT channel.
## Trust and Directory Source
- **Authoritative directory source(s):** TBD per sector.
- **Trust constraints:** TBD. Prefer tenant scoping and explicit allow/deny.
## Connector
- **packages/connectors/ktt**: Placeholder adapter with `ingestFromFile` and `ingestFromApi` stubs. When the rail is defined, implement ingest that maps external participants/identifiers/endpoints into the core directory model.
## Acceptance
- A valid `ktt.*` identifier resolves to at least one RouteDirective when directory data is present.
- Adapter supports ingest (file + API modes) as stubs; full implementation when KTT is clarified.

View File

@@ -0,0 +1,9 @@
# ISO 20022 over AS4 — Golden test vectors
Use these as golden request/response pairs for resolver tests. See [testing-strategy](../../architecture/testing-strategy.md).
- **bic-resolution.json** — BIC + iso20022.fi + credit.transfer → primary directive with profile as4.fifi.iso20022.v1.
- **lei-resolution.json** — LEI resolution; evidence may include LEI→BIC mapping.
- **negative-unknown-identifier.json** — Unknown identifier → empty directives, negative_cache_ttl set.
Tests: seed store (or routing artifact) with participant/endpoint for BIC/LEI; run resolve with request; assert output matches expectedResponse (or key fields). Placeholder URLs and cert refs (e.g. https://as4.bankus.com/fi) are for assertion only; replace with test fixtures as needed.

View File

@@ -0,0 +1,29 @@
{
"description": "BIC-based resolution for ISO 20022 FI-to-FI. Service iso20022.fi, action credit.transfer.",
"request": {
"identifiers": [
{ "type": "as4.partyId", "value": "BANKUS33XXX", "scope": "BIC" }
],
"serviceContext": {
"service": "iso20022.fi",
"action": "credit.transfer"
}
},
"expectedResponse": {
"primary": {
"target_protocol": "as4",
"target_address": "https://as4.bankus.com/fi",
"transport_profile": "as4.fifi.iso20022.v1",
"security": {
"signRequired": true,
"encryptRequired": true,
"keyRefs": ["vault://certs/bankus/iso20022"]
},
"service_context": {
"service": "iso20022.fi",
"action": "credit.transfer"
}
},
"resolution_trace": [{ "source": "internal directory" }]
}
}

View File

@@ -0,0 +1,30 @@
{
"description": "LEI-based resolution; directory maps LEI to BIC(s) then to AS4 endpoint. Evidence may include LEI->BIC mapping source.",
"request": {
"identifiers": [
{ "type": "as4.partyId", "value": "5493001KJTIIGC8Y1R12", "scope": "LEI" }
],
"serviceContext": {
"service": "iso20022.fi",
"action": "fi.credit.transfer"
}
},
"expectedResponse": {
"primary": {
"target_protocol": "as4",
"target_address": "https://as4.bankus.com/fi",
"transport_profile": "as4.fifi.iso20022.v1",
"security": {
"signRequired": true,
"encryptRequired": true,
"keyRefs": ["vault://certs/bankus/iso20022"]
},
"service_context": {
"service": "iso20022.fi",
"action": "fi.credit.transfer"
},
"evidence": [{ "source": "internal directory", "message": "LEI to BIC mapping applied" }]
},
"resolution_trace": [{ "source": "internal directory" }]
}
}

View File

@@ -0,0 +1,16 @@
{
"description": "Unknown BIC/LEI: no match. Response must have no primary (or empty directives) and negative_cache_ttl set.",
"request": {
"identifiers": [
{ "type": "as4.partyId", "value": "UNKNOWNBICXXX", "scope": "BIC" }
],
"serviceContext": {
"service": "iso20022.fi",
"action": "credit.transfer"
}
},
"expectedResponse": {
"directives": [],
"negative_cache_ttl": 60
}
}

0
docs/security/.gitkeep Normal file
View File

View File

@@ -0,0 +1,32 @@
# Sensitive Data Classification
Data in the as4-411 directory is classified into tiers. Storage, access control, and encryption must follow these tiers. See [ADR-004](../adr/004-sensitive-data-classification.md).
## Tiers
| Tier | Name | Examples | Storage / access |
|------|-----------------|-----------------------------------------------|------------------|
| **0** | Public | BIC, LEI, public BIN range metadata | No encryption required; may be shared across tenants where applicable |
| **1** | Internal | PartyId, endpoint URL, participant name | Access-controlled; tenant-scoped; encrypt in transit |
| **2** | Confidential | MID, TID, contract routing, DTC participant/account IDs | Field-level encryption at rest; strict RBAC/ABAC; per-tenant keys preferred |
| **3** | Regulated/secrets | Tokens, key refs, PII-like attributes | Strongest controls; vault refs only; immutable audit; never log in plaintext |
## Mapping: tables and fields
- **identifiers:** `value` is Tier 0 when type is BIC/LEI/public; Tier 2 when type is mid, tid, dtc.participantId, dtc.accountId, or other contract-scoped IDs. `identifier_type` and `scope` are Tier 1.
- **endpoints:** `address` and `profile` are Tier 1 (internal). If they encode tenant-specific routes, treat as Tier 2 in policy.
- **credentials:** Only references (vault_ref, fingerprint)—Tier 3 for the ref; no private material in DB.
- **routing_artifacts:** Payload content may include Tier 2 (e.g. BIN table overrides with tenant/MID). Encrypt payload or use per-tenant encryption for Tier 2 content.
- **participants / tenants:** Names and IDs are Tier 1; tenant-private participant data is Tier 1 or Tier 2 depending on protocol (see protocol docs).
- **policies / audit_log:** Tier 1; audit_log must be immutable and optionally hash-chained.
## Enforcement
- **Field-level encryption:** Tier 2+ fields must be encrypted at rest (application-level or TDE with per-tenant keys where required). Tier 3: store only references; material in vault/KMS.
- **RBAC/ABAC:** Strict role- and attribute-based access; resolution and admin APIs enforce tenant scope and policy. See [tenant-model](../architecture/tenant-model.md) and [ADR-003](../adr/003-multi-tenancy-and-rls.md).
- **Audit:** All access to Tier 2+ and all mutations must be logged in audit_log; logs must not contain Tier 3 material in plaintext.
- **Allowed storage and access:** Document per table in operations runbooks; new fields must be assigned a tier before merge.
## Trust model for resolve consumers
Who may call resolve, what they can see, and how to prevent endpoint enumeration are described in [trust-model.md](trust-model.md).

View File

@@ -0,0 +1,34 @@
# Key Reference Model and Rotation
## Overview
as4-411 does not store private keys or raw secrets. It stores **references** to key material held in a vault or KMS. This document describes the reference model and rotation procedure.
## Key Reference Model
### Stored Data
- **credentials** table (per participant):
- `credential_type`: `tls` | `sign` | `encrypt`
- `vault_ref`: URI or identifier for the key in the vault/KMS (e.g. `vault://secret/tenant1/cert-id`, or KMS key ARN).
- `fingerprint`: Optional certificate or public key fingerprint for verification.
- `valid_from` / `valid_to`: Validity window for the referenced material.
- No private key material, no PEM bodies, and no long-lived secrets are stored in the directory database.
### Resolution Output
- `RouteDirective.security.keyRefs` can carry the same vault/KMS references (or short-lived tokens) so that gateways resolve “which key” and then fetch material from the vault within their trust boundary.
## Rotation Procedure
1. **Stage new cert/key** in vault/KMS; obtain new `vault_ref` and optional `fingerprint`.
2. **Add or update** credential record with new `vault_ref`, `valid_from` set to now (or overlap start).
3. **Dual-valid overlap:** Keep previous credential valid until cutover. Configure overlap window so gateways can refresh to the new key.
4. **Cutover:** Set old credentials `valid_to` to end of overlap (or mark inactive). Prefer new credential via higher priority or by updating endpoint/participant metadata.
5. **Revoke/archive** old key in vault per policy; remove or expire old credential record.
## Compliance Notes
- Audit log records changes to credential refs (who/what/when).
- Per-rail requirements (e.g. card networks, DTC) may impose additional constraints on key lifecycle and storage; see [protocol docs](../protocols/) where applicable.

View File

@@ -0,0 +1,23 @@
# Trust Model for Resolve Consumers
This document describes who can call the resolve API, what they can see, and how we limit abuse (e.g. endpoint enumeration). See also [data-classification.md](data-classification.md) and the [API spec](../api/openapi.yaml).
## Who can call resolve
- **Authenticated callers:** Resolve must be gated by authentication. Support at least: API keys or JWT scoped to a tenant (and optionally to a set of protocols or contracts). Per-tenant auth claims ensure that a caller only receives directives for data they are entitled to.
- **Authorization:** After authentication, apply tenant scope and ABAC policies so that the result set only includes participants, endpoints, and routing artifacts the caller is allowed to use. No cross-tenant leakage.
## What callers can see
- Responses are **filtered by entitlement:** Only protocols and endpoints the caller is entitled to appear in the directive list. Internal identifiers or participant details not needed for routing may be omitted or redacted in the response.
- **Evidence and trace:** Resolution evidence and `resolution_trace` may expose source names (e.g. "internal directory", "SMP cache"). Do not include raw confidential data (Tier 2+) in trace; use source labels and optional correlation IDs only where needed for debugging.
## Preventing endpoint enumeration
- **Rate limits and anomaly detection:** Apply per-tenant (and optionally per-API-key) rate limits to resolve and bulk-resolve. Detect and throttle anomalous patterns (e.g. large volumes of distinct identifiers in short windows) to reduce enumeration risk.
- **Response filtering:** Only return directives for identifiers and contracts the caller is authorized for. Return a generic "not found" or empty result for unauthorized or missing keys where appropriate, without leaking existence of data the caller cannot access.
- **Optional proof of possession:** For high-assurance deployments, require mTLS client certificates or signed tokens so that only approved gateways or clients can call resolve. Document in API and operations.
## Operations
- Document required auth method (e.g. API key, JWT, mTLS) in deployment and API docs. Document rate limits and any per-tenant TTL or quota in operations runbooks.