hivecast-sdk/packages/federation/ARCHITECTURE-NOTES.md
hypnotranz 1a0419dc0c feat: initial Matrix SDK
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>
2026-06-07 15:54:15 -06:00

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:

  1. 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)
}
  1. 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/false detection
  • 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:

  1. Subscribe to internal topic mx/1/matrix-3/<toPath>
  2. Read replyTo from the request payload
  3. 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.