@shortlink-org/portolan 0.3.0 → 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 (188) hide show
  1. package/README.md +151 -6
  2. package/catalog/model.go +147 -0
  3. package/catalog/roundtrip_test.go +7 -0
  4. package/cli/portolan.mjs +6 -1
  5. package/go.mod +7 -0
  6. package/go.sum +20 -6
  7. package/package.json +10 -9
  8. package/plugin/describe.go +38 -0
  9. package/plugins/README.md +296 -5
  10. package/plugins/extract-argocd/options.schema.json +44 -0
  11. package/plugins/extract-celery/main.py +1 -0
  12. package/plugins/extract-csharp-ddd/README.md +213 -0
  13. package/plugins/extract-csharp-ddd/options.schema.json +55 -0
  14. package/plugins/extract-django/README.md +13 -2
  15. package/plugins/extract-django/choices.py +47 -0
  16. package/plugins/extract-django/domain.py +2 -1
  17. package/plugins/extract-django/extract_test.py +13 -0
  18. package/plugins/extract-django/lifecycle.py +2 -28
  19. package/plugins/extract-django/main.py +1 -0
  20. package/plugins/extract-django/rules.py +154 -0
  21. package/plugins/extract-django/rules_test.py +158 -0
  22. package/plugins/extract-go-sqs/options.schema.json +21 -0
  23. package/plugins/extract-http-clients/describe.go +5 -4
  24. package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
  25. package/plugins/extract-k8s/options.schema.json +32 -0
  26. package/plugins/extract-laravel/Cargo.lock +962 -0
  27. package/plugins/extract-laravel/Cargo.toml +20 -0
  28. package/plugins/extract-laravel/README.md +200 -0
  29. package/plugins/extract-laravel/options.schema.json +68 -0
  30. package/plugins/extract-laravel/rustfmt.toml +1 -0
  31. package/plugins/extract-php-ddd/Cargo.lock +972 -0
  32. package/plugins/extract-php-ddd/Cargo.toml +22 -0
  33. package/plugins/extract-php-ddd/README.md +141 -0
  34. package/plugins/extract-php-ddd/options.schema.json +50 -0
  35. package/plugins/extract-php-ddd/rustfmt.toml +1 -0
  36. package/plugins/extract-python-kafka/main.py +1 -0
  37. package/plugins/extract-terraform/options.schema.json +26 -0
  38. package/plugins/extract-ts/main.ts +1 -0
  39. package/plugins/phpscan/Cargo.lock +953 -0
  40. package/plugins/phpscan/Cargo.toml +19 -0
  41. package/plugins/phpscan/rustfmt.toml +1 -0
  42. package/plugins/portolan-go.wasm +0 -0
  43. package/plugins/pyplugin/catalog.py +24 -1
  44. package/plugins/verify-otel/options.schema.json +12 -0
  45. package/portolan.json +130 -3
  46. package/rules/builtin.json +218 -0
  47. package/schema/portolan.schema.json +880 -13
  48. package/scripts/README.md +3 -1
  49. package/scripts/delivery-presets.mjs +36 -3
  50. package/scripts/gen-likec4.mjs +149 -16
  51. package/scripts/gen-likec4.test.mjs +96 -0
  52. package/scripts/gen.mjs +30 -3
  53. package/scripts/gitops-example.test.mjs +108 -0
  54. package/scripts/host-plugins/fetch-argocd.mjs +338 -0
  55. package/scripts/host-plugins/fetch-argocd.options.json +49 -0
  56. package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
  57. package/scripts/host-plugins/fetch-bsr.mjs +1 -0
  58. package/scripts/host-plugins/fetch-csr.mjs +1 -0
  59. package/scripts/host-plugins/fetch-git.mjs +1 -0
  60. package/scripts/host-plugins/fetch-k8s.mjs +263 -0
  61. package/scripts/host-plugins/fetch-k8s.options.json +50 -0
  62. package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
  63. package/scripts/host-plugins/k8s-topology.mjs +183 -0
  64. package/scripts/json-format.mjs +192 -0
  65. package/scripts/json-format.test.mjs +97 -0
  66. package/scripts/local-api.mjs +216 -8
  67. package/scripts/local-api.test.mjs +88 -2
  68. package/scripts/local-discovery.mjs +12 -0
  69. package/scripts/manifest.mjs +10 -1
  70. package/scripts/plugin-host.mjs +23 -1
  71. package/scripts/plugins-fresh.mjs +97 -0
  72. package/scripts/plugins-fresh.test.mjs +64 -0
  73. package/scripts/run-builtin.mjs +26 -5
  74. package/scripts/schema.mjs +138 -19
  75. package/scripts/trace-trials.mjs +176 -0
  76. package/scripts/trace-trials.test.mjs +142 -0
  77. package/scripts/warning-policy.mjs +12 -6
  78. package/scripts/warning-policy.test.mjs +40 -3
  79. package/src/app/Breadcrumbs.test.ts +3 -0
  80. package/src/app/Breadcrumbs.tsx +3 -0
  81. package/src/app/CatalogApp.tsx +2 -0
  82. package/src/app/SidebarFooter.tsx +20 -4
  83. package/src/catalog-index.ts +25 -0
  84. package/src/catalog-model.ts +246 -1
  85. package/src/catalog-validation.ts +104 -2
  86. package/src/catalog.test.ts +40 -1
  87. package/src/components/ChannelRows.test.tsx +76 -1
  88. package/src/components/ChannelRows.tsx +223 -60
  89. package/src/components/DeploymentRows.tsx +131 -0
  90. package/src/components/DocsLinks.test.tsx +27 -0
  91. package/src/components/DocsLinks.tsx +56 -0
  92. package/src/components/FieldTree.tsx +5 -2
  93. package/src/components/HTTPDestinationEvidence.tsx +1 -1
  94. package/src/components/PluginIcon.tsx +77 -0
  95. package/src/components/ProblemRow.tsx +173 -153
  96. package/src/components/RuleMarks.tsx +22 -0
  97. package/src/components/ShapeRows.tsx +24 -20
  98. package/src/data.ts +21 -0
  99. package/src/enrich.test.ts +127 -3
  100. package/src/enrich.ts +104 -6
  101. package/src/er/ErCanvas.tsx +218 -12
  102. package/src/er/GroupNode.tsx +57 -0
  103. package/src/er/StoreHeader.tsx +1 -0
  104. package/src/er/layout.test.ts +85 -2
  105. package/src/er/layout.ts +140 -5
  106. package/src/er/spec.test.ts +17 -0
  107. package/src/er/spec.ts +23 -10
  108. package/src/flow/Recordings.test.tsx +52 -0
  109. package/src/flow/Recordings.tsx +236 -0
  110. package/src/flow/StepDetail.tsx +53 -0
  111. package/src/flow/TraceTrial.tsx +419 -0
  112. package/src/flow/examples.test.ts +33 -0
  113. package/src/flow/examples.ts +37 -0
  114. package/src/flow/outline.test.ts +28 -0
  115. package/src/flow/outline.ts +7 -2
  116. package/src/flow/trace-trial-resume.test.ts +42 -0
  117. package/src/flow/trace-trial-resume.ts +74 -0
  118. package/src/graph/elk.ts +78 -0
  119. package/src/index.css +24 -0
  120. package/src/landing/LandingPage.tsx +15 -11
  121. package/src/landing/ProductTour.tsx +6 -6
  122. package/src/lib/all-problems.ts +27 -17
  123. package/src/lib/centrality.test.ts +251 -0
  124. package/src/lib/centrality.ts +232 -0
  125. package/src/lib/confluence.test.ts +37 -0
  126. package/src/lib/confluence.ts +41 -0
  127. package/src/lib/context-color.ts +1 -1
  128. package/src/lib/deployment-drift.ts +22 -0
  129. package/src/lib/derive.ts +43 -69
  130. package/src/lib/environments.test.ts +83 -0
  131. package/src/lib/environments.ts +57 -0
  132. package/src/lib/integration-url.test.ts +30 -0
  133. package/src/lib/integration-url.ts +63 -0
  134. package/src/lib/kafka-ui.ts +3 -45
  135. package/src/lib/local-api.ts +96 -2
  136. package/src/lib/notion.ts +13 -0
  137. package/src/lib/plugin-index.json +2765 -0
  138. package/src/lib/plugins.test.ts +68 -0
  139. package/src/lib/plugins.ts +264 -0
  140. package/src/lib/problem-flows.test.ts +61 -0
  141. package/src/lib/problem-flows.ts +78 -0
  142. package/src/lib/problem-rules-cel.d.mts +43 -0
  143. package/src/lib/problem-rules-cel.mjs +407 -0
  144. package/src/lib/problem-rules.test.ts +287 -0
  145. package/src/lib/problem-rules.ts +271 -0
  146. package/src/lib/problem-subjects.ts +737 -0
  147. package/src/lib/rule-entries.ts +39 -0
  148. package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
  149. package/src/lib/rules-deploy.test.ts +133 -0
  150. package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
  151. package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
  152. package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
  153. package/src/lib/rules.test.ts +51 -0
  154. package/src/lib/rules.ts +86 -0
  155. package/src/lib/shape.test.ts +32 -0
  156. package/src/lib/shape.ts +30 -6
  157. package/src/lib/tech.ts +16 -0
  158. package/src/lib/trace-project.test.ts +34 -0
  159. package/src/lib/trace-project.ts +50 -0
  160. package/src/lib/use-problems.ts +23 -0
  161. package/src/lib/warnings.test.ts +9 -0
  162. package/src/lib/warnings.ts +10 -10
  163. package/src/likec4/ids.test.ts +6 -2
  164. package/src/likec4/ids.ts +43 -0
  165. package/src/main.tsx +23 -0
  166. package/src/merge-deployments.test.ts +127 -0
  167. package/src/merge.test.ts +82 -0
  168. package/src/merge.ts +173 -9
  169. package/src/pages/AggregatePage.tsx +57 -7
  170. package/src/pages/ContextMap.tsx +45 -3
  171. package/src/pages/ContextPage.tsx +2 -0
  172. package/src/pages/EventPage.tsx +15 -5
  173. package/src/pages/FlowDetail.tsx +23 -2
  174. package/src/pages/GraphPage.tsx +40 -3
  175. package/src/pages/Overview.tsx +152 -12
  176. package/src/pages/PluginIndex.tsx +190 -0
  177. package/src/pages/Problems.tsx +396 -128
  178. package/src/pages/ServicePage.tsx +58 -2
  179. package/src/pages/Settings.tsx +28 -2
  180. package/src/pages/settings/AboutSettings.tsx +8 -1
  181. package/src/pages/settings/IntegrationsSettings.tsx +63 -17
  182. package/src/pages/settings/RecordingSettings.tsx +138 -0
  183. package/src/pages/settings/RulesSettings.tsx +825 -0
  184. package/src/routes.test.ts +9 -0
  185. package/src/routes.ts +22 -1
  186. package/src/lib/data-problems.ts +0 -314
  187. package/src/lib/proto-problems.ts +0 -237
  188. package/src/lib/wire-problems.ts +0 -342
package/README.md CHANGED
@@ -87,8 +87,9 @@ reference).
87
87
  - **Diagrams** — LikeC4 C4 views (estate landscape, every container in the
88
88
  estate with its technology and the protocol on each edge, one per context,
89
89
  two per service, one dynamic view per flow), an ELK-routed dependency graph,
90
- and a context map. The app never draws these itself; `npm run likec4:gen`
91
- writes the model from the catalog.
90
+ and a context map. The app never draws these itself; `npm run gen` writes
91
+ the model from the catalog as its last step, and holds it to the catalog in
92
+ `gen:check`, and `npm run likec4:gen` writes it on its own before `dev`.
92
93
  - **ER canvases** — per store: tables, views, keys and crow's feet, plus column
93
94
  lineage (`from`) drawn dashed; hovering a column lights the whole chain back
94
95
  to where the value came from.
@@ -116,7 +117,43 @@ first:
116
117
  with a second writer, a table that no longer holds the aggregate it claims,
117
118
  a column whose type has drifted from its field's, an outbox with no payload;
118
119
  - a channel with a second publisher, an event on a channel its service does not
119
- declare, a declared channel no event names, a subscription nothing publishes.
120
+ declare, a declared channel no event names, a subscription nothing publishes;
121
+ - what the deployer runs from a place no service lives at, and where the GitOps
122
+ tree and the deployer disagree.
123
+
124
+ Each check is a rule in `rules/builtin.json`: a passport - id, severity, what
125
+ it looks for and what to do about a row - and the rule itself, in CEL over one
126
+ subject. A subject is one row with every fact a rule would otherwise have to
127
+ look up already on it - a table knows who writes it, a channel knows who else
128
+ publishes there, a call knows whether its peer is in the estate - so a rule is
129
+ one line a reader can read, copy and tighten. **Settings → Rules** lists them
130
+ with the rows each produces now. A shipped rule can be switched off or
131
+ re-graded in `portolan.json`, with a reason, and a rule of your own is written
132
+ there the same way - over a service, a call, a copy, an event, a consumer, a
133
+ channel, a subscription, a table, a column, a deployment, a flow or an
134
+ aggregate - and runs in the page the moment it is saved:
135
+
136
+ ```json
137
+ {
138
+ "problemRules": [
139
+ { "id": "shared-store", "enabled": false, "reason": "the estate shares one database by design" },
140
+ {
141
+ "id": "team.quiet-event",
142
+ "over": "event",
143
+ "severity": "warning",
144
+ "title": "Event nobody consumes",
145
+ "when": "size(event.consumers) == 0 && !event.name.endsWith('Audit')",
146
+ "message": "'nothing consumes ' + event.id",
147
+ "peer": "event.service"
148
+ }
149
+ ]
150
+ }
151
+ ```
152
+
153
+ Expressions are type-checked against the subject's fields when the manifest is
154
+ read - `event.nme` is refused, not shown as an empty page - and again in the
155
+ page, by the same module. The Rules page writes the entry after a preview of
156
+ the rows it would add (portolan.0016, portolan.0017).
120
157
 
121
158
  ## Where the facts come from
122
159
 
@@ -125,17 +162,24 @@ Plugins, one JSON message in and one out (`plugins/README.md`), declared in
125
162
 
126
163
  | phase | plugins |
127
164
  | --- | --- |
128
- | extract | `extract-project`, `extract-go`, `extract-ts`, `extract-rust`, `extract-java`, `extract-django`, `extract-celery`, `extract-python-kafka`, `extract-openapi`, `extract-wsdl`, `extract-http-clients`, `extract-redis`, `extract-asyncapi`, `extract-graphql`, `extract-proto`, `extract-river`, `extract-watermill`, `extract-go-nats`, `extract-csr`, `extract-sql`, `extract-flows`, `extract-adr`, `extract-glossary`, `extract-commands` |
165
+ | extract | `extract-project`, `extract-go`, `extract-ts`, `extract-rust`, `extract-java`, `extract-django`, `extract-laravel`, `extract-php-ddd`, `extract-csharp-ddd`, `extract-celery`, `extract-python-kafka`, `extract-openapi`, `extract-wsdl`, `extract-http-clients`, `extract-redis`, `extract-asyncapi`, `extract-graphql`, `extract-proto`, `extract-river`, `extract-watermill`, `extract-go-nats`, `extract-go-sqs`, `extract-csr`, `extract-sql`, `extract-flows`, `extract-adr`, `extract-glossary`, `extract-commands`, `extract-k8s`, `extract-argocd` |
129
166
  | verify | `verify-otel` — reads traces, marks the hops they show as `verified`; `verify-codeowners` — reads CODEOWNERS, says who to ask about each service |
130
167
  | generate | `gen-markdown` — `docs/`, `gen-mermaid` — standalone flow diagrams, `gen-backstage` — Backstage entities |
131
168
 
132
169
  `fetch-git`, `fetch-bsr` and `fetch-csr` bring in sources from other
133
170
  repositories, the Buf Schema Registry and a Confluent Schema Registry, against a
134
- lock, so a later build can reproduce them without a socket.
171
+ lock, so a later build can reproduce them without a socket. `fetch-k8s` reads a
172
+ live cluster through `kubectl` into the names each service answers on and
173
+ dials, and replays the committed fragment when there is no cluster to ask.
174
+ `fetch-argocd` reads the applications an Argo CD server manages into a snapshot
175
+ of where each service runs - environment, cluster, namespace, revision, images -
176
+ against the same kind of lock; the service page shows it under "Where it runs".
135
177
 
136
178
  Each plugin describes its own options; `npm run schema` asks all of them and
137
179
  composes `schema/portolan.schema.json`, which editors complete against and `gen`
138
- checks before running anything.
180
+ checks before running anything. The same answers are rendered as the site's
181
+ plugin reference - `/plugins` on any generated site, grouped by what each reads
182
+ or makes, with the options it takes - so the list above is the short form.
139
183
 
140
184
  Non-fatal extractor diagnostics remain attached to their pipeline step. The
141
185
  Settings page groups repetitions by plugin, stable rule and severity, and shows
@@ -257,6 +301,107 @@ live in a dedicated architecture repository: `fetch-git` pins the service
257
301
  repositories at immutable commits and the normal merge, check, diff, and build
258
302
  commands operate on the combined estate.
259
303
 
304
+ ### A real one: Bagisto
305
+
306
+ The services under `examples/` are small on purpose. For what the catalog
307
+ looks like on a real application, the site ships a third profile: pick
308
+ **Bagisto** in the catalog selector. [Bagisto](https://github.com/bagisto/bagisto)
309
+ is an open-source e-commerce platform on Laravel - 41 packages, one per
310
+ module, talking to each other through named events - read by
311
+ `extract-laravel` alone, with nothing annotated for the catalog.
312
+
313
+ What comes out of Bagisto 2.4, pinned in `vendor/repos/bagisto/bagisto`:
314
+
315
+ | | |
316
+ | --- | --- |
317
+ | model groups | 28, one per package with models, 125 Eloquent models and 22 enums between them |
318
+ | events | 309, most of them named - `Event::dispatch('sales.order.cancel.after', …)` - owned by the package their name says, 50 with a listener |
319
+ | HTTP | 520 operations in 10 interfaces, one per route file, with an inferred OpenAPI document |
320
+ | database | 138 tables and 1300 columns replayed from 189 migrations, 185 foreign keys, 120 places the code reads or writes them |
321
+ | queues | 17 jobs on one queue, each a hop in the flow that dispatches it and a worker flow of its own |
322
+ | flows | 610 |
323
+
324
+ Go to the service and read it the way the tabs are ordered: **bus** for the
325
+ queue, **data** for the schema laid out by model group - the picker beside
326
+ the arrangement toggle switches groups off, the search brings a table up
327
+ close - and **flows** for `shop-checkout-onepage-orders-store`, the checkout
328
+ followed from the controller through the repository it holds to the events
329
+ and the job it ends in. Every "view source" link opens the file on GitHub at
330
+ the pinned commit.
331
+
332
+ The warnings the extractor left are findings about Bagisto, not about the
333
+ tool: 218 events under the admin package are dispatched by name and declare
334
+ no payload; `Settings\CurrencyController` answers admin routes and does not
335
+ exist; the sitemap job reads a `pages` table no migration creates; a
336
+ foreign key points at a `theme_customizations` table no migration creates;
337
+ one listener waits for `bagisto.shop.products.price.after`, which nothing
338
+ dispatches, and one for the repository events of a vendored package. How the
339
+ fragments were made, and how to refresh them, is in
340
+ `vendor/repos/bagisto/bagisto/README.md`; the reading rules are in
341
+ `plugins/extract-laravel/README.md`.
342
+
343
+ ### The small one, by the book: CodelyTV php-ddd-example
344
+
345
+ The fourth profile, **CodelyTV php-ddd-example**, is the opposite kind of
346
+ real: [php-ddd-example](https://github.com/CodelyTV/php-ddd-example) is
347
+ CodelyTV's reference for DDD, hexagonal architecture and CQRS in PHP, a
348
+ Symfony monorepository laid out as `src/<Context>/<Module>/{Domain,Application,Infrastructure}`
349
+ with the deployables under `apps/`, read by `extract-php-ddd` alone - the
350
+ layout is the claim, and nothing is annotated. What comes out, pinned in
351
+ `vendor/repos/CodelyTV/php-ddd-example`: four contexts (Mooc, Backoffice,
352
+ Analytics, Retention), six services, five aggregates with their value
353
+ objects and three domain events on one RabbitMQ exchange, one queue per
354
+ subscriber, the MySQL tables from the Doctrine mappings including a JOINED
355
+ `steps` family, and sixteen flows. The one to read is
356
+ `backoffice-frontend-courses-post`: the back office's form dispatches
357
+ Mooc's `CreateCourseCommand` over an in-memory bus, so the flow crosses to
358
+ `mooc.backend` with a `call` step, records `CourseCreated`, saves the
359
+ course, and the subscribers in Mooc, Backoffice and Analytics pick the event
360
+ up from their own queues.
361
+
362
+ The warnings are findings about the project: Analytics and Retention have no
363
+ application under `apps/`, so their code deploys nowhere and Analytics'
364
+ subscriber to every event runs nowhere; `TrimVideoCommandHandler` is named
365
+ like a handler and implements neither bus interface; Retention's four modules
366
+ and Mooc's Notifications are empty scaffolding; the front end routes
367
+ `/api/courses` to a controller that does not exist. How the fragments were
368
+ made is in `vendor/repos/CodelyTV/php-ddd-example/README.md`; the reading
369
+ rules are in `plugins/extract-php-ddd/README.md`.
370
+
371
+ ### The same lesson in C#: Modular Monolith with DDD
372
+
373
+ The fifth profile, **Modular Monolith with DDD**, is Kamil Grzybek's
374
+ [modular-monolith-with-ddd](https://github.com/kgrzybek/modular-monolith-with-ddd),
375
+ the reference for the pattern in .NET: five modules laid out as
376
+ `src/Modules/<Module>/{Domain,Application,Infrastructure,IntegrationEvents}`,
377
+ one API host, one SQL Server database with a schema per module, read by
378
+ `extract-csharp-ddd` alone - through Roslyn, the compiler's own parser, over
379
+ the tree as one compilation and without restoring a single package. What
380
+ comes out, pinned in `vendor/repos/kgrzybek/modular-monolith-with-ddd`: five
381
+ contexts with one service each, nineteen aggregates and five model groups,
382
+ sixty domain events dispatched in process and seven integration events on
383
+ the in-memory bus, sixty-one commands and twenty-nine queries, an internal command queue
384
+ per module, the forty-three tables and seventeen views of the database
385
+ project with the root tables mapped to their aggregates through the EF
386
+ configurations, fifty-one HTTP operations across four documents, the
387
+ project's seventeen decision records, and a hundred and two flows. The one
388
+ to read is `register-new-user`: the User Access controller hands
389
+ `RegisterNewUserCommand` to the Registrations module's facade in process,
390
+ the flow crosses with a `call` step, `NewUserRegisteredDomainEvent` is
391
+ raised and saved, and from there the outbox republishes it to the bus as an
392
+ integration event that Meetings, Administration and Payments each hear and
393
+ turn into a job on their own internal queue.
394
+
395
+ The warnings are findings about the project: `MemberCreatedIntegrationEvent`
396
+ is declared and published nowhere while User Access subscribes to it;
397
+ Payments subscribes to `MeetingGroupProposalAcceptedIntegrationEvent` and
398
+ has no handler; the `app` schema belongs to no module; the view is created
399
+ as `v_Countriess` and the query reads `v_Countries`; and two modules keep a
400
+ `Domain/Users` directory with a typed id and nothing to hold it. How the
401
+ fragments were made is in
402
+ `vendor/repos/kgrzybek/modular-monolith-with-ddd/README.md`; the reading
403
+ rules are in `plugins/extract-csharp-ddd/README.md`.
404
+
260
405
  ## Develop Portolan itself
261
406
 
262
407
  ```bash
package/catalog/model.go CHANGED
@@ -44,9 +44,59 @@ type Catalog struct {
44
44
  Modules []ProtoModule `json:"modules,omitempty"`
45
45
  Terms []Term `json:"terms,omitempty"`
46
46
  Repos []RepoPin `json:"repos,omitempty"`
47
+ Deployments []Deployment `json:"deployments,omitempty"`
47
48
  Externals []External `json:"externals,omitempty"`
48
49
  }
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
+
50
100
  // External is a system outside the estate with a contract: what it answers on,
51
101
  // read from the copy of its document vendored beside the adapter that calls it,
52
102
  // and nothing else. It sits at the root beside the contexts, so its id is its
@@ -160,6 +210,23 @@ type Service struct {
160
210
  // list is the one the runner would accept. Absent when nothing declares
161
211
  // any, which is not the same as a service that cannot be built.
162
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"`
163
230
  }
164
231
 
165
232
  // Command is one entry of a task runner's file: a make target, an npm script,
@@ -477,6 +544,12 @@ type Operation struct {
477
544
  // appear in RpcService.Methods. Empty means nothing outside the service can
478
545
  // reach it, which is a fact worth having rather than a gap.
479
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"`
480
553
  }
481
554
 
482
555
  // Block is an entity or a value object. The two are told apart by the list
@@ -603,6 +676,30 @@ type Field struct {
603
676
  // Number is the protobuf field number when the source carries one. Other
604
677
  // schema and domain extractors leave it absent.
605
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"`
606
703
  }
607
704
 
608
705
  type TypeDef struct {
@@ -619,6 +716,7 @@ const (
619
716
  StoreKindMongoDB StoreKind = "mongodb"
620
717
  StoreKindClickHouse StoreKind = "clickhouse"
621
718
  StoreKindS3 StoreKind = "s3"
719
+ StoreKindDynamoDB StoreKind = "dynamodb"
622
720
  StoreKindOther StoreKind = "other"
623
721
  )
624
722
 
@@ -788,6 +886,48 @@ type Flow struct {
788
886
  // Participants order is significant: it is the lane order.
789
887
  Participants []Participant `json:"participants"`
790
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"`
791
931
  }
792
932
 
793
933
  type FlowTrigger struct {
@@ -926,6 +1066,9 @@ type Step struct {
926
1066
  // enriched with its Redis operation and key family after store fragments
927
1067
  // have been merged into the catalog.
928
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"`
929
1072
  }
930
1073
 
931
1074
  func (*Step) NodeType() string { return "step" }
@@ -985,6 +1128,10 @@ type AltBranch struct {
985
1128
  // Without it, the steps drawn after the alt read as if they follow this
986
1129
  // branch too.
987
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"`
988
1135
  }
989
1136
 
990
1137
  type Loop struct {
@@ -27,6 +27,13 @@ func TestRoundTrip(t *testing.T) {
27
27
  if len(files) == 0 {
28
28
  t.Fatal("no catalogs under ../data - this test proves nothing without one")
29
29
  }
30
+ // The snapshot a fetcher wrote is a catalog too, and the one place the
31
+ // deployments field is exercised by a real file.
32
+ snapshots, err := filepath.Glob("../examples/argocd/argocd.apps.json")
33
+ if err != nil {
34
+ t.Fatal(err)
35
+ }
36
+ files = append(files, snapshots...)
30
37
 
31
38
  for _, path := range files {
32
39
  t.Run(filepath.Base(path), func(t *testing.T) {
package/cli/portolan.mjs CHANGED
@@ -213,7 +213,9 @@ export async function prepareSite(workspace) {
213
213
  rmSync(stage, { recursive: true, force: true });
214
214
  mkdirSync(stage, { recursive: true });
215
215
 
216
- for (const directory of ["src", "public", "scripts"]) {
216
+ // rules/ rides along: scripts/manifest.mjs and src/lib/problem-rules.ts read
217
+ // ../rules/builtin.json beside themselves.
218
+ for (const directory of ["src", "public", "scripts", "rules"]) {
217
219
  cpSync(resolve(installRoot, directory), resolve(stage, directory), { recursive: true });
218
220
  }
219
221
  for (const file of ["index.html", "vite.config.ts", "tsconfig.json"]) {
@@ -382,6 +384,9 @@ function prepareHost(workspace) {
382
384
  mkdirSync(host, { recursive: true });
383
385
  cpSync(resolve(installRoot, "scripts"), resolve(host, "scripts"), { recursive: true });
384
386
  cpSync(resolve(installRoot, "src"), resolve(host, "src"), { recursive: true });
387
+ // scripts/manifest.mjs and src/lib/problem-rules.ts read ../rules/builtin.json
388
+ // beside themselves; the host is the package with the same shape.
389
+ cpSync(resolve(installRoot, "rules"), resolve(host, "rules"), { recursive: true });
385
390
  writeFileSync(resolve(host, "package.json"), `${JSON.stringify({ private: true, type: "module", version: VERSION })}\n`);
386
391
  symlinkSync(dependencyRoot(), resolve(host, "node_modules"), process.platform === "win32" ? "junction" : "dir");
387
392
  return host;
package/go.mod CHANGED
@@ -3,12 +3,19 @@ module github.com/shortlink-org/portolan
3
3
  go 1.27.0
4
4
 
5
5
  require (
6
+ github.com/hashicorp/hcl/v2 v2.24.0
6
7
  github.com/pgplex/pgparser v0.2.0
8
+ github.com/zclconf/go-cty v1.19.0
7
9
  golang.org/x/tools v0.50.0
8
10
  gopkg.in/yaml.v3 v3.0.1
9
11
  )
10
12
 
11
13
  require (
14
+ github.com/agext/levenshtein v1.2.1 // indirect
15
+ github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
16
+ github.com/apparentlymart/go-textseg/v17 v17.0.1 // indirect
17
+ github.com/mitchellh/go-wordwrap v1.0.1 // indirect
12
18
  golang.org/x/mod v0.41.0 // indirect
13
19
  golang.org/x/sync v0.23.0 // indirect
20
+ golang.org/x/text v0.25.0 // indirect
14
21
  )
package/go.sum CHANGED
@@ -1,17 +1,31 @@
1
+ github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
2
+ github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
3
+ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
4
+ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
5
+ github.com/apparentlymart/go-textseg/v17 v17.0.1 h1:bpMXRgQ5cEoRNuQke1a80/Nl6w3G5eoIbWo9f3gXkAs=
6
+ github.com/apparentlymart/go-textseg/v17 v17.0.1/go.mod h1:fa8X4jgGeevslICIY6LcdjkSecWnXmYd9Lk34z/VxZs=
7
+ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8
+ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9
+ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
10
+ github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
1
11
  github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2
12
  github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
13
+ github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
14
+ github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
15
+ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
16
+ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
3
17
  github.com/pgplex/pgparser v0.2.0 h1:BB7I/Mrwetiw07PTq/AlXcKfajlCwoJfwB7KZ7V9irI=
4
18
  github.com/pgplex/pgparser v0.2.0/go.mod h1:DWhK7oZJn/16KTFeOQ+mk78+sRnEYya1XPghIoP9NFU=
5
- golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74=
6
- golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY=
19
+ github.com/zclconf/go-cty v1.19.0 h1:IV8WdqYZc2c5rLX9bEoLNXKojBAp0MZPBHMIrCoa/s4=
20
+ github.com/zclconf/go-cty v1.19.0/go.mod h1:12W89jGn3JCOIQi7infWr9m80rOkb5RNYJqXMZcN4c8=
21
+ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
22
+ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
7
23
  golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
8
24
  golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
9
- golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
10
- golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
11
25
  golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
12
26
  golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
13
- golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
14
- golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
27
+ golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
28
+ golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
15
29
  golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU=
16
30
  golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
17
31
  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@shortlink-org/portolan",
3
3
  "description": "Generate a navigable architecture catalog from code and specifications.",
4
4
  "private": false,
5
- "version": "0.3.0",
5
+ "version": "0.4.0",
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "homepage": "https://github.com/shortlink-org/portolan#readme",
@@ -21,6 +21,7 @@
21
21
  },
22
22
  "files": [
23
23
  "cli",
24
+ "rules",
24
25
  "scripts",
25
26
  "src",
26
27
  "public",
@@ -61,7 +62,7 @@
61
62
  "build": "node cli/portolan.mjs build",
62
63
  "preview": "vite preview",
63
64
  "test": "vitest run",
64
- "test:go": "go test ./...",
65
+ "test:go": "GOFLAGS=-mod=mod go test ./...",
65
66
  "pretest:package": "npm run plugins:build",
66
67
  "test:package": "node scripts/package-smoke.mjs",
67
68
  "test:watch": "vitest",
@@ -69,7 +70,7 @@
69
70
  "likec4:gen": "node scripts/gen-likec4.mjs && likec4 gen react likec4 -o src/likec4/generated.jsx --no-use-dot",
70
71
  "likec4:validate": "likec4 validate likec4",
71
72
  "predev": "npm run likec4:gen",
72
- "plugins:build": "GOOS=wasip1 GOARCH=wasm go build -trimpath -ldflags=\"-s -w\" -o plugins/portolan-go.wasm ./plugins/cmd/portolan-go && javac --release 21 -d plugins/extract-java/build plugins/extract-java/src/org/portolan/extract/*.java",
73
+ "plugins:build": "GOFLAGS=-mod=mod GOOS=wasip1 GOARCH=wasm go build -trimpath -ldflags=\"-s -w\" -o plugins/portolan-go.wasm ./plugins/cmd/portolan-go && javac --release 21 -d plugins/extract-java/build plugins/extract-java/src/org/portolan/extract/*.java && dotnet build plugins/extract-csharp-ddd -c Release --nologo -v q",
73
74
  "schema": "npm run plugins:build && node scripts/schema.mjs",
74
75
  "schema:check": "npm run plugins:build && node scripts/schema.mjs --check",
75
76
  "gen": "npm run plugins:build && node scripts/gen.mjs",
@@ -78,8 +79,8 @@
78
79
  "prepack": "npm run plugins:build"
79
80
  },
80
81
  "dependencies": {
81
- "@ai-sdk/openai-compatible": "3.0.47",
82
- "@ai-sdk/react": "4.0.100",
82
+ "@ai-sdk/openai-compatible": "3.0.48",
83
+ "@ai-sdk/react": "4.0.102",
83
84
  "@asyncapi/react-component": "^3.1.8",
84
85
  "@clack/prompts": "^1.8.0",
85
86
  "@floating-ui/react": "^0.27.20",
@@ -89,16 +90,16 @@
89
90
  "@tailwindcss/vite": "^4.0.0",
90
91
  "@tanstack/react-query": "^5.102.8",
91
92
  "@tanstack/react-table": "^9.2.4",
92
- "@tanstack/react-virtual": "^3.14.10",
93
+ "@tanstack/react-virtual": "^3.14.12",
93
94
  "@vitejs/plugin-react": "^6.1.1",
94
95
  "@xyflow/react": "^12.11.5",
95
- "ai": "7.0.97",
96
+ "ai": "7.0.99",
96
97
  "ajv": "^8.20.0",
97
98
  "elkjs": "^0.12.0",
98
99
  "html-to-image": "^1.11.13",
99
100
  "likec4": "^1.59.2",
100
101
  "lowlight": "^3.3.0",
101
- "lucide-react": "^1.38.0",
102
+ "lucide-react": "^1.45.0",
102
103
  "mermaid": "^12.0.0",
103
104
  "motion": "^13.2.0",
104
105
  "oxc-parser": "^0.149.0",
@@ -111,7 +112,7 @@
111
112
  "remark-gfm": "^4.0.0",
112
113
  "simple-icons": "^16.30.0",
113
114
  "tailwindcss": "^4.0.0",
114
- "vite": "^8.2.2",
115
+ "vite": "^8.3.0",
115
116
  "vite-plugin-node-polyfills": "^0.28.0",
116
117
  "zustand": "^5.0.15"
117
118
  },
@@ -32,6 +32,40 @@ const (
32
32
  PhaseGenerate = "generate"
33
33
  )
34
34
 
35
+ // Categories a plugin sorts itself under on the plugin index: what it reads,
36
+ // or what it makes. A phase says where in a run a plugin goes; a category
37
+ // says what kind of fact it is after, which is the question a reader choosing
38
+ // extractors for an estate is asking.
39
+ const (
40
+ // CategoryCode reads a language: the aggregates, events and use cases a
41
+ // service declares in its source.
42
+ CategoryCode = "code"
43
+ // CategoryContracts reads an interface description - OpenAPI, protobuf,
44
+ // GraphQL, WSDL, AsyncAPI, a registry's schemas.
45
+ CategoryContracts = "contracts"
46
+ // CategoryMessaging reads queues, subjects, topics and the jobs on them.
47
+ CategoryMessaging = "messaging"
48
+ // CategoryData reads the stores a service keeps and their shape.
49
+ CategoryData = "data"
50
+ // CategoryInfrastructure reads what the service is deployed on - the
51
+ // queues, topics, functions and stores a Terraform module declares, and
52
+ // the wiring between them that only the infrastructure knows.
53
+ CategoryInfrastructure = "infrastructure"
54
+ // CategoryRepository reads what the repository says about itself: its
55
+ // metadata, its task runners.
56
+ CategoryRepository = "repository"
57
+ // CategoryDocuments reads what people wrote by hand: decisions, glossaries,
58
+ // flows.
59
+ CategoryDocuments = "documents"
60
+ // CategoryEvidence checks the catalog against something outside the code:
61
+ // traces, ownership rules.
62
+ CategoryEvidence = "evidence"
63
+ // CategorySources brings in trees from elsewhere for the extractors to read.
64
+ CategorySources = "sources"
65
+ // CategoryExports turns the catalog into something else.
66
+ CategoryExports = "exports"
67
+ )
68
+
35
69
  // Descriptor is a plugin's answer to KindDescribe.
36
70
  type Descriptor struct {
37
71
  // Name is what the plugin calls itself. The manifest is free to declare it
@@ -42,6 +76,10 @@ type Descriptor struct {
42
76
  // Summary is one line, and it is the line an editor shows beside the step.
43
77
  Summary string `json:"summary,omitempty"`
44
78
 
79
+ // Category is one of the Category constants: what the plugin reads or
80
+ // makes, which is how the plugin index groups it.
81
+ Category string `json:"category"`
82
+
45
83
  // Phases are the manifest sections this plugin belongs in.
46
84
  Phases []string `json:"phases"`
47
85