Telemetry implementation
The main ACC owns the telemetry queue. Nodes have no client, keys or direct collector transport.
Both owner choices start off. The owner runs the reference collector at telemetry.rynhult.dev as a pre-release staging service
(Frankfurt, since 2026-10-26). The client is not yet qualified end to end against it.
Authenticated owner → Settings / preview / report review / deletion
↓
Local counters + closed wire validators → immutable queue bytes
↓ consent + schema + policy + test/CI gates
verified HTTPS, signed requests
↓
Separate owner-hosted collector → SQLite raw data → anonymous monthly totals
Client state and interfaces
src/acc/server/telemetry.py owns counters, redaction, key files, queue retention and the sender.
src/acc/telemetry_schema.py is the shared closed contract used by client and collector; its generated
schemas in schemas/telemetry/ are checked against the validators in tests. No telemetry SDK is used.
Migration 0044 adds telemetry_counter (UTC day/event/provider aggregate counts, seven-day retention)
and telemetry_item (exact canonical UTF-8 payload, kind, key id, review/send state, retries, receipt and
send time). Setting records consent with actor/time/schema versions, collector address, key metadata,
one-time notice state and an optional force-off policy adapter. Private Ed25519 bytes are in 0600 files
under data/secrets/telemetry/; they rotate after 90 days. Usage and reports, and different collectors,
use unrelated keys. Old keys remain only for deletion and expire after at most 397 days.
Owner-only API:
| Endpoint | Purpose |
|---|---|
GET /api/telemetry?offset=0 |
Consent, destination, 50 log/queue entries and total count |
PUT /api/telemetry |
Independent choices, collector URL, optional deletion on revocation |
GET /api/telemetry/preview |
Local exact next usage bytes and pending report bytes |
POST /api/telemetry/reports/{id} |
Send, discard, or delete the message while pending |
POST /api/telemetry/delete |
Queue signed erasure for every retained key at its collector |
Usage preview after opt-in and the minute-based supervisor sender use the same durable queue entry.
Report approval changes its state, not its bytes. The UI only allows removing the message before
approval. The local log retains successful payloads for 90 days after send, with paginated access.
Pending/failed entries expire after seven days. Audit events contain metadata, never payload content.
Collector changes record their old and new URLs in telemetry.collector audit events. Browser mutations
invalidate both state and payload-preview queries so a visible preview follows the new frozen queue.
telemetry.effective_collector tracks observed destinations; supervisor passes audit managed override
changes and removal as installation-configuration changes, including while sharing is off.
Migration 0045 discards all unsent pre-review reports and resolves their Inbox notices, without
modifying sent bytes or unrelated usage/deletion requests.
Counters bind fixed audit action names, selected successful route templates, scheduled launches,
team creation, guardrail events and actual backup attempts to schema enum members. They never retain
request bodies or filled-in paths. Inventory is bucketed and OS/provider/features are allowlisted.
Unimplemented product features are absent from the feature set. Checkouts report dev.
Captured report sources are unhandled server exceptions, isolated supervisor-stage exceptions, failed main updates, broker launch failures and abnormal agent-process exits observed during reconciliation. The crash path carries metadata only. This first implementation omits helper/log tails and does not invent broker restart or installer signals where ACC has no reliable recorded trigger. Those broader proposal sources require separate integration before being offered. Frames resolve inside the ACC package only; locals and arguments are never read.
The scrubber inventories current/retained vault values, infrastructure credentials and their retained values, TOTP and model endpoint keys, secret files (including JSON values), and known inventory names. It strips controls, normalizes URL encodings, removes authorization/cookie headers, credential assignments and textual base64 values, and redacts known values (including short credentials) and sensitive shapes before trimming, then scrubs again. Multiline free text is omitted: neither removing nor inserting whitespace at a wrap boundary can safely reconstruct arbitrary known secrets and names. Relative path text is also omitted rather than guessing where a spaced/punctuated folder name begins. Paths are removed conservatively through the end of the message, including spaces and punctuation in home-directory names; IPv6 zone identifiers are removed with the address. Failure to read/decrypt the inventory drops the report. Unknown names and unusual encodings remain a best-effort limitation; the owner must approve every report. No transcripts or terminal content enter this pipeline. No automatic-report mode is exposed.
Configuration and transport
- Default address: the reserved owner-hosted collector; not deployed yet. Owners can choose their own HTTPS URL.
ACC_TELEMETRY_URL: overrides the owner setting; empty disables outbound collection. Keep environment configuration outside agent-writable data. Changing the address discards pending payloads, never redirects them.ACC_TELEMETRY_USAGE=offandACC_TELEMETRY_REPORTS=off: administrator force-off controls.telemetry.policySetting: reserved integration adapter for force-off policy (usage/reports:off).- Presence of
ACC_TESTorCI: refuses every network send, including deletion. Pytest and browser harnesses setACC_TEST; network tests remove the flag only after injecting a local mock transport.
HTTPS certificate verification, no redirects, ten-second timeout, no environment proxies or cookies. Caps are 4 KiB usage and 16 KiB report. Non-successes use generic local outcomes and exponential backoff; 429 waits a day. A shared async lock serializes the sender and consent changes. Deletion is an explicit owner action, allowed while sharing is off, signed by each retained key for its original collector; only uncompressed, bounded string UUID receipts are accepted from responses. Requests advertise identity encoding and reject encoded receipts before invoking a decompressor.
Collector and qualification
SR10-4 narrows the shared report/1 wire contract: build and log are not
accepted fields, including placeholder hashes and empty log lists. Client captures
omit them and the collector rejects them as extra properties. Previously queued
payloads containing either field fail client validation and are discarded without
sending. The legacy remove_log review flag remains accepted as a no-op; no log
field is added to a report. Session crashes remain metadata-only. Privacy notice
0.4 is unchanged; operators must deploy matching client and collector contracts.
deploy/telemetry/ contains the ASGI factory and operator instructions; reusable code is
src/acc/telemetry_collector.py. It validates canonical payloads and Ed25519 signatures, binds signatures
to endpoint and timestamp, separates key kinds, enforces memory-only address limits and per-key limits,
deduplicates retries, applies retention hourly and on writes, and deletes raw data immediately on request.
Storage must be a private, service-owned directory and regular database file; symlinks and hard-linked
database files are refused. Only DELETE journal mode is supported, because WAL can retain earlier
payload pages after a successful SQL deletion. Idle retention logs a generic SQLite failure and retries
after 60 seconds; successful maintenance resumes the hourly interval.
The factory defaults to a private data subdirectory, but refuses to ignore an existing legacy
./telemetry.sqlite when no explicit ACC_COLLECTOR_DB is set. Secure/move that file and configure
its path rather than silently abandoning its retention/deletion state.
No report-reading/admin UI or public statistics endpoint exists. Proxy logging, backup erasure, operator
second-factor sign-in, disk alerts and the final controller contact must be verified before deployment.
See docs/TELEMETRY-COLLECTOR.md for the explicit VM installation, systemd/Caddy trust boundary,
resource limits, upgrades, backup/restore, erasure and incident procedures. The installer never starts
services. Caddy is the sole public listener and reaches a protected Unix socket; it replaces forwarding
headers and discards request-related logs. Authenticated keys have an independent memory-only hourly
limit across IPs/deletions. Monthly counts finalize after late arrivals close and stay immutable; the
local export omits cells below ten with no grand totals. No service was deployed or started by this task.
The collector re-scrubs report text by shape (scrub_text, mirroring the client rules) before storage,
refuses new data beyond ACC_COLLECTOR_MAX_BYTES, buckets IPv6 addresses per /64, and keeps a 45-day
erasure ledger (key and report ids only) that telemetry_collector_ops erasures/restore replay into
restored backups. Dependencies install hash-locked as wheels.
The tests use in-process collectors, injected transports and a real local worker over a Unix socket; they do not qualify a live HTTPS service or its proxy/backups.
Resource ownership
The SQLAlchemy flush listener holds a weak reference to the telemetry client. Replacing an application
or session factory therefore releases the old client and SQLite pool, including database, WAL and
shared-memory descriptors. Send requests and responses, key files and collector connections use scoped
contexts. The Linux regression repeats preview, scrubbing and signed sends to the reference collector
across client lifetimes and checks that /proc/self/fd stays flat after collection.
The merged migration chain is 0042 → 0043 (DNS ownership) → 0044 (telemetry) → 0045
(privacy cleanup), with one head. Telemetry revisions were renumbered before integration with main.
This page is generated from docs/wiki/Internal/Telemetry.md; the same page is under Guide inside Rynhult.