Configure terminology routing
Coded ports ({ kind: "coded", valueSet }) are validated against a
TerminologyPort. The instance's terminology: map routes each code system to
the resolver that owns it — a plugin-contributed adapter (e.g. SNOMED CT via
Hermes) — and lets every other system fall through to the built-in registry.
This recipe adds one routing entry and verifies it resolves.
For what a coded type is and how value-sets fit the type system, see The type system. For adding a value-set or plugging a new resolver plugin, see Add vocabulary and types.
1. Identify the adapter to route to
Terminology resolvers are contributed by plugins as terminology adapters —
the same mechanism as any other port adapter
(libs/shared/src/plugin/manifest.ts, AdapterContribSchema). The bundled
@posos/snomed plugin is the concrete example
(external-plugins/snomed/plugin.yaml):
contributes:
connectionTypes:
- name: hermes
label: Serveur Hermes
auth: [none, basic, header, bearer]
params:
- { name: languageRefsetId, label: Jeu de références linguistique }
adapters:
- port: terminology
name: hermes
module: ./adapters/hermes.ts
systems: [snomed-ct] # informational only — not used for routing
connection: { type: hermes }
The name (hermes) is what you reference from the instance manifest. systems
documents which code systems the adapter is meant for, but it does not drive
routing by itself — routing is entirely decided by the instance's terminology:
keys (step 3). The adapter reaches the server through a connection of type
hermes (step 2). Make sure the plugin is loaded first: see
Declare and install plugins.
2. Declare the Hermes connection
Declare a connection of type hermes in the instance manifest, or create one in the
console (Connexions), and make it the default connection of its type. See
Manage connections for both origins.
connections:
hermes-local:
type: hermes
label: Hermes local
baseUrl: http://localhost:8081
# languageRefsetId: "10031000315102" # display language, see step 6
connectionDefaults:
hermes: hermes-local
The connection carries the URL, the authentication (none for a bare Hermes;
basic, header or bearer for a server behind a gateway) and the request timeout
(timeoutMs, 30 s by default). Its parameter languageRefsetId selects the display
language by language reference set (step 6). Write it as a string: a SNOMED CT
identifier can exceed the precision of a JavaScript number.
3. Add a routing entry to the instance manifest
In instance.yaml (or the file pointed to by INSTANCE_CONFIG), add one entry
per code system under terminology:, keyed by the code system identifier
(the value-set id, e.g. snomed-ct) and pointing at an adapter name:
terminology:
# routing by code system; systems not listed fall back to internal resolution.
snomed-ct: { adapter: hermes } # default connection of type hermes
# snomed-ct: { adapter: hermes, connection: hermes-fr } # a named connection
buildTerminology (apps/api/src/instance.ts) instantiates the adapter with its
config and, because the adapter declares connection: { type: hermes }, an accessor
on the connection named by connection, else on the default connection of the type.
The connection is resolved at each call: a connection edited in the console applies to
the next calls, and the adapter empties its caches when the connection or its revision
changes.
When no connection resolves, the Hermes adapter degrades as when the server is
unreachable — codes accepted, subsumption refused, raw code as label — and logs
[hermes] connexion indisponible (…) once. The adapter
still reads its former configuration with a warning: config.baseUrl and
config.languageRefsetId on the routing entry, then HERMES_BASE_URL and
HERMES_LANGUAGE_REFSET_ID. There is no implicit URL.
If binding.adapter doesn't match any loaded adapter's name, the system is
logged as a warning at boot and silently gets no dedicated resolver — it falls
through to the same internal default as an unlisted system (see next step).
4. Leave every other system unrouted
You do not need an entry for systems that should resolve internally. The
resulting RoutingTerminology (libs/shared/src/domain/terminology.ts) tries
providers[system] first and falls back to InternalTerminology — the
in-repo value-set registry (libs/shared/src/domain/value-sets.ts) — for
anything not in the map:
private route(valueSet: string): TerminologyPort {
return this.providers[valueSet] ?? this.fallback; // fallback = new InternalTerminology()
}
This fallback is fixed to the internal registry — there is no manifest option
to point unrouted systems at a generic external server instead. If you need a
system resolved externally, contribute (or select) a terminology adapter for
it explicitly, as in step 1.
Be aware of one asymmetry: a value-set contributed with external: true (like
snomed-ct itself — its codes aren't enumerated in-repo) has no concepts for
InternalTerminology to check against, so its internal validateCode fails
open (accepts any code) rather than rejecting it
(libs/shared/src/domain/terminology.ts, InternalTerminology.validateCode).
In practice this means: route every external value-set you actually want
validated, and treat "unrouted + external" as unchecked, not as "denied".
5. Verify the routing
Run the instance-selection test, which asserts both branches — a routed system
resolving through the adapter, and an unlisted system falling back internally
(apps/api/tests/test-instance.ts):
cd apps/api
pnpm run test:instance
It exercises:
const okSnomed = await ports.terminology!.validateCode("snomed-ct", "24700007");
// → true, routed to Hermes
const okGender = await ports.terminology!.validateCode("administrative-gender", "male");
// → true, unlisted system, resolved by the internal fallback
Both tests declare a hermes connection on HERMES_BASE_URL (default
http://localhost:8081). test:instance passes even without Hermes reachable —
the adapter fails open and logs a warning (hermes.ts: "serveur injoignable … :
validation SNOMED en mode fail-open"). To exercise a real Hermes round-trip (start
it first), run the dedicated end-to-end test instead:
pnpm run test:snomed
6. The deployed instance
Staging runs Hermes in-cluster as applications/apps/meridian-hermes
(posos-tech/vanilla-argonaute) — a StatefulSet with a PVC, reachable on
ClusterIP only. The meridian-instance ConfigMap routes snomed-ct to it at
http://hermes.ps-staging-apps.svc.cluster.local:8080; until it declares a hermes
connection, that URL sits in the routing entry's former config.baseUrl, read with a
warning. Two things differ from a
Hermes you run locally:
The database is built on first boot, by two initContainers, from the SNOMED CT édition nationale française RF2 held in a private GCS bucket. That edition is a full edition, not a language extension — it carries the international content it depends on, so it is a single import. The ANS download is authenticated (SMT account + French NRC affiliate number), so the zip is uploaded by hand, once per annual (June) release.
French display is selected by the adapter, not by the server. Hermes resolves locales through a hardcoded table in which
fr-frmeans the international French language reference set,722131000. The ANS national edition ships its own,10031000315102, which that table does not know — so--locale frmakes the server refuse to start, and Hermes's documented escape hatch (a BCP 47 private-use tag,fr-x-10031000315102) fails too, because the match is then filtered against the recognised locales only. The server therefore runs onen-US, and thehermesconnection carries the parameterlanguageRefsetId: "10031000315102"so the adapter picks the term preferred in the French refset off/extended.The translation is delivered incrementally, so
display()returns French where translated and falls back to the server's preferred term elsewhere.--bind-address 0.0.0.0is required. Hermes binds to loopback by default, which is invisible to both the Service and the kubelet's readiness probe.
Related
- Add vocabulary and types — add a value-set or a new terminology adapter.
- Declare and install plugins — get the resolver's plugin loaded in the first place.
- Instance manifest — full
terminology:andports:schema. - Manage connections — connections of the manifest and of the console, defaults, secrets.
- The type system — coded types and value-sets.
- Bundled plugins — what
@posos/snomedand the other core plugins contribute.