Full SDK workspace: core, contracts, sdk, cli, browser-host, browser-kit, federation, omega-core, oracle, self-healing, strategies, tools, emacs. Clean extraction from the development monorepo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.9 KiB
Architecture Notes
Architect's design rationale for the typescript-federation package. Documents accommodation of Matrix-3 integration requirements while preserving federation invariants.
Design Philosophy
This package implements a classic Anti-Corruption Layer (Fowler) sitting between:
- Federation / Edge (MXENV/MXURI semantics + mailbox transport constraints), and
- Local component runtime (your existing component system / handler contracts).
The trick is: make the integration seam tolerant and observable, while keeping the core routing/security invariants rigid.
This is implemented as a "strangler fig" compatibility layer, not a rewrite.
Invariants (MUST NOT VIOLATE)
The invariants live in the federation boundary, not in the developer ergonomics:
| Invariant | Description |
|---|---|
| Mailbox pattern (no wildcards) | EdgeGateway subscribes to exact ingress topics (public.ingress.<realm> and/or mx/in/1/<realm>/00) |
| Semantic URI ≠ delivery topic | MXURI parsing canonicalizes to toRealm + toPath; delivery goes to mailbox topic |
| Anti-relay | Block any attempt to turn EdgeGateway into an open relay; enforce replyTo.realm == from.realm |
| Replay / rate limit / policy | Centralized at ingress boundary, not on local bus |
Core principle: Strict at trust boundaries, tolerant inside the trust domain.
Accommodations Implemented
P0 - Integration Seam (Must-Have)
#7 Internal Request Format
Invariant pressure: None (intra-realm only, local bus).
Pattern: Bounded context with stable contract + extension points.
Implementation:
- Documented internal wire contract:
interface InternalRequestWire {
correlationId: string;
replyTo: string; // internal topic (string)
params: unknown; // decoded body
_mx?: { // optional (config)
fromRealm: string;
incomingProfile: 'matrix3' | 'mxenv1';
msgId?: string;
principalKey?: string;
traceId?: string;
};
envelope?: unknown; // optional (config)
}
- Config switches on EdgeGateway:
| Option | Default | Purpose |
|---|---|---|
includeInternalMeta |
true |
Set false if handlers are strict-schema and choke on extra fields |
includeInboundEnvelope |
false |
Set true if handlers want full inbound envelope visibility |
This is the Strategy/Policy pattern at the seam without infecting the core.
#8 Lenient Internal Replies
Invariant pressure: None (local bus only).
Pattern: Tolerant Reader (Postel's Law) + consumer-driven contract.
Implementation: normalizeInternalReply(rawPayload, fallbackCorrelationId)
EdgeGateway accepts any of these reply shapes:
| Shape | Interpretation |
|---|---|
{ correlationId, ok, body, error? } |
Full strict reply |
{ ok: true, body: ... } |
Semi-strict (no correlationId) |
{ ok: false, error: { code, message } } |
Error reply |
{ result: ... } |
Inferred ok=true, body=result |
Raw JSON value ("hello", 123, {...}) |
Inferred ok=true, body=<value> |
| Invalid JSON string | Inferred ok=true, body=<raw string> |
This allows existing handlers to work without forced schema migration.
#5 Matrix-3 Compatibility Tests
Invariant pressure: None (proof harnessing).
Implementation: tests/integration/matrix3_compat.test.ts
Covers:
- Legacy request envelope with
ttlMs - Flat string
replyTo - Legacy replies with
ok:true/falsedetection - Legacy URI parsing (no
/-/delimiter) - Lenient internal reply
{ result: ... }end-to-end
Executable evidence, not hand-wavy claims.
P1 - Observability + Typing
#3 EdgeGateway Events
Invariant pressure: None (pure side-effects).
Pattern: Observer (GoF) / structured tracing hooks.
Implementation:
interface EdgeGatewayOptions {
onInboundRequest?: (envelope: TaggedEnvelope, internalTopic: string) => void;
onInboundReply?: (envelope: TaggedEnvelope) => void;
onOutboundReply?: (replyEnvelope, ingressTopic: string) => void;
onTimeout?: (correlationId: string, toPath: string) => void;
onDrop?: (reason: string, topic: string, rawPayload: string) => void;
onSecurityBlock?: (reason: SecurityBlockReason, envelope: TaggedEnvelope) => void;
}
Makes integration debugging feasible (especially with nats sub and correlated logs).
#6 Export Envelope Type Aliases
export type {
CrossRealmEnvelopeLegacy as CrossRealmEnvelope,
ReplyEnvelopeLegacy as ReplyEnvelope,
} from './envelope';
Low-friction TypeScript ergonomics: import from package and keep type compatibility.
#9 TTL Clarification
| Field | Semantics |
|---|---|
MxEnvelopeV1.ttl |
Hop limit (routing TTL) |
Legacy ttlMs |
Expiry duration (milliseconds) |
The compat layer does not coerce ttlMs to ttl (different units, different semantics).
P2 - Transport Ergonomics
#2 Transport Interface Meta
interface Transport {
publish(topic: string, payload: string, meta?: TransportMeta): void;
subscribe(topic: string, handler: (topic, payload, meta?) => void): () => void;
}
interface TransportMeta {
qos?: 0 | 1 | 2;
retain?: boolean;
// extension keys allowed
}
Pattern: Ports-and-adapters. Core stays oblivious, adapters preserve delivery guarantees.
Metrics + Config Validation
Implementation:
// Fail-fast validation
const validation = EdgeGateway.validateOptions(opts);
if (!validation.valid) throw new Error(validation.errors.join('; '));
// Runtime metrics
const metrics = edge.getMetrics();
// { inboundRequests, inboundReplies, outboundReplies, timeouts, securityBlocks, pendingRequests }
edge.resetMetrics();
What Was NOT Implemented (And Why)
File Naming (PascalCase)
Safe but cosmetic. Cross-platform casing hazards (macOS vs Linux CI). Do as a single mechanical commit with consistent import updates if desired.
Raw Mode
With lenient reply normalization + includeInboundEnvelope, raw mode is largely redundant.
If still needed, formalize an InternalCodec Strategy - but that's a bigger API commitment.
Graceful Shutdown (Drain Pending)
Lifecycle semantic decision: fail fast (send negative replies) or wait? Should be pinned to product requirements before implementing.
Integration Recipe
Minimal "no churn" bridge wiring:
import {
EdgeGateway,
WIRE_PROFILES,
} from '@open-matrix/federation';
const edge = new EdgeGateway({
localRealm: 'matrix-3',
profile: WIRE_PROFILES.matrix3, // legacy mailbox + legacy envelope
backbone: backboneTransport, // NatsTransport instance
localBus: localTransport, // NatsTransport or InMemoryBroker
// Integration seam knobs:
includeInternalMeta: false, // if handlers are strict schema
includeInboundEnvelope: true, // if handlers want full envelope visibility
// Observability:
onInboundRequest: (env, topic) => console.log('IN', env.profile, topic),
onOutboundReply: (rep, ingressTopic) => console.log('OUT', ingressTopic),
onTimeout: (cid, toPath) => console.warn('TIMEOUT', cid, toPath),
onSecurityBlock: (reason) => console.warn('SECURITY', reason),
});
edge.start();
On the internal side, your component runtime just needs to:
- Subscribe to internal topic
mx/1/matrix-3/<toPath> - Read
replyTofrom the request payload - Publish any reply shape to
replyTo(raw value is fine)
No forced schema migration.
Future Enhancements (Invariant-Safe)
The next "developer happiness" increment is to formalize an InternalCodec Strategy (encode/decode) to plug existing internal envelope formats 1:1. But with the changes above, this is likely unnecessary - integration without churn is already achieved while keeping the federation boundary strict.