---
schema_version: 1
id: RAC-KTYPAAAN9Q20
type: roadmap
---
# RAC v0.10.6 — Anonymous Usage Sharing

## Status

Planned

## Context

v0.10.4 gave Guide opt-in local telemetry, with hand-submitted GitHub
issue reports as the only remote channel. That works while every user
can be reached individually; past roughly thirty teams it cannot
measure retention, and the product questions shift from "is Guide
used" to "do installs come back".

ADR-041 settles the phase-2 shape: a consented, anonymous, pinned
daily ping to PostHog — a random install id, the version, and an
active-repo count, never repository contents, paths, queries, or
anything identifying. It deliberately amends ADR-040's "no network
code" clause while keeping its spirit: the network surface of RAC is
one readable module, nothing sends without recorded consent, and an
empty endpoint key keeps every build inert.

This milestone implements that decision: a consent record, the ping
sender, an honest one-line consent prompt in `rac init` (the CLI's
first interactive prompt), an explicit `rac telemetry` command, and a
rewrite of every place the docs currently say nothing leaves the
machine.

## Outcomes

- `rac init` asks once — "Share anonymous usage to help shape Lore?
  [y/N]" — only on a real TTY, defaulting No, never blocking CI; the
  answer persists either way so the question is asked at most once.
- `rac telemetry on|off|status` flips and inspects consent at any
  time, showing exactly what is shared.
- With consent on and an endpoint key configured, `rac mcp` sends at
  most one ping per 24 hours carrying the install id, version, and
  active-repo count — nothing else; opt-out or an empty key means
  silence.
- Tool responses are byte-identical with sharing on and off; the
  existing Guide batteries pass unchanged.

## Initiatives

### Initiative 1 — Consent Record (ADR-041)

A stdlib-only consent module outside the server layer: a frozen
record under the XDG config directory holding the sharing choice, a
random install id minted at opt-in, a local-only salt for repo
digests, and the consent timestamp. Corruption-tolerant by the
Explorer preferences posture; opt-in preserves an existing install id
across off-and-on toggles so the retention curve stays continuous.

### Initiative 2 — Ping Sender

A stdlib-only sender in the server layer — the only module in RAC
permitted to import a network client, enforced by the isolation
battery. A daemon thread started by `rac mcp` when consent is on:
at most one attempt per 24 hours tracked by a local marker, a short
socket timeout, every failure swallowed, no retries, and the pinned
payload as the entire transmission. Active repos are counted from a
local file of salted path digests pruned to a thirty-day window; only
the count is sent.

### Initiative 3 — Consent Surfaces

The one-time `rac init` prompt (TTY-gated, default No, skipped with
`--json` and whenever a record exists) and the `rac telemetry`
command (`on`, `off`, `status`, defaulting to `status`). Both speak
plainly about what is and is not sent, and `status` names the
empty-key state explicitly.

### Initiative 4 — Honesty Surfaces

Every place that currently says nothing leaves the machine is
rewritten: the README trust bullet, the MCP guide's telemetry
section, the CLI reference, the changelog, and a one-line amendment
note in ADR-040 itself.

### Initiative 5 — Contract Battery

A ping battery pins the payload field-for-field, the consent
round-trip, the 24-hour dedupe, the swallowed-failure no-retry
posture, the salted-digest privacy of the active-repo file, and the
TTY gating of the init prompt. A new isolation rule pins the
strongest claim: network client modules are importable only by the
ping module.

## Constraints

- The payload is pinned field-for-field; adding a field is a new
  recorded decision, not a patch (ADR-041).
- The local salt and raw repository paths are never transmitted; only
  the active-repo count crosses the wire.
- The ping never blocks, delays, or alters a tool call; tool
  responses stay byte-identical (ADR-032 untouched).
- `urllib.request` is importable only by the ping module, enforced by
  the isolation battery.
- Sharing is independent of the local `--telemetry` flag; each is its
  own opt-in.
- Consent defaults to No everywhere: empty answers, EOF, non-TTY, and
  corrupted consent records all mean no sharing.

## Non-Goals

- Opt-out-by-default telemetry, or any prompt that nags twice
- A second event type, per-tool remote metrics, or latency reporting
- The PostHog SDK as a dependency; the POST is plain stdlib
- Retries, offline queueing, or batching of failed pings
- Transmitting any part of the local telemetry log

## Implementation Contract

The following decisions are pinned for v0.10.6.

### Module Locations

- Consent record and PostHog constants live in `src/rac/consent.py`,
  outside the server layer, so `rac init` and `rac telemetry` never
  import the MCP SDK.
- The sender lives in `src/rac/mcp/ping.py` — the only module in RAC
  that imports `urllib.request`.
- The server hook is one call in `run_server`; `build_server` is
  untouched.

### Ping Payload

The entire transmission, one POST per 24 hours at most:
`api_key` (public project write key), `event` (`lore-daily-ping`),
`timestamp` (ISO 8601 UTC), and `properties` containing `distinct_id`
(random install id), `$process_person_profile` (`false`, so PostHog
creates no person profile), `schema_version` (`"1"`), `rac_version`,
and `active_repos` (int) — PostHog's documented capture shape.

### State and Config Paths

- Consent: `$XDG_CONFIG_HOME/rac/telemetry.json`
- Last ping marker: `$XDG_STATE_HOME/rac/last-ping`
- Active repos: `$XDG_STATE_HOME/rac/active-repos.json` (salted
  digests and dates only)

### CLI and Exit Codes

- `rac telemetry [on | off | status]`: exit 0 for every valid action,
  including when no endpoint key is configured; invalid actions are
  argparse usage errors (exit 2).
- The init prompt never changes `rac init` exit codes or `--json`
  output.

## Success Measures

- A consenting install produces exactly one ping per day whose body
  matches the pinned payload byte-for-byte against a captured POST.
- Declining at `rac init` writes a record and the question is never
  asked again on that machine.
- `rac telemetry status` answers "what is shared, where, and is it
  currently possible" without reading source code.
- The isolation battery fails any change that adds a network import
  outside the ping module.

## Risks

- A quiet consent prompt yields few opt-ins. Accepted: the honest
  prompt is the point; launch messaging can ask, the tool never nags.
- PostHog as a third-party processor draws scrutiny. Mitigated: the
  payload is pinned and content-free, the ADR records the trade-off,
  and the sink is swappable behind one constant.
- A future field addition erodes the pinned payload. Mitigated: the
  battery asserts exact key sets at both payload levels; additions
  require a new ADR.

## Assumptions

- PostHog Cloud's free tier comfortably covers daily pings at any
  plausible install count.
- The public project write key is safe to embed (write-only by
  design); the empty-key kill switch covers builds before the
  project exists.

## Related Decisions

- ADR-041
- ADR-040
- ADR-032
- ADR-035
- ADR-027

## Related Designs

- guide-tool-surface

## Related Requirements

- rac-agent-context-guide
- rac-trust-transparency

## Dependencies

- v0.10.4 local telemetry (consent vocabulary and trust posture)
