Table of Contents
- Decisions not to relitigate
- Nothing expires. There is no retention timer.
- Suppression is a record, not a hole.
- Scope is deny-by-default, and an absent setting means deny.
- Structural refusals come before content inspection.
- The gate runs the pipeline before the write, not before the upload.
- The index is not encrypted, and that is on purpose.
- The browser extension uses native messaging, not a local port.
- Collectors learn their grant from the gate.
- Heartbeats are on a clock, not per record.
- The verbosity ladder climbs on cards shown, never on time.
- SQLCipher was rejected, and full-file encryption with it.
- Not built, and named so it is not mistaken for done
Decisions not to relitigate
Each of these was decided once, for a reason that has not changed. If you are about to propose one of them, read the reason first — and if the reason is now wrong, say why it is wrong rather than re-proposing the original idea.
Nothing expires. There is no retention timer.
This data is training data as well as verification data, so it is kept until somebody deletes it. Not thirty days, not ninety, not "configurable with a default".
Every place that used to assert a number now says -1 / "until you delete it": the registry,
all seven manifests, and the console's Account tab. contracts-lint fails the build if the
registry and a manifest disagree, and here-contract has tests asserting retention_days == -1
and that every signal declares dpv:ResearchAndDevelopment.
That last part is what makes keeping it defensible rather than merely convenient: the training purpose is declared at collection time, in the same manifest the person is shown before they grant the signal. Retro-fitting a purpose onto data you already hold is the thing that is not defensible.
Suppression is a record, not a hole.
A denied app does not vanish from the timeline. It produces a row with gate_state = suppressed and timing only. Deleting the row entirely would be more private and much worse,
because then nobody — including the person — can tell the difference between "nothing happened"
and "something happened and was withheld".
Scope is deny-by-default, and an absent setting means deny.
Both modes exist, but the default denies, deny rules win in both modes, and a store with no scope setting at all denies rather than allowing. That last detail is deliberate: it means an old store cannot widen its own scope by being upgraded.
Structural refusals come before content inspection.
The reliable way not to capture a password is not to read the field. Detection (L2) exists because structure is not always enough, not as the primary defence. A design that reads everything and filters afterwards has already put the secret in a process's memory.
The gate runs the pipeline before the write, not before the upload.
A secret scrubbed on the way out has already been written to a file that a backup client, an EDR agent, or another process running as that user can read. "Encrypted at rest" does not undo it either, since anything that can read the key can read the file.
The index is not encrypted, and that is on purpose.
Payloads and blobs are AES-256-GCM. Timestamps, event types and app ids are plaintext, so the person can be shown what exists without the process decrypting anything, and so a deleted row is visibly deleted from outside the process. Every document says this in the same words. Do not "fix" it by encrypting the index without also solving how the person is shown their own data.
The browser extension uses native messaging, not a local port.
Collectors have no network path; a localhost socket would have meant opening one, which is exactly the capability the design removes. The extension has no content scripts and no host permissions, so it cannot read pages even if it wanted to, and the collector refuses anything URL-shaped rather than trusting it to send only hosts.
Collectors learn their grant from the gate.
They do not read policy themselves. They send a heartbeat and read the grant out of the reply, which is why the screenshot collector takes no pixels at all until screenshots are on. Capturing and letting the gate discard the result would mean the pixels existed. Every collector must read that reply or the pipe desynchronises.
Heartbeats are on a clock, not per record.
An idle machine produces no records, so an event-driven heartbeat made a live collector look
dead. The staleness window is 45 seconds because input.metrics works in 15-second windows.
The verbosity ladder climbs on cards shown, never on time.
10, then 30, then 60 cards. Sitting idle for a week does not earn silence, because the ladder is about how much you have been told, not how long the software has been installed. A newly seen app always cards, at every rung. An explicit "quiet this" beats the ladder; a new app beats the quiet.
SQLCipher was rejected, and full-file encryption with it.
No nasm on the reference machine, so a vendored-OpenSSL build would have broken cargo test
as a one-command story. Per-field AES-256-GCM gets the property that matters (content is not
readable on disk) without making the build a prerequisite conversation.
Not built, and named so it is not mistaken for done
- Code signing. Needs an EV certificate on Windows and a Developer ID on macOS. Until then
SmartScreen and Gatekeeper will both object, and
modules.signature_okrecordsfalserather than pretending. This is also what blocks Keychain key storage on macOS: an item's access list is per-binary, this client is several processes, and a second unsigned binary reading an item the first created blocks on a modal dialog. - An installer.
- Operator identity. Devices authenticate; people do not yet. The
administerscope is enforced but nothing issues it. - A durable admin store. The dev server is in-memory and is a stand-in for the platform.
- Everything server-side. See For the platform team.
here-telemetry
How it works
Working on it
Taking it further
In the repo: README.md for the
invariants, HANDOFF.md for
current state.