Proposed (STA-845 part 1, batch stackra-59). Replaces the one-off map built by hand from the
catalogues on 2026-10-26 with data gen derives on every run, so the map cannot drift from the
code. Reads with 0260
(clients and events from the producer’s manifest).
Context
The owner wants one map of the whole system: products, Commerce, the OS, Stackra packages and outside providers, and how data moves between them, at three levels (the whole system, one flow step by step, one service). A one-off map was built by hand from the catalogues; it drifted within a day (the gap list in its ownmeta). Gen already reads
everything the map needs: each member’s modules, the clients it injects or imports, the events its listeners consume
and the wire names their producers publish, its models and tables, its packages and the drivers it binds. What is
missing is a file that collects it and a step that joins the repos.
Decision
- A member graph per gen run. Every full
stackra genrun writes.stackra/graph.jsonbeside the member’s other generated files, derived bymemberGraphOffrom the IR and the built manifest:modules,calls(service keys of#stackra/clients/<service>, injected or imported),eventsOutandeventsIn(integration event wire names, consumed ones resolved through the producer’s committed manifest),tables,packages(@stackra/*and every dependency that commits a manifest) anddrivers(port key and driver candidates, the provider drivers). Nothing in it is authored. - A repo graph and one merge step, the same derivation.
writeRepoGraph(root, repo)(@stackra/gen/node) finds the member graphs under a repo, gives each node the id<repo>.<member>and its folder, and writesstackra.graph.jsonat the repo root.mergeSystemGraphs(paths, out)merges the os, commerce, product and stackra repo graphs into one system graph. Both callsystemGraph, which resolves each call to a node (the same repo first, else the only node answering it) and each consumed event to every publisher, derives each node’susedBy(callers and package users), and listsgapsin words: an unanswered or ambiguous call, an event nobody publishes, a flow step naming no node, a duplicate id. A gap never fails a run, because one repo’s graph points at others; the merged graph is where gaps should reach zero. - Flows are declared once, as configuration. Each repo declares its flows in
stackra.flows.jsonat the root: an id, a title, a summary and ordered steps (from,to,kindcallorevent,label, optionaleventanddata), where a step names node ids,actor.<key>orprovider.<key>. The graph derives each node’s flow tags (flows) from the steps, so level 2 of the map is generated from the same file. Tags on actions and events are not added now: they would need option changes in the runtime packages, and one file per repo is enough to start. - One shape. A repo graph and the merged graph are the same
SystemGraph(version: 1,repos,nodes,flows,edges,gaps), so a renderer reads either and merging merged graphs is the same step.
Consequences
- The docs renderer (stackra-60) reads the merged graph; it needs no hand edits.
- Every member’s next gen run adds one generated file; repo graphs exist only after each member has been generated.
- Until a
stackra graphcommand exists in@stackra/cli, the repo and merge steps are library calls a CI job or script makes. - The map’s quality follows the code’s: a service that injects no client of another service shows no call to it.
Enforcement
memberGraphOf,systemGraph,writeRepoGraphandmergeSystemGraphshave unit tests in@stackra/gen; the generate-member test lists.stackra/graph.jsonin every full run.- The gaps of the merged graph are the review list for STA-845’s “every service appears with its real event names”.
Supersedes and sources
Supersedes nothing. Sources: STA-845; the one-off maphandover/system-map/graph.json (its node and flow shape);
0294 (the committed manifest is the IR);
0260.