User journeys as typed entities: actor, goal, ordered steps joined to the endpoints they cross.

User-journey model (product-goal → implementation bridge)

Intent — Model the product's user journeys as first-class typed entities: each an actor pursuing a goal through ordered steps, every boundary-crossing step joined to the endpoint it calls. The path from what the product is for to what the code must provide becomes a queryable model. Graft it into the service-architecture dialect you already lint and query rather than standing up a parallel model (our instance: a UML-use-case-flavored Journey entity kind added to the existing Backstage service-flow dialect).

SummaryUser journeys as typed entities: actor, goal, ordered steps joined to the endpoints they cross.
TargetBridge · System models
Formtyped-ir
EnforcementHard (deterministic) — a typed source-of-truth held true by a call-site drift lint (declared deps ↔ real call sites) plus two-way endpoint-coverage audits; the lints land audit-only, then promote to blocking

Motivation — the failure it kills

The path from a product goal (a user opens a document in the editor, applies fixes, re-checks conformance) to the code that serves it lives in people's heads. Nothing ties a journey to the endpoints it hits, the tests that cover it, or the services it needs. So journeys go untested without anyone noticing; endpoints accrete that no journey needs and no one dares delete; a component's declared dependencies quietly fall out of date with the calls its code actually makes; and the backend is sized for a blurred average of all traffic rather than the journey underway. Each is a quiet failure: a coverage hole, dead surface, a stale dependency map, mis-sized capacity. None is visible from the code alone, because the journey is the one thing the code never names.

Why it's not just "the service-flow model" (a new kind, not a new model)

This model and the structural service-flow model share the same substrate: one dialect, one loader, one query surface, one lint. What differs is the entity. Service-flow's kinds are structural (a component, an API, a system) saying which services exist and how they are wired. A journey is behavioral and goal-anchored: an actor, a goal, an ordered sequence of steps, each step naming the endpoint it crosses a service boundary to reach. Modeling journeys as a new kind within the structural dialect, rather than a parallel journey model, pays off twice: it is the one entity anchored at product goals (so it bridges down to code where the structural kinds map across it), and it inherits the structural model's whole machinery (dead-ref join, query surface, lint) instead of standing up a second architecture model to maintain. Distinct model, shared carrier.

Mechanism

Each journey is a typed entity: an actor, a goal, and an ordered list of steps, each step naming the endpoints it calls. The shape is borrowed from the genre's best-in-class, a UML use-case (actor + goal + ordered activity flow) with the C4 dynamic-view idea that each ordered step carries a description and a call. It adopts that conceptual schema while skipping its interchange runtime, hosted in the dialect already in use. A journey also records its call-site anchors: the points in code where it issues each service call, which are the reconciliation key.

A journey's dep list is a derived fact (the deps come from real call sites), so the risk is drift, and a call-site drift lint holds it true both directions: every declared dependency must have a real call site (no stale declaration), and every service-client call site in the journey's own code must appear in its declared deps (no silent under-declaration). This makes the model a checked cache of the call-site truth, not a hand-authoritative document that rots. It converts an O(N)-PRs silent drift into an O(1)-at-PR correction. Two coverage audits ride on the same model:

All three are the existing drift/parity machinery applied to a new model: the model is the new thing, the mechanisms come from machinery that already exists.

Prerequisites

Consequences & costs

Capability beyond governance — differential scale-up, gated on the drift lint

Past the audits, the same model unlocks a runtime capability. Because a journey's declared deps name exactly the services it needs, an orchestrator can wake or scale only the tier the active journey requires instead of the whole fleet: an editor session need not warm the batch-remediation workers. But the capability is safe only because the governance is: a stale journey→deps map would wake too few services and fail a request cold, so journey-aware scaling is gated on the drift lint being blocking, the map provably equal to the territory. This is the sharpest version of the model's value: the same mechanism that keeps the audits honest is what makes the optimization safe to turn on. A capability the model enables, gated on the governance that keeps the model true.

Known uses

Related mechanisms