ADR-0013: JIT Federated Discovery (The Oracle)
- Status: Implemented
- Date: 2026-05-15
Context
Previously, Framework M relied on a Broadcast Discovery model where every service unconditionally published its entire schema and metadata to a shared bus (NATS) or local file. While simple, this approach suffered from:
- Noise: Constant "heartbeat" broadcasts created high CPU/network overhead in local dev.
- Scalability: The "Global Schema" became too large to synchronize reliably across many services.
- Monolith/Macroservice Friction: A monolith pretending to be many services would broadcast overlapping data, causing discovery conflicts.
We needed a system that was Lazy, Batch-capable, and Mode-aware.
Decision
Implement JIT (Just-In-Time) Federated Discovery, centralizing resource ownership resolution into a gateway-backed "Oracle" pattern.
1. The Frontend Oracle: DiscoveryClient
The DiscoveryClient singleton in framework-m-plugin-sdk is the primary interface for all discovery.
- It maintains a local cache of
DiscoveryUnitobjects (Service URL, ApiUrl, MetaUrl). - It performs Batch Queries to resolve missing resource owners before any data fetching occurs.
- It is initialized during the Shell's
prewarmCorephase to resolve essential services (Auth, Settings).
2. The JIT Handshake (Protocol)
When a resource (e.g., sales.Invoice) is requested but its owner is unknown:
- Fuzzy Match: The
PluginRegistrychecks ifInvoiceis a suffix of any known registered resource. - Batch Query: If unknown, the
DiscoveryClientpings/api/gateway/discovery/query(the Oracle).- Request:
{"resources": ["sales.Invoice"], "services": {"finance": 0}} - Response:
{"resources": {"sales.Invoice": "finance"}, "services": {"finance": {"url": "http://...", "apiUrl": "/api/finance/v1"}}}
- Request:
3. Local Development: The Shared-File Protocol
To enable macroservice discovery locally without NATS:
- All services write their manifests to
~/.framework_m_discovery.jsonon startup. - The
EnvDiscoveryAdapterinframework-m-standardwatches this file. - This creates a decentralized discovery bucket that is zero-conf for developers.
4. Production: NATS KV & Real-time Resource Maps
- Services publish their manifests to a NATS Key-Value store.
- The
NatsDiscoveryAdaptermaintains a subscription to manifest updates. - It rebuilds a high-performance
_resource_map(DocType -> ServiceName) in memory to serve JIT queries with sub-millisecond latency.
5. DocType Ownership Filtering
To prevent "over-claiming":
BroadcastSchemanow usesget_installed_apps()to filter DocTypes.- In
MACROSERVICEmode, a service only broadcasts DocTypes whose module prefix matches an app it explicitly owns.
Consequences
Positive
- Efficiency: Services only broadcast once on startup; clients only query what they need.
- Robustness: Monoliths no longer conflict with macroservices in the same environment.
- Transparency: Every API resolution is now traceable through the
DiscoveryClientlogs.
Negative
- Test Isolation: Because the
DiscoveryClientis a singleton, all tests MUST now callDiscoveryClient.reset()andPluginRegistry.reset()inafterEachhooks to prevent state leakage. - Latency: The first request for a new resource type incurs a discovery round-trip (mitigated by batching and pre-warming).
References
- ADR-0012: Dynamic API Prefixing and Service Namespacing
libs/framework-m-plugin-sdk/src/core/DiscoveryClient.tslibs/framework-m-standard/src/framework_m_standard/adapters/discovery/env.py