feat(addon): Rust native add-on SDK + rust-sample reference (#3425) #3460

Merged
mfreeman451 merged 5 commits from feat/native-addon-rust-sdk into staging 2026-05-31 05:26:04 +00:00
Owner

What

Rust native add-on SDK + reference add-on for the agent feature-sets framework (#3425), plus a correctness fix so the reference manifest matches the binary it ships.

This branch is stacked — vs staging it contains three commits:

Commit Origin
37be0ebd1 feat(addons): native add-on manifest schema + Go validator + build-hygiene gates — this is the feat/native-addon-build-signing work
bc5abb31b feat(addon): Rust SDK (rust/addon-sdk) + rust-sample reference + Go interop test
6222bbea3 fix(addon): rename phantom fingerprintd → honest rust-sample manifest

Rust SDK (bc5abb31b)

  • rust/addon-sdk: go-plugin v1.8.0 handshake, AutoMTLS (tls.rs, DER-pinned verifier, aws-lc-rs for the host's ECDSA P-521 cert), tonic gRPC server + health, Addon trait, serviceradar-rust-sample-addon reference binary.
  • manager_rust_addon_test.go: Go↔Rust interop test driving the real addon.Manager + go-plugin client.

Honesty fix (6222bbea3)

The reference consumer shipped as addons/fingerprintd/ declaring id: fingerprintd / binary: serviceradar-fingerprintd, but the bundle packages serviceradar-rust-sample-addon, whose Info() reports id: rust-sample. The manifest disagreed with the binary. "fingerprintd" was a placeholder; the real passive-fingerprinting daemon is netprobe (see migrate-netprobe-to-native-addon). Renamed addons/fingerprintd/addons/rust-sample-addon/ and aligned id/capabilities/binary/schema with the binary's runtime Info().

Verification

  • cargo build -p addon-sdk + cargo test -p addon-sdk pass, including the handshake_mtls AutoMTLS integration test (runs unconditionally).
  • go run ./go/tools/addon-manifest-validator/... passes for addons/rust-sample-addon/ and addons/sample-addon/; validator tests green.

Merge notes / caveats

  • Stacking: this branch is rebased on top of feat/native-addon-build-signing (PR #3463) — its base commits here are the build-signing slice plus its lint fix. Merge #3463 first; once it lands in staging, this PR's diff reduces to just the Rust-SDK commits (Rust SDK + the rust-sample fix + the interop CI lane).
  • The Go↔Rust interop test (manager_rust_addon_test.go) is skip-gated — it only runs when SERVICERADAR_RUST_ADDON_BIN is set (or SERVICERADAR_BUILD_RUST_ADDON=1). No CI lane wires that yet, so the polyglot proof is skipped on a plain go test ./....
  • fingerprintd was never a real daemon; the real capability is netprobe, migrated separately.

🤖 Generated with Claude Code

## What Rust native add-on SDK + reference add-on for the agent feature-sets framework (#3425), plus a correctness fix so the reference manifest matches the binary it ships. This branch is **stacked** — vs `staging` it contains three commits: | Commit | Origin | | --- | --- | | `37be0ebd1` | `feat(addons)`: native add-on manifest schema + Go validator + build-hygiene gates — **this is the `feat/native-addon-build-signing` work** | | `bc5abb31b` | `feat(addon)`: Rust SDK (`rust/addon-sdk`) + `rust-sample` reference + Go interop test | | `6222bbea3` | `fix(addon)`: rename phantom `fingerprintd` → honest `rust-sample` manifest | ### Rust SDK (`bc5abb31b`) - `rust/addon-sdk`: go-plugin v1.8.0 handshake, AutoMTLS (`tls.rs`, DER-pinned verifier, `aws-lc-rs` for the host's ECDSA P-521 cert), tonic gRPC server + health, `Addon` trait, `serviceradar-rust-sample-addon` reference binary. - `manager_rust_addon_test.go`: Go↔Rust interop test driving the real `addon.Manager` + go-plugin client. ### Honesty fix (`6222bbea3`) The reference consumer shipped as `addons/fingerprintd/` declaring `id: fingerprintd` / `binary: serviceradar-fingerprintd`, but the bundle packages `serviceradar-rust-sample-addon`, whose `Info()` reports `id: rust-sample`. The manifest disagreed with the binary. "fingerprintd" was a placeholder; the real passive-fingerprinting daemon is **netprobe** (see `migrate-netprobe-to-native-addon`). Renamed `addons/fingerprintd/` → `addons/rust-sample-addon/` and aligned id/capabilities/binary/schema with the binary's runtime `Info()`. ## Verification - `cargo build -p addon-sdk` + `cargo test -p addon-sdk` pass, **including** the `handshake_mtls` AutoMTLS integration test (runs unconditionally). - `go run ./go/tools/addon-manifest-validator/...` passes for `addons/rust-sample-addon/` and `addons/sample-addon/`; validator tests green. ## Merge notes / caveats - **Stacking:** this branch is rebased on top of `feat/native-addon-build-signing` (PR **#3463**) — its base commits here are the build-signing slice plus its lint fix. **Merge #3463 first**; once it lands in `staging`, this PR's diff reduces to just the Rust-SDK commits (Rust SDK + the rust-sample fix + the interop CI lane). - The Go↔Rust interop test (`manager_rust_addon_test.go`) is **skip-gated** — it only runs when `SERVICERADAR_RUST_ADDON_BIN` is set (or `SERVICERADAR_BUILD_RUST_ADDON=1`). No CI lane wires that yet, so the polyglot proof is skipped on a plain `go test ./...`. - `fingerprintd` was never a real daemon; the real capability is netprobe, migrated separately. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(addons): native add-on manifest schema + validator and build-hygiene gates (#3425)
Some checks failed
Golang Tests / test-go (push) Failing after 2m16s
lint / lint (push) Failing after 2m32s
Secret Scan / gitleaks (pull_request) Successful in 22s
lint / lint (pull_request) Failing after 1m0s
CI / build (pull_request) Failing after 2m28s
37be0ebd13
Implements the unblocked, secret-free slice of the add-native-addon-build-signing
OpenSpec change: manifest validation (tasks 1.1/1.2), the dependency-isolation gate
(3.2), and the forbid-stdlib-plugin + binary-size portions of the build-hygiene
gates (3.1). The Cosign/ed25519 signing, discovery-index, verify-before-release,
and control-plane importer tasks remain blocked on a keyed signing environment and
registry/object-store access and are intentionally NOT touched.

- addons/native-addon-manifest.schema.json: JSON-Schema 2020-12 for addon.yaml
  (id, name, version, kind, delivery, supervision, capabilities, requires,
  artifacts, exec, state_dirs, config_schema), formalized from the de-facto sample
  and fingerprintd manifests.
- go/tools/addon-manifest-validator: dependency-light Go validator (command +
  internal/manifestschema library, embedded schema) that fails closed on a manifest
  missing required fields or with an unknown kind/delivery/supervision value.
  Unit tests with valid + invalid fixtures and a schema-drift guard.
- build/native_addons/assemble_addon_bundle.py: validates the manifest before
  writing any bundle output, so raw `bazel build` also fails closed before bundling.
- scripts/check-addon-dependency-isolation.sh: go list -deps gate asserting the base
  serviceradar-agent's transitive package set excludes add-on implementation
  packages (go/pkg/addon/sdk, go/cmd/serviceradar-*-addon); allows the agent-side
  contract/manager packages. Fails with the offending import path.
- scripts/check-addon-no-stdlib-plugin.sh: forbids the Go stdlib `plugin` package in
  the agent + add-on builds (go list -deps + direct-import scan).
- scripts/check-addon-binary-size.sh: per-artifact size regression gate; go-size-
  analyzer/gsa is a CI-image prerequisite (REQUIRE_GSA=1 to fail closed if missing).
- Makefile: validate_addon_manifests, check_addon_dependency_isolation,
  check_addon_no_stdlib_plugin, check_addon_binary_size, addon_build_gates,
  build_native_addons targets.

Verified: validator passes the shipped manifests and fails closed on an invalid
fixture; both isolation gates pass on the clean tree and fail (with the offending
path) on an injected violating import; openspec validate --strict passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(addon): Rust native add-on SDK & reference (go-plugin AutoMTLS interop) (#3425)
Some checks failed
lint / lint (push) Failing after 1m23s
Fingerprint Licensing / netprobe-fingerprint-licenses (push) Successful in 1m33s
Golang Tests / test-go (push) Failing after 2m49s
bc5abb31be
Implements OpenSpec change add-native-addon-rust-sdk: a Rust helper crate that
lets a Rust add-on be launched and supervised by the agent's EXISTING Go
go-plugin client with no host-side changes, plus a Rust reference add-on and the
fingerprintd reference manifest.

rust/addon-sdk:
- tonic/prost stubs generated from proto/agent/addon/v1 (build.rs).
- Full HashiCorp go-plugin v1.8.0 handshake in Rust: magic-cookie guard, UDS
  bind honoring PLUGIN_UNIX_SOCKET_DIR/GROUP, the CORE|APP|unix|addr|grpc|cert
  stdout handshake line (7th multiplex field only when PLUGIN_MULTIPLEX_GRPC),
  AddonService (Info/Configure/Health) + the grpc.health.v1 service the client
  pings for the "plugin" service.
- Working AutoMTLS, byte-compatible with the unmodified Go client. Three details
  were load-bearing and are documented in the crate: (1) the server cert must be
  a self-signed CA (Go pins it as the sole RootCAs and presents it as the leaf);
  (2) the cert must be serialized exactly once (rcgen re-signs per call with
  randomized ECDSA sigs, so split DER/PEM serializations don't match the pinned
  bytes); (3) the crypto provider must be aws-lc-rs, not ring, because go-plugin
  client certs are ECDSA P-521 which ring cannot verify. Custom rustls verifiers
  pin the peer cert by exact DER (Go's pin semantics) on both directions.
- serviceradar-rust-sample-addon reference binary mirroring the Go sample.

Build/manifest:
- addons/fingerprintd/ (pushed-artifact / agent-sidecar / language: rust) as the
  reference consumer manifest + config schema.
- build/native_addons: addon_inventory.bzl gains a language field and a Rust
  bundle; defs.bzl branches Rust (rust_binary) vs Go (go_cross_binary).

Tests (all passing):
- rust/addon-sdk handshake_mtls integration test (host role in Rust, DER-pinned
  mTLS, Info/Configure/Health).
- go/pkg/agent/addon/manager_rust_addon_test.go launches the Rust add-on via the
  REAL go-plugin client/Manager and drives Info/Configure/Health to StateRunning
  — the authoritative cross-language AutoMTLS proof.

openspec validate add-native-addon-rust-sdk --strict passes; tasks 1.1-4.2 done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(addon): make Rust reference add-on honest (rust-sample, not phantom fingerprintd) (#3425)
Some checks failed
lint / lint (push) Failing after 1m11s
Golang Tests / test-go (push) Failing after 1m53s
Secret Scan / gitleaks (pull_request) Successful in 33s
lint / lint (pull_request) Failing after 1m14s
Fingerprint Licensing / netprobe-fingerprint-licenses (pull_request) Successful in 1m59s
CI / build (pull_request) Failing after 2m4s
6222bbea31
The Rust reference consumer shipped as addons/fingerprintd/, declaring
id=fingerprintd / binary=serviceradar-fingerprintd / capabilities=[fingerprintd].
But the bundle (rust_sample_addon_bundle) packages serviceradar-rust-sample-addon,
whose Info() RPC reports id=rust-sample / capabilities=[rust-sample]. The manifest
therefore disagreed with the binary it was bundled with, and "fingerprintd" was a
placeholder name for a daemon that does not exist — the real passive-fingerprinting
daemon is netprobe (rust/netprobe), migrated separately.

Repoint the reference manifest to honestly describe the rust-sample binary:
- Rename addons/fingerprintd/ -> addons/rust-sample-addon/ (git mv, preserves history).
- addon.yaml: id/name/binary/capabilities now match the binary's runtime Info().
- config.schema.json: mirror the Go sample's "message" field (the sample echoes a
  SHA-256 config hash; the prior interfaces/scan_interval schema implied a real
  fingerprinting daemon that this binary is not).
- BUILD.bazel filegroup fingerprintd_manifest -> rust_sample_manifest.
- addon_inventory.bzl manifest_entries -> //addons/rust-sample-addon (+ comment).
- Refresh two stale docs lines that pointed at the planned "fingerprintd".

Validated: `go run ./go/tools/addon-manifest-validator/...` passes for
addons/rust-sample-addon/addon.yaml and addons/sample-addon/addon.yaml; validator
package tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The change shipped a `rust-sample` reference add-on, not a "fingerprintd". Update
proposal/tasks/design to name the real artifact (rust-sample) and cross-reference the
actual passive-fingerprinting capability, netprobe, which is migrated onto the framework
by migrate-netprobe-to-native-addon. `openspec validate add-native-addon-rust-sdk
--strict` passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ci: add Go<->Rust native add-on interop test lane (#3425)
Some checks failed
lint / lint (push) Failing after 1m4s
Secret Scan / gitleaks (pull_request) Successful in 58s
lint / lint (pull_request) Failing after 1m20s
Fingerprint Licensing / netprobe-fingerprint-licenses (pull_request) Successful in 2m0s
Rust Add-on Interop Test / interop (push) Successful in 2m12s
Golang Tests / test-go (push) Failing after 2m15s
CI / build (pull_request) Failing after 2m7s
eb963a3999
The interop test (go/pkg/agent/addon/manager_rust_addon_test.go) skips unless a
prebuilt Rust reference add-on is supplied, so the polyglot proof never ran in CI.
Add a forgejo workflow that builds serviceradar-rust-sample-addon via `sfw cargo`
(matching the repo's other cargo lanes) and runs the interop test pointed at it via
SERVICERADAR_RUST_ADDON_BIN.

Verified locally: `cargo build -p addon-sdk --bin serviceradar-rust-sample-addon`
then the test with SERVICERADAR_RUST_ADDON_BIN set -> TestManagerLaunchesRustAddon
OverGoPlugin and TestManagerStopsRustAddon both PASS. CI-runner specifics (sfw
crates.io egress, dtolnay toolchain on ubuntu24) mirror tests-rust.yml and need a
CI run to confirm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mfreeman451 left a comment

lgtm

lgtm
mfreeman451 force-pushed feat/native-addon-rust-sdk from eb963a3999
Some checks failed
lint / lint (push) Failing after 1m4s
Secret Scan / gitleaks (pull_request) Successful in 58s
lint / lint (pull_request) Failing after 1m20s
Fingerprint Licensing / netprobe-fingerprint-licenses (pull_request) Successful in 2m0s
Rust Add-on Interop Test / interop (push) Successful in 2m12s
Golang Tests / test-go (push) Failing after 2m15s
CI / build (pull_request) Failing after 2m7s
to 2b4f42c861
Some checks failed
Golang Tests / test-go (push) Failing after 1m41s
Secret Scan / gitleaks (pull_request) Successful in 1m27s
lint / lint (pull_request) Failing after 1m37s
lint / lint (push) Failing after 2m24s
Fingerprint Licensing / netprobe-fingerprint-licenses (pull_request) Successful in 2m19s
CI / build (pull_request) Failing after 2m27s
2026-05-31 05:06:34 +00:00
Compare
test(addon): drop always-StateRunning param from waitForState (unparam) (#3425)
Some checks failed
Secret Scan / gitleaks (pull_request) Successful in 28s
lint / lint (push) Successful in 1m15s
lint / lint (pull_request) Successful in 1m46s
CI / build (pull_request) Failing after 1m48s
Fingerprint Licensing / netprobe-fingerprint-licenses (pull_request) Successful in 1m53s
Golang Tests / test-go (push) Failing after 1m57s
Rust Add-on Interop Test / interop (push) Successful in 1m58s
66824fbaee
Adding manager_rust_addon_test.go gives the addon package 5 waitForState call
sites, all passing StateRunning, which trips golangci-lint's unparam on the
`want` parameter. Since no caller waits for any other state, drop the parameter
and wait for StateRunning directly; update all call sites.

golangci-lint run ./go/pkg/agent/addon/... -> 0 issues; addon package tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar!3460
No description provided.