Skip to content

Commit 8723bf8

Browse files
committed
feat(daemon): wire app-usage telemetry for app-store supervisor (PILOT-402)
The appstoreAdapter now holds telemetryURL and identityPath, using them to create a consent-gated telemetry.Client on Start. The client is wrapped in an telemetryEmitter that satisfies appstore.TelemetryEmitter, passing app_usage events from supervisor.callFrom into the signed telemetry pipeline. When consent is off (empty telemetry URL) the client is a permanent no-op — no goroutines, no dials, no buffering. Related: app-store module also updated for PILOT-402 (commit 8edfed7efa72e78499f02260ea84ae42b724f49a).
1 parent 83c84c8 commit 8723bf8

4 files changed

Lines changed: 71 additions & 16 deletions

File tree

cmd/daemon/appstore_adapter.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,60 @@ package main
1515

1616
import (
1717
"context"
18+
"encoding/json"
19+
"time"
1820

1921
"github.com/pilot-protocol/app-store/plugin/appstore"
2022
"github.com/pilot-protocol/common/coreapi"
23+
"github.com/TeoSlayer/pilotprotocol/pkg/telemetry"
2124
)
2225

2326
type appstoreAdapter struct {
24-
svc *appstore.Service
27+
svc *appstore.Service
28+
telemetryURL string
29+
identityPath string
30+
}
31+
32+
// telemetryEmitter wraps the consent-gated telemetry client to satisfy
33+
// the appstore.TelemetryEmitter interface. Events are sent as
34+
// "app_usage" kind with the supervisor-provided fields as payload.
35+
// Best-effort: send errors are logged but never block the caller.
36+
type telemetryEmitter struct {
37+
client *telemetry.Client
38+
}
39+
40+
func (e *telemetryEmitter) Emit(ev appstore.TelemetryEvent) {
41+
if e == nil || e.client == nil {
42+
return
43+
}
44+
payload, err := json.Marshal(ev)
45+
if err != nil {
46+
return
47+
}
48+
_ = e.client.Send(telemetry.Event{
49+
Kind: "app_usage",
50+
TS: time.Now().UTC().Format(time.RFC3339),
51+
Payload: payload,
52+
})
2553
}
2654

2755
func (a *appstoreAdapter) Name() string { return a.svc.Name() }
2856
func (a *appstoreAdapter) Order() int { return a.svc.Order() }
2957
func (a *appstoreAdapter) Start(ctx context.Context, deps coreapi.Deps) error {
58+
// Build a consent-gated telemetry client for app-usage events.
59+
// When the URL is empty or identity is absent the client is a
60+
// permanent no-op — the emitter never sends anything.
61+
client := telemetry.NewClientFromIdentity(a.telemetryURL, a.identityPath, 0)
62+
emitter := &telemetryEmitter{client: client}
63+
3064
return a.svc.Start(ctx, appstore.Deps{
31-
Streams: deps.Streams,
32-
Identity: deps.Identity,
33-
Resolver: deps.Resolver,
34-
Events: deps.Events,
35-
Logger: deps.Logger,
36-
Trust: deps.Trust,
65+
Streams: deps.Streams,
66+
Identity: deps.Identity,
67+
Resolver: deps.Resolver,
68+
Events: deps.Events,
69+
Logger: deps.Logger,
70+
Trust: deps.Trust,
71+
Telemetry: emitter,
3772
})
3873
}
3974
func (a *appstoreAdapter) Stop(ctx context.Context) error { return a.svc.Stop(ctx) }

cmd/daemon/main.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,29 @@ func main() {
307307
if home, herr := os.UserHomeDir(); herr == nil {
308308
appstoreInstallRoot = filepath.Join(home, ".pilot", "apps")
309309
}
310-
if err := rt.Register(&appstoreAdapter{svc: appstore.NewService(appstore.Config{
311-
InstallRoot: appstoreInstallRoot,
312-
RescanInterval: 2 * time.Second,
313-
// Real catalogue trust anchor (replaces the all-zeros
314-
// placeholder default): the embedded ed25519 catalogue key.
315-
CatalogPubkey: []byte(catalogtrust.PublicKey()),
316-
})}); err != nil {
310+
// The app-usage telemetry emitter shares the daemon's identity file
311+
// and telemetry URL. When consent is off (empty URL) the client is
312+
// a permanent no-op — no goroutines, no dials, no buffering.
313+
idPath := *identityPath
314+
if idPath == "" {
315+
if home, herr := os.UserHomeDir(); herr == nil {
316+
defaultID := filepath.Join(home, ".pilot", "identity.json")
317+
if _, serr := os.Stat(defaultID); serr == nil {
318+
idPath = defaultID
319+
}
320+
}
321+
}
322+
if err := rt.Register(&appstoreAdapter{
323+
svc: appstore.NewService(appstore.Config{
324+
InstallRoot: appstoreInstallRoot,
325+
RescanInterval: 2 * time.Second,
326+
// Real catalogue trust anchor (replaces the all-zeros
327+
// placeholder default): the embedded ed25519 catalogue key.
328+
CatalogPubkey: []byte(catalogtrust.PublicKey()),
329+
}),
330+
telemetryURL: *telemetryURL,
331+
identityPath: idPath,
332+
}); err != nil {
317333
log.Fatalf("register appstore: %v", err)
318334
}
319335

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.25.10
44

55
require (
66
github.com/coder/websocket v1.8.14
7-
github.com/pilot-protocol/app-store v1.0.1-beta.1.0.20260609061942-8852c785a264
7+
github.com/pilot-protocol/app-store v1.0.1-beta.1.0.20260616142430-8edfed7efa72
88
github.com/pilot-protocol/beacon v0.2.6
99
github.com/pilot-protocol/common v0.4.9-0.20260615113553-d5cbbfb3e5b6
1010
github.com/pilot-protocol/dataexchange v0.2.1-beta.1.0.20260615113607-fac933edea98
@@ -18,7 +18,7 @@ require (
1818
github.com/pilot-protocol/trustedagents v0.2.3
1919
github.com/pilot-protocol/updater v0.2.2-0.20260529065627-220ed5b8383f
2020
github.com/pilot-protocol/webhook v0.2.0
21-
golang.org/x/sys v0.45.0
21+
golang.org/x/sys v0.46.0
2222
)
2323

2424
require (

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/pilot-protocol/app-store v1.0.0-rc1.0.20260609015400-d02db7da3924 h1:
88
github.com/pilot-protocol/app-store v1.0.0-rc1.0.20260609015400-d02db7da3924/go.mod h1:f0umeJxswDG8/CctHpSFMlr5GLtE2GlPKkijIQErZuc=
99
github.com/pilot-protocol/app-store v1.0.1-beta.1.0.20260609061942-8852c785a264 h1:NL9rFdakbVQ0V7xfJbCk8RJZSaQ1AmvdhAJwFIouMsk=
1010
github.com/pilot-protocol/app-store v1.0.1-beta.1.0.20260609061942-8852c785a264/go.mod h1:zoCxHYoNdj0V44OkG3Yzcye0jnwZDVUcJgAvR5Z1kwc=
11+
github.com/pilot-protocol/app-store v1.0.1-beta.1.0.20260616142430-8edfed7efa72 h1:vDiQ7ZheKIzlNqfviu5zeQzGVTMP63k1hC5HodEuyeQ=
12+
github.com/pilot-protocol/app-store v1.0.1-beta.1.0.20260616142430-8edfed7efa72/go.mod h1:leZPtX43gE2JB7xeljexXri81g6qhdZfYExLtzI+bhg=
1113
github.com/pilot-protocol/beacon v0.2.5 h1:5+pkSPoA35r+u4Hfrph/ZfOltOyiy8lh1sCfK5XqXKs=
1214
github.com/pilot-protocol/beacon v0.2.5/go.mod h1:I/UhEv097g1z/qtAVDZbEhf3R5tzM0Dp71vGHah52A4=
1315
github.com/pilot-protocol/beacon v0.2.6 h1:grxwaVyPRUT0W6coyjYfNkO0rpzOIrwrKn94S21DuVE=
@@ -56,3 +58,5 @@ golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
5658
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
5759
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
5860
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
61+
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
62+
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=

0 commit comments

Comments
 (0)