@shortlink-org/portolan 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/README.md +174 -6
  2. package/catalog/enum_test.go +46 -0
  3. package/catalog/evidence_test.go +35 -0
  4. package/catalog/model.go +1213 -0
  5. package/catalog/roundtrip_test.go +210 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/init.test.mjs +6 -1
  8. package/cli/portolan.mjs +14 -1
  9. package/cli/portolan.test.mjs +49 -0
  10. package/go.mod +21 -0
  11. package/go.sum +34 -0
  12. package/internal/gocall/README.md +19 -0
  13. package/internal/gocall/analyze.go +189 -0
  14. package/internal/gocall/analyze_test.go +107 -0
  15. package/internal/gohttp/analyze.go +2562 -0
  16. package/internal/gohttp/destination.go +373 -0
  17. package/internal/gohttp/endpoints.go +1067 -0
  18. package/internal/gohttp/roots.go +320 -0
  19. package/internal/gohttp/typed.go +96 -0
  20. package/internal/goscan/constants.go +85 -0
  21. package/internal/goscan/goscan_test.go +227 -0
  22. package/internal/goscan/index.go +629 -0
  23. package/internal/goscan/index_test.go +66 -0
  24. package/internal/goscan/names.go +52 -0
  25. package/internal/goscan/parse_test.go +11 -0
  26. package/internal/goscan/source.go +37 -0
  27. package/internal/goscan/tree.go +284 -0
  28. package/internal/goscan/types.go +99 -0
  29. package/internal/wsdl/ids.go +127 -0
  30. package/internal/wsdl/ids_test.go +21 -0
  31. package/internal/wsdl/model.go +70 -0
  32. package/internal/wsdl/parse.go +949 -0
  33. package/internal/wsdl/parse_test.go +170 -0
  34. package/package.json +28 -15
  35. package/plugin/describe.go +156 -0
  36. package/plugin/describe_test.go +114 -0
  37. package/plugin/protocol.go +141 -0
  38. package/plugin/schematest/schematest.go +126 -0
  39. package/plugins/README.md +404 -50
  40. package/plugins/cmd/portolan-http-clients/main.go +19 -0
  41. package/plugins/extract-argocd/options.schema.json +44 -0
  42. package/plugins/extract-celery/extract.py +0 -2
  43. package/plugins/extract-celery/extract_test.py +1 -1
  44. package/plugins/extract-celery/main.py +1 -0
  45. package/plugins/extract-csharp-ddd/README.md +213 -0
  46. package/plugins/extract-csharp-ddd/options.schema.json +55 -0
  47. package/plugins/extract-django/README.md +52 -19
  48. package/plugins/extract-django/choices.py +47 -0
  49. package/plugins/extract-django/domain.py +30 -18
  50. package/plugins/extract-django/extract.py +21 -7
  51. package/plugins/extract-django/extract_test.py +68 -2
  52. package/plugins/extract-django/lifecycle.py +4 -28
  53. package/plugins/extract-django/main.py +1 -0
  54. package/plugins/extract-django/operations.py +1 -1
  55. package/plugins/extract-django/routing_test.py +109 -1
  56. package/plugins/extract-django/rules.py +154 -0
  57. package/plugins/extract-django/rules_test.py +158 -0
  58. package/plugins/extract-django/store.py +1 -1
  59. package/plugins/extract-django/transport.py +101 -55
  60. package/plugins/extract-django/verbs.py +241 -0
  61. package/plugins/extract-go/README.md +47 -0
  62. package/plugins/extract-go-sqs/options.schema.json +21 -0
  63. package/plugins/extract-http-clients/describe.go +20 -0
  64. package/plugins/extract-http-clients/describe_test.go +11 -0
  65. package/plugins/extract-http-clients/extract.go +740 -0
  66. package/plugins/extract-http-clients/extract_test.go +1561 -0
  67. package/plugins/extract-http-clients/main.go +41 -0
  68. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  69. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  70. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  71. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  72. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  73. package/plugins/extract-k8s/options.schema.json +32 -0
  74. package/plugins/extract-laravel/Cargo.lock +962 -0
  75. package/plugins/extract-laravel/Cargo.toml +20 -0
  76. package/plugins/extract-laravel/README.md +200 -0
  77. package/plugins/extract-laravel/options.schema.json +68 -0
  78. package/plugins/extract-laravel/rustfmt.toml +1 -0
  79. package/plugins/extract-php-ddd/Cargo.lock +972 -0
  80. package/plugins/extract-php-ddd/Cargo.toml +22 -0
  81. package/plugins/extract-php-ddd/README.md +141 -0
  82. package/plugins/extract-php-ddd/options.schema.json +50 -0
  83. package/plugins/extract-php-ddd/rustfmt.toml +1 -0
  84. package/plugins/extract-python-kafka/extract.py +0 -2
  85. package/plugins/extract-python-kafka/extract_test.py +1 -1
  86. package/plugins/extract-python-kafka/main.py +1 -0
  87. package/plugins/extract-terraform/options.schema.json +26 -0
  88. package/plugins/extract-ts/extract.test.ts +2 -2
  89. package/plugins/extract-ts/extract.ts +4 -5
  90. package/plugins/extract-ts/graphql.test.ts +1 -1
  91. package/plugins/extract-ts/main.ts +1 -0
  92. package/plugins/openapi/ids.go +261 -0
  93. package/plugins/openapi/ids_test.go +98 -0
  94. package/plugins/phpscan/Cargo.lock +953 -0
  95. package/plugins/phpscan/Cargo.toml +19 -0
  96. package/plugins/phpscan/rustfmt.toml +1 -0
  97. package/plugins/portolan-go.wasm +0 -0
  98. package/plugins/pyplugin/catalog.py +24 -1
  99. package/plugins/pyplugin/protocol.py +1 -5
  100. package/plugins/verify-otel/options.schema.json +12 -0
  101. package/portolan.json +132 -4
  102. package/rules/builtin.json +218 -0
  103. package/schema/portolan.schema.json +905 -4
  104. package/scripts/README.md +21 -13
  105. package/scripts/catalog-sources.mjs +6 -0
  106. package/scripts/delivery-presets.mjs +57 -14
  107. package/scripts/diff.mjs +5 -1
  108. package/scripts/django-aggregates.test.mjs +58 -0
  109. package/scripts/gen-likec4.mjs +150 -17
  110. package/scripts/gen-likec4.test.mjs +96 -0
  111. package/scripts/gen.mjs +148 -118
  112. package/scripts/gitops-example.test.mjs +108 -0
  113. package/scripts/go-discovery.test.mjs +30 -0
  114. package/scripts/history.mjs +186 -3
  115. package/scripts/history.test.mjs +1 -1
  116. package/scripts/host-plugins/fetch-argocd.mjs +338 -0
  117. package/scripts/host-plugins/fetch-argocd.options.json +49 -0
  118. package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
  119. package/scripts/host-plugins/fetch-bsr.mjs +1 -0
  120. package/scripts/host-plugins/fetch-csr.mjs +1 -0
  121. package/scripts/host-plugins/fetch-git.mjs +78 -21
  122. package/scripts/host-plugins/fetch-git.test.mjs +62 -8
  123. package/scripts/host-plugins/fetch-k8s.mjs +263 -0
  124. package/scripts/host-plugins/fetch-k8s.options.json +50 -0
  125. package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
  126. package/scripts/host-plugins/k8s-topology.mjs +183 -0
  127. package/scripts/json-format.mjs +192 -0
  128. package/scripts/json-format.test.mjs +97 -0
  129. package/scripts/local-api.mjs +287 -12
  130. package/scripts/local-api.test.mjs +150 -5
  131. package/scripts/local-discovery.mjs +94 -9
  132. package/scripts/manifest.mjs +14 -3
  133. package/scripts/manifest.test.mjs +24 -0
  134. package/scripts/output-diff.mjs +94 -0
  135. package/scripts/output-diff.test.mjs +36 -0
  136. package/scripts/package-smoke.mjs +62 -4
  137. package/scripts/plugin-host.mjs +45 -3
  138. package/scripts/plugin-host.test.mjs +9 -0
  139. package/scripts/plugin-wasm-worker.mjs +4 -1
  140. package/scripts/plugins-fresh.mjs +97 -0
  141. package/scripts/plugins-fresh.test.mjs +64 -0
  142. package/scripts/provenance.mjs +72 -0
  143. package/scripts/provenance.test.mjs +149 -0
  144. package/scripts/run-builtin.mjs +62 -7
  145. package/scripts/schema.mjs +167 -19
  146. package/scripts/trace-trials.mjs +176 -0
  147. package/scripts/trace-trials.test.mjs +142 -0
  148. package/scripts/warning-policy.mjs +167 -0
  149. package/scripts/warning-policy.test.mjs +93 -0
  150. package/src/app/Breadcrumbs.test.ts +3 -0
  151. package/src/app/Breadcrumbs.tsx +3 -0
  152. package/src/app/CatalogApp.tsx +2 -0
  153. package/src/app/Sidebar.tsx +3 -3
  154. package/src/app/SidebarFooter.tsx +20 -4
  155. package/src/catalog-docs.test.ts +64 -0
  156. package/src/catalog-docs.ts +35 -0
  157. package/src/catalog-error.test.ts +15 -0
  158. package/src/catalog-index.ts +25 -0
  159. package/src/catalog-model.ts +293 -5
  160. package/src/catalog-validation.ts +113 -2
  161. package/src/catalog.test.ts +40 -1
  162. package/src/chat/Starter.tsx +5 -11
  163. package/src/chat/tools.test.ts +27 -0
  164. package/src/chat/tools.ts +5 -9
  165. package/src/components/CatalogStamp.tsx +10 -8
  166. package/src/components/ChannelRows.test.tsx +76 -1
  167. package/src/components/ChannelRows.tsx +223 -60
  168. package/src/components/DeploymentRows.tsx +131 -0
  169. package/src/components/DocsLinks.test.tsx +27 -0
  170. package/src/components/DocsLinks.tsx +56 -0
  171. package/src/components/FieldTree.tsx +5 -2
  172. package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
  173. package/src/components/HTTPDestinationEvidence.tsx +31 -0
  174. package/src/components/Integrations.tsx +1 -1
  175. package/src/components/MachineDocs.tsx +6 -5
  176. package/src/components/MethodRows.tsx +9 -2
  177. package/src/components/PluginIcon.tsx +77 -0
  178. package/src/components/ProblemRow.tsx +173 -153
  179. package/src/components/RelationEvidence.test.tsx +14 -0
  180. package/src/components/RelationEvidence.tsx +53 -0
  181. package/src/components/RuleMarks.tsx +22 -0
  182. package/src/components/ShapeRows.tsx +24 -20
  183. package/src/data.ts +46 -7
  184. package/src/enrich.test.ts +459 -4
  185. package/src/enrich.ts +308 -7
  186. package/src/er/ErCanvas.tsx +218 -12
  187. package/src/er/GroupNode.tsx +57 -0
  188. package/src/er/StoreHeader.tsx +1 -0
  189. package/src/er/layout.test.ts +85 -2
  190. package/src/er/layout.ts +140 -5
  191. package/src/er/spec.test.ts +17 -0
  192. package/src/er/spec.ts +23 -10
  193. package/src/flow/Recordings.test.tsx +52 -0
  194. package/src/flow/Recordings.tsx +236 -0
  195. package/src/flow/StepDetail.tsx +59 -0
  196. package/src/flow/TraceTrial.tsx +419 -0
  197. package/src/flow/evidence.test.ts +16 -0
  198. package/src/flow/evidence.ts +34 -0
  199. package/src/flow/examples.test.ts +33 -0
  200. package/src/flow/examples.ts +37 -0
  201. package/src/flow/outline.test.ts +28 -0
  202. package/src/flow/outline.ts +7 -2
  203. package/src/flow/trace-trial-resume.test.ts +42 -0
  204. package/src/flow/trace-trial-resume.ts +74 -0
  205. package/src/graph/elk.ts +78 -0
  206. package/src/index.css +68 -0
  207. package/src/landing/DraggableReveal.tsx +3 -2
  208. package/src/landing/EvidencePipeline.tsx +105 -0
  209. package/src/landing/LandingPage.tsx +17 -70
  210. package/src/landing/ProductTour.tsx +6 -6
  211. package/src/lib/all-problems.ts +27 -17
  212. package/src/lib/catalog-diff.ts +1 -1
  213. package/src/lib/centrality.test.ts +251 -0
  214. package/src/lib/centrality.ts +232 -0
  215. package/src/lib/confluence.test.ts +37 -0
  216. package/src/lib/confluence.ts +41 -0
  217. package/src/lib/context-color.ts +1 -1
  218. package/src/lib/deployment-drift.ts +22 -0
  219. package/src/lib/derive.ts +43 -69
  220. package/src/lib/django-aggregates.d.mts +9 -0
  221. package/src/lib/django-aggregates.mjs +36 -0
  222. package/src/lib/django-aggregates.test.ts +29 -0
  223. package/src/lib/django-aggregates.ts +5 -0
  224. package/src/lib/environments.test.ts +83 -0
  225. package/src/lib/environments.ts +57 -0
  226. package/src/lib/integration-url.test.ts +30 -0
  227. package/src/lib/integration-url.ts +63 -0
  228. package/src/lib/kafka-ui.ts +3 -45
  229. package/src/lib/local-api.ts +116 -4
  230. package/src/lib/notion.ts +13 -0
  231. package/src/lib/plugin-index.json +2765 -0
  232. package/src/lib/plugins.test.ts +68 -0
  233. package/src/lib/plugins.ts +264 -0
  234. package/src/lib/problem-flows.test.ts +61 -0
  235. package/src/lib/problem-flows.ts +78 -0
  236. package/src/lib/problem-rules-cel.d.mts +43 -0
  237. package/src/lib/problem-rules-cel.mjs +407 -0
  238. package/src/lib/problem-rules.test.ts +287 -0
  239. package/src/lib/problem-rules.ts +271 -0
  240. package/src/lib/problem-subjects.ts +737 -0
  241. package/src/lib/rule-entries.ts +39 -0
  242. package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
  243. package/src/lib/rules-deploy.test.ts +133 -0
  244. package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
  245. package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
  246. package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
  247. package/src/lib/rules.test.ts +51 -0
  248. package/src/lib/rules.ts +86 -0
  249. package/src/lib/setup-info.test.ts +17 -0
  250. package/src/lib/setup-info.ts +58 -0
  251. package/src/lib/shape.test.ts +32 -0
  252. package/src/lib/shape.ts +30 -6
  253. package/src/lib/tech.ts +16 -0
  254. package/src/lib/trace-project.test.ts +34 -0
  255. package/src/lib/trace-project.ts +50 -0
  256. package/src/lib/use-problems.ts +23 -0
  257. package/src/lib/warnings.test.ts +63 -0
  258. package/src/lib/warnings.ts +260 -0
  259. package/src/likec4/ids.test.ts +6 -2
  260. package/src/likec4/ids.ts +43 -0
  261. package/src/main.tsx +23 -0
  262. package/src/map/ContextMapGraph.tsx +76 -32
  263. package/src/merge-deployments.test.ts +127 -0
  264. package/src/merge.test.ts +82 -0
  265. package/src/merge.ts +189 -18
  266. package/src/pages/AggregatePage.tsx +65 -14
  267. package/src/pages/ContextMap.tsx +45 -3
  268. package/src/pages/ContextPage.tsx +8 -5
  269. package/src/pages/EventPage.tsx +15 -5
  270. package/src/pages/FlowDetail.tsx +23 -2
  271. package/src/pages/GraphPage.tsx +40 -3
  272. package/src/pages/Overview.tsx +152 -12
  273. package/src/pages/PluginIndex.tsx +190 -0
  274. package/src/pages/Problems.tsx +396 -128
  275. package/src/pages/ServicePage.tsx +62 -5
  276. package/src/pages/Settings.tsx +217 -43
  277. package/src/pages/settings/AboutSettings.tsx +8 -1
  278. package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
  279. package/src/pages/settings/IntegrationsSettings.tsx +63 -17
  280. package/src/pages/settings/RecordingSettings.tsx +138 -0
  281. package/src/pages/settings/RulesSettings.tsx +825 -0
  282. package/src/routes.test.ts +9 -0
  283. package/src/routes.ts +22 -1
  284. package/src/selection/DetailPanel.tsx +15 -0
  285. package/src/virtual-provenance.d.ts +11 -0
  286. package/vite.config.ts +5 -0
  287. package/scripts/vendor-lock.mjs +0 -58
  288. package/scripts/vendor-lock.test.mjs +0 -69
  289. package/src/lib/data-problems.ts +0 -314
  290. package/src/lib/proto-problems.ts +0 -237
  291. package/src/lib/wire-problems.ts +0 -342
@@ -0,0 +1,1213 @@
1
+ // Package catalog is the Go side of the contract in src/catalog.ts.
2
+ //
3
+ // It is a MIRROR, not a second definition (portolan.0003). The TypeScript file is where the
4
+ // shape is decided and where the prose explaining each decision lives; this
5
+ // package exists so a plugin written in Go can read a catalog without
6
+ // reinventing it, and it is held to the original by a round-trip test rather
7
+ // than by anyone remembering to update both.
8
+ //
9
+ // Nothing here validates. A plugin receives a catalog that the host has
10
+ // already validated, and a second opinion in a second language is how the two
11
+ // drift apart.
12
+ package catalog
13
+
14
+ import (
15
+ "encoding/json"
16
+ "fmt"
17
+ )
18
+
19
+ type Status string
20
+
21
+ const (
22
+ StatusVerified Status = "verified"
23
+ StatusDeclared Status = "declared"
24
+ StatusUnresolved Status = "unresolved"
25
+ )
26
+
27
+ // Catalog is one source of facts. The estate a reader sees is the merge of
28
+ // several of these, so nothing in a plugin may assume it holds all of them.
29
+ type Catalog struct {
30
+ // The stamp the HOST writes, from the last commit that touched the input
31
+ // this source was read from - never a plugin, which would have to read a
32
+ // clock and produce a different fragment on every run. Both are omitted
33
+ // when empty, because a source nobody generates carries no stamp at all
34
+ // and a mirror that wrote `"generatedAt": ""` would be inventing an answer
35
+ // to a question the file does not answer. The merge dates the estate from
36
+ // the sources that do carry one.
37
+ GeneratedAt string `json:"generatedAt,omitempty"`
38
+ Commit string `json:"commit,omitempty"`
39
+ Contexts []BoundedContext `json:"contexts"`
40
+ Defs map[string]TypeDef `json:"defs"`
41
+ Flows []Flow `json:"flows"`
42
+ Adrs []Adr `json:"adrs"`
43
+ Stores []Store `json:"stores,omitempty"`
44
+ Modules []ProtoModule `json:"modules,omitempty"`
45
+ Terms []Term `json:"terms,omitempty"`
46
+ Repos []RepoPin `json:"repos,omitempty"`
47
+ Deployments []Deployment `json:"deployments,omitempty"`
48
+ Externals []External `json:"externals,omitempty"`
49
+ }
50
+
51
+ // Deployment is one place a service runs: an Argo CD Application as the
52
+ // deployer listed it, reduced to what a deploy changes. A list on the catalog
53
+ // rather than a field on Service: the fetcher that writes it reads a control
54
+ // plane and does not know which service an Application is, and one service
55
+ // stands in several places.
56
+ type Deployment struct {
57
+ // ID is "<argocd namespace>/<application name>".
58
+ ID string `json:"id"`
59
+ Name string `json:"name"`
60
+ Project string `json:"project"`
61
+ Environment string `json:"environment"`
62
+ Cluster string `json:"cluster"`
63
+ Namespace string `json:"namespace"`
64
+ // Repo is spelled the way Service.Repo spells it; empty for a chart
65
+ // from a registry.
66
+ Repo string `json:"repo"`
67
+ Path string `json:"path"`
68
+ // Chart is the Helm chart name when the source is a registry.
69
+ Chart string `json:"chart,omitempty"`
70
+ TargetRevision string `json:"targetRevision"`
71
+ Revision string `json:"revision"`
72
+ // Tool is helm, kustomize, directory or plugin, as Argo CD says it.
73
+ Tool string `json:"tool"`
74
+ URL string `json:"url"`
75
+ // Service is the id of the service the Application's labels name;
76
+ // empty when they name none, and the path decides.
77
+ Service string `json:"service,omitempty"`
78
+ Images []string `json:"images,omitempty"`
79
+ // Basis says who said so: "manifest" for a row read out of a GitOps
80
+ // tree, "api" for one read off the deployer, "both" for one the merge
81
+ // folded from the two. Empty reads as "api", which is what every row
82
+ // was before there were trees to read.
83
+ Basis string `json:"basis,omitempty"`
84
+ // Drift is what the tree says where it and the deployer disagree, set
85
+ // by the merge when both spoke (portolan.0013).
86
+ Drift *DeploymentDrift `json:"drift,omitempty"`
87
+ }
88
+
89
+ // DeploymentDrift is the GitOps tree's word on the fields where the
90
+ // deployer says otherwise. A field is set only when the two differ.
91
+ type DeploymentDrift struct {
92
+ Project string `json:"project,omitempty"`
93
+ Cluster string `json:"cluster,omitempty"`
94
+ Namespace string `json:"namespace,omitempty"`
95
+ Path string `json:"path,omitempty"`
96
+ TargetRevision string `json:"targetRevision,omitempty"`
97
+ Images []string `json:"images,omitempty"`
98
+ }
99
+
100
+ // External is a system outside the estate with a contract: what it answers on,
101
+ // read from the copy of its document vendored beside the adapter that calls it,
102
+ // and nothing else. It sits at the root beside the contexts, so its id is its
103
+ // slug and carries no dot.
104
+ type External struct {
105
+ ID string `json:"id"`
106
+ Slug string `json:"slug"`
107
+ Name string `json:"name"`
108
+ Summary string `json:"summary"`
109
+ URL string `json:"url,omitempty"`
110
+ Provides []RpcService `json:"provides"`
111
+ }
112
+
113
+ // RepoPin is a repository the estate was read at, and the commit it was read
114
+ // at, so a source path in another repository can still be a link.
115
+ //
116
+ // A list on the catalog rather than a field on Service: the pin is a fact
117
+ // about the estate, and a repository holding three services is fetched once.
118
+ type RepoPin struct {
119
+ // Repo is spelled the way Service.Repo spells it: "github.com/acme/shop".
120
+ Repo string `json:"repo"`
121
+
122
+ // Commit is the full sha. Nothing resolves it locally, so there is
123
+ // nothing a short one could be expanded against.
124
+ Commit string `json:"commit"`
125
+ }
126
+
127
+ type Classification string
128
+
129
+ const (
130
+ ClassificationCore Classification = "core"
131
+ ClassificationSupporting Classification = "supporting"
132
+ ClassificationGeneric Classification = "generic"
133
+ )
134
+
135
+ // How a context is read. The keys stay `contexts` and `services` (portolan.0004).
136
+ type GroupKind string
137
+
138
+ const (
139
+ GroupKindBoundedContext GroupKind = "bounded-context"
140
+ GroupKindSystem GroupKind = "system"
141
+ GroupKindProduct GroupKind = "product"
142
+ GroupKindTeam GroupKind = "team"
143
+ GroupKindNamespace GroupKind = "namespace"
144
+ )
145
+
146
+ type BoundedContext struct {
147
+ ID string `json:"id"`
148
+ Slug string `json:"slug"`
149
+ Name string `json:"name"`
150
+ Summary string `json:"summary"`
151
+ Kind GroupKind `json:"kind,omitempty"`
152
+ Classification Classification `json:"classification,omitempty"`
153
+ ViewID string `json:"viewId,omitempty"`
154
+ Services []Service `json:"services"`
155
+ }
156
+
157
+ type ComponentKind string
158
+
159
+ const (
160
+ ComponentKindService ComponentKind = "service"
161
+ ComponentKindApplication ComponentKind = "application"
162
+ ComponentKindWebapp ComponentKind = "webapp"
163
+ ComponentKindWorker ComponentKind = "worker"
164
+ ComponentKindJob ComponentKind = "job"
165
+ ComponentKindFunction ComponentKind = "function"
166
+ ComponentKindCLI ComponentKind = "cli"
167
+ ComponentKindLibrary ComponentKind = "library"
168
+ ComponentKindDataPipeline ComponentKind = "data-pipeline"
169
+ )
170
+
171
+ type Service struct {
172
+ ID string `json:"id"`
173
+ Slug string `json:"slug"`
174
+ Name string `json:"name"`
175
+ Repo string `json:"repo"`
176
+ Path string `json:"path"`
177
+ Readme string `json:"readme"`
178
+ Kind ComponentKind `json:"kind,omitempty"`
179
+ Technologies []string `json:"technologies,omitempty"`
180
+ Provides []RpcService `json:"provides"`
181
+ Consumes []RpcCall `json:"consumes"`
182
+ // Copies are interfaces read from vendored proto modules. They are kept
183
+ // apart from Provides because this service calls rather than implements
184
+ // them, but retain the shapes needed to compare the copy with its publisher.
185
+ Copies []RpcService `json:"copies,omitempty"`
186
+ Aggregates []Aggregate `json:"aggregates"`
187
+ // Stores this service touches, by id. Ownership is not stated here - a
188
+ // store names its own owner, so an id in this list that the store does not
189
+ // call its owner is a read.
190
+ Stores []string `json:"stores,omitempty"`
191
+
192
+ // Modules this service publishes or vendors, by id. Same rule as Stores: a
193
+ // module names its own owner, so an id here that does not call this service
194
+ // its owner is one the service reads.
195
+ Modules []string `json:"modules,omitempty"`
196
+
197
+ // Channels this service declares it publishes on or listens to, read out of
198
+ // an AsyncAPI document. Absent for a service that has no such document,
199
+ // which is not the same as a service that speaks to nobody.
200
+ Channels []Channel `json:"channels,omitempty"`
201
+
202
+ // Owners is who to ask about it, as CODEOWNERS spells them. Handles and
203
+ // nothing more: resolving one to people is a call to a forge, and the
204
+ // handle is what a reviewer types anyway.
205
+ Owners []string `json:"owners,omitempty"`
206
+
207
+ // Commands are what a developer types against the checkout - the make
208
+ // targets, npm scripts, just recipes and task-runner tasks the repository
209
+ // declares. Read from the runner files, never from the README, so the
210
+ // list is the one the runner would accept. Absent when nothing declares
211
+ // any, which is not the same as a service that cannot be built.
212
+ Commands []Command `json:"commands,omitempty"`
213
+
214
+ // Hosts are the names this service answers on, read from what deploys
215
+ // it: a Kubernetes Service's name in its short, namespaced, `svc` and
216
+ // fully qualified forms, and the hosts of the Ingress or HTTPRoute in
217
+ // front of it. Written so that a call another service is configured to
218
+ // make to `pricing.shop.svc` can find the service that answers. Absent
219
+ // when nothing in the tree says where the service is reachable.
220
+ Hosts []string `json:"hosts,omitempty"`
221
+
222
+ // Dials are the in-cluster names this service's workload is configured
223
+ // to reach, read out of its environment and config maps and reduced to
224
+ // the host alone. A value is never kept: not the variable it came from,
225
+ // not the scheme, port, path or credentials around the name. Only a name
226
+ // the cluster resolves qualifies - a Service in the same tree, or a
227
+ // `<name>.<namespace>.svc` form - so a password in an environment
228
+ // variable is not something this list can hold by shape.
229
+ Dials []string `json:"dials,omitempty"`
230
+ }
231
+
232
+ // Command is one entry of a task runner's file: a make target, an npm script,
233
+ // a just recipe, a Taskfile task, a poe or pdm task.
234
+ //
235
+ // Run is the whole point - the line a reader copies - and it is spelled here
236
+ // rather than rebuilt from Runner and Name, because `npm test` and
237
+ // `npm run typecheck` are two spellings of one runner and the page should not
238
+ // have to know which scripts npm treats specially.
239
+ type Command struct {
240
+ // Runner is the tool the line is typed at: make, npm, pnpm, yarn, bun,
241
+ // just, task, poe, pdm.
242
+ Runner string `json:"runner"`
243
+ // Name is the target, script, recipe or task as its file spells it.
244
+ Name string `json:"name"`
245
+ // Run is the line to type at a shell in the service's directory.
246
+ Run string `json:"run"`
247
+ // Doc is what the file says the command is for, when it says anything: a
248
+ // `## comment` on a make target, a `#` line over a just recipe, a task's
249
+ // `desc`, a poe task's `help`. Most files say nothing.
250
+ Doc string `json:"doc,omitempty"`
251
+ // Body is what the runner executes for it: the recipe, the script line,
252
+ // the cmds. Carried so a name that says nothing can still be read, and
253
+ // shown folded, because a build script is not a sentence.
254
+ Body string `json:"body,omitempty"`
255
+ // Source is the file and line the entry was read at.
256
+ Source string `json:"source,omitempty"`
257
+ }
258
+
259
+ type RpcService struct {
260
+ ID string `json:"id"`
261
+ Methods []RpcMethod `json:"methods"`
262
+ Source string `json:"source"`
263
+ Messages []RpcMessage `json:"messages,omitempty"`
264
+ // Enums are the closed sets the messages' fields take values from, reached
265
+ // the way Messages are: from the methods, through the fields, as far as
266
+ // the protos read here declare them.
267
+ Enums []RpcEnum `json:"enums,omitempty"`
268
+
269
+ // Module is the schema module declaring this interface, by ProtoModule.ID.
270
+ Module string `json:"module,omitempty"`
271
+ }
272
+
273
+ // RpcEnum is an enum a message field names, as the interface's document
274
+ // declares it. Values carry their wire number because a proto consumer sees
275
+ // the number, not the name, in a binary message.
276
+ type RpcEnum struct {
277
+ Name string `json:"name"`
278
+ Doc string `json:"doc,omitempty"`
279
+ Values []RpcEnumValue `json:"values"`
280
+ }
281
+
282
+ type RpcEnumValue struct {
283
+ Name string `json:"name"`
284
+ Number int `json:"number"`
285
+ Doc string `json:"doc,omitempty"`
286
+ }
287
+
288
+ // RpcMethod is one method of one interface.
289
+ //
290
+ // A string would have done for the name, and did until protos were read. What
291
+ // a string could not carry is the shapes on either side: an endpoint whose
292
+ // request and response are named is one a reader can follow without opening
293
+ // the source, and a streaming method drawn as a unary call is a lie about how
294
+ // the two ends are coupled.
295
+ //
296
+ // Only Name is required. An interface read from an OpenAPI document supplies
297
+ // nothing else, and must keep reading the way it always did.
298
+ type RpcMethod struct {
299
+ // Name is what the interface declares - a proto method, an OpenAPI
300
+ // operationId. This is what Operation.ExposedBy names.
301
+ Name string `json:"name"`
302
+ Doc string `json:"doc,omitempty"`
303
+
304
+ // Request and Response name messages in RpcService.Messages; the Ref forms
305
+ // key Catalog.Defs when the shape is shared. The same pairing, for the same
306
+ // reason, as Field.Type and Field.Ref.
307
+ Request string `json:"request,omitempty"`
308
+ RequestRef string `json:"requestRef,omitempty"`
309
+ Response string `json:"response,omitempty"`
310
+ ResponseRef string `json:"responseRef,omitempty"`
311
+
312
+ // Streaming is empty for a unary method, which is most of them.
313
+ Streaming Streaming `json:"streaming,omitempty"`
314
+ Deprecated bool `json:"deprecated,omitempty"`
315
+
316
+ // HTTP is the route, for a method read from an OpenAPI document. It is
317
+ // what lets a request seen on the wire be read back to the operation.
318
+ HTTP *HttpRoute `json:"http,omitempty"`
319
+
320
+ // SOAP is the concrete binding of a WSDL operation. Unlike Doc, these
321
+ // fields participate in comparison and let renderers present the contract
322
+ // without reparsing the source document.
323
+ SOAP *SoapRoute `json:"soap,omitempty"`
324
+ }
325
+
326
+ type HttpRoute struct {
327
+ // Method is upper case: POST. It is empty when a framework extractor
328
+ // proved the mount but no declaration proved the verb (extract-django's
329
+ // `Planet.fetch`); such a route is never matched against an outbound
330
+ // call, and a renderer shows the path alone.
331
+ Method string `json:"method"`
332
+ // Path is the template as the document writes it: /v1/users/{id}.
333
+ Path string `json:"path"`
334
+ }
335
+
336
+ // SoapRoute is the wire-level part of one WSDL operation. Request, response
337
+ // and fault shapes remain on RpcMethod/RpcService, beside their proto and
338
+ // OpenAPI equivalents.
339
+ type SoapRoute struct {
340
+ Action string `json:"action,omitempty"`
341
+ Version string `json:"version,omitempty"`
342
+ Style string `json:"style,omitempty"`
343
+ Endpoint string `json:"endpoint,omitempty"`
344
+ Binding string `json:"binding,omitempty"`
345
+ Faults []string `json:"faults,omitempty"`
346
+ Headers []string `json:"headers,omitempty"`
347
+ }
348
+
349
+ type Streaming string
350
+
351
+ const (
352
+ StreamingClient Streaming = "client"
353
+ StreamingServer Streaming = "server"
354
+ StreamingBidi Streaming = "bidi"
355
+ )
356
+
357
+ type RpcMessage struct {
358
+ Name string `json:"name"`
359
+ Fields []Field `json:"fields"`
360
+ Discriminator *RpcDiscriminator `json:"discriminator,omitempty"`
361
+ }
362
+
363
+ // RpcDiscriminator explains how a polymorphic OpenAPI message selects its
364
+ // concrete shape on the wire.
365
+ type RpcDiscriminator struct {
366
+ Property string `json:"property"`
367
+ Variants []RpcVariant `json:"variants"`
368
+ }
369
+
370
+ type RpcVariant struct {
371
+ Value string `json:"value"`
372
+ Message string `json:"message"`
373
+ }
374
+
375
+ type HTTPDestination struct {
376
+ Transforms []HTTPDestinationJoin `json:"transforms,omitempty"`
377
+ CallSite string `json:"callSite"`
378
+ EndpointExpression string `json:"endpointExpression"`
379
+ Method string `json:"method"`
380
+ LocalPath string `json:"localPath,omitempty"`
381
+ BaseURL *HTTPBaseURL `json:"baseURL,omitempty"`
382
+ ServiceDiscoveryAlias string `json:"serviceDiscoveryAlias,omitempty"`
383
+ FullPath string `json:"fullPath,omitempty"`
384
+ Join *HTTPDestinationJoin `json:"join,omitempty"`
385
+ Resolution *HTTPDestinationResolution `json:"resolution,omitempty"`
386
+ }
387
+ type HTTPBaseURL struct {
388
+ Expression string `json:"expression"`
389
+ ConfigField string `json:"configField,omitempty"`
390
+ EnvironmentVariable string `json:"environmentVariable,omitempty"`
391
+ Value string `json:"value,omitempty"`
392
+ Kind string `json:"kind"`
393
+ Source string `json:"source"`
394
+ OptionSource string `json:"optionSource,omitempty"`
395
+ }
396
+ type HTTPDestinationJoin struct {
397
+ Expression string `json:"expression"`
398
+ Source string `json:"source"`
399
+ }
400
+ type HTTPDestinationResolution struct {
401
+ Basis string `json:"basis"`
402
+ Provider string `json:"provider"`
403
+ Route string `json:"route"`
404
+ }
405
+
406
+ type RpcCall struct {
407
+ Evidence []RelationEvidence `json:"evidence,omitempty"`
408
+ Destination *HTTPDestination `json:"destination,omitempty"`
409
+ ID string `json:"id"`
410
+ Peer string `json:"peer"`
411
+ Status Status `json:"status"`
412
+ Source string `json:"source"`
413
+ Note string `json:"note,omitempty"`
414
+
415
+ // Module is the module the vendored copy this call was read from belongs to.
416
+ Module string `json:"module,omitempty"`
417
+
418
+ // Via is set when the call was derived from a flow step rather than declared.
419
+ Via *EdgeVia `json:"via,omitempty"`
420
+ }
421
+
422
+ // EdgeVia names the flow step a derived consumer or call was read from. A
423
+ // plugin never writes one: the host derives edges from flows after the merge,
424
+ // and this is how the generated output can say where an arrow came from.
425
+ type EdgeVia struct {
426
+ Flow string `json:"flow"`
427
+ Step string `json:"step"`
428
+ }
429
+
430
+ // ProtoModule is a schema module: a set of .proto files with a name, a version
431
+ // and a publisher - "buf.build/acme/shop".
432
+ //
433
+ // It sits at the top level rather than inside the service that publishes it,
434
+ // because the interesting fact about a module is usually who ELSE reads it.
435
+ //
436
+ // Its ID is the module's own registry-global name and NOT "<owner>.<slug>" the
437
+ // way a Store's is. A store is declared by exactly one source. A module is
438
+ // declared by several that do not know each other - the producer, and each
439
+ // consumer reading a vendored copy in another repository - and since the merge
440
+ // unions top-level entities BY ID, an owner-derived id would grow one module
441
+ // per consumer.
442
+ //
443
+ // What it carries is identity and inventory, not schema: the interfaces are
444
+ // found through RpcService.Module and the shapes live in RpcService.Messages.
445
+ type ProtoModule struct {
446
+ // ID is "buf.build/acme/shop", or "local:proto/shop" for a set never
447
+ // published to a registry.
448
+ ID string `json:"id"`
449
+
450
+ // Slug is unique across the catalog, and what the URL uses: "acme-shop".
451
+ Slug string `json:"slug"`
452
+ Name string `json:"name"`
453
+
454
+ // Registry is "buf.build", empty when the module was never published.
455
+ Registry string `json:"registry,omitempty"`
456
+
457
+ // Owner is the service that publishes it, when the estate knows. Empty is
458
+ // an honest answer rather than a defect: a module published by a team, or
459
+ // by a repository outside the estate, is the ordinary case.
460
+ Owner string `json:"owner,omitempty"`
461
+
462
+ Commit string `json:"commit,omitempty"`
463
+ Digest string `json:"digest,omitempty"`
464
+
465
+ Packages []string `json:"packages"`
466
+ Files []string `json:"files"`
467
+ Deps []string `json:"deps,omitempty"`
468
+ Source string `json:"source"`
469
+ }
470
+
471
+ type Aggregate struct {
472
+ ID string `json:"id"`
473
+ Slug string `json:"slug"`
474
+ Name string `json:"name"`
475
+ Readme string `json:"readme"`
476
+ // Kind is "model-group" for a source grouping without an aggregate boundary.
477
+ Kind string `json:"kind,omitempty"`
478
+ Root string `json:"root"`
479
+ Entities []Block `json:"entities"`
480
+ ValueObjects []Block `json:"valueObjects"`
481
+ Operations []Operation `json:"operations"`
482
+ Events []Event `json:"events"`
483
+ // Enums are the closed sets the aggregate's fields take values from. A
484
+ // list beside Entities and ValueObjects rather than a Block with values:
485
+ // an enum has no fields, and a consumer switches on it rather than reads
486
+ // it. Omitted when the source declares none.
487
+ Enums []Enum `json:"enums,omitempty"`
488
+ Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
489
+ }
490
+
491
+ // Enum is a closed set of values a field can hold - a reason, a status, a
492
+ // code. What a consumer of an event switches on, and so what it has to be
493
+ // told: free text from a schema row says "reason: Reason", and this is what
494
+ // Reason can be.
495
+ type Enum struct {
496
+ ID string `json:"id"` // "<aggregate id>.<slug>"
497
+ Slug string `json:"slug"`
498
+ Name string `json:"name"`
499
+ Doc string `json:"doc"`
500
+ // Deprecated marks the whole set as on its way out.
501
+ Deprecated bool `json:"deprecated,omitempty"`
502
+ Values []EnumValue `json:"values"`
503
+ }
504
+
505
+ // EnumValue is one member of an Enum. Name is what a consumer sees on the
506
+ // wire when the source says so - a Go constant's literal - and the variant's
507
+ // own name otherwise.
508
+ type EnumValue struct {
509
+ Name string `json:"name"`
510
+ Doc string `json:"doc"`
511
+ Deprecated bool `json:"deprecated,omitempty"`
512
+ }
513
+
514
+ // Lifecycle is the root's state machine as the code writes it down: the
515
+ // states in the order listed, the first being where a new root starts, and
516
+ // every move between them. A state nothing leads out of is terminal.
517
+ type Lifecycle struct {
518
+ States []string `json:"states"`
519
+ Transitions []Transition `json:"transitions"`
520
+ }
521
+
522
+ // Transition is one move: the method on the root that makes it, and the
523
+ // event it hands back for it when it hands one back.
524
+ type Transition struct {
525
+ From string `json:"from"`
526
+ To string `json:"to"`
527
+ On string `json:"on"`
528
+ Emits string `json:"emits,omitempty"`
529
+ Source string `json:"source,omitempty"`
530
+ }
531
+
532
+ type OperationKind string
533
+
534
+ const (
535
+ OperationCommand OperationKind = "command"
536
+ OperationQuery OperationKind = "query"
537
+ )
538
+
539
+ type Operation struct {
540
+ ID string `json:"id"`
541
+ Kind OperationKind `json:"kind"`
542
+ Doc string `json:"doc,omitempty"`
543
+ // ExposedBy names the interface methods that run this operation, as they
544
+ // appear in RpcService.Methods. Empty means nothing outside the service can
545
+ // reach it, which is a fact worth having rather than a gap.
546
+ ExposedBy []string `json:"exposedBy,omitempty"`
547
+ // Fields is what the caller hands in: the command's or query's own shape
548
+ // as the message class declares it. Nil when the extractor does not read
549
+ // messages; an empty list is a message that carries nothing.
550
+ Fields []Field `json:"fields,omitempty"`
551
+ // Source is where the handler is, "path:line".
552
+ Source string `json:"source,omitempty"`
553
+ }
554
+
555
+ // Block is an entity or a value object. The two are told apart by the list
556
+ // they sit in, not by a field, which is why one struct serves both.
557
+ type Block struct {
558
+ ID string `json:"id"`
559
+ Slug string `json:"slug"`
560
+ Name string `json:"name"`
561
+ Doc string `json:"doc"`
562
+ // Ref names a shared catalog.defs entry. When it is set the shape is that
563
+ // def's, and Fields is empty; when it is not, Fields carries a shape local
564
+ // to this aggregate.
565
+ Ref string `json:"ref,omitempty"`
566
+ Fields []Field `json:"fields,omitempty"`
567
+ }
568
+
569
+ type Event struct {
570
+ ID string `json:"id"`
571
+ Slug string `json:"slug"`
572
+ Name string `json:"name"`
573
+ Versions []EventVersion `json:"versions"`
574
+ Consumers []EventConsumer `json:"consumers"`
575
+ // Wire is how the event leaves the service; nil when the source does
576
+ // not say.
577
+ Wire *EventWire `json:"wire,omitempty"`
578
+ }
579
+
580
+ // EventWire is the event as the bus sees it: its name on the message and the
581
+ // channel it is published on. A trace carries both, as `event.name` and
582
+ // `messaging.destination.name`, and this is where the catalog meets it.
583
+ type EventWire struct {
584
+ // Name is the name on the message: "cart.BasketCreated".
585
+ Name string `json:"name"`
586
+ // Channel is the topic, subject or stream: "cart_basket". Empty when the
587
+ // source names the event but not where it goes.
588
+ Channel string `json:"channel,omitempty"`
589
+ }
590
+
591
+ // Channel is a topic, subject or stream a service says it uses, and the
592
+ // messages that travel on it. It is what an AsyncAPI document declares, and it
593
+ // is the async half of what an OpenAPI document says about routes.
594
+ //
595
+ // The catalog already knew about channels, but only by inference: an event
596
+ // carries a Wire, and the channel was whatever the events happened to name. A
597
+ // declaration is a different fact. It says what the service means to put on the
598
+ // bus whether or not any event was found saying so, and it says what the
599
+ // service listens for - which nothing in a publisher's source could ever say.
600
+ type Channel struct {
601
+ // Address is the channel as the broker knows it: "shop.cart.basket". It is
602
+ // the same string an event's Wire.Channel carries, and comparing the two is
603
+ // how a document and the code it belongs to are held against each other.
604
+ Address string `json:"address"`
605
+ // Kind distinguishes domain-event channels, work queues, and generic message
606
+ // streams. Only event channels are checked against the domain event model.
607
+ Kind ChannelKind `json:"kind,omitempty"`
608
+
609
+ Title string `json:"title,omitempty"`
610
+ Doc string `json:"doc,omitempty"`
611
+
612
+ Messages []ChannelMessage `json:"messages"`
613
+
614
+ // Source is the document this was read out of.
615
+ Source string `json:"source,omitempty"`
616
+ }
617
+
618
+ type ChannelKind string
619
+
620
+ const (
621
+ ChannelKindEvent ChannelKind = "event"
622
+ ChannelKindJob ChannelKind = "job"
623
+ ChannelKindMessage ChannelKind = "message"
624
+ )
625
+
626
+ // ChannelDirection is which way a message travels, from this service's side.
627
+ //
628
+ // It decides ownership: a service that sends on a channel is publishing on it,
629
+ // and a channel has one publisher. A service that only receives is a
630
+ // subscriber, and any number of those is fine.
631
+ type ChannelDirection string
632
+
633
+ const (
634
+ ChannelSend ChannelDirection = "send"
635
+ ChannelReceive ChannelDirection = "receive"
636
+ )
637
+
638
+ // ChannelMessage is one message on a channel, by the name it goes by on the
639
+ // wire - the same name an event's Wire.Name carries.
640
+ type ChannelMessage struct {
641
+ Name string `json:"name"`
642
+ Title string `json:"title,omitempty"`
643
+ Doc string `json:"doc,omitempty"`
644
+ Direction ChannelDirection `json:"direction"`
645
+ // Encoding is the normalized payload serialization, for example msgpack.
646
+ // It is deliberately open-ended: a catalog must be able to carry a format
647
+ // before Portolan learns special presentation or compatibility rules for it.
648
+ Encoding string `json:"encoding,omitempty"`
649
+ // ContentType keeps the exact media type declared by a contract. Kafka
650
+ // client code commonly proves an encoding without declaring a media type,
651
+ // so the two facts are independent.
652
+ ContentType string `json:"contentType,omitempty"`
653
+ }
654
+
655
+ type EventConsumer struct {
656
+ Service string `json:"service"`
657
+ Status Status `json:"status"`
658
+ Note string `json:"note,omitempty"`
659
+
660
+ // Via is set when the consumer was derived from a flow step rather than declared.
661
+ Via *EdgeVia `json:"via,omitempty"`
662
+ }
663
+
664
+ type EventVersion struct {
665
+ Version string `json:"version"`
666
+ Doc string `json:"doc"`
667
+ Source string `json:"source"`
668
+ Fields []Field `json:"fields"`
669
+ }
670
+
671
+ type Field struct {
672
+ Name string `json:"name"`
673
+ Type string `json:"type"`
674
+ Doc string `json:"doc"`
675
+ Ref string `json:"ref,omitempty"`
676
+ // Number is the protobuf field number when the source carries one. Other
677
+ // schema and domain extractors leave it absent.
678
+ Number int `json:"number,omitempty"`
679
+ // Required is set when the source says the field must be sent: a
680
+ // Protovalidate `required`, a name in a JSON Schema `required` list.
681
+ // Absent means the source does not say, which in proto3 and OpenAPI alike
682
+ // means it may be left out.
683
+ Required bool `json:"required,omitempty"`
684
+ // Rules is what the source says a value must satisfy, in the order it
685
+ // said it.
686
+ Rules []FieldRule `json:"rules,omitempty"`
687
+ }
688
+
689
+ // FieldRule is one constraint on a field's value, in the catalog's own
690
+ // vocabulary so a Protovalidate `min_len` and a JSON Schema `minLength` are
691
+ // one rule: `min_len`, `max_len`, `len`, `pattern`, `prefix`, `suffix`,
692
+ // `contains`, `not_contains`, `format`, `gt`, `gte`, `lt`, `lte`, `const`,
693
+ // `in`, `not_in`, `multiple_of`, `min_items`, `max_items`, `unique`,
694
+ // `min_pairs`, `max_pairs`, `defined_only`, `lt_now`, `gt_now`, `cel`. A rule
695
+ // on what a list holds is prefixed `items.`; on a map's keys or values,
696
+ // `keys.` or `values.`. A custom option the catalog has no word for keeps the
697
+ // name the source gave it, `(acme.pii)`, so it is shown rather than lost.
698
+ type FieldRule struct {
699
+ Name string `json:"name"`
700
+ // Value is the bound as the source wrote it, text so a 64-bit number
701
+ // survives; absent for a bare flag such as `unique`.
702
+ Value string `json:"value,omitempty"`
703
+ }
704
+
705
+ type TypeDef struct {
706
+ Fields []Field `json:"fields"`
707
+ }
708
+
709
+ type StoreKind string
710
+
711
+ const (
712
+ StoreKindPostgres StoreKind = "postgres"
713
+ StoreKindMySQL StoreKind = "mysql"
714
+ StoreKindSQLite StoreKind = "sqlite"
715
+ StoreKindRedis StoreKind = "redis"
716
+ StoreKindMongoDB StoreKind = "mongodb"
717
+ StoreKindClickHouse StoreKind = "clickhouse"
718
+ StoreKindS3 StoreKind = "s3"
719
+ StoreKindDynamoDB StoreKind = "dynamodb"
720
+ StoreKindOther StoreKind = "other"
721
+ )
722
+
723
+ type Store struct {
724
+ ID string `json:"id"`
725
+ Slug string `json:"slug"`
726
+ Name string `json:"name"`
727
+ Kind StoreKind `json:"kind"`
728
+ Owner string `json:"owner"`
729
+ Tables []Table `json:"tables"`
730
+ Views []View `json:"views,omitempty"`
731
+ Keyspaces []RedisKeyspace `json:"keyspaces,omitempty"`
732
+ Source string `json:"source,omitempty"`
733
+ }
734
+
735
+ type RedisOperation string
736
+
737
+ const (
738
+ RedisOperationRead RedisOperation = "read"
739
+ RedisOperationWrite RedisOperation = "write"
740
+ RedisOperationDelete RedisOperation = "delete"
741
+ RedisOperationExists RedisOperation = "exists"
742
+ RedisOperationExpire RedisOperation = "expire"
743
+ RedisOperationCount RedisOperation = "count"
744
+ )
745
+
746
+ // RedisKeyspace is a family of keys proved by the expressions passed to a
747
+ // Redis client. Pattern keeps literal separators and writes dynamic pieces in
748
+ // braces; square brackets mark a conditional suffix.
749
+ type RedisKeyspace struct {
750
+ Pattern string `json:"pattern"`
751
+ Operations []RedisOperation `json:"operations"`
752
+ TTL string `json:"ttl,omitempty"`
753
+ Value string `json:"value,omitempty"`
754
+ Source string `json:"source,omitempty"`
755
+ // Persists links the value stored under this key family back to the domain
756
+ // model. It is optional because a counter, lock or coordination key may not
757
+ // hold an aggregate at all.
758
+ Persists *Persists `json:"persists,omitempty"`
759
+ Accesses []RedisAccess `json:"accesses,omitempty"`
760
+ }
761
+
762
+ // RedisAccess is one proved client call, kept separately even when several
763
+ // calls use the same key family. Operations is the compact schema summary;
764
+ // accesses answers who performs each read or write and where the proof lives.
765
+ type RedisAccess struct {
766
+ Operation RedisOperation `json:"operation"`
767
+ Method string `json:"method,omitempty"`
768
+ TTL string `json:"ttl,omitempty"`
769
+ Value string `json:"value,omitempty"`
770
+ Source string `json:"source,omitempty"`
771
+ }
772
+
773
+ type TableRole string
774
+
775
+ const (
776
+ TableRoleAggregateRoot TableRole = "aggregate-root"
777
+ TableRoleChild TableRole = "child"
778
+ TableRoleOutbox TableRole = "outbox"
779
+ TableRoleProjection TableRole = "projection"
780
+ TableRoleLookup TableRole = "lookup"
781
+ TableRoleOther TableRole = "other"
782
+ )
783
+
784
+ type Table struct {
785
+ Evidence []RelationEvidence `json:"evidence,omitempty"`
786
+ ID string `json:"id"`
787
+ Name string `json:"name"`
788
+ Doc string `json:"doc,omitempty"`
789
+ Columns []Column `json:"columns"`
790
+ Indexes []TableIndex `json:"indexes,omitempty"`
791
+ Persists *Persists `json:"persists,omitempty"`
792
+ Role TableRole `json:"role,omitempty"`
793
+ // Accesses are the source-backed repository methods that touch this table.
794
+ // They answer who reads or writes the rows; the DDL alone cannot.
795
+ Accesses []TableAccess `json:"accesses,omitempty"`
796
+ }
797
+
798
+ type TableOperation string
799
+
800
+ const (
801
+ TableOperationRead TableOperation = "read"
802
+ TableOperationWrite TableOperation = "write"
803
+ TableOperationDelete TableOperation = "delete"
804
+ )
805
+
806
+ // TableAccess is one SQL statement proved inside a repository method.
807
+ type TableAccess struct {
808
+ Operation TableOperation `json:"operation"`
809
+ Method string `json:"method,omitempty"`
810
+ Source string `json:"source,omitempty"`
811
+ }
812
+
813
+ // Persists is the link back to the model: which domain object these rows hold.
814
+ type Persists struct {
815
+ Evidence []RelationEvidence `json:"evidence,omitempty"`
816
+ Aggregate string `json:"aggregate,omitempty"`
817
+ Block string `json:"block,omitempty"`
818
+ }
819
+
820
+ type TableIndex struct {
821
+ Name string `json:"name"`
822
+ Columns []string `json:"columns"`
823
+ Unique bool `json:"unique"`
824
+ }
825
+
826
+ type Column struct {
827
+ Name string `json:"name"`
828
+ // Type is the db type as declared - uuid, timestamptz, jsonb - never
829
+ // normalised. A reader comparing a column to a migration wants the string
830
+ // the migration used.
831
+ Type string `json:"type"`
832
+ Nullable bool `json:"nullable"`
833
+ PK bool `json:"pk,omitempty"`
834
+ FK *FK `json:"fk,omitempty"`
835
+ // From is lineage: where this value CAME FROM, as "<table or view id>.<column>".
836
+ // A foreign key answers a different question - which row it points at.
837
+ From []string `json:"from,omitempty"`
838
+ Maps string `json:"maps,omitempty"`
839
+ Doc string `json:"doc,omitempty"`
840
+ }
841
+
842
+ type FK struct {
843
+ Table string `json:"table"`
844
+ Column string `json:"column"`
845
+ OnDelete string `json:"onDelete,omitempty"`
846
+ }
847
+
848
+ type View struct {
849
+ ID string `json:"id"`
850
+ Name string `json:"name"`
851
+ Doc string `json:"doc,omitempty"`
852
+ // Materialized: the database keeps the rows rather than recomputing them,
853
+ // which means they can be stale. A reader has to know that before believing
854
+ // one, so it is drawn differently rather than noted in prose.
855
+ Materialized bool `json:"materialized,omitempty"`
856
+ Columns []Column `json:"columns"`
857
+ Reads []string `json:"reads,omitempty"`
858
+ Definition string `json:"definition,omitempty"`
859
+ Persists *Persists `json:"persists,omitempty"`
860
+ Source string `json:"source,omitempty"`
861
+ }
862
+
863
+ // Flow is a sequence read out of source. Extractors may attach the execution
864
+ // trigger they proved; authored flows omit it when that evidence is not part of
865
+ // the document.
866
+ type Flow struct {
867
+ ID string `json:"id"`
868
+ Slug string `json:"slug"`
869
+ Name string `json:"name"`
870
+ Summary string `json:"summary"`
871
+ Source string `json:"source,omitempty"`
872
+ // Trigger says how execution enters this flow and how strong the static
873
+ // evidence is. Authored flows may omit it; source extractors should not.
874
+ Trigger *FlowTrigger `json:"trigger,omitempty"`
875
+ // EntryPoint is the source function this fragment expands. It is machine
876
+ // evidence for composing a queue handler with the outbound flow extracted
877
+ // independently from the same function; ordinary authored flows omit it.
878
+ EntryPoint string `json:"entrypoint,omitempty"`
879
+ // Includes names source-backed flow fragments composed into this root flow.
880
+ // It makes composition idempotent and leaves visible provenance for readers.
881
+ Includes []string `json:"includes,omitempty"`
882
+ // Owner is the bounded context the flow belongs to. The extractor knows it
883
+ // - it read the service's own tree to find the flow - so it says so rather
884
+ // than leaving a reader to work it back out of a path.
885
+ Owner string `json:"owner"`
886
+ // Participants order is significant: it is the lane order.
887
+ Participants []Participant `json:"participants"`
888
+ Steps FlowNodes `json:"steps"`
889
+ // Examples are recordings of this flow running: one per trace a verifier
890
+ // kept, each naming the steps it showed with what the spans said about
891
+ // them. They are examples of data, not evidence - the evidence is the
892
+ // status on the step.
893
+ Examples []FlowExample `json:"examples,omitempty"`
894
+ }
895
+
896
+ // FlowExample is one recorded run of a flow, read from one trace.
897
+ type FlowExample struct {
898
+ // ID is "<recording>#<trace id>": unique within the flow, stable across
899
+ // runs of the same files.
900
+ ID string `json:"id"`
901
+ // Recording is the file the trace was read from, relative to the step's
902
+ // input root - where the verifier was pointed, which is where an uploaded
903
+ // recording is kept.
904
+ Recording string `json:"recording"`
905
+ TraceID string `json:"traceId"`
906
+ // RecordedAt is when the root span started, RFC 3339 in UTC; absent when
907
+ // the recording carries no clock.
908
+ RecordedAt string `json:"recordedAt,omitempty"`
909
+ // DurationMs is root start to the last end the trace shows.
910
+ DurationMs float64 `json:"durationMs"`
911
+ // Steps are the steps this trace showed, in the order it showed them,
912
+ // with what an allowlist of span attributes said about each.
913
+ Steps []ExampleStep `json:"steps"`
914
+ }
915
+
916
+ // ExampleStep is what one span said about one step: how long it took and
917
+ // the names it carried. Values that could be somebody's data - a query text,
918
+ // a header, a path with an id in it - are not carried; see verify-otel.
919
+ type ExampleStep struct {
920
+ Step string `json:"step"`
921
+ Label string `json:"label,omitempty"`
922
+ DurationMs float64 `json:"durationMs"`
923
+ Attributes map[string]string `json:"attributes,omitempty"`
924
+ }
925
+
926
+ // StepSeen says how many recordings showed a step. On a declared step it
927
+ // accompanies the raised status; on a step the code does not declare it is
928
+ // the reason the step is in the flow at all.
929
+ type StepSeen struct {
930
+ Traces int `json:"traces"`
931
+ }
932
+
933
+ type FlowTrigger struct {
934
+ Kind string `json:"kind"`
935
+ Label string `json:"label,omitempty"`
936
+ Confidence string `json:"confidence"`
937
+ }
938
+
939
+ type ParticipantKind string
940
+
941
+ const (
942
+ ParticipantActor ParticipantKind = "actor"
943
+ ParticipantService ParticipantKind = "service"
944
+ ParticipantBroker ParticipantKind = "broker"
945
+ ParticipantStore ParticipantKind = "store"
946
+ ParticipantExternal ParticipantKind = "external"
947
+ ParticipantUnknown ParticipantKind = "unknown"
948
+ )
949
+
950
+ type Participant struct {
951
+ ID string `json:"id"`
952
+ Kind ParticipantKind `json:"kind"`
953
+ // Context is null for actors and brokers, which is a fact rather than an
954
+ // absence - hence a pointer that marshals to null, not an empty string.
955
+ Context *string `json:"context"`
956
+ Label string `json:"label,omitempty"`
957
+ }
958
+
959
+ // FlowNode is one entry in a flow: a step, or one of the three shapes that
960
+ // hold steps. It is an interface rather than a struct with four optional
961
+ // members so that a renderer has to say which shape it is handling - a flow
962
+ // that quietly treats an alt as a sequence reads as "the order was cancelled
963
+ // and then charged".
964
+ type FlowNode interface {
965
+ NodeType() string
966
+ }
967
+
968
+ // FlowNodes is a list of them. The custom unmarshaller is the only place that
969
+ // knows how the union is discriminated on the wire.
970
+ type FlowNodes []FlowNode
971
+
972
+ func (n *FlowNodes) UnmarshalJSON(data []byte) error {
973
+ var raw []json.RawMessage
974
+ if err := json.Unmarshal(data, &raw); err != nil {
975
+ return err
976
+ }
977
+
978
+ out := make(FlowNodes, 0, len(raw))
979
+ for i, item := range raw {
980
+ var probe struct {
981
+ Type string `json:"type"`
982
+ }
983
+ if err := json.Unmarshal(item, &probe); err != nil {
984
+ return fmt.Errorf("node %d: %w", i, err)
985
+ }
986
+
987
+ var node FlowNode
988
+ switch probe.Type {
989
+ case "step":
990
+ node = &Step{}
991
+ case "parallel":
992
+ node = &Parallel{}
993
+ case "alt":
994
+ node = &Alt{}
995
+ case "loop":
996
+ node = &Loop{}
997
+ default:
998
+ return fmt.Errorf("node %d: unknown flow node type %q", i, probe.Type)
999
+ }
1000
+
1001
+ if err := json.Unmarshal(item, node); err != nil {
1002
+ return fmt.Errorf("node %d (%s): %w", i, probe.Type, err)
1003
+ }
1004
+ out = append(out, node)
1005
+ }
1006
+
1007
+ *n = out
1008
+
1009
+ return nil
1010
+ }
1011
+
1012
+ type StepKind string
1013
+
1014
+ const (
1015
+ StepRPC StepKind = "rpc"
1016
+ StepEvent StepKind = "event"
1017
+ StepCall StepKind = "call"
1018
+ StepResponse StepKind = "response"
1019
+ )
1020
+
1021
+ // RelationEvidence records why an extractor or catalog resolution produced a
1022
+ // relationship. It describes source evidence, never observed execution.
1023
+ type RelationEvidence struct {
1024
+ Kind string `json:"kind"`
1025
+ Rule string `json:"rule"`
1026
+ Source string `json:"source,omitempty"`
1027
+ Symbol string `json:"symbol,omitempty"`
1028
+ Candidates []string `json:"candidates,omitempty"`
1029
+ }
1030
+
1031
+ type Step struct {
1032
+ Evidence []RelationEvidence `json:"evidence,omitempty"`
1033
+ Destination *HTTPDestination `json:"destination,omitempty"`
1034
+ Type string `json:"type"`
1035
+ ID string `json:"id"`
1036
+ From string `json:"from"`
1037
+ // To is a participant id. From == To is a self-message.
1038
+ To string `json:"to"`
1039
+ Kind StepKind `json:"kind"`
1040
+ // Ref is an Event.id, an RpcCall.id or a provided RPC method id. It
1041
+ // resolves, or Status says unresolved; there is no third option.
1042
+ Ref string `json:"ref,omitempty"`
1043
+ Label string `json:"label,omitempty"`
1044
+ Status Status `json:"status"`
1045
+ Note string `json:"note,omitempty"`
1046
+ Line string `json:"line,omitempty"`
1047
+ // ReplyTo names the synchronous request step this synthesized response
1048
+ // returns from. Extractors record requests; composition adds the response
1049
+ // only when it can prove the nested HTTP or unary RPC execution returns.
1050
+ ReplyTo string `json:"replyTo,omitempty"`
1051
+ // HTTP describes the wire response when source inspection can prove it.
1052
+ // BodyRef names the RPC method whose response is serialized, so the merged
1053
+ // catalog can resolve the schema without copying it into every flow.
1054
+ HTTP *HTTPResponse `json:"http,omitempty"`
1055
+ // ContinuesAt names the source function execution enters after this step.
1056
+ // The merge uses it only when exactly one flow declares that entry point.
1057
+ ContinuesAt string `json:"continuesAt,omitempty"`
1058
+ // Reaches names source functions proven to execute on the path represented
1059
+ // by this step. The merge may use them to attach independently extracted
1060
+ // protocol fragments that expand those exact functions.
1061
+ Reaches []string `json:"reaches,omitempty"`
1062
+ // Handoff identifies a source-backed send or receive through an asynchronous
1063
+ // channel. Matching is exact on kind, channel, and message.
1064
+ Handoff *FlowHandoff `json:"handoff,omitempty"`
1065
+ // StoreAccess identifies the repository method at extraction time and is
1066
+ // enriched with its Redis operation and key family after store fragments
1067
+ // have been merged into the catalog.
1068
+ StoreAccess *FlowStoreAccess `json:"storeAccess,omitempty"`
1069
+ // Seen counts the recordings that showed this hop. A verifier sets it;
1070
+ // on a step no source declares, it is what put the step here.
1071
+ Seen *StepSeen `json:"seen,omitempty"`
1072
+ }
1073
+
1074
+ func (*Step) NodeType() string { return "step" }
1075
+
1076
+ type HTTPResponse struct {
1077
+ Status int `json:"status,omitempty"`
1078
+ ContentType string `json:"contentType,omitempty"`
1079
+ Body string `json:"body,omitempty"`
1080
+ BodyRef string `json:"bodyRef,omitempty"`
1081
+ Encoding string `json:"encoding,omitempty"`
1082
+ Outcome string `json:"outcome,omitempty"`
1083
+ Warning string `json:"warning,omitempty"`
1084
+ Source string `json:"source,omitempty"`
1085
+ Fields []Field `json:"fields,omitempty"`
1086
+ }
1087
+
1088
+ type FlowHandoff struct {
1089
+ Kind string `json:"kind"`
1090
+ Transport string `json:"transport"`
1091
+ Channel string `json:"channel"`
1092
+ Message string `json:"message,omitempty"`
1093
+ Direction string `json:"direction"`
1094
+ }
1095
+
1096
+ type FlowStoreAccess struct {
1097
+ Store string `json:"store"`
1098
+ Method string `json:"method,omitempty"`
1099
+ Operation RedisOperation `json:"operation,omitempty"`
1100
+ Keyspace string `json:"keyspace,omitempty"`
1101
+ Source string `json:"source,omitempty"`
1102
+ }
1103
+
1104
+ type Parallel struct {
1105
+ Type string `json:"type"`
1106
+ ID string `json:"id"`
1107
+ Title string `json:"title,omitempty"`
1108
+ Branches []FlowNodes `json:"branches"`
1109
+ }
1110
+
1111
+ func (*Parallel) NodeType() string { return "parallel" }
1112
+
1113
+ // Alt is a choice: exactly one branch runs. The branches are not a sequence
1114
+ // and nothing that reads a flow may render them as one.
1115
+ type Alt struct {
1116
+ Type string `json:"type"`
1117
+ ID string `json:"id"`
1118
+ Branches []AltBranch `json:"branches"`
1119
+ }
1120
+
1121
+ func (*Alt) NodeType() string { return "alt" }
1122
+
1123
+ type AltBranch struct {
1124
+ // Title is the condition under which this branch runs, in words.
1125
+ Title string `json:"title"`
1126
+ Steps FlowNodes `json:"steps"`
1127
+ // Terminal marks a branch that ENDS the flow instead of rejoining it.
1128
+ // Without it, the steps drawn after the alt read as if they follow this
1129
+ // branch too.
1130
+ Terminal bool `json:"terminal,omitempty"`
1131
+ // Seen counts the recordings that went this way, on a frame a verifier
1132
+ // wrote where recordings parted. A branch with no steps and a count is
1133
+ // the recordings that went no further.
1134
+ Seen *StepSeen `json:"seen,omitempty"`
1135
+ }
1136
+
1137
+ type Loop struct {
1138
+ Type string `json:"type"`
1139
+ ID string `json:"id"`
1140
+ Title string `json:"title"`
1141
+ Steps FlowNodes `json:"steps"`
1142
+ }
1143
+
1144
+ func (*Loop) NodeType() string { return "loop" }
1145
+
1146
+ type AdrStatus string
1147
+
1148
+ const (
1149
+ AdrProposed AdrStatus = "proposed"
1150
+ AdrAccepted AdrStatus = "accepted"
1151
+ AdrSuperseded AdrStatus = "superseded"
1152
+ AdrDeprecated AdrStatus = "deprecated"
1153
+ AdrRejected AdrStatus = "rejected"
1154
+ )
1155
+
1156
+ // AdrScope is org-wide, or one context, or one service. The kind says which of
1157
+ // the two ids is set.
1158
+ type AdrScope struct {
1159
+ Kind string `json:"kind"`
1160
+ Context string `json:"context,omitempty"`
1161
+ Service string `json:"service,omitempty"`
1162
+ }
1163
+
1164
+ // Adr is frozen history. It says what was decided and when, not what the model
1165
+ // looks like now, so nothing on it is ever regenerated from the current
1166
+ // catalog.
1167
+ type Adr struct {
1168
+ ID string `json:"id"`
1169
+ Slug string `json:"slug"`
1170
+ Number int `json:"number"`
1171
+ Title string `json:"title"`
1172
+ Status AdrStatus `json:"status"`
1173
+ Date string `json:"date"`
1174
+ Scope AdrScope `json:"scope"`
1175
+ Body string `json:"body"`
1176
+ Note string `json:"note,omitempty"`
1177
+ SupersededBy string `json:"supersededBy,omitempty"`
1178
+ Supersedes []string `json:"supersedes,omitempty"`
1179
+ Relates AdrRelates `json:"relates"`
1180
+ Source string `json:"source"`
1181
+
1182
+ // Created is the commit that first added the file, and Revised the one
1183
+ // that last touched it, when that is a different commit. Absent when the
1184
+ // tree has no history to read.
1185
+ Created *AdrCommit `json:"created,omitempty"`
1186
+ Revised *AdrCommit `json:"revised,omitempty"`
1187
+ }
1188
+
1189
+ // AdrCommit is one commit of a record's file: who made it and when. The
1190
+ // markdown says when a decision was taken; git says when it was written down
1191
+ // and by whom, which is the other half of "who decided this".
1192
+ type AdrCommit struct {
1193
+ Commit string `json:"commit"`
1194
+ Author string `json:"author"`
1195
+ Date string `json:"date"`
1196
+ }
1197
+
1198
+ type AdrRelates struct {
1199
+ Services []string `json:"services,omitempty"`
1200
+ Events []string `json:"events,omitempty"`
1201
+ Flows []string `json:"flows,omitempty"`
1202
+ }
1203
+
1204
+ // Term is one entry of a context's glossary: a word, and what it means inside
1205
+ // the boundary that means it.
1206
+ type Term struct {
1207
+ ID string `json:"id"`
1208
+ Slug string `json:"slug"`
1209
+ Context string `json:"context"`
1210
+ Name string `json:"name"`
1211
+ Definition string `json:"definition"`
1212
+ Source string `json:"source"`
1213
+ }