Skip to content

feat(fabric): add the RoutingFabric seam for binding-cache invalidation (RIG-3108) - #955

Open
rigel-mintaka wants to merge 1 commit into
compass-managed/rig-3107-fabric-wildcard-subscribefrom
compass-managed/rig-3108-routing-fabric
Open

feat(fabric): add the RoutingFabric seam for binding-cache invalidation (RIG-3108)#955
rigel-mintaka wants to merge 1 commit into
compass-managed/rig-3107-fabric-wildcard-subscribefrom
compass-managed/rig-3108-routing-fabric

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 6 PRs:

  1. main
  2. chore(go): raise module floor to 1.26 + absorb modernize sweep (RIG-3107) #876
  3. feat(fabric): add NATS EventFabric + RunnerFabric seams (RIG-3107) #877
  4. feat(fabric): add tenant-wildcard comms subscribe (RIG-3107) #903
  5. "feat(fabric): add the RoutingFabric seam for binding-cache invalidation (RIG-3108)" (this PR)
  6. refactor(store): maintain updated_at with a trigger, not by hand (RIG-3495) #989
  7. feat(store): add the durable session_bindings table and its store methods (RIG-3108) #990

Session bindings are moving from one hub's RAM to Postgres, with the hub's maps
demoted to per-instance caches. Those caches need an invalidation signal, and
neither existing seam can carry one.

EventFabric cannot: both its read paths are durable queue-group consumers, so
each event is claimed by exactly one instance. An invalidation delivered that
way reaches one hub and leaves every other serving a stale binding, with no
error and no missing ack to reveal it. Its interface also promises durable
at-least-once fan-out, while an invalidation is deliberately droppable — a lost
one degrades to a cache-miss re-read against Postgres, which is the arbiter. Two
opposite delivery contracts behind one interface would leave a caller unable to
tell from the type whether a message can be dropped, so this is a third seam
rather than a method on an existing one.

The subject grammar is compass.routing.binding.<tenant>, with the literal
binding pinned to token 3. That ordering is a correctness requirement: the
comms stream captures compass.*.comms.*, which matches any four-token subject
whose third token is comms, so the other ordering
(compass.routing.<tenant>.binding) would see a tenant literally named comms
publish into the JetStream comms stream. Pinning the literal makes the collision
unrepresentable for every tenant value instead of merely unlikely.

Publish cross-checks the payload's tenant against the subject's, because the read
side subscribes tenant-wildcard and its callback never sees the subject — the
payload's tenant is the receiver's only scope.

The record decided the mechanism ("core NATS carrying invalidation/update
events... core NATS at-most-once suffices because a dropped invalidation degrades
to a cache-miss re-read") but not the seam; §T4's "over the EventFabric" phrasing
is a paraphrase of that section, not a separate ruling.

The store migration and the hub's cache demotion follow in later commits; this
adds only the seam.

Co-authored-by: Matt Wilkinson matt@rigel.build

@linear-code

linear-code Bot commented Sep 7, 2026

Copy link
Copy Markdown

RIG-3108

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-managed-rig-3108-rou.compass-eng-docs.pages.dev

Deployed from compass-managed/rig-3108-routing-fabric at 90c232e.

Changed pages:

…on (RIG-3108)

Session bindings are moving from one hub's RAM to Postgres, with the hub's maps
demoted to per-instance caches. Those caches need an invalidation signal, and
neither existing seam can carry one.

`EventFabric` cannot: both its read paths are durable queue-group consumers, so
each event is claimed by exactly one instance. An invalidation delivered that
way reaches one hub and leaves every other serving a stale binding, with no
error and no missing ack to reveal it. Its interface also promises durable
at-least-once fan-out, while an invalidation is deliberately droppable — a lost
one degrades to a cache-miss re-read against Postgres, which is the arbiter. Two
opposite delivery contracts behind one interface would leave a caller unable to
tell from the type whether a message can be dropped, so this is a third seam
rather than a method on an existing one.

The subject grammar is `compass.routing.binding.<tenant>`, with the literal
`binding` pinned to token 3. That ordering is a correctness requirement: the
comms stream captures `compass.*.comms.*`, which matches any four-token subject
whose third token is `comms`, so the other ordering
(`compass.routing.<tenant>.binding`) would see a tenant literally named `comms`
publish into the JetStream comms stream. Pinning the literal makes the collision
unrepresentable for every tenant value instead of merely unlikely.

Publish cross-checks the payload's tenant against the subject's, because the read
side subscribes tenant-wildcard and its callback never sees the subject — the
payload's tenant is the receiver's only scope.

The record decided the mechanism ("core NATS carrying invalidation/update
events... core NATS at-most-once suffices because a dropped invalidation degrades
to a cache-miss re-read") but not the seam; §T4's "over the EventFabric" phrasing
is a paraphrase of that section, not a separate ruling.

The store migration and the hub's cache demotion follow in later commits; this
adds only the seam.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-managed/rig-3108-routing-fabric branch from 21efbea to 90c232e Compare September 7, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant