A Registry That Knows What’s Alive

CaseHub has an EndpointRegistry — register a descriptor, resolve it by path, discover by type. It works well for what it does: mapping named endpoints to their protocol and properties. But the modular fleet architecture needs something the endpoint model can’t express. Services come and go. They have heartbeats and TTLs. They form relationships — an app owns pools, pools run on nodes, channels serve apps. When a service misses its heartbeat window, something needs to notice. When an app deregisters, its pools need draining.

EndpointRegistry keys on (Path, tenancyId). The topology we need keys on (id, type, namespace) — a different model entirely. Extending EndpointRegistry would have meant cramming runtime lifecycle semantics into a registry designed for static endpoint descriptors. A sibling SPI was the cleaner call.

RegistryService landed today as a new SPI in platform-api. The core contract: register typed entries with a TTL, send heartbeats to stay alive, link entries via typed relationships (owns, runs-on, serves, consumes), and watch for state transitions. Entries that miss their TTL window transition to DOWN and fire a HEALTH_CHANGED event. Deregistration cascades through relationships — remove an entry and all its links disappear with it.

The implementation follows the platform’s dual-core pattern. InMemoryRegistryService is a framework-neutral POJO taking EntityManager and Consumer<RegistryEvent> — no CDI, no Spring. Quarkus gets @Produces @Alternative @Priority(50) wiring. The in-memory variant uses ConcurrentHashMap for entries and CopyOnWriteArrayList for relationships and watch subscribers. HeartbeatScheduler runs a cross-tenant scan, marking expired entries as DOWN.

The JPA tier shares the same dual-core approach. JpaRegistryService is another framework-neutral POJO in registry-jpa-common — same constructor signature, same Consumer<RegistryEvent> bridge. The Quarkus module wraps it with CDI; the Spring module is auto-generated by spring-generator from the Quarkus @Produces method. One @Produces line in Quarkus produces a complete Spring @AutoConfiguration with ApplicationEventPublisher bridging — the generator handled the Event<T> → Consumer<T> translation and the EntityManager injection without any manual Spring code.

CascadeRule types went in as Stage 4 foundation. Four actions: DEREGISTER (auto-remove targets), MARK_ORPHANED (degrade), NOTIFY (fire event, let domain code decide), NONE. The rules are storable in both InMemory and JPA backends. The actual cascade execution — the durable delivery part where deregistering an app reliably drains its pools even if downstream services are temporarily down — that’s Stage 4 proper.

The interesting architectural question is whether the registry will ever need to be more than an infrastructure service. Right now it’s designed as something Claudony, scaffold, and other ops-tier components talk to over REST. Spring apps connect via REST too — they don’t need the SPI on their classpath. But having the Spring auto-config ready means someone could embed a registry inside a Spring Boot app if the use case surfaces. Near-zero cost for future optionality.

Seven new modules, one SPI that unifies what was previously four separate registration mechanisms across qhorus and claudony. Stage 2 migrates those existing registries onto this one. The strangler fig pattern — old registries stay operational until each consumer migrates.