Echo/Docs

CLI command

privacy

What Echo keeps about people and for how long: consent at the collectors, a declared retention period per personal store, erasing a person from every store on request, and exporting every row Echo holds about them.

Invocation#

Shell
echo-cli privacy retention [--store <table>]
echo-cli privacy keep --store <table> --days <n> --by <name>
echo-cli privacy forget --store <table>
echo-cli privacy run --by <name>
echo-cli privacy erase [--user <id>] [--anonymous <id>] [--email <address>] --by <name>
echo-cli privacy erasures
echo-cli privacy export [--user <id>] [--anonymous <id>] [--email <address>]

Inputs and output#

retention writes { stores, policies, runs }: every personal store with its table, time (the column retention cuts on) and the columns that identify a person (ids, uuids, emails, hashes, jsonEmails); every declared period (store, days, set_by, set_at); and the retention runs, newest first, or only --store's (id, store, cutoff, deleted, error, ran_by, ran_at). keep writes the period as stored and forget the one it removed. run writes one run per declared store. erase writes the erasure: id, subject_sha256, identifiers (how many were found), report (each store with deleted, remaining and error), requested_by, erased_at and error. erasures writes every erasure, newest first.

State effect#

Every verb calls Echo Web's authenticated /api/privacy/retention, /api/privacy/retention/run or /api/privacy/erasures with ECHO_INTERNAL_BEARER at ECHO_WEB_BASE_URL; the Privacy section of the tracking screen and of Echo Desktop's GEO screen calls the same operations, and echo-mcp reads retention (echo_privacy_retention) and erasures (echo_list_erasures) but changes neither. The collectors POST /api/analytics/web/collect and /api/analytics/mobile/collect store nothing for an event whose request carries Global Privacy Control (Sec-GPC: 1) or whose body carries consent: false; they answer ok with webEventRecorded (or mobileEventRecorded) false and consentWithheld naming which of the two withheld it, and write no event, attribution or identity row. A store keeps its rows until a period is declared for it; the daily /api/cron/privacy-retention run, and privacy run, delete every declared store's rows written before that many days ago by its time column and record the cutoff, the count and the error or none. erase widens the user id, anonymous id and email it is given to every identifier the identity edges (matched also by the email's sha256) and the events carrying both a user id and an anonymous id join with, until no more join; deletes every row of every personal store that names any of them; and reads each store again. The erasure is recorded with the sha256 of what was named and never the identifiers; a store that still holds a row, or failed, makes the erasure the error, and the incomplete erasure stays recorded.

Refusals#

  • A wrong invocation is refused as a usage error before anything is sent: "privacy takes <every usage, separated by semicolons>; got <word>", "privacy <verb> does not take <flag>; it takes …", "<flag> needs a value", "privacy <verb> needs <flag>" for each flag the verb needs, and "--days must be a whole number of days; got <text>".
  • Echo Web answers a bad request with "store must be one of <every personal store>; got <value>", "days must be a positive whole number of days to keep <store>'s rows; got <value>", "user_id, anonymous_id or email is required: who to erase" (export: "… who to export"), "<field>, when sent, must be non-blank text" or "<field> is required: an internal caller has no signed-in user, so it names who is acting"; not found with "<store> has no retention policy; echo-cli privacy retention shows the declared ones". An erasure a store did not complete fails with "the erasure <id> is incomplete: <store>: <rows> rows still name the person" or "<store>: deleting by <column> failed: <cause>".
  • export calls /api/privacy/exports (the Privacy section of the tracking screen and of Echo Desktop's GEO screen do the same) and writes { identifiers, email, exported_at, stores }: the identifiers widened as erase widens them, and one entry per store with store, erased_on_request (true for every personal store erase empties; false for email_consents and contact_suppressions, which erase keeps so mail never reaches the person again, read by the address), rows (every row naming the person, once) and error (a read that failed, the other stores still answered). Nothing is changed or stored.