fluid-telegram/internal/adapter/telemetry.go
tegwick bfebca3aee Implement the R-1 adapter (FT-WP-0001 T05/T09); mark workplans blocked
Builds internal/adapter and cmd/adapter: the three contracts/r1.openapi.yaml
operations, idempotency on post_id, the reviewed_by/consent-basis refusals,
per-form (R-2/R-3) length limits, and Markdown-to-Telegram rendering, all
against a Sender interface so it's testable without a live bot token.

Every other open task in FT-WP-0001 and FT-WP-0002 needs something this repo
can't supply on its own: the human session bootstrap in FT-WP-0002 T01, the
fluid CLI from fluid-core, or a person's judgment (consent basis, editorial
review, publishing pace). Set both workplans to status: blocked and flagged
FT-WP-0002-T01 needs_human on the hub, since it's the single blocker gating
everything else.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 278411@bnt-lap001
Assistant-Session: 19568045-15bc-4b88-ba3d-8e95b4f9c6f1
2026-09-27 23:15:37 +02:00

46 lines
1.2 KiB
Go

package adapter
import (
"bytes"
"context"
"encoding/json"
"net/http"
"time"
)
// HTTPTelemetry POSTs to the control plane, per docs/adapter-contract.md.
// It must never carry subscriber identity or post body text; callers control
// that by what they put in fields.
type HTTPTelemetry struct {
ControlURL string
hc *http.Client
}
// NewHTTPTelemetry builds a telemetry emitter posting to
// <controlURL>/control/v1/telemetry.
func NewHTTPTelemetry(controlURL string) *HTTPTelemetry {
return &HTTPTelemetry{ControlURL: controlURL, hc: &http.Client{Timeout: 10 * time.Second}}
}
func (t *HTTPTelemetry) Emit(ctx context.Context, event string, fields map[string]any) {
if t.ControlURL == "" {
return
}
payload := map[string]any{"event": event, "fields": fields}
body, err := json.Marshal(payload)
if err != nil {
return
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, t.ControlURL+"/control/v1/telemetry", bytes.NewReader(body))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/json")
// Best-effort: a dropped telemetry event must never fail the publication
// it describes.
resp, err := t.hc.Do(req)
if err != nil {
return
}
_ = resp.Body.Close()
}