Documentation for v2.10.0. Read the latest, v2.12.0.

Start

Concepts

Sanshain is small enough to hold in one head. This page is the whole model; the glossary has every term in one or two sentences, and the Developer track has the details.

Producers and Consumers#

A Producer publishes its complete API specification: OpenAPI, AsyncAPI or gRPC/Proto. A Consumer does not take that document. It asks for the endpoints, channels or methods it actually calls and receives a document that contains nothing else: a snippet, cut from the Producer's spec with the schemas it references. Producer and Consumer are roles, not kinds of system: a system is usually both, and Sanshain lists it once in each role.

Whoever calls Sanshain on a system's behalf is tooling: the Maven plugin, a CLI, a curl line in a pipeline. Tooling reads one file, sanshain.yaml, and turns it into the calls below.

How Sanshain works Producers on the left provide their full specs. Sanshain in the middle stores each as an entry of the Producer's version line and splits it into endpoints. Consumers on the right require single endpoints at an exact pinned version. Producers orders provides openapi.yaml payments provides payments.proto provide the full spec Sanshain version lines orders 1.0.0 ga · 1.1.0 snap payments 2.0.0 ga split into endpoints GET /orders GET /orders/{id} rpc Charge one snippet per endpoint require at a pin Consumers web-shop orders 1.0.0 billing payments 2.0.0
Provide on the left, version lines in the middle, pins on the right.

Version lines, snapshot and GA#

A version is MAJOR.MINOR.PATCH and lives in the spec file: info.version, or for proto a mandatory // sanshain-version: comment. Sanshain never assigns one. The versions one Producer has published for one API type form its version line; a Producer's OpenAPI line and its proto line evolve independently.

Every provide declares a stability, and there are exactly two:

How a build decides is the tooling's business: every client provides a snapshot unless its ga switch (SANSHAIN_GA=true, --ga, -Dsanshain.ga=true) is set, which CI does on its release pipelines. Sanshain never looks at a git branch. Publishing GA needs the releaser role.

Two rules keep the numbers honest, and both are self-service: a GA provide under an existing GA number with different content, and a GA provide whose changes against the previous GA are breaking without a major bump, are refused with 409 and a proposed version. Set it in the spec and republish. The API lifecycle chapter draws the rules and quotes the messages.

Pins, and how a require is answered#

A Consumer's dependency is a pin: the exact version it builds against, written in its own sanshain.yaml. No ranges, no latest, no default. A require names the Producer, the pin and one endpoint; a bundle names several endpoints of one Producer at one pin and gets one merged document with each shared schema once.

How a require is resolved The Consumer web-shop pins orders 1.0.0 and requires GET /orders/{id}. Sanshain looks at the version line of orders: 1.1.0 snapshot, 1.0.0 GA, 0.9.0 GA. The pin lands on 1.0.0. If the entry contains the endpoint the answer is served (200) and the edge is recorded; if the entry exists but lacks the endpoint the answer is absent (410); if the line has no such version the answer is unknown (404). Nothing falls back to another version. Consumer web-shop orders 1.0.0 GET /orders/{id} the pin, in sanshain.yaml require version line of orders · openapi 1.1.0 snapshot · overwritable the pin 1.0.0 GA · immutable 0.9.0 GA One entry per version, snapshot or GA. No fallback to any other version. served · 200 snippet answered, edge recorded absent · 410 1.0.0 exists, lacks the endpoint unknown · 404 the line has no 1.0.0
A require lands on exactly one entry of the line and ends in exactly one of three states.

Resolution looks at the one entry the line holds for the pinned version, snapshot or GA; nothing else is consulted and nothing waits. A successful require is served and records the dependency. A version that exists without the endpoint is absent (410): the spec was complete, so the omission is deliberate. A version that does not exist is unknown (404): fix the pin. A failed require records nothing, so the graph never shows a dependency that could not have compiled.

Because pins are exact, a new version can never break a Consumer. The version rules above exist so that a Consumer reading 2.4.0 → 2.5.0 can trust the upgrade is compatible.

The dependency graph#

Every recorded require is an edge, so Sanshain knows which Consumer pins which version of which endpoint, and draws it as the dependency graph. The graph highlights two things a pin set can hide: an outdated pin, below the latest GA of its line, and a snapshot-pinned one, built against overwritable content. Both are display states; neither changes what is served.

The dependency graph page: service nodes connected by pinned-version edges
The graph on /graph.html.

Streams: dev, trunk and main#

A plain provide or require records dev activity: the dev graph is everything ever recorded, snapshots and feature builds included, thinned by a 30-day window. That is noise when the question is "what is production?". So a build may declare its stream: trunk: true on the calls of your default-branch CI stamps the version as trunk's and maintains the main graph, the set of trunk pins, deterministic after every trunk build. A trunk entry not refreshed for half the trunk TTL (45 days by default) is highlighted as stale; one not refreshed for the whole TTL (90 days) leaves the main graph. Nightly re-runs of an unchanged trunk build are what keeps them fresh. Streams never change version rules or what a require is answered; they only decide which graph records the call.

Sanshain-branches: what a release is#

A sanshain-branch is a named copy of the main graph (or of another branch) at a chosen instant: the release cut. A releaser creates it, with a past instant if the cut was forgotten. Hotfix builds send tag: "<branch>" and update that branch instead of trunk. Branches are never auto-created: an unknown tag still succeeds, is recorded in the dev graph only, and answers a Sanshain-Warning header. Graph writes are append-only, so the main graph and every branch have a timeline: any of them can be rendered as it was on any date, and any two can be diffed. The Release graphs chapter draws it.

Periphery and catalog context#

The graph knows only Producers and Consumers until a build pushes the system's Backstage catalog-info.yaml (the catalog push). Of it Sanshain reads the resource: refs in spec.dependsOn, which become peripheral resources: a database, bucket or queue as its own node, shared by every system that names it. Since 2.6.0 it also reads the system's owner, description, source location and links, the catalog context shown on the node panel and on the system's page, and since 2.9.0 the Backstage system the Component belongs to, which fills the Producer's domain (the graph's cluster) when an administrator set none. A component: ref that names no Sanshain system is drawn as a missing component. Edges between systems that Sanshain knows are never read from Backstage; those are what requires record.

Credentials#

People log in and get a session; builds and assistants send an API token. A token belongs to one user, is shown once, expires, and has a scope fixed at creation: full may do everything its owner may, read_only may read reports, versions, the update streams and the MCP endpoint, and is refused with 403 on every call that changes the catalog. Mint a read-only token for an AI assistant and a full one for CI. What a token may do beyond its scope is its owner's permissions: a GA provide needs the releaser role, deleting a version needs admin or maintainer rights on that Producer.

Webhooks and MCP#

Everything Sanshain records can also be pushed out: a webhook is a signed POST for a version published, a provide rejected, a deprecation, a new dependency, a branch cut or a catalog push. A Producer's hooks live on its page; a Consumer's hooks receive the events of every Producer it currently requires. Receivers must be on an administrator's allow-list. And an AI assistant can ask the catalog directly over MCP, read-only: who provides what, where an endpoint is, what changed between two versions.

Where next#

Checked against v2.6.0.