Shared Go library every parabellum worker links against. Carries the wire contract between the control plane and the workers, plus the runtime plumbing every worker would otherwise re-implement: River subscription, Postgres pool, OpenTelemetry tracing, Prometheus metrics, finding deduplication, graceful shutdown.
The SDK's surface is intentionally tiny: three interfaces, a handful of
types, one Serve entry point. New tools implement Tool.Run and call
worker.Serve(t). That's it. Everything else is hidden.
worker-sdk is the wire contract. Breaking changes cost ~one PR per
worker, so they are handled carefully:
- MINOR / PATCH bumps preserve full backward compatibility.
- MAJOR bumps go through a deprecation cycle of at least one MINOR before the old API is removed.
- The SDK ships its own
Versionconstant; the control plane reads each worker'smanifest.sdk_versionto assert compatibility.
See ../platform/docs/VERSIONING.md for the org-wide policy.
worker-sdk/
├── worker/ # the public package; what `import` consumers use
│ ├── tool.go # Tool interface, Job, Result, Finding, Asset
│ ├── manifest.go # YAML manifest schema + load/validate (incl. Description)
│ ├── jobargs.go # CascadeArgs wire contract (matches controlplane/jobtype)
│ ├── serve.go # Serve() entry point: River + signal handling
│ ├── runtime.go # PG pool, metrics, OTel, config reload
│ ├── river_adapter.go # River JobArgs binding + worker registration
│ ├── asset_writer.go # UpsertAsset + sync.Pool fingerprint
│ ├── dedup.go # finding hash canonicalization
│ └── once.go # --once / --asset synthetic-job mode (no DB, no River)
├── sdk/ # opt-in helpers; workers import only what they need
│ ├── mtls/ # cleanhttp-style http.Client with mTLS roots
│ ├── httpcache/ # cluster body cache + SourceCache
│ ├── dns/ # dns-service HTTP client wrapper + local fallback
│ ├── secretbox/ # AES-256-GCM decrypt (read-only by design)
│ ├── metrics/ # shared prometheus collectors for the worker side
│ └── tracing/ # OTLP exporter helpers
├── docs/ # ASSETS / FINDINGS / IDEMPOTENCE / CONCURRENCY / MANIFEST
├── grafana/ # ready-to-import dashboards for worker-side metrics
└── CHANGELOG.md
The consumes.filter DSL is parsed inside the controlplane (cascade
engine), not the SDK. Workers receive jobs that already match the
filter. The SDK only ships the manifest type that declares it.
package main
import (
"context"
"git.vozec.fr/Parabellum/worker-sdk/worker"
)
type MyTool struct{}
func (t *MyTool) Name() string { return "tm-mytool" }
func (t *MyTool) Run(ctx context.Context, j worker.Job) (worker.Result, error) {
return worker.Result{
Findings: []worker.Finding{{
Kind: "demo",
Severity: worker.SeverityInfo,
Title: "Hello from " + j.Asset.Value,
}},
}, nil
}
func main() { worker.Serve(&MyTool{}) }A worker with that body will:
- read
manifest.yamlnext to the binary - connect to Postgres (
PG_DSNenv) - subscribe to its declared phase queue via River
- expose
/metricsand/healthzon:9090(configurable) - emit OTLP traces if
OTEL_EXPORTER_OTLP_ENDPOINTis set
docs/IDEMPOTENCE.md- why yourRunmust be safe to retry, what the SDK guarantees vs what you owndocs/MANIFEST.md- the YAML schema in detaildocs/ASSETS.md- kinds, attrs conventions per kind, JSONB round-trip gotchasdocs/FINDINGS.md- finding shape, dedup hash recipe, kinds + severity ladderdocs/CONCURRENCY.md- per-host limits, AIMD, circuit breaker, dns-side concurrencydocs/OBSERVABILITY.md- free metrics the SDK exposes, custom metrics + structured logging conventions
See CHANGELOG.md for release notes by version.
MIT.