NATS Subject Structure

Microbus carries every microservice-to-microservice HTTP request and response over NATS. The NATS subject is the addressing string the broker uses to route the message. The layout of the subject is load-bearing for Microbus’s security model: NATS PUB/SUB ACLs are written against it, and the sender’s identity is extracted from it.

Request Subjects

A request subject has eight or more segments:

<plane>.<trust>.<port>.<src>.<dest>.<id_or_locality>.<method>.<path...>
SegmentSourceWildcarded in subscriptions?
planeTenant/test isolation prefixNo
trustsafe for non-:666, danger for :666, reply for repliesNo
portNumeric port the endpoint listens on, or _ when irrelevant* when port is 0
srcCaller hostname, flattened (._, other specials %xx)Always *
destCallee hostname, flattened (._, other specials %xx)No
id_or_localityid-XXXX, loc-XXX-YYY, or _No
methodUppercase HTTP method* when method is ANY
path...One segment per path part, percent-encoded* for path args, > for greedy path args

The path is variable-length: each /-separated path segment becomes one NATS segment. An empty path becomes the single segment _.

Plane

plane is an opaque prefix matching [0-9a-zA-Z]* that isolates one Microbus app’s traffic from another’s on the same NATS cluster. Three sources, in priority order:

  • MICROBUS_PLANE env var or connector.SetPlane(...). Used in production to multi-tenant a shared NATS cluster.
  • Under go test with no plane set, the plane is derived from the test name so concurrent test runs do not leak messages into each other.
  • Default microbus for local development.

Trust

trust collapses two access tiers into a single segment:

  • safe: every port other than :666.
  • danger: port :666, the trust-root tier (token mint, shell exec, privileged writes).
  • reply: used only on response subjects (described below).

The trust segment exists so “any port except :666” can be expressed as one ACL pattern (<plane>.safe.*.<from>.>) without colliding with the trust-root tier. Without it, NATS deny-precedence semantics make the carve-out impossible.

Source and Destination Hostnames

Hostnames are flattened to a single NATS segment in two steps. First, every . is replaced with _ so the hostname doesn’t collide with the NATS segment separator. Then any URL-special character that a route hostname is allowed to carry ($, !, ~, etc.) is percent-encoded as %xx (2-digit lowercase hex per byte). So payments.core becomes payments_core, www.example.com becomes www_example_com, and a route hostname my$.xml becomes my%24_xml.

Service identity hostnames pass strict validation that forbids URL specials and the underscore, so for typical traffic the destination segment contains only [a-z0-9_-] and matches the legacy form one-to-one. Route hostnames (registered via absolute //some.host/path subscriptions) are the only place %xx appears in a hostname segment.

The source segment is always * in subscriptions. A service does not discriminate at the SUB layer over which peers may call it, since that is the publisher’s PUB ACL’s responsibility. Per-source SUB would also explode the subscription count by allowed-callers.

In production, NATS PUB ACLs pin each connected microservice to publish only under its own <src> segment. The receiving connector parses the source segment off the inbound message and unconditionally overwrites the publisher-set Microbus-From-Host header with that ACL-verified value before any handler runs. A malformed subject yields an empty source, which the framework treats as an unverifiable inbound message and rejects.

id_or_locality Segment

A dedicated single-segment, distinct from the destination, that carries one of three values:

  • id-XXXX: a specific replica’s instance ID. Publishers use this to pin a fragment stream to the replica that acked fragment 1, to address a specific replica directly, or to route a reply back to the replica that made the original request.
  • loc-XXX-YYY: a locality identifier, broadest-first and hyphen-joined (the AWS region/AZ shape). For example, locality us-west-b-1 registers slots loc-us, loc-us-west, loc-us-west-b, loc-us-west-b-1. The publisher picks which slot to address based on the locality returned in prior response headers, narrowing toward the most specific slot whose subscribers actually answer; NATS only queue-group-dispatches within the chosen slot.
  • _: placeholder when neither prefix applies (the bare position).

Both prefixes are reserved. The framework rejects service hostnames whose first segment matches ^id- or ^loc- as well as absolute subscription routes (//host/...) that use the same prefixes. Publishers parse the URL hostname’s first segment and lift any id- or loc- prefix into the id/locality segment, leaving the remainder as the destination segment.

Method

Uppercased before emission. The literal string ANY is replaced with * in subscriptions to match every HTTP method. Unknown method tokens are rejected at registration time with 405 Method Not Allowed.

Path

Each /-separated segment of the route becomes one NATS segment. Encoding rules per segment:

InputOutput
empty pathsingle _ segment
empty segment (//)_
{name} or bare * (subscription only)* (NATS wildcard, matches any segment)
{name...} (subscription only, last segment)> (greedy, must be last)
[A-Za-z0-9-]passed through as-is
anything else (including ., _, {, }, URL specials, multi-byte UTF-8)%xx per byte (2-digit lowercase hex, matching url.PathEscape’s casing)

Path encoding is case-sensitive. Uppercase letters are passed through. Hostnames, methods, and the id/locality segment are lowercased; path segments are not.

In a published request, the path goes through the general escape rule: {foo} becomes %7bfoo%7d and a literal * segment becomes %2a, because publishers send concrete path values rather than patterns. The subscription wildcard exception only applies when registering a subscription, where {foo} and bare * are equivalent ways to write “any segment in this position”.

Note that path encoding is uniform %xx for every non-alphanumeric byte (including . and _), while hostname encoding keeps the legacy ._ substitution. The two segments live at different positions in the subject and are extracted independently, so the asymmetric encoding is unambiguous.

Reply Subjects

Replies use a fixed six-segment layout:

<plane>.reply._.<src>.<dest>.<id>

The reply trust segment alone identifies the channel; the port slot uses the _ placeholder to keep the subject at constant depth. Every connector subscribes once at startup to its own response subject with the source segment wildcarded:

<plane>.reply._.*.<own_dest>.<id>

This is the first subscription the connector activates, before OnStartup runs, because the connector itself makes outbound requests during startup (e.g. to configurator.core) and replies need somewhere to land.

Wildcards Summary

NATS supports two wildcards: * matches a single segment, > matches all remaining segments (must be last). Microbus uses both:

PositionSubscription formReason
Source segment*Subscriptions don’t discriminate over caller identity.
Port (when route is :0)*Wildcard port, by convention nothing publishes to 0.
Method (when ANY)*Endpoint accepts every HTTP method.
Path argument {name}*Single-segment path arg.
Greedy arg {name...}>Captures the path tail; must be the last route segment.
Reply source*Replies arrive from whichever destination handled the request.

Worked Examples

Published Subjects

ScenarioSubject
GET /PATH/to/file.html from by.com to example.com:80microbus.safe.80.by_com.example_com._.GET.PATH.to.file%2ehtml
Root path GET / from by.com to www.example.com:443microbus.safe.443.by_com.www_example_com._.GET._
Trust-root POST /mint on :666microbus.danger.666.by_com.example_com._.POST.mint
Per-instance pin to replica id-abcd1234microbus.safe.443.by_com.example_com.id-abcd1234.GET.path
Locality-aware call to loc-us-west.example.commicrobus.safe.443.by_com.example_com.loc-us-west.GET.path
Reply to by.com from example.com instance id-1234microbus.reply._.by_com.example_com.id-1234
Route hostname with URL special: my$.xmlmicrobus.safe.443.by_com.my%24_xml._.GET.path

Subscription Subjects

ScenarioSubject
Subscription for GET /PATH/to/file.html on :80microbus.safe.80.*.example_com._.GET.PATH.to.file%2ehtml
Greedy route POST /DIR/{file...} on :123microbus.safe.123.*.example_com._.POST.DIR.>
Wildcard port :0, method ANY, route /foo/{f}/bar/{b}microbus.safe.*.*.example_com._.*.foo.*.bar.*
Reply subscription owned by example.com instance id-1234microbus.reply._.*.example_com.id-1234

ACL Implications

ACLs are derived from source code at deploy time by gencreds, which scans each microservice’s code for the call patterns that produce traffic on the bus and emits one signed <hostname>_nats.creds file per microservice. Each microservice’s .creds carries an allow-list of the exact subject patterns the code actually publishes to and subscribes from, capability-style rather than coarsely permissive.

Every microservice emits the same six framework-implicit rules (control-plane PUB on :888, reply PUB, broad SUB on own destination, reply SUB, and broadcast control SUB on all), plus a danger-port SUB only when the microservice exposes :666 endpoints. Per-route rules layered on top come from the source-code scan: typed-client method calls produce per-call PUB rules, hooks produce per-event SUB rules, outbound triggers produce self-PUB rules.

Two patterns cover the bulk of production NATS ACL design:

  • Allow a service to receive from anyone on its own port range: SUB <plane>.safe.*.*.<own_dest>.> (the source wildcard is mandatory).
  • Allow a service to publish to a specific destination: PUB <plane>.safe.*.<own_src>.<dest>.> (the source segment is the verified identity; pinned by the broker to this user’s hostname).
  • Trust-root carve-out: Restrict PUB <plane>.danger.666.<own_src>.<dest>.> to the small named set of callers whose code actually invokes a :666 endpoint. Without the safe/danger split, this carve-out can’t be expressed without conflicting with the broad safe allow rules.
  • Replies: Each microservice needs SUB <plane>.reply._.*.<own_dest>.* to receive its own response stream, and PUB <plane>.reply._.<own_src>.> to publish replies (the greedy > covers any destination and instance id).

The receiving connector relies on NATS having enforced the <src> segment before delivery. That is what makes the verified-source overwrite of Microbus-From-Host a real authentication signal rather than a self-attestation.

Credentials and the Plane

gencreds produces .creds tied to a specific plane. The same source code can be signed for multiple deployments (prod, staging, and so on) by re-running with a different --plane.

Each microservice loads its own <hostname>_nats.creds from CWD at startup, with a shared-default nats.creds as a fallback. Each microservice in a bundle opens its own NATS connection with its own narrow .creds. Plane wildcards in ACLs (*.safe.*.<src>.<dest>.>) are an anti-pattern. They break the cross-environment isolation the plane segment exists to provide.

Short-Circuit Transport

When two connectors live in the same process, requests bypass NATS entirely and dispatch through a process-global prefix trie. The trie supports the same * and > wildcards as NATS, so subjects emitted for short-circuit delivery are byte-identical to the NATS form. This is intentional: the receiving connector’s verified-source path runs uniformly across both transports, and in-process traffic gets the same observability and per-caller throttle behavior as cross-bundle traffic.

There is no broker to enforce ACLs in-process, so short-circuit’s source segment is publisher-attested rather than broker-verified. This is acceptable because short-circuit only delivers between connectors that already share an OS process boundary.