The consent file¶
The consent module persists a user's decision about collection so the
answer survives between runs. It is a small, deliberately boring format.
On-disk format¶
| Key | Type | Required | Meaning |
|---|---|---|---|
version |
integer | yes | Schema version. Must be exactly 1. |
state |
"enabled" | "disabled" | "unset" |
yes | The recorded decision. |
decided_at |
string | no | RFC 3339 UTC timestamp of the decision. Omitted when absent. |
decided_at is written with the sub-second precision the clock reports, as
above — not truncated to whole seconds. It is stored as a plain string
rather than a parsed timestamp so that a future schema version can carry a
richer format without breaking deserialisation of version 1.
Where the file lives¶
This crate does not choose the path. Every function takes a &Path
from the caller. The convention across the toolkit is
<config_dir>/<tool>/consent.toml, and the CLI runtime
(rtb-cli) resolves <config_dir> from
directories::ProjectDirs and owns the telemetry status / enable /
disable / reset commands that write it. A tool not built on rtb-cli picks
its own path and gets to keep both halves.
ConsentState and how it maps to a policy¶
ConsentState |
CollectionPolicy |
|---|---|
Enabled |
Enabled |
Disabled |
Disabled |
Unset (the Default) |
Disabled |
Unset and Disabled both mean "do not collect". They are distinct
because a tool usually wants to prompt someone who has never been asked
and never prompt someone who has said no.
The conversion is a From impl, so CollectionPolicy::from(state) or
state.into() both work.
Constructors¶
| Constructor | State | decided_at |
|---|---|---|
Consent::unset() |
Unset |
None |
Consent::enabled_now() |
Enabled |
current UTC time |
Consent::disabled_now() |
Disabled |
current UTC time |
Consent::SCHEMA_VERSION is the constant 1, and every constructor stamps
it.
Reading, writing and deleting¶
consent::read(path) -> Result<Option<Consent>, TelemetryError>
consent::write(path, &consent) -> Result<(), TelemetryError>
consent::reset(path) -> Result<(), TelemetryError>
read returns Ok(None) when the file does not exist — a missing file
is not an error, and callers treat it as Unset. It returns
TelemetryError::Serde for malformed TOML and for a version other than
1:
That is a hard refusal, not a downgrade: a file written by a future version
of the format will stop the tool reading consent rather than being
guessed at. Any other filesystem failure — a permissions problem, say —
comes back as TelemetryError::Io.
write creates parent directories on demand and serialises with
toml::to_string_pretty. It is not atomic: it writes in place, so a
process killed mid-write can leave a truncated file, which the next read
will reject as malformed. If that matters, write to a temporary file
yourself and rename it over the top.
reset deletes the file and returns Ok(()) if it was already absent,
so telemetry reset is idempotent. It deletes the decision, not any
collected data — see
What this does not do.
Reading consent at startup¶
let policy = match consent::read(&path)? {
Some(record) => CollectionPolicy::from(record.state),
None => CollectionPolicy::Disabled,
};
That is the whole integration. Nothing in the crate reads the file for you, and nothing watches it for changes: a context built from a policy keeps that policy until it is rebuilt.