@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.
- package/README.md +174 -6
- package/catalog/enum_test.go +46 -0
- package/catalog/evidence_test.go +35 -0
- package/catalog/model.go +1213 -0
- package/catalog/roundtrip_test.go +210 -0
- package/catalog/via_test.go +38 -0
- package/cli/init.test.mjs +6 -1
- package/cli/portolan.mjs +14 -1
- package/cli/portolan.test.mjs +49 -0
- package/go.mod +21 -0
- package/go.sum +34 -0
- package/internal/gocall/README.md +19 -0
- package/internal/gocall/analyze.go +189 -0
- package/internal/gocall/analyze_test.go +107 -0
- package/internal/gohttp/analyze.go +2562 -0
- package/internal/gohttp/destination.go +373 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +96 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/index.go +629 -0
- package/internal/goscan/index_test.go +66 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +37 -0
- package/internal/goscan/tree.go +284 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +70 -0
- package/internal/wsdl/parse.go +949 -0
- package/internal/wsdl/parse_test.go +170 -0
- package/package.json +28 -15
- package/plugin/describe.go +156 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +141 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +404 -50
- package/plugins/cmd/portolan-http-clients/main.go +19 -0
- package/plugins/extract-argocd/options.schema.json +44 -0
- package/plugins/extract-celery/extract.py +0 -2
- package/plugins/extract-celery/extract_test.py +1 -1
- package/plugins/extract-celery/main.py +1 -0
- package/plugins/extract-csharp-ddd/README.md +213 -0
- package/plugins/extract-csharp-ddd/options.schema.json +55 -0
- package/plugins/extract-django/README.md +52 -19
- package/plugins/extract-django/choices.py +47 -0
- package/plugins/extract-django/domain.py +30 -18
- package/plugins/extract-django/extract.py +21 -7
- package/plugins/extract-django/extract_test.py +68 -2
- package/plugins/extract-django/lifecycle.py +4 -28
- package/plugins/extract-django/main.py +1 -0
- package/plugins/extract-django/operations.py +1 -1
- package/plugins/extract-django/routing_test.py +109 -1
- package/plugins/extract-django/rules.py +154 -0
- package/plugins/extract-django/rules_test.py +158 -0
- package/plugins/extract-django/store.py +1 -1
- package/plugins/extract-django/transport.py +101 -55
- package/plugins/extract-django/verbs.py +241 -0
- package/plugins/extract-go/README.md +47 -0
- package/plugins/extract-go-sqs/options.schema.json +21 -0
- package/plugins/extract-http-clients/describe.go +20 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +740 -0
- package/plugins/extract-http-clients/extract_test.go +1561 -0
- package/plugins/extract-http-clients/main.go +41 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-k8s/options.schema.json +32 -0
- package/plugins/extract-laravel/Cargo.lock +962 -0
- package/plugins/extract-laravel/Cargo.toml +20 -0
- package/plugins/extract-laravel/README.md +200 -0
- package/plugins/extract-laravel/options.schema.json +68 -0
- package/plugins/extract-laravel/rustfmt.toml +1 -0
- package/plugins/extract-php-ddd/Cargo.lock +972 -0
- package/plugins/extract-php-ddd/Cargo.toml +22 -0
- package/plugins/extract-php-ddd/README.md +141 -0
- package/plugins/extract-php-ddd/options.schema.json +50 -0
- package/plugins/extract-php-ddd/rustfmt.toml +1 -0
- package/plugins/extract-python-kafka/extract.py +0 -2
- package/plugins/extract-python-kafka/extract_test.py +1 -1
- package/plugins/extract-python-kafka/main.py +1 -0
- package/plugins/extract-terraform/options.schema.json +26 -0
- package/plugins/extract-ts/extract.test.ts +2 -2
- package/plugins/extract-ts/extract.ts +4 -5
- package/plugins/extract-ts/graphql.test.ts +1 -1
- package/plugins/extract-ts/main.ts +1 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/phpscan/Cargo.lock +953 -0
- package/plugins/phpscan/Cargo.toml +19 -0
- package/plugins/phpscan/rustfmt.toml +1 -0
- package/plugins/portolan-go.wasm +0 -0
- package/plugins/pyplugin/catalog.py +24 -1
- package/plugins/pyplugin/protocol.py +1 -5
- package/plugins/verify-otel/options.schema.json +12 -0
- package/portolan.json +132 -4
- package/rules/builtin.json +218 -0
- package/schema/portolan.schema.json +905 -4
- package/scripts/README.md +21 -13
- package/scripts/catalog-sources.mjs +6 -0
- package/scripts/delivery-presets.mjs +57 -14
- package/scripts/diff.mjs +5 -1
- package/scripts/django-aggregates.test.mjs +58 -0
- package/scripts/gen-likec4.mjs +150 -17
- package/scripts/gen-likec4.test.mjs +96 -0
- package/scripts/gen.mjs +148 -118
- package/scripts/gitops-example.test.mjs +108 -0
- package/scripts/go-discovery.test.mjs +30 -0
- package/scripts/history.mjs +186 -3
- package/scripts/history.test.mjs +1 -1
- package/scripts/host-plugins/fetch-argocd.mjs +338 -0
- package/scripts/host-plugins/fetch-argocd.options.json +49 -0
- package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
- package/scripts/host-plugins/fetch-bsr.mjs +1 -0
- package/scripts/host-plugins/fetch-csr.mjs +1 -0
- package/scripts/host-plugins/fetch-git.mjs +78 -21
- package/scripts/host-plugins/fetch-git.test.mjs +62 -8
- package/scripts/host-plugins/fetch-k8s.mjs +263 -0
- package/scripts/host-plugins/fetch-k8s.options.json +50 -0
- package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
- package/scripts/host-plugins/k8s-topology.mjs +183 -0
- package/scripts/json-format.mjs +192 -0
- package/scripts/json-format.test.mjs +97 -0
- package/scripts/local-api.mjs +287 -12
- package/scripts/local-api.test.mjs +150 -5
- package/scripts/local-discovery.mjs +94 -9
- package/scripts/manifest.mjs +14 -3
- package/scripts/manifest.test.mjs +24 -0
- package/scripts/output-diff.mjs +94 -0
- package/scripts/output-diff.test.mjs +36 -0
- package/scripts/package-smoke.mjs +62 -4
- package/scripts/plugin-host.mjs +45 -3
- package/scripts/plugin-host.test.mjs +9 -0
- package/scripts/plugin-wasm-worker.mjs +4 -1
- package/scripts/plugins-fresh.mjs +97 -0
- package/scripts/plugins-fresh.test.mjs +64 -0
- package/scripts/provenance.mjs +72 -0
- package/scripts/provenance.test.mjs +149 -0
- package/scripts/run-builtin.mjs +62 -7
- package/scripts/schema.mjs +167 -19
- package/scripts/trace-trials.mjs +176 -0
- package/scripts/trace-trials.test.mjs +142 -0
- package/scripts/warning-policy.mjs +167 -0
- package/scripts/warning-policy.test.mjs +93 -0
- package/src/app/Breadcrumbs.test.ts +3 -0
- package/src/app/Breadcrumbs.tsx +3 -0
- package/src/app/CatalogApp.tsx +2 -0
- package/src/app/Sidebar.tsx +3 -3
- package/src/app/SidebarFooter.tsx +20 -4
- package/src/catalog-docs.test.ts +64 -0
- package/src/catalog-docs.ts +35 -0
- package/src/catalog-error.test.ts +15 -0
- package/src/catalog-index.ts +25 -0
- package/src/catalog-model.ts +293 -5
- package/src/catalog-validation.ts +113 -2
- package/src/catalog.test.ts +40 -1
- package/src/chat/Starter.tsx +5 -11
- package/src/chat/tools.test.ts +27 -0
- package/src/chat/tools.ts +5 -9
- package/src/components/CatalogStamp.tsx +10 -8
- package/src/components/ChannelRows.test.tsx +76 -1
- package/src/components/ChannelRows.tsx +223 -60
- package/src/components/DeploymentRows.tsx +131 -0
- package/src/components/DocsLinks.test.tsx +27 -0
- package/src/components/DocsLinks.tsx +56 -0
- package/src/components/FieldTree.tsx +5 -2
- package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
- package/src/components/HTTPDestinationEvidence.tsx +31 -0
- package/src/components/Integrations.tsx +1 -1
- package/src/components/MachineDocs.tsx +6 -5
- package/src/components/MethodRows.tsx +9 -2
- package/src/components/PluginIcon.tsx +77 -0
- package/src/components/ProblemRow.tsx +173 -153
- package/src/components/RelationEvidence.test.tsx +14 -0
- package/src/components/RelationEvidence.tsx +53 -0
- package/src/components/RuleMarks.tsx +22 -0
- package/src/components/ShapeRows.tsx +24 -20
- package/src/data.ts +46 -7
- package/src/enrich.test.ts +459 -4
- package/src/enrich.ts +308 -7
- package/src/er/ErCanvas.tsx +218 -12
- package/src/er/GroupNode.tsx +57 -0
- package/src/er/StoreHeader.tsx +1 -0
- package/src/er/layout.test.ts +85 -2
- package/src/er/layout.ts +140 -5
- package/src/er/spec.test.ts +17 -0
- package/src/er/spec.ts +23 -10
- package/src/flow/Recordings.test.tsx +52 -0
- package/src/flow/Recordings.tsx +236 -0
- package/src/flow/StepDetail.tsx +59 -0
- package/src/flow/TraceTrial.tsx +419 -0
- package/src/flow/evidence.test.ts +16 -0
- package/src/flow/evidence.ts +34 -0
- package/src/flow/examples.test.ts +33 -0
- package/src/flow/examples.ts +37 -0
- package/src/flow/outline.test.ts +28 -0
- package/src/flow/outline.ts +7 -2
- package/src/flow/trace-trial-resume.test.ts +42 -0
- package/src/flow/trace-trial-resume.ts +74 -0
- package/src/graph/elk.ts +78 -0
- package/src/index.css +68 -0
- package/src/landing/DraggableReveal.tsx +3 -2
- package/src/landing/EvidencePipeline.tsx +105 -0
- package/src/landing/LandingPage.tsx +17 -70
- package/src/landing/ProductTour.tsx +6 -6
- package/src/lib/all-problems.ts +27 -17
- package/src/lib/catalog-diff.ts +1 -1
- package/src/lib/centrality.test.ts +251 -0
- package/src/lib/centrality.ts +232 -0
- package/src/lib/confluence.test.ts +37 -0
- package/src/lib/confluence.ts +41 -0
- package/src/lib/context-color.ts +1 -1
- package/src/lib/deployment-drift.ts +22 -0
- package/src/lib/derive.ts +43 -69
- package/src/lib/django-aggregates.d.mts +9 -0
- package/src/lib/django-aggregates.mjs +36 -0
- package/src/lib/django-aggregates.test.ts +29 -0
- package/src/lib/django-aggregates.ts +5 -0
- package/src/lib/environments.test.ts +83 -0
- package/src/lib/environments.ts +57 -0
- package/src/lib/integration-url.test.ts +30 -0
- package/src/lib/integration-url.ts +63 -0
- package/src/lib/kafka-ui.ts +3 -45
- package/src/lib/local-api.ts +116 -4
- package/src/lib/notion.ts +13 -0
- package/src/lib/plugin-index.json +2765 -0
- package/src/lib/plugins.test.ts +68 -0
- package/src/lib/plugins.ts +264 -0
- package/src/lib/problem-flows.test.ts +61 -0
- package/src/lib/problem-flows.ts +78 -0
- package/src/lib/problem-rules-cel.d.mts +43 -0
- package/src/lib/problem-rules-cel.mjs +407 -0
- package/src/lib/problem-rules.test.ts +287 -0
- package/src/lib/problem-rules.ts +271 -0
- package/src/lib/problem-subjects.ts +737 -0
- package/src/lib/rule-entries.ts +39 -0
- package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
- package/src/lib/rules-deploy.test.ts +133 -0
- package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
- package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
- package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
- package/src/lib/rules.test.ts +51 -0
- package/src/lib/rules.ts +86 -0
- package/src/lib/setup-info.test.ts +17 -0
- package/src/lib/setup-info.ts +58 -0
- package/src/lib/shape.test.ts +32 -0
- package/src/lib/shape.ts +30 -6
- package/src/lib/tech.ts +16 -0
- package/src/lib/trace-project.test.ts +34 -0
- package/src/lib/trace-project.ts +50 -0
- package/src/lib/use-problems.ts +23 -0
- package/src/lib/warnings.test.ts +63 -0
- package/src/lib/warnings.ts +260 -0
- package/src/likec4/ids.test.ts +6 -2
- package/src/likec4/ids.ts +43 -0
- package/src/main.tsx +23 -0
- package/src/map/ContextMapGraph.tsx +76 -32
- package/src/merge-deployments.test.ts +127 -0
- package/src/merge.test.ts +82 -0
- package/src/merge.ts +189 -18
- package/src/pages/AggregatePage.tsx +65 -14
- package/src/pages/ContextMap.tsx +45 -3
- package/src/pages/ContextPage.tsx +8 -5
- package/src/pages/EventPage.tsx +15 -5
- package/src/pages/FlowDetail.tsx +23 -2
- package/src/pages/GraphPage.tsx +40 -3
- package/src/pages/Overview.tsx +152 -12
- package/src/pages/PluginIndex.tsx +190 -0
- package/src/pages/Problems.tsx +396 -128
- package/src/pages/ServicePage.tsx +62 -5
- package/src/pages/Settings.tsx +217 -43
- package/src/pages/settings/AboutSettings.tsx +8 -1
- package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
- package/src/pages/settings/IntegrationsSettings.tsx +63 -17
- package/src/pages/settings/RecordingSettings.tsx +138 -0
- package/src/pages/settings/RulesSettings.tsx +825 -0
- package/src/routes.test.ts +9 -0
- package/src/routes.ts +22 -1
- package/src/selection/DetailPanel.tsx +15 -0
- package/src/virtual-provenance.d.ts +11 -0
- package/vite.config.ts +5 -0
- package/scripts/vendor-lock.mjs +0 -58
- package/scripts/vendor-lock.test.mjs +0 -69
- package/src/lib/data-problems.ts +0 -314
- package/src/lib/proto-problems.ts +0 -237
- package/src/lib/wire-problems.ts +0 -342
package/plugins/README.md
CHANGED
|
@@ -18,7 +18,7 @@ the plugin itself rather than its work:
|
|
|
18
18
|
→ { "portolanVersion": "0.1.0", "kind": "describe" }
|
|
19
19
|
← { "files": [],
|
|
20
20
|
"describe": { "name": "extract-go", "summary": "...",
|
|
21
|
-
"phases": ["extract"],
|
|
21
|
+
"category": "code", "phases": ["extract"],
|
|
22
22
|
"options": { "type": "object", "additionalProperties": false,
|
|
23
23
|
"properties": { "context": { ... } } } } }
|
|
24
24
|
```
|
|
@@ -31,6 +31,16 @@ is what makes that worth having: `encoding/json` drops a field it does not
|
|
|
31
31
|
recognise, so before this a misspelled option was no option at all and nothing
|
|
32
32
|
said so.
|
|
33
33
|
|
|
34
|
+
`category` is what the plugin reads or makes, one of `code`, `contracts`,
|
|
35
|
+
`messaging`, `data`, `infrastructure`, `repository`, `documents`, `evidence`,
|
|
36
|
+
`sources` and `exports` (the constants in `plugin/describe.go`). A phase says
|
|
37
|
+
where in a run a plugin goes; the category says what kind of fact it is after,
|
|
38
|
+
which is how the site's `/plugins` page groups it. `npm run schema` also writes
|
|
39
|
+
`src/lib/plugin-index.json` - every declared plugin as it describes itself,
|
|
40
|
+
with how the manifest runs it - and that page and the landing page's list of
|
|
41
|
+
inputs are rendered from it, so a plugin added to `portolan.json` appears on
|
|
42
|
+
both without being written down twice.
|
|
43
|
+
|
|
34
44
|
A descriptor may also carry `needs`: what the host must put in the request
|
|
35
45
|
beyond the tree, because a sandboxed module cannot reach it. The one need so
|
|
36
46
|
far is `history` (portolan.0007) - when each file under the root was first
|
|
@@ -54,7 +64,10 @@ Three obligations, and they are the whole of it:
|
|
|
54
64
|
unsafe filename or invalid response fails the run. Non-fatal extraction
|
|
55
65
|
notes go to stderr; there is no advisory response property a caller may
|
|
56
66
|
accidentally ignore. A note that opens with `warning: ` is kept beside the
|
|
57
|
-
step in `.portolan/build-report.json
|
|
67
|
+
step in `.portolan/build-report.json`. The Settings page classifies and
|
|
68
|
+
groups those notes by plugin, stable rule and severity. Typed CEL policies
|
|
69
|
+
in `portolan.json` can suppress reviewed diagnostics only with an explicit
|
|
70
|
+
reason; the host evaluates them and records the decision in the build report.
|
|
58
71
|
|
|
59
72
|
The repository enforces those obligations with schema/field coverage tests,
|
|
60
73
|
byte-for-byte permutation tests, generated-link and anchor checks, Mermaid
|
|
@@ -78,12 +91,14 @@ either be rendered or be explicitly acknowledged by the relevant exporter.
|
|
|
78
91
|
`go:embed` and returned in the descriptor. `schematest.Check` in a test keeps
|
|
79
92
|
it from drifting from the options struct: a field renamed on one side and not
|
|
80
93
|
the other fails, and so does an option with no description.
|
|
81
|
-
3. Build it. A built-in Go plugin is a library package with
|
|
94
|
+
3. Build it. A sandboxed built-in Go plugin is a library package with
|
|
82
95
|
`Serve(io.Reader, io.Writer) error`; add it to the map in
|
|
83
96
|
`plugins/cmd/portolan-go/main.go`, and `plugins:build` in `package.json`
|
|
84
97
|
puts it in `plugins/portolan-go.wasm` with the rest (a test keeps the map
|
|
85
|
-
and `portolan.json` in step). A
|
|
86
|
-
`
|
|
98
|
+
and `portolan.json` in step). A built-in that genuinely needs the Go
|
|
99
|
+
toolchain gets a small command under `plugins/cmd/` and is declared as a
|
|
100
|
+
`process`; `http-clients` is the example. A plugin of your own is its own
|
|
101
|
+
module: `GOOS=wasip1 GOARCH=wasm go build`.
|
|
87
102
|
4. Declare it in `portolan.json`, under `plugins` (how to run it) and
|
|
88
103
|
`generate` (what to run it on), then run `npm run schema` so the manifest
|
|
89
104
|
schema learns its options.
|
|
@@ -149,7 +164,14 @@ a value's name is the constant's literal, because that is what the wire
|
|
|
149
164
|
carries, and the constant's own name only for an iota. A `Deprecated:`
|
|
150
165
|
paragraph in the doc marks the value. In Rust it is a `pub enum` whose every
|
|
151
166
|
variant is a bare name, the literal an `as_str` answers standing in for the
|
|
152
|
-
variant. In Java it is a top-level enum in the aggregate's package. In
|
|
167
|
+
variant. In Java it is a top-level enum in the aggregate's package. In PHP it
|
|
168
|
+
is a backed `enum` anywhere in the module, and on an Eloquent model the
|
|
169
|
+
constants that share a prefix - `STATUS_PENDING = 'pending'` beside
|
|
170
|
+
`STATUS_CLOSED = 'closed'` - with `@deprecated` in a constant's docblock
|
|
171
|
+
marking the value; see `extract-laravel/README.md`. In a PHP tree laid out
|
|
172
|
+
by bounded context (`extract-php-ddd`) it is a backed `enum` under a module's
|
|
173
|
+
`Domain/`. In a .NET modular monolith (`extract-csharp-ddd`) it is a C# `enum`
|
|
174
|
+
in the aggregate's directory, `[Obsolete]` marking a value. In proto,
|
|
153
175
|
the enums the messages reach through their fields sit on the interface as
|
|
154
176
|
`enums`, with the numbers the wire uses.
|
|
155
177
|
|
|
@@ -179,10 +201,23 @@ the original collection layout for TypeScript, Rust, Java, or custom trees and
|
|
|
179
201
|
may also point directly at one feature repository.
|
|
180
202
|
|
|
181
203
|
`extract-river` is another independent enrichment for Go repositories. It
|
|
182
|
-
joins a job argument's `Kind()` to `Client.Insert`/`InsertTx
|
|
183
|
-
queue, `Worker[Args].Work`, and
|
|
184
|
-
channel plus a two-hop
|
|
185
|
-
|
|
204
|
+
joins a job argument's `Kind()` to `Client.Insert`/`InsertTx` and the
|
|
205
|
+
`InsertMany*` batches, the selected queue, `Worker[Args].Work`, and
|
|
206
|
+
`river.AddWorker`. The result is a work-queue channel plus a two-hop
|
|
207
|
+
enqueue/dispatch flow, with payload fields and source lines. It does not need
|
|
208
|
+
aggregates and does not treat a job as a domain event. The job type is
|
|
209
|
+
followed through locals, constructors' results and parameters up to the
|
|
210
|
+
callers, so an `Enqueue(ctx, args river.JobArgs, opts)` wrapper is read once
|
|
211
|
+
per concrete job passed to it, and a worker registered as
|
|
212
|
+
`river.AddWorker(workers, NewSendWorker(deps))` is registered. The queue is
|
|
213
|
+
the `InsertOpts.Queue` at the call (a literal, a constant, a config `default:`
|
|
214
|
+
tag, or what the callers pass), else the args type's own `InsertOpts()`, else
|
|
215
|
+
`default`. What no Insert in the tree feeds is not dropped: a registered
|
|
216
|
+
worker's queue is the one its type chooses or the one queue `river.Config`
|
|
217
|
+
lists, and with several and no choice the worker is kept as a flow whose
|
|
218
|
+
receive is unresolved. The diagnostics keep the two cases apart - an Insert
|
|
219
|
+
whose job type or queue the reader could not follow is said at the call, a
|
|
220
|
+
worker with no Insert in the tree is said at the worker.
|
|
186
221
|
|
|
187
222
|
`extract-redis` finds runtime construction of go-redis, rueidis and redigo
|
|
188
223
|
clients in non-test Go source. That source evidence adds a service-owned Redis
|
|
@@ -193,16 +228,27 @@ key patterns with their read/write/delete use, TTL, value type and source. The
|
|
|
193
228
|
patterns remain Redis keyspaces rather than being presented as SQL tables.
|
|
194
229
|
|
|
195
230
|
`extract-watermill` reads Watermill `Router.AddHandler` and
|
|
196
|
-
`AddNoPublisherHandler` registrations
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
231
|
+
`AddNoPublisherHandler` registrations, and `Subscriber.Subscribe` calls made
|
|
232
|
+
without a router. It resolves literal and constant topics - typed constants
|
|
233
|
+
through `string(...)`, constants concatenated with `+`, single-return
|
|
234
|
+
helpers - plus defaults on env-config structs, nested (`cfg.Kafka.Orders`) or
|
|
235
|
+
not; a topic, a name or a handler that a registering function takes as a
|
|
236
|
+
parameter is filled in once per caller, so a `handle(r, name, topic, fn)`
|
|
237
|
+
wrapper yields one handler per call. The handler value may be a function
|
|
238
|
+
literal, a function or method of the tree (`h.OnOrder`, `handlers.OnShipped`)
|
|
239
|
+
or a constructor returning a literal. It follows direct `Publisher.Publish`
|
|
240
|
+
calls and one-hop publishing helpers, records what the service publishes
|
|
241
|
+
outside any handler as a `send` on the topic, and traces JSON marshal/unmarshal
|
|
242
|
+
values back to their Go payload structs. It carries enclosing `if`/`else`
|
|
243
|
+
conditions and early returns through the handler's control flow: proven
|
|
244
|
+
alternatives become one catalog `alt`; publications whose relationship cannot
|
|
245
|
+
be proven remain separate possible routes. A handler whose topic does not
|
|
246
|
+
resolve is kept, with its receive step unresolved and no channel claimed, and
|
|
247
|
+
named in a diagnostic. The channels merge normally with AsyncAPI declarations
|
|
248
|
+
by address. Generic `NewEventHandler[T]` and `NewCommandHandler[T]`
|
|
249
|
+
registrations on Watermill CQRS processors are also extracted; fixed topic
|
|
250
|
+
generators and the standard event/command-name generator form are resolved
|
|
251
|
+
from source.
|
|
206
252
|
|
|
207
253
|
`extract-go-nats` reads nats.go and JetStream calls into the subjects a service
|
|
208
254
|
listens on and publishes to. A call is known by the type it is made on -
|
|
@@ -219,6 +265,61 @@ channel's doc. A subject read off a database row is a warning at the call,
|
|
|
219
265
|
not a channel. Consumer configs give the filter subject and the durable name;
|
|
220
266
|
streams, wildcard subjects and work-queue retention are not read yet.
|
|
221
267
|
|
|
268
|
+
`extract-go-sqs` is its twin for Amazon SQS through aws-sdk-go-v2. A call is
|
|
269
|
+
known by being made on `*sqs.Client` - `SendMessage`, `SendMessageBatch`,
|
|
270
|
+
`ReceiveMessage` - and the queue is the `QueueUrl` of the input struct,
|
|
271
|
+
written inline or built into a local first. `aws.String` and `aws.ToString`
|
|
272
|
+
are taken off, and the `QueueUrl` of a `GetQueueUrl` result is read as the
|
|
273
|
+
`QueueName` that was asked for. The queue is then followed like a subject: to
|
|
274
|
+
a literal, a constant, a config default, or a parameter up to two hops through
|
|
275
|
+
the callers, and when it sits in a field of the receiver, to the constructor
|
|
276
|
+
that filled the field and on to that constructor's callers - the worker's
|
|
277
|
+
shape, where the queue comes in at `New` and is read at `Run`. A URL becomes
|
|
278
|
+
the queue's name, its last path segment. Channels are `message` streams, not
|
|
279
|
+
domain events: a queue is point to point, and the event model is not asked to
|
|
280
|
+
match it. A port that takes exactly one other string beside the queue names
|
|
281
|
+
the message; a direct call names none. `DeleteMessage`, visibility changes and
|
|
282
|
+
`CreateQueue` say nothing about direction and are not read; SNS is not read.
|
|
283
|
+
|
|
284
|
+
`extract-terraform` reads the other half: the infrastructure the code runs
|
|
285
|
+
on, from the `*.tf` files of the directory given as `dir` - or the root, or
|
|
286
|
+
the one directory under it that holds any. Nothing is evaluated; the files
|
|
287
|
+
are read as syntax with `hashicorp/hcl`, and a name is followed the way a
|
|
288
|
+
subject is - to a literal, a variable's default, a local, the argument a
|
|
289
|
+
calling `module` block passed in, or a child module's output - and left out
|
|
290
|
+
with a warning when none of those reaches it, never taken from the label.
|
|
291
|
+
Local modules (`source = "./..."`) are read where they are called. A registry
|
|
292
|
+
module the reader knows - `terraform-aws-modules/lambda`, `sqs`, `sns`,
|
|
293
|
+
`s3-bucket` with its `notification` submodule, `dynamodb-table`, whether
|
|
294
|
+
named through the registry or as the git repository behind it with a ref -
|
|
295
|
+
is read as the resource it wraps: its inputs are that resource's attributes
|
|
296
|
+
under the module's names (`environment_variables`, `attributes = [...]`,
|
|
297
|
+
`event_source_mapping = {...}`, `subscriptions = {...}`, `create_dlq`), and
|
|
298
|
+
its outputs (`lambda_function_arn`, `queue_arn` or `sqs_queue_arn`,
|
|
299
|
+
`dead_letter_queue_arn`, `dynamodb_table_stream_arn`) are references to it,
|
|
300
|
+
so `module.queue` reads as `aws_sqs_queue` would; the dead-letter queue the
|
|
301
|
+
queue module makes beside itself is `module.queue.dlq`, and a layer the
|
|
302
|
+
lambda module makes is not a function. Any other registry module is noted
|
|
303
|
+
and skipped. A reference from one resource to another
|
|
304
|
+
(`aws_sqs_queue.orders.arn`) is the edge itself and needs no value. What
|
|
305
|
+
comes out: an `aws_sqs_queue` is a `message` channel and an `aws_sns_topic`
|
|
306
|
+
an `event` channel, each with `messages: []` and the facts in the doc - FIFO,
|
|
307
|
+
the dead-letter queue and its receive count from `redrive_policy`, the SNS
|
|
308
|
+
subscription that fills a queue, the S3 notification that fills either; an
|
|
309
|
+
`aws_lambda_function` is a component of kind `function` in the same context,
|
|
310
|
+
its runtime among its technologies - or the service itself when its name
|
|
311
|
+
slugs to the `service` option; an `aws_lambda_event_source_mapping` puts the
|
|
312
|
+
queue on the function as received, and a topic reaching the function through
|
|
313
|
+
a subscribed queue is listed on it too; a value in the function's
|
|
314
|
+
`environment` that reaches a queue, topic, table or bucket lists that on the
|
|
315
|
+
function with the variable's name, and without a direction, because
|
|
316
|
+
Terraform does not know one. `aws_s3_bucket`, `aws_dynamodb_table` (keys and
|
|
317
|
+
secondary indexes as the table), `aws_db_instance` and `aws_rds_cluster`
|
|
318
|
+
(kind from `engine`) are stores at the root, owned by the `service` option;
|
|
319
|
+
a queue or topic nothing in the module touches is listed on that service as
|
|
320
|
+
well, and says so. Kinesis, EventBridge, API Gateway, Step Functions,
|
|
321
|
+
ElastiCache, MSK and ECS resources are named once as not read yet.
|
|
322
|
+
|
|
222
323
|
`extract-python-kafka` is the framework-independent Kafka enrichment for
|
|
223
324
|
Python. It recognizes confluent-kafka, kafka-python and aiokafka by their
|
|
224
325
|
imported client types, follows literal topics through constants, environment
|
|
@@ -260,16 +361,19 @@ literals, direct assignments, or setters. A standalone flow says whether no
|
|
|
260
361
|
source caller exists or callers exist but no inbound/asynchronous root was
|
|
261
362
|
proved, so the UI exposes the missing evidence instead of implying a complete
|
|
262
363
|
business path.
|
|
263
|
-
When a provider branch still ends before its transport, the extractor
|
|
264
|
-
the module with `go/packages`, builds
|
|
364
|
+
When a provider branch still ends before its transport, the extractor runs as
|
|
365
|
+
a workspace-local native sidecar, loads the module with `go/packages`, builds
|
|
366
|
+
SSA, and uses `x/tools` VTA to resolve
|
|
265
367
|
calls through interface parameters, function values, return values, and
|
|
266
368
|
interface-typed struct fields. Typed edges are followed only after a concrete
|
|
267
369
|
factory branch is selected: applying context-insensitive VTA to a shared
|
|
268
370
|
dispatcher would otherwise attach every request implementation to every
|
|
269
371
|
endpoint. Factory conditions and HTTP/SOAP meaning continue to come from the
|
|
270
|
-
source extractor.
|
|
271
|
-
|
|
272
|
-
|
|
372
|
+
source extractor. The sidecar is built from the Portolan package through the
|
|
373
|
+
project's Go toolchain and runs with the project as its working directory.
|
|
374
|
+
Module loading is read-only and bounded; unavailable private dependencies,
|
|
375
|
+
type errors, or a timeout produce a warning and retain the syntax-only result
|
|
376
|
+
rather than failing generation.
|
|
273
377
|
Routes without a provider factory are also joined to their outbound calls,
|
|
274
378
|
including handlers invoked from closures and methods on locally constructed
|
|
275
379
|
values. A Swagger `@Router` annotation is medium-confidence root evidence for
|
|
@@ -454,9 +558,10 @@ far side.
|
|
|
454
558
|
The demo estate's org-wide and context-wide records live in `data/adr`, and are
|
|
455
559
|
read by a step that points at that directory with a glob of its own. Root
|
|
456
560
|
`docs/` is where `gen-markdown` writes, so nothing hand-written can live there.
|
|
457
|
-
`in` is the directory of records rather than `data` itself:
|
|
458
|
-
|
|
459
|
-
`in: data` with `out: data` would
|
|
561
|
+
`in` is the directory of records rather than `data` itself: what `gen --check`
|
|
562
|
+
lists as changed inputs is the root less the output directories inside it, and
|
|
563
|
+
`in: data` with `out: data` would list the file the step writes among the
|
|
564
|
+
reasons it was rewritten.
|
|
460
565
|
|
|
461
566
|
```json
|
|
462
567
|
{
|
|
@@ -621,6 +726,178 @@ line with its description, and as entity links of type `command`, each
|
|
|
621
726
|
leading to the line of the runner file it was read from, when `sourceBaseUrl`
|
|
622
727
|
says where the repository is.
|
|
623
728
|
|
|
729
|
+
## Where it answers, what it dials: the manifests, and the cluster
|
|
730
|
+
|
|
731
|
+
A catalog that knows what a service provides and what it consumes still has
|
|
732
|
+
holes where the two do not meet by route alone: two services answer
|
|
733
|
+
`POST /foo`, and the caller's code says only that it posts to `$LEDGER_URL`.
|
|
734
|
+
What that variable holds is not in the code. It is in the Deployment, the
|
|
735
|
+
ConfigMap it reads, and the Service and Ingress objects that give the far end
|
|
736
|
+
a name. Two plugins read those - `extract-k8s` from the manifests in a
|
|
737
|
+
repository, `fetch-k8s` from a live cluster - and put two lists on the
|
|
738
|
+
service:
|
|
739
|
+
|
|
740
|
+
- **Hosts** - the names it answers on: a Kubernetes Service's name in its
|
|
741
|
+
short, namespaced, `svc` and fully qualified forms, and the hosts of the
|
|
742
|
+
Ingress or Gateway API HTTPRoute in front of it, from a rule whose backend
|
|
743
|
+
is that Service.
|
|
744
|
+
- **Dials** - the in-cluster names its workload is configured to reach, cut
|
|
745
|
+
out of its containers' `env` and the ConfigMaps they read.
|
|
746
|
+
|
|
747
|
+
The merge then has both facts in one place. A call that names a host, or a
|
|
748
|
+
caller whose manifests dial one, resolves against the providers of its route
|
|
749
|
+
when exactly one of them answers on that name, and the evidence says
|
|
750
|
+
`kubernetes-host` as the basis. A host only ever decides between providers of
|
|
751
|
+
the route; it never conjures a provider that does not have it.
|
|
752
|
+
|
|
753
|
+
### What is never written
|
|
754
|
+
|
|
755
|
+
A manifest holds configuration, and configuration is where the passwords are.
|
|
756
|
+
The rule that keeps them out is a rule of shape, not a list of words:
|
|
757
|
+
|
|
758
|
+
- A `Secret`, a `SealedSecret`, an `ExternalSecret`, or any document with
|
|
759
|
+
`stringData` is passed over at its `kind`, before its body is decoded. A
|
|
760
|
+
`secretKeyRef` or `secretRef` is not followed.
|
|
761
|
+
- A value from `env` or a ConfigMap is reduced to the host it names, or to
|
|
762
|
+
nothing. A URL gives up its hostname and keeps nothing else - not the
|
|
763
|
+
scheme, the port, the path, the query, nor the user and password in front
|
|
764
|
+
of the host. A bare `host[:port]` gives up the port. `info`, `true`, a
|
|
765
|
+
token, a DSN written as `key=value` pairs: none of these is a host, and
|
|
766
|
+
none passes.
|
|
767
|
+
- What is left qualifies only when the cluster resolves it: a form the tree's
|
|
768
|
+
own Services answer to, or `<name>.<namespace>.svc[.cluster.local]`, the
|
|
769
|
+
one shape no name outside a cluster has. A bare `cart` from another
|
|
770
|
+
repository's manifests is not taken, because nothing distinguishes it from
|
|
771
|
+
a word.
|
|
772
|
+
- Warnings name files and directories, never values, because the build
|
|
773
|
+
report is committed.
|
|
774
|
+
|
|
775
|
+
The fragment carries the variable's name no more than its value: the
|
|
776
|
+
`environmentVariable` the HTTP client extractor records stays what it was, and
|
|
777
|
+
no value is attached to it.
|
|
778
|
+
|
|
779
|
+
### The manifests: extract-k8s
|
|
780
|
+
|
|
781
|
+
Every `*.yaml` and `*.yml` under the root, or under the `paths` named in the
|
|
782
|
+
options; anything without `apiVersion` and `kind` - a compose file, a
|
|
783
|
+
workflow - is passed over silently. Multi-document files are split. A file
|
|
784
|
+
holding `{{` is a template and not YAML until rendered, so a directory of
|
|
785
|
+
Helm templates is passed over with one warning for the directory; render the
|
|
786
|
+
chart into the tree, or point `paths` at plain manifests, to have it read.
|
|
787
|
+
Kustomize is read without running it: the base and the overlays are all under
|
|
788
|
+
the root, and two documents of one kind and name - a Deployment and the patch
|
|
789
|
+
laid over it - fold into one, their env and labels unioned. `namespace`
|
|
790
|
+
narrows the read to one namespace; an object naming none is read either way.
|
|
791
|
+
|
|
792
|
+
The workload the step is about is the Deployment, StatefulSet, DaemonSet,
|
|
793
|
+
ReplicaSet, Job or CronJob named like the service, or labelled so with
|
|
794
|
+
`app.kubernetes.io/name` or `app`, or the only one in the tree. A Job or
|
|
795
|
+
CronJob makes the service's kind `job`; a Deployment says nothing about kind,
|
|
796
|
+
because a webapp and a worker both deploy that way. Several workloads and
|
|
797
|
+
none named is a warning, not a guess.
|
|
798
|
+
|
|
799
|
+
### The cluster: fetch-k8s
|
|
800
|
+
|
|
801
|
+
The manifests say what should run; the cluster says what does, and for an
|
|
802
|
+
estate deployed by Helm or Argo the rendered objects are in no repository at
|
|
803
|
+
all. `fetch-k8s` asks `kubectl` for the workloads, Services, Ingresses,
|
|
804
|
+
Gateway API routes and ConfigMaps of the namespaces named - never for a
|
|
805
|
+
Secret - and answers with one fragment for every workload it saw, placed
|
|
806
|
+
into contexts and services by label. It runs in the host (portolan.0008)
|
|
807
|
+
because it needs the binary, the socket and kubectl's own credential, which
|
|
808
|
+
comes from the kubeconfig and is never in the manifest.
|
|
809
|
+
|
|
810
|
+
```json
|
|
811
|
+
{ "name": "k8s-cluster", "host": "fetch-k8s" }
|
|
812
|
+
|
|
813
|
+
{
|
|
814
|
+
"plugin": "k8s-cluster",
|
|
815
|
+
"in": ".",
|
|
816
|
+
"out": "data",
|
|
817
|
+
"options": {
|
|
818
|
+
"kubeContext": "prod-eu",
|
|
819
|
+
"namespaces": ["shop", "auth", "payments"],
|
|
820
|
+
"labels": { "context": "app.kubernetes.io/part-of", "service": "app.kubernetes.io/name" },
|
|
821
|
+
"namespaceContexts": { "shop-jobs": "shop" },
|
|
822
|
+
"cache": "data",
|
|
823
|
+
"out": "k8s-cluster.json"
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
The context is the value of the `app.kubernetes.io/part-of` label and the
|
|
829
|
+
service the value of `app.kubernetes.io/name`; an estate that labels
|
|
830
|
+
differently names its own keys under `labels`. A workload without the
|
|
831
|
+
context label falls back to `namespaceContexts`; one with neither is counted
|
|
832
|
+
in a warning for its namespace and left out, not guessed at. A workload
|
|
833
|
+
without the service label is named after itself. Several workloads landing
|
|
834
|
+
on one service - a Deployment and the CronJob beside it - fold into one; a
|
|
835
|
+
service that is only jobs is a `job`, one with a Deployment beside them is
|
|
836
|
+
whatever its code says.
|
|
837
|
+
|
|
838
|
+
The same offline rule as fetch-git: with `PORTOLAN_OFFLINE` set, or in CI,
|
|
839
|
+
the fragment committed at `cache` is used unchanged; when the cluster cannot
|
|
840
|
+
be reached it is used with a warning that names the failed command and its
|
|
841
|
+
exit status, not the server; with no committed copy, that is an error and
|
|
842
|
+
not a short answer. What kubectl itself said on stderr goes to the terminal,
|
|
843
|
+
where the person at the keyboard can read the connection error, and not into
|
|
844
|
+
the build report, which is committed. Nothing but the fragment is written to
|
|
845
|
+
disk: the objects live in the process for the length of one call.
|
|
846
|
+
|
|
847
|
+
portolan.0011 records why both readers keep names and nothing else.
|
|
848
|
+
|
|
849
|
+
## Where it runs: the GitOps tree, and the deployer
|
|
850
|
+
|
|
851
|
+
A service's manifests say what it is; what deploys them says where it
|
|
852
|
+
stands - which environment, cluster and namespace, from which directory of
|
|
853
|
+
which repository, tracking which revision. Two plugins read that, and the
|
|
854
|
+
merge lays one under the other (portolan.0012, portolan.0013):
|
|
855
|
+
|
|
856
|
+
- **extract-argocd** reads the Argo CD `Application` and `ApplicationSet`
|
|
857
|
+
objects in a GitOps tree, as wasm over the workspace, and answers with
|
|
858
|
+
what *should* run: one deployment per Application, basis `manifest`. An
|
|
859
|
+
ApplicationSet is expanded as far as a tree allows - `list` generators
|
|
860
|
+
whole, `git` directories and files by walking this repository, `matrix`
|
|
861
|
+
over those, the template rendered as Go `text/template` or fasttemplate
|
|
862
|
+
as it declared. A `clusters`, `pullRequest`, `scmProvider`, `merge` or
|
|
863
|
+
`plugin` generator, or a `git` one pointing at another repository, is a
|
|
864
|
+
warning naming the ApplicationSet and no rows. When the source path holds
|
|
865
|
+
a `kustomization.yaml`, the tool is kustomize and the images the overlay
|
|
866
|
+
pins are the declared images.
|
|
867
|
+
- **fetch-argocd** asks the Argo CD API for what *does* run - the revision
|
|
868
|
+
synced, the images running, the link - and commits it as a snapshot
|
|
869
|
+
beside a lock, basis `api` (see the fetchers above).
|
|
870
|
+
|
|
871
|
+
```json
|
|
872
|
+
{ "name": "argocd-gitops", "wasm": { "url": "file://plugins/portolan-go.wasm" } }
|
|
873
|
+
|
|
874
|
+
{
|
|
875
|
+
"plugin": "argocd-gitops",
|
|
876
|
+
"in": "examples/gitops",
|
|
877
|
+
"out": "examples/gitops/portolan",
|
|
878
|
+
"options": {
|
|
879
|
+
"repo": "github.com/shortlink-org/portolan",
|
|
880
|
+
"paths": ["bootstrap", "appsets"],
|
|
881
|
+
"labels": { "context": "app.kubernetes.io/part-of", "service": "app.kubernetes.io/name" },
|
|
882
|
+
"out": "argocd.json"
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
An Application is placed on a service by the labels it carries -
|
|
888
|
+
`app.kubernetes.io/part-of` and `app.kubernetes.io/name`, the same pair
|
|
889
|
+
fetch-k8s reads off a workload, or whichever the manifest names under
|
|
890
|
+
`labels` - and, without them, by deploying from the service's repository
|
|
891
|
+
inside the service's directory. One that matches neither way is listed on
|
|
892
|
+
the Problems page as unclaimed rather than guessed at.
|
|
893
|
+
|
|
894
|
+
Where both plugins spoke for one Application the merge folds the two into
|
|
895
|
+
one row, basis `both`: the deployer's shape, the tree filling what the
|
|
896
|
+
deployer did not say, and the fields the two disagree on kept as the tree's
|
|
897
|
+
word under `drift` - a `deployment-drift` problem, and a chip on the
|
|
898
|
+
service page that says the difference in words. A row only the tree spoke
|
|
899
|
+
for wears `declared`.
|
|
900
|
+
|
|
624
901
|
## Outside the estate: an external with a contract
|
|
625
902
|
|
|
626
903
|
A service calls things nobody here builds - a card network, a tax API, a
|
|
@@ -744,13 +1021,60 @@ called" - and `unresolved` is never raised, because a trace does not put the
|
|
|
744
1021
|
far end in the catalog. A consumer span inside a trace opens a flow of its own
|
|
745
1022
|
and is matched the same way, so one password change verifies both the
|
|
746
1023
|
request's flow and the policy's. A root no flow opens is written down as
|
|
747
|
-
`observed-<service>-<route>`, once per
|
|
1024
|
+
`observed-<service>-<route>`, once per opening, with a summary saying how many
|
|
748
1025
|
traces showed it.
|
|
749
1026
|
|
|
1027
|
+
Every recording that opened a flow is laid over it. A declared step carries
|
|
1028
|
+
`seen: { traces }`, how many recordings showed it. An rpc or an event a
|
|
1029
|
+
recording showed that the code does not declare is put into the flow after
|
|
1030
|
+
the declared step it followed, as a step with an id the code never gave
|
|
1031
|
+
(`seen1`, `seen2`) and a note saying in how many recordings; a lane such a
|
|
1032
|
+
hop needed is added after the declared ones. A store call is never added -
|
|
1033
|
+
the code's word on which repository method ran is the better one - and a
|
|
1034
|
+
consumer met on the way belongs to the flow it opens.
|
|
1035
|
+
|
|
1036
|
+
Where recordings part, the flow says so. What every recording showed after
|
|
1037
|
+
a step is a run of plain steps; where they went different ways is an `alt`
|
|
1038
|
+
(`seen-alt1`, or `alt1` in an observed flow) with a branch for each way,
|
|
1039
|
+
titled by the hop it starts with and counting the recordings that went it,
|
|
1040
|
+
each branch laid over the same way inside; what they all showed again
|
|
1041
|
+
after parting comes after the frame, once. A recording that went no
|
|
1042
|
+
further where others went on is the branch titled `otherwise`, with no
|
|
1043
|
+
steps and its count, so that a reader sees the hop is not always taken.
|
|
1044
|
+
Two observed recordings that open the same way are one flow built this
|
|
1045
|
+
way, so a happy path and a refusal read as one sequence that parts. The
|
|
1046
|
+
merge takes all of this from a second declaration of a flow because it
|
|
1047
|
+
says where it came from: `seen` on a step the first declaration does not
|
|
1048
|
+
have, or a frame whose every step and branch carries it.
|
|
1049
|
+
|
|
1050
|
+
A recording is also kept as an example of each flow it showed, under
|
|
1051
|
+
`examples` on the flow: the recording and trace it came from, when it ran,
|
|
1052
|
+
how long it took, and for each step the span's name, its length and an
|
|
1053
|
+
allowlist of its attributes - `http.route`, `http.request.method`,
|
|
1054
|
+
`http.response.status_code`, `rpc.service`, `rpc.method`, `db.operation.name`,
|
|
1055
|
+
`db.collection.name`, `messaging.destination.name`, `event.name`,
|
|
1056
|
+
`server.address` and a few more of that kind. A query text, a header, a full
|
|
1057
|
+
URL or a path with an id in it is somebody's data and is never carried; the
|
|
1058
|
+
list is closed in `examples.go`, and a new attribute is added there with the
|
|
1059
|
+
argument for it. `examples` in the options says how many recordings a flow
|
|
1060
|
+
keeps, the ones that show the most of it first: five when unset, zero for
|
|
1061
|
+
none.
|
|
1062
|
+
|
|
1063
|
+
A recording added from the page lands under the project's
|
|
1064
|
+
`telemetry/recordings/` and widens (or adds) the project's `otel` verify step
|
|
1065
|
+
to read that directory, so every run after reads it too; the page runs the
|
|
1066
|
+
generator over a copy of the workspace first and shows which flows the
|
|
1067
|
+
recording showed before anything is written (portolan.0014).
|
|
1068
|
+
|
|
750
1069
|
`service.name` is matched to the one service whose slug it is, `event.name` to
|
|
751
1070
|
the one event whose `wire.name` it is, or failing that to the one event of the
|
|
752
1071
|
publisher's with that last segment; `services` and `events` in the options say
|
|
753
|
-
otherwise where an estate's names differ. A
|
|
1072
|
+
otherwise where an estate's names differ. A server span's route is matched to
|
|
1073
|
+
the operation whose `http` verb and path template it fits; `routes` in the
|
|
1074
|
+
options names an operation for a route the estate spells another way - a
|
|
1075
|
+
prefix a gateway adds, a path the document writes differently - as
|
|
1076
|
+
`{ "POST /api/v1/sessions": "login" }`, and the name has to be one the
|
|
1077
|
+
service's interface declares. A publish span whose
|
|
754
1078
|
`messaging.destination.name` is not the event's `wire.channel` is a warning:
|
|
755
1079
|
the event went out, but not where the code says it does.
|
|
756
1080
|
|
|
@@ -775,9 +1099,9 @@ enforces: a team that owns a directory is a team that gets the pull request.
|
|
|
775
1099
|
```
|
|
776
1100
|
|
|
777
1101
|
Point `in` at the directory the file is in, not at the repository root: the
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1102
|
+
subject of this step is the `CODEOWNERS` file, and `gen --check` explains a
|
|
1103
|
+
changed fragment by what moved under the step's input. Rooted at the
|
|
1104
|
+
repository, every commit ever made would be named. Left with no `file`, the three
|
|
781
1105
|
places a forge looks are tried in order - `CODEOWNERS`, `.github/CODEOWNERS`,
|
|
782
1106
|
`docs/CODEOWNERS` - and a `file` that names something absent fails the run,
|
|
783
1107
|
because answering "nobody owns anything" to a typo is only noticed a month
|
|
@@ -822,21 +1146,23 @@ the forge looks.
|
|
|
822
1146
|
`wasm` is the default and should stay that way. The module gets no network, no
|
|
823
1147
|
environment and no way to start a process. A generator gets no filesystem
|
|
824
1148
|
either. An extract or verify step gets the workspace preopened as `/`
|
|
825
|
-
(portolan.0006), which is how
|
|
826
|
-
Go toolchain on the machine:
|
|
827
|
-
the single module `plugins/portolan-go.wasm`, which answers to the plugin
|
|
828
|
-
the host passes as `argv[0]`. WASI preopens read-write, so an extractor is
|
|
1149
|
+
(portolan.0006), which is how syntax-only built-in Go extractors read a tree
|
|
1150
|
+
without a Go toolchain on the machine: those extractors and the three generators
|
|
1151
|
+
are the single module `plugins/portolan-go.wasm`, which answers to the plugin
|
|
1152
|
+
name the host passes as `argv[0]`. WASI preopens read-write, so an extractor is
|
|
829
1153
|
trusted not to write the tree it reads, the same trust a process plugin has
|
|
830
1154
|
today; a `sha256` pins that trust to a build.
|
|
831
1155
|
|
|
832
|
-
`process` is the escape hatch for a plugin that needs a toolchain: the
|
|
833
|
-
Java, Python and TypeScript extractors run in
|
|
834
|
-
|
|
1156
|
+
`process` is the escape hatch for a plugin that needs a toolchain: the typed Go
|
|
1157
|
+
HTTP client analyzer and the Rust, Java, Python, PHP and TypeScript extractors run in
|
|
1158
|
+
their own runtimes. It gets the same protocol
|
|
835
1159
|
and none of the sandbox, which is the trade being made and the reason it is
|
|
836
1160
|
not the default. It declares `command` and an `args` array; the host never
|
|
837
|
-
feeds a command string through a shell.
|
|
838
|
-
|
|
839
|
-
|
|
1161
|
+
feeds a command string through a shell. For a built-in declared as `go run`,
|
|
1162
|
+
the adapter compiles a workspace-local native sidecar from the package and
|
|
1163
|
+
then runs it with the scanned workspace as its current directory. This keeps
|
|
1164
|
+
module resolution in Portolan's shipped source and project loading in the
|
|
1165
|
+
project being analyzed.
|
|
840
1166
|
|
|
841
1167
|
`host` is for Portolan's own code that needs what only the host has - a git
|
|
842
1168
|
binary, a socket - and so runs inside the host process (portolan.0008):
|
|
@@ -859,12 +1185,18 @@ written.
|
|
|
859
1185
|
`fetch-git` is `fetch-bsr` for a repository rather than a registry, and it
|
|
860
1186
|
lives by the same four rules. A pin is a repository, a commit and the paths
|
|
861
1187
|
actually read; the step fetches exactly those directories at exactly that
|
|
862
|
-
commit and hands
|
|
1188
|
+
commit and hands their text files back, so the host writes them into the tree
|
|
863
1189
|
beside a `git.lock.json` naming the commit and the digest of every file. The
|
|
864
1190
|
paths inside the copy are the repository's own, which is the point: the
|
|
865
1191
|
extract step that follows points its `in` at the vendored service and reads
|
|
866
1192
|
it exactly as it would read that service's checkout.
|
|
867
1193
|
|
|
1194
|
+
Binary files are not part of the extractor input. Known binary extensions are
|
|
1195
|
+
discarded before their blobs are read; files without a known extension are
|
|
1196
|
+
sniffed for NUL bytes and invalid UTF-8 before the response is assembled. The
|
|
1197
|
+
lock records only their path, byte size and skip reason, never Base64 content,
|
|
1198
|
+
so online fetch and offline replay produce the same bounded text-only copy.
|
|
1199
|
+
|
|
868
1200
|
It runs inside the host (`scripts/host-plugins/fetch-git.mjs`,
|
|
869
1201
|
portolan.0008) rather than as a module, because it needs a git binary and a
|
|
870
1202
|
socket, and a manifest names it with `host` rather than `wasm` or `process`.
|
|
@@ -919,11 +1251,12 @@ Nothing else can say it. A service says which repository it lives in, and an
|
|
|
919
1251
|
extractor reads a directory as a pure function of what is on disk; neither has
|
|
920
1252
|
any idea which commit somebody fetched. Without that line, every source path
|
|
921
1253
|
of every vendored service is dead text on the page - the file and the line are
|
|
922
|
-
known, and there is nowhere to send a reader
|
|
923
|
-
the
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1254
|
+
known, and there is nowhere to send a reader. With it, `sourceHref` links the
|
|
1255
|
+
line at the commit it was read at. The fragments read out of the copy are
|
|
1256
|
+
dated, like every other source, by the commit of THIS repository that last
|
|
1257
|
+
changed them (portolan.0010) - which is the fetch that brought the change in,
|
|
1258
|
+
and is what "how fresh is this" means to the estate that vendored it; the pin
|
|
1259
|
+
is what says which upstream commit that was.
|
|
927
1260
|
|
|
928
1261
|
One more line is needed for the app itself: `SOURCE_GLOBS` in `src/data.ts`,
|
|
929
1262
|
where the same patterns are written out a second time because
|
|
@@ -1020,7 +1353,8 @@ workflow already does.
|
|
|
1020
1353
|
|
|
1021
1354
|
Four rules govern what happens when a fetch does not:
|
|
1022
1355
|
|
|
1023
|
-
1. Fetch succeeded → the fetched files and a
|
|
1356
|
+
1. Fetch succeeded → the fetched text files, binary-skip metadata and a
|
|
1357
|
+
regenerated lock.
|
|
1024
1358
|
2. Skipped or failed, cache complete and matching its digests → the cached files
|
|
1025
1359
|
byte-identically, plus a warning. Output unchanged, so `--check` stays clean.
|
|
1026
1360
|
3. Failed **and** no usable cache → a non-zero exit, never a short file list. The
|
|
@@ -1202,3 +1536,23 @@ it, the call before the decorator before `task_routes` before the default,
|
|
|
1202
1536
|
and `transaction.on_commit(...)` around an enqueue is a note on the step,
|
|
1203
1537
|
which is the one fact about *when* a message leaves that the code states
|
|
1204
1538
|
plainly.
|
|
1539
|
+
|
|
1540
|
+
### HTTP destination provenance
|
|
1541
|
+
|
|
1542
|
+
`extract-http-clients` emits structured `destination` evidence on HTTP consumes
|
|
1543
|
+
and flow steps: the request call site and endpoint expression, local path,
|
|
1544
|
+
base URL expression, config field and environment variable, source locations,
|
|
1545
|
+
and a full path when source proves the join. Functional options that assign a
|
|
1546
|
+
URL field to a returned client are followed through adapter constructors.
|
|
1547
|
+
Bindings belong to the adapter's client field; conflicting construction sites
|
|
1548
|
+
remain unresolved instead of sharing another client's settings.
|
|
1549
|
+
|
|
1550
|
+
A Go `default` struct tag is recorded as `config-default`, not as the effective
|
|
1551
|
+
runtime configuration. For example, `WithBaseURL(cfg.SettingAddr)` with
|
|
1552
|
+
`default:"http://localhost:8000/settings"` and a concatenated
|
|
1553
|
+
`/get-admin-settings` yields `/settings/get-admin-settings`. The merge matches
|
|
1554
|
+
that full path exactly and never falls back to a suffix if it is absent.
|
|
1555
|
+
Legacy suffix matches remain explicitly labeled `unique-suffix` in the evidence
|
|
1556
|
+
shown in RPC step details. Later runtime URL modifiers are listed separately;
|
|
1557
|
+
the recovered full path describes the static join. Unreadable options may
|
|
1558
|
+
leave only the local route available.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Command portolan-http-clients is the native sidecar for typed Go call-graph
|
|
2
|
+
// analysis. It intentionally does not compile for WASI: go/packages needs the
|
|
3
|
+
// project Go toolchain to load build tags and dependencies before SSA/VTA can
|
|
4
|
+
// resolve interface calls.
|
|
5
|
+
package main
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
"fmt"
|
|
9
|
+
"os"
|
|
10
|
+
|
|
11
|
+
extracthttpclients "github.com/shortlink-org/portolan/plugins/extract-http-clients"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
func main() {
|
|
15
|
+
if err := extracthttpclients.Serve(os.Stdin, os.Stdout); err != nil {
|
|
16
|
+
fmt.Fprintln(os.Stderr, "portolan-http-clients:", err)
|
|
17
|
+
os.Exit(1)
|
|
18
|
+
}
|
|
19
|
+
}
|