Skip to content

feat(daemonapi): runtime plugin registry + Daemon contract#3

Merged
TeoSlayer merged 1 commit into
mainfrom
add-daemonapi-plugin-registry
May 28, 2026
Merged

feat(daemonapi): runtime plugin registry + Daemon contract#3
TeoSlayer merged 1 commit into
mainfrom
add-daemonapi-plugin-registry

Conversation

@TeoSlayer
Copy link
Copy Markdown
Contributor

Summary

Adds a dependency-free contract layer that breaks the web4 ↔ daemon-plugin cycle.

common/daemonapi contains:

  • Plugin lifecycle: Plugin interface (Name, Init(Daemon), Shutdown(ctx)) + Factory type
  • Registry: RegisterPlugin / LoadAll / ShutdownAll — process-global, append-only. Plugins register from init(); daemon engine iterates.
  • Daemon contract: 33-method interface covering everything handshake/runtime/libpilot actually call (audited)
  • Services: TrustChecker, HandshakeService, PolicyManager, PolicyRunner, WebhookManager (copied from web4/pkg/daemon/contract.go — already primitive-only)
  • Opaque markers: Connection, PortAllocator, TunnelRegistry for engine-internal types plugins treat as tokens
  • Event bus: Event struct + EventBus interface (Publish/Subscribe)

Why this unblocks 'not static'

  • Daemon engine has zero hardcoded list of plugins. cmd/daemon does blank-imports; LoadAll iterates the registry.
  • Same mechanism works for compiled-in plugins and plugin.Open .so files.
  • Plugins import only common; daemon imports only its own pkg/daemon. No upward import.

Tests

4 tests pass:

  • TestRegisterAndLoadAll — registers two factories, verifies sorted Init order + daemon flow-through
  • TestRegisterDuplicatePanics — duplicate name is a programming error
  • TestLoadAllStopsOnFirstError — partial-load list returned, no further Init attempts
  • TestShutdownAllReverseOrder — reverse of load order

Next PRs

  • TeoSlayer/pilotprotocol: add var _ daemonapi.Daemon = (*Daemon)(nil) assertion in pkg/daemon. Compiler verifies the contract.
  • handshake / runtime / libpilot: replace direct *daemon.Daemon references with daemonapi.Daemon. Drop the web4 require.

🤖 Generated with Claude Code

Adds the dependency-free contract layer that breaks the
web4 ↔ daemon-plugin cycle. Plugins import only common/daemonapi;
the concrete *daemon.Daemon in web4/pkg/daemon satisfies the
interfaces via Go's structural typing — neither side imports the
other.

Files:

  daemonapi/doc.go          package overview + the 'not static'
                            wiring story.
  daemonapi/plugin.go       Plugin interface + Factory.
  daemonapi/registry.go     RegisterPlugin / LoadAll / ShutdownAll.
                            Process-global append-only map; plugin
                            packages register from init(); the
                            daemon iterates whatever's there.
                            Same mechanism works for compile-time
                            blank-imports and for runtime plugin.Open
                            of .so files.
  daemonapi/daemon.go       Daemon interface — the union of what
                            handshake, runtime, and libpilot actually
                            call. Return types: common-package
                            concrete types (*crypto.Identity,
                            *registry.Client, protocol.Addr) where
                            available; opaque marker interfaces
                            (Connection, PortAllocator, TunnelRegistry)
                            where the concrete return type lives
                            inside the daemon engine.
  daemonapi/connection.go   Connection (opaque marker) +
                            ConnReadWriter (read/write adapter) +
                            PortAllocator + TunnelRegistry.
  daemonapi/event.go        Event struct + EventBus interface.
  daemonapi/services.go     Copy of pkg/daemon/contract.go: TrustChecker,
                            HandshakeService, PolicyRunner, PolicyManager,
                            WebhookManager, and the *Record value types
                            they exchange.

Smoke-tested:
  - RegisterPlugin + LoadAll wires plugins in sorted order
  - Duplicate registration panics in init()
  - LoadAll stops on first Init error and returns partial-loaded list
  - ShutdownAll runs in reverse-of-load order

Next: web4/pkg/daemon adds an interface-conformance assertion
(var _ daemonapi.Daemon = (*Daemon)(nil)) so the compiler enforces
the contract. Then handshake, runtime, libpilot migrate off the
direct web4 imports.
@TeoSlayer TeoSlayer merged commit 5ddae85 into main May 28, 2026
1 check passed
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 76.92308% with 12 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
daemonapi/registry.go 76.92% 8 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants