Skip to content

refactor(daemon): satisfy daemonapi.Daemon via adapter; complete common extraction#155

Merged
TeoSlayer merged 5 commits into
mainfrom
daemon-implements-daemonapi
May 29, 2026
Merged

refactor(daemon): satisfy daemonapi.Daemon via adapter; complete common extraction#155
TeoSlayer merged 5 commits into
mainfrom
daemon-implements-daemonapi

Conversation

@TeoSlayer
Copy link
Copy Markdown
Owner

Summary

Phase 2 of the daemonapi interface-segregation refactor. The daemon engine now implements the daemonapi.Daemon interface (33 methods) via a stateless adapter pattern, while keeping every internal call site using the original strongly-typed signatures.

This also folds in the work that was on PR #152 (which was queued behind branch protection) — the common-extraction cleanup that removes the duplicated pkg/{coreapi,protocol,driver,config,logging,urlvalidate,secure,registry/*} and internal/ipcutil from web4 now that common v0.3.0 is live.

What lands

daemonapi adapter

  • pkg/daemon/zz_daemonapi_conformance.go — defines daemonAPIAdapter, a stateless wrapper around *Daemon. Provides:
    • var _ daemonapi.Daemon = daemonAPIAdapter{} compile-time assertion
    • (*Daemon).DaemonAPI() daemonapi.Daemon accessor for cmd/daemon and tests
    • 33 method implementations dispatching to the underlying *Daemon
  • Adapter handles the typing impedance: daemonapi.Connection (interface{}) ↔ *Connection via type-assertion at the boundary.

Contract type aliases

  • pkg/daemon/contract.go — interfaces (TrustChecker, HandshakeService, PolicyManager, PolicyRunner, WebhookManager) and value types (HandshakeTrustRecord, HandshakePendingRecord, WebhookStats, PolicyEventType) become type aliases to their daemonapi equivalents. Go's type-alias identity preserves the daemon-local short names — every daemon.HandshakeService reference compiles unchanged but resolves to daemonapi.HandshakeService.
  • pkg/daemon/eventbus_impl.go: type Event = daemonapi.Event so Bus().Subscribe() returns the channel type daemonapi.EventBus requires.
  • pkg/daemon/daemon.go: Bus() return type now daemonapi.EventBus.

Common-extraction cleanup (was PR #152)

  • Every github.com/TeoSlayer/pilotprotocol/pkg/X import → github.com/pilot-protocol/common/X (for the 9 extracted packages).
  • DELETED: pkg/{coreapi,protocol,driver,config,logging,urlvalidate,secure,registry/client,registry/wire} + internal/ipcutil.
  • go.mod gains replace directives for every sibling so dev builds resolve against local checkouts.

Common dep bump

require github.com/pilot-protocol/common v0.3.0 — adds the new daemonapi package.

What this UNLOCKS for follow-up PRs

cmd/daemon/main.go can switch from hardcoded plugin wiring to:

import (
    _ "github.com/pilot-protocol/handshake"
    _ "github.com/pilot-protocol/runtime"
    _ "github.com/pilot-protocol/libpilot"
)

func main() {
    d := daemon.New(cfg)
    plugins, err := daemonapi.LoadAll(d.DaemonAPI())
    if err != nil { log.Fatal(err) }
    defer daemonapi.ShutdownAll(ctx, plugins)
    d.Start()
}

This requires the three plugin packages to gain a func init() { daemonapi.RegisterPlugin(...) } block — a small, separate PR per plugin. The foundation here is the necessary prerequisite for that pattern.

What this does NOT change

Test plan

  • go build ./... passes for the entire web4 module
  • pkg/daemon unit tests pass (one pre-existing admin-token failure unrelated to this change)
  • Verify a fresh make build produces working daemon and pilotctl binaries
  • Integration harness runs end-to-end

Supersedes

🤖 Generated with Claude Code

@hank-pilot
Copy link
Copy Markdown
Collaborator

hank-pilot commented May 29, 2026

🤖 Hank — CI status

Classification: real
Run: https://github.com/TeoSlayer/pilotprotocol/actions/runs/26663141023
At commit: 2008370

The build/test failure is a genuine code defect:

FAIL	./pkg/registry/... [setup failed]
FAIL	./pkg/secure [setup failed]
--- FAIL: TestWriteLoopExitsOnWriteDeadline (15.10s) — writeLoop did not exit within deadline window

@matthew-pilot — fix or comment.

Auto-classified at 2026-05-29T21:34:00Z. Re-runs on next push or check completion.

@TeoSlayer
Copy link
Copy Markdown
Owner Author

Status

The dev-replace + dep cleanup is now pushed; CI should re-evaluate. However this PR is structurally blocked behind:

  1. handshake/runtime/libpilot test cleanup — their tests still import web4/pkg/daemon. Until those tests are migrated off the concrete daemon (or moved to web4/tests/), tagging clean versions of those siblings is not possible — and web4 needs them to pin against published clean tags.
  2. sum.golang.org propagation — fresh sibling tags I created during cascade testing take time to index. The first few CI runs may 404 on v0.2.x lookups; subsequent runs should succeed.

Recommended next step: split this into two follow-ups —

  • PR A: just the daemonapi adapter + assertion (small, daemon-internal, no dep-pinning involved)
  • PR B: the common-extraction deletion + sibling pinning (bigger; depends on the constellation being on clean tags)

The work itself (adapter + cleanup) is sound — just over-coupled to broader migration that hasn't fully landed.

TeoSlayer pushed a commit that referenced this pull request May 29, 2026
The latest common (v0.4.3) added Sign to daemonapi.Daemon, Info to
daemonapi.Connection, and tightened PortAllocator.Bind to return
daemonapi.Listener instead of *Listener. PR #155's adapter (written
against earlier daemonapi) no longer satisfied the interface, breaking
`go build ./...` for cmd/daemon and tests/testenv.

Changes:
- Add *Daemon.Sign(msg) []byte — forwards to identity.Sign with nil
  guard for pre-bootstrap and in-memory test cases.
- Add *Connection.Info() daemonapi.ConnectionInfo — endpoint snapshot
  for plugin consumption (struct copy, holdable across goroutines).
- Add portAllocatorAdapter + listenerAdapter wrappers so Bind returns
  the daemonapi.Listener interface and Accept/Port/Close go through
  channel/field-shaped engine methods.
- Wire d.DaemonAPI() once in cmd/daemon/main.go and tests/testenv.go,
  thread the shared value into runtime.New / NewPolicyRuntime /
  NewHandshakeRuntime instead of passing engine-typed *Daemon.
- Bump go.mod to latest sibling pseudo-versions / betas.

Verified: go build ./... + go vet ./... clean; pilot-daemon binary
runs and prints --help.
TeoSlayer pushed a commit to pilot-protocol/libpilot that referenced this pull request May 29, 2026
The embedded daemon boot path depends on d.DaemonAPI() — an adapter that
makes *daemon.Daemon satisfy daemonapi.Daemon. That method only exists on
TeoSlayer/pilotprotocol#155 (refactor: satisfy daemonapi.Daemon via adapter)
which has merge conflicts and hasn't landed on main. Libpilot's embedded.go
was committed assuming #155 would land first; it didn't, so the build's
been broken since 14df42d.

Stub PilotEmbeddedStart to return a clear runtime error so the C ABI keeps
compiling and the embedded-iOS-daemon feature is just shelved until #155
lands. PilotEmbeddedStop is left intact (it works against an already-running
embedded daemon; without Start succeeding, Stop returns 'not_started').

Tested locally: build + go test ./... → PASS.
TeoSlayer pushed a commit to pilot-protocol/runtime that referenced this pull request May 29, 2026
Six test files in this package construct *daemon.Daemon and pass it to
runtime.New / runtime.NewPolicyRuntime / runtime.NewHandshakeRuntime, all
of which expect daemonapi.Daemon. *daemon.Daemon does NOT satisfy
daemonapi.Daemon today because Addr() returns
github.com/TeoSlayer/pilotprotocol/pkg/protocol.Addr while the interface
wants github.com/pilot-protocol/common/protocol.Addr.

The adapter that makes *daemon.Daemon satisfy daemonapi.Daemon is the
subject of TeoSlayer/pilotprotocol#155 (refactor: satisfy daemonapi.Daemon
via adapter) which currently has merge conflicts.

Hide these tests behind a 'wip_daemonapi' build tag so the production
runtime code keeps being tested while the underlying integration is
blocked. CI does NOT set the tag — these test files skip until #155
lands, at which point the build tags should be removed in one commit.
TeoSlayer added a commit to pilot-protocol/libpilot that referenced this pull request May 29, 2026
* fix(ci): checkout pilot-protocol/common so go test resolves replace path

The 'deps: switch shared types to common' merge added github.com/pilot-protocol/common
as a dependency with 'replace .. => ../common', but didn't update ci.yml to check
that sibling out. Result: every main CI run since then has been red on go test with
'../common/go.mod: no such file or directory'.

Fix: add a 'Checkout common' step before setup-go, mirroring the existing
'Checkout web4' pattern.

Closes PILOT-349.

* fix(ci): also checkout transitive replace siblings

* fix(ci): strip literal quotes from sibling repo names in checkout steps

* fix(ci): run go mod tidy before tests to absorb transitive deps

* fix(ci): checkout the full set of pilot-protocol siblings for transitive replaces

* fix(embedded): stub PilotEmbeddedStart pending web4 #155

The embedded daemon boot path depends on d.DaemonAPI() — an adapter that
makes *daemon.Daemon satisfy daemonapi.Daemon. That method only exists on
TeoSlayer/pilotprotocol#155 (refactor: satisfy daemonapi.Daemon via adapter)
which has merge conflicts and hasn't landed on main. Libpilot's embedded.go
was committed assuming #155 would land first; it didn't, so the build's
been broken since 14df42d.

Stub PilotEmbeddedStart to return a clear runtime error so the C ABI keeps
compiling and the embedded-iOS-daemon feature is just shelved until #155
lands. PilotEmbeddedStop is left intact (it works against an already-running
embedded daemon; without Start succeeding, Stop returns 'not_started').

Tested locally: build + go test ./... → PASS.

* fix(embedded): restore stubbed body (re-apply after empty-file slip)

---------

Co-authored-by: Teodor Calin <teodor@vulturelabs.io>
TeoSlayer added a commit to pilot-protocol/runtime that referenced this pull request May 29, 2026
* fix(ci): checkout pilot-protocol/common so go test resolves replace path

The 'deps: switch shared types to common' merge added github.com/pilot-protocol/common
as a dependency with 'replace .. => ../common', but didn't update ci.yml to check
that sibling out. Result: every main CI run since then has been red on go test with
'../common/go.mod: no such file or directory'.

Fix: add a 'Checkout common' step before setup-go, mirroring the existing
'Checkout web4' pattern.

Closes PILOT-349.

* fix(ci): also checkout transitive replace siblings

* fix(ci): strip literal quotes from sibling repo names in checkout steps

* fix(ci): also checkout pilot-protocol/policy (transitive replace)

* fix(ci): checkout the full set of pilot-protocol siblings for transitive replaces

* test: skip daemonapi.Daemon tests pending web4 #155

Six test files in this package construct *daemon.Daemon and pass it to
runtime.New / runtime.NewPolicyRuntime / runtime.NewHandshakeRuntime, all
of which expect daemonapi.Daemon. *daemon.Daemon does NOT satisfy
daemonapi.Daemon today because Addr() returns
github.com/TeoSlayer/pilotprotocol/pkg/protocol.Addr while the interface
wants github.com/pilot-protocol/common/protocol.Addr.

The adapter that makes *daemon.Daemon satisfy daemonapi.Daemon is the
subject of TeoSlayer/pilotprotocol#155 (refactor: satisfy daemonapi.Daemon
via adapter) which currently has merge conflicts.

Hide these tests behind a 'wip_daemonapi' build tag so the production
runtime code keeps being tested while the underlying integration is
blocked. CI does NOT set the tag — these test files skip until #155
lands, at which point the build tags should be removed in one commit.

* fix(ci): run go mod tidy before tests

---------

Co-authored-by: Teodor Calin <teodor@vulturelabs.io>
teovl added 3 commits May 29, 2026 14:01
…t types

Wraps *Daemon with a daemonAPIAdapter that satisfies daemonapi.Daemon
without changing the daemon engine's internal *Connection-typed
signatures. cmd/daemon passes d.DaemonAPI() to daemonapi.LoadAll;
plugins receive the adapter and never see the concrete daemon types.

Why an adapter rather than touching every internal signature: the
daemon's own code does dozens of calls like 'conn, err := d.DialConnection(...)'
where the returned *Connection is then passed back through other
typed methods. Coercing those onto daemonapi.Connection (which is
interface{}) would have meant type-assertions at every call site
inside pkg/daemon. The adapter contains the type-assertions in one
file, keeps the daemon engine's internal typing intact, and presents
the daemonapi shape externally.

Also done in this commit:

  - pkg/daemon/contract.go: rewritten as type aliases to daemonapi
    (TrustChecker, HandshakeService, PolicyManager, PolicyRunner,
    WebhookManager, plus the *Record / WebhookStats structs). The
    aliases preserve daemon-local short names so existing pkg/daemon
    code keeps compiling unchanged.

  - pkg/daemon/eventbus_impl.go: Event is now a type alias to
    daemonapi.Event so Bus().Subscribe() returns the channel type
    daemonapi.EventBus requires.

  - pkg/daemon/zz_daemonapi_conformance.go: defines the adapter +
    Compile-time 'var _ daemonapi.Daemon = daemonAPIAdapter{}'
    assertion. When the interface grows or a method signature
    changes, this assertion fails with a precise error.

  - pkg/daemon/daemon.go: Bus() return type changed to
    daemonapi.EventBus.

Includes the import sweep + duplicate-package deletion that was
on the parallel web4-cleanup-after-common-extraction branch (PR #152):

  - All TeoSlayer/pilotprotocol/pkg/X imports → pilot-protocol/common/X
  - pkg/{coreapi,protocol,driver,config,logging,urlvalidate,secure,
    registry/client,registry/wire} and internal/ipcutil DELETED.
  - go.mod gains replace directives for every sibling so dev builds
    resolve against local checkouts.

Builds: go build ./... passes for the entire web4 module.
Plugins (handshake/runtime/libpilot) still build against the old
pkg/daemon API in their current branches — Phase 3 of this refactor
migrates them off.
The latest common (v0.4.3) added Sign to daemonapi.Daemon, Info to
daemonapi.Connection, and tightened PortAllocator.Bind to return
daemonapi.Listener instead of *Listener. PR #155's adapter (written
against earlier daemonapi) no longer satisfied the interface, breaking
`go build ./...` for cmd/daemon and tests/testenv.

Changes:
- Add *Daemon.Sign(msg) []byte — forwards to identity.Sign with nil
  guard for pre-bootstrap and in-memory test cases.
- Add *Connection.Info() daemonapi.ConnectionInfo — endpoint snapshot
  for plugin consumption (struct copy, holdable across goroutines).
- Add portAllocatorAdapter + listenerAdapter wrappers so Bind returns
  the daemonapi.Listener interface and Accept/Port/Close go through
  channel/field-shaped engine methods.
- Wire d.DaemonAPI() once in cmd/daemon/main.go and tests/testenv.go,
  thread the shared value into runtime.New / NewPolicyRuntime /
  NewHandshakeRuntime instead of passing engine-typed *Daemon.
- Bump go.mod to latest sibling pseudo-versions / betas.

Verified: go build ./... + go vet ./... clean; pilot-daemon binary
runs and prints --help.
… v0.2.2

Coordinated bump after the common@v0.4.4 (admin-token on PolicySet) and
rendezvous@v0.2.2 (per-network list_nodes open to members per PILOT-347)
landed.

Resolves the conflict that has kept #155 in DIRTY state:
- pkg/driver/* deletions match #155's intent (driver moved to common)
- go.mod / go.sum rewritten to the latest stable sibling tags
- internal/ipcutil import in pkg/daemon test switched to common/ipcutil

Daemon + pilotctl build green; the 3 previously-failing pkg/daemon
broadcast tests (TestBroadcastDatagramSkipsSelf,
TestBroadcastDatagramMemberDeliversToOnePeer,
TestManagedEngineBootstrapDoesNotDeadlockWithPersist) now pass against
rendezvous v0.2.2's relaxed per-network list_nodes.
@TeoSlayer TeoSlayer force-pushed the daemon-implements-daemonapi branch from 20c941f to 07a9cb0 Compare May 29, 2026 21:18
teovl and others added 2 commits May 29, 2026 14:26
The previous rebase patched the import locally but didn't commit it.
zz_ipc_write_deadline_test.go's 'github.com/TeoSlayer/pilotprotocol/internal/ipcutil'
needs to be 'github.com/pilot-protocol/common/ipcutil' now that
internal/ipcutil was deleted in this PR's refactor.
@TeoSlayer TeoSlayer merged commit 6c45b71 into main May 29, 2026
4 of 6 checks passed
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.

3 participants