# Culprit data map

Strong template, not certified legal advice. Review before relying.

Operator: Noumenon, operating from Florida, United States.
Controller: Noumenon. Contact: noumenon-ai@gmail.com.

## On the computer

- Collector snapshots contain app and process resource readings, hardware readings,
  listening ports, project paths, branch names and masked commands. They are not uploaded.
- SQLite history retains 30 days of aggregated app and system readings locally.
  History continues after the trial; paid access gates views, alerts, dev servers and export.
- Window preferences store the dismissed welcome, advanced mode, last screen,
  window dimensions and maximized state locally. App/process search stays in
  memory and is not sent to the license service.
- Local alert rules (`<data>/rules.json`, 0600) and exports stay on disk. Export
  files have no automatic deletion. Quiet-app overrides are stored in the same rules file.
- `<data>/rules-defaults-version` (0600) holds the default rule migration version
  (currently 2). It prevents deleted defaults from being added again on later starts.
- `<data>/engine.lock` (0600) is an empty, persistent coordination file. Its OS lock
  exists only while the host or CLI daemon runs; it contains no metrics or identity.
- Remove history closes SQLite and deletes `history.sqlite`, `history.sqlite-wal`,
  `history.sqlite-shm` and any rollback journal, then creates an empty database.
  License state, rules and the default-rule marker remain. Host CSV exports use
  an explicitly supplied path, create a new private file and never overwrite one.
- Private license.json contains trial state, salted identity material, key and verification
  timestamps. A separate private trial.json merges trial age across restarts. The raw
  machine ID is read locally only to compute a SHA-256 identity using a per-install salt.
- Removing the app does not itself promise erasure of saved files. The user controls
  local data and backups. No telemetry or analytics pipeline exists.

## Network flows and recipients

- Flow: Activation and weekly verification, with failure retries.
  Data: key, salted machine hash, app_version; app user agent.
  Recipient and purpose: Cloudflare license API, contract and abuse prevention.
  Retention: License records have no automatic expiry.

- Flow: Requested deactivation.
  Data: key and salted machine hash.
  Recipient and purpose: Cloudflare license API, free a device slot.
  Retention: Rolling 365-day removal timestamps.

- The desktop app has no automatic update check. The existing server update
  endpoint is not called by the window or agent.

- Flow: Public installer download.
  Data: exact artifact name; no key required.
  Recipient and purpose: Cloudflare R2 through the Worker.
  Retention: Release files until replaced or removed by owner.

- Flow: Website and local font delivery.
  Data: path, IP address, ordinary HTTP metadata.
  Recipient and purpose: Vercel hosting.
  Retention: Vercel's own schedule for request metadata; no additional logs are kept by us.

- Flow: Pricing.
  Data: no user payload; IP reaches edge.
  Recipient and purpose: Cloudflare; server requests Stripe launch-link count.
  Retention: Pricing cache 60 seconds.

- Flow: Purchase.
  Data: email, billing and payment details in hosted checkout.
  Recipient and purpose: Stripe, hosted checkout, payment processing and fraud checks. Noumenon is the seller (standard Stripe Payments on the Noumenon account).
  Retention: held by Stripe and by us for as long as tax, accounting and fraud-prevention law requires.

- Flow: Claim after checkout.
  Data: checkout session ID.
  Recipient and purpose: Cloudflare, verify paid Culprit purchase with Stripe and return key.
  Retention: Session-to-key index, license record.

- Flow: Support.
  Data: information the user chooses to send.
  Recipient and purpose: noumenon-ai@gmail.com, resolve requests.
  Retention: until the request is resolved; deleted on request unless the law requires keeping it.


Every connection reveals its source IP to the hosting provider. The app does not
send metrics, history, raw machine ID, hostnames or usernames. Activation and
deactivation are user actions in addition to weekly license verification.
Downloading a replacement installer is a user action in the browser.

## Server storage

Cloudflare Durable Objects hold authoritative license records; KV mirrors them.
A record contains session ID, email supplied by Stripe, key, creation time, personal
plan, revocation status, activations (salted hash and last-seen time), and removal
timestamps. Session indexes and permanent revocation tombstones prevent refund replay.
Device hashes remain until removed or replaced. Inactive slots may be replaced
only after more than 90 days. There is currently no automatic license-record erasure.
License records are kept for as long as the license can be used. Erasure is handled on request by the owner through noumenon-ai@gmail.com, after review, because deletion affects activation and fraud prevention.

Per-route rate limits store SHA-256 IP identities (IPv6 grouped by /64) and counters
for ten-minute windows, removed by expiry alarms. Key limits use hashed normalized
keys. Pricing stores counts and availability, not customer identities. Application
code logs no keys, emails, bodies or request URLs. Worker observability is disabled, so no
request logs are stored by us; Cloudflare keeps platform metadata under its own schedule.

The thanks page keeps the claimed key only in memory, clears it on pagehide and
reclaims it on a restored page. It uses no cookies or browser storage. The session
ID is a bearer credential that can retrieve the key, so the page link must stay
private. Support asks for the Stripe receipt number or purchase email instead.
The claim response contains the key but no email. The session ID may still appear in browser history and hosting logs;
no-referrer, no-store and noindex reduce secondary disclosure, not original receipt.

## Legal basis, rights and owner completion

Contract covers purchasing, licensing and support. Legitimate interests cover rate
limits and fraud prevention. Legal obligations cover required payment records.
No advertising sale or sharing, no tracking cookies and no analytics are used.
Stripe has its own hosted checkout privacy practices. We never store full card or
bank details.

Access, correction, deletion, portability, objection and restriction requests go to
noumenon-ai@gmail.com. Verify identity proportionately without requesting a full card or
license key. Explain any legally required retention and impact on licensing. Users
can withdraw consent where relevant and complain to a supervisory authority.

Before publication, fill entity/contact, retention policies, provider log settings,
[INTERNATIONAL_TRANSFER_SAFEGUARDS], and confirm Stripe merchant-of-record approval.
Providers may process data in the United States and other countries; confirm the
applicable transfer contracts and safeguards. Public legal pages must remain in
sync with this map. Culprit is not directed to children under 13.

## Agent core files and local IPC (Phase 2d-2)

- `<config>/settings.json` (0600, atomically replaced) holds popup preferences,
  pause time, first-run and notice markers, the last license notice state and the
  last disk-full popup time. It contains no license key. Invalid files are kept
  as `settings.bad-<random>` before defaults are saved; future versions are
  preserved and used only through in-memory defaults. Missing or unwritable local
  files never prevent monitoring from starting. Start-at-login state belongs to
  the OS entry in Task 33.
- `<data>/alerts.jsonl` (0600) records alerts and popup outcomes, including app
  names and alert subjects. Queries retain the newest 500 entries within 30 days;
  the file is compacted atomically after 600 lines. Startup reads it without
  writing; read failures use an empty in-memory log. Nothing is uploaded.
- `<data>/agent.log` (0600) holds only diagnostic categories, peer uids and
  durations. At 1 MiB it rotates to `agent.log.1`. No app names, commands, home
  paths or license keys enter this log.
- `$XDG_RUNTIME_DIR/culprit/agent.sock` (0600 inside a 0700 directory) is a local
  pathname socket. Both endpoints check the peer uid. An interrupted or timed-out
  client stream requires reconnection; request deadlines are 20 seconds. Snapshot/history data and
  requested license activation keys cross only this same-user channel; keys are
  never returned. The agent removes its socket on exit.
- Action tokens exist only in agent memory: 128 random bits, at most 64 tokens,
  expiring after 24 hours. Close and stop tokens are single use and open a
  confirmation; they do not themselves close apps.
- Requested CSV files use `culprit-history-<window>-<date>[-<number>].csv` in the
  configured Downloads folder, with Downloads/home fallbacks. Files are 0600,
  created exclusively and never overwrite a prior export. They remain until
  the user removes them.
- Atomic settings and alert-log writes use private sibling `.tmp-<random>` files
  during replacement. Notification delivery, tray integration, autostart and
  desktop integration files are not installed by this phase.

## Linux desktop integration (Phase 2d-3)

- `$XDG_CONFIG_HOME/autostart/culprit-agent.desktop` (else
  `~/.config/autostart/culprit-agent.desktop`, 0600) holds the absolute agent
  launch path, or AppImage path, and sign-in arguments. `settings.json` records
  that default autostart initialization has happened. Turning it off writes
  `Hidden=true`, preserving the choice even if settings are lost.
  `X-GNOME-Autostart-enabled=false` also counts as disabled. Active entries are
  refreshed after moves or AppImage updates without switching installation types;
  missing entries are not recreated after initialization. Paths containing `%`
  cannot enable start at login or install apps-list integration.
- AppImage integration creates `$XDG_DATA_HOME/applications/com.noumenonai.culprit.desktop`
  and `$XDG_DATA_HOME/icons/hicolor/128x128/apps/com.noumenonai.culprit.png` (else under
  `~/.local/share`). Files are 0600. Shared XDG parent folders keep existing
  permissions, allow parent symlinks, and use normal user permissions when created.
  Atomic writes refuse symlinks at the final filename. Integration is skipped when
  a system `com.noumenonai.culprit.desktop` exists outside the AppImage mount. Removing the
  integration removes these two files and the autostart entry, then flushes and
  stops the agent. History is retained unless separately removed through Settings.
- Popup text and random action tokens cross the local session bus to the desktop
  notification service. The agent keeps notification ids and one-use activation
  tokens in memory, clears ids on service restart, and uses the same explicit
  zbus connection for notifications, the ksni tray and GNOME extension queries.
  Slow replies remain tracked: late notification ids support actions and withdrawal.
  Failed capability discovery retries on show attempts at most every 30 seconds.
  Notification dismissal leaves alert cooldowns intact. The desktop may retain
  popup text in its notification history according to its own settings.
- The tray exports a rounded CPU number, active alert title and local menu actions
  over that bus. Extension state is read only when requested; enabling Ubuntu's
  AppIndicators extension requires the explicit button request. No Shell eval or
  `org.gnome.Shell` call is used.
- Window launches use the sibling executable or the AppImage, a random action
  token as an argument, and any activation token in `XDG_ACTIVATION_TOKEN` and
  `DESKTOP_STARTUP_ID`. App names, pids and ports are never launch arguments.
  The agent clears inherited activation variables before starting worker threads.
- Executable identity (device, inode, size and modification time) is held only in
  memory and checked every 60 seconds. Replacement flushes history and re-execs;
  two consecutive missing-file checks flush and stop. No update service is called.
- The pinned ksni 0.3.6 source in `vendor/ksni` has a documented connection-injection
  patch, so it does not open an additional or ambient session connection in Culprit.
  Tests use only private dbus-daemon instances, generated allow-all EXTERNAL-auth
  configs without service directories, and injected temporary filesystem paths.


## Native Linux window and packages

- The window uses the same Linux data/config roots as the agent. It receives
  snapshots, history, license status (never the key), settings, tray status and
  action tokens over the same-user socket. One reader dispatches replies and
  events without cancelling a partially read frame. Queues and waits are bounded.
- The native GTK window receives snapshots and local navigation over the same-user
  IPC connection. Closing sends WindowClosing, stops the stream and exits the
  window process. The background agent continues until Quit or removal.
- Window preferences (advanced tools, welcome dismissal, dimensions, maximized
  state and last page) are stored in `$XDG_CONFIG_HOME/culprit/window-gtk.json`
  or the corresponding `.config` path. The GTK window uses no webview and creates
  no WebKit browser profile. Older explicitly selected Tauri builds may retain
  their previous browser caches; changing to GTK does not erase those files.
- The license-entry field is password-masked. Its value is not stored in window
  preferences or logs. Failed activation retains the entry for correction;
  successful activation clears it.
- Installed deb/rpm files: `/usr/bin/culprit-app`, `/usr/bin/culprit-agent`,
  `/usr/share/applications/com.noumenonai.culprit.desktop`, and hicolor `com.noumenonai.culprit` icons.
  Package removal removes these, not the user's history or autostart file.
- Desktop build outputs live under `target/release-artifacts/<version>/linux-gtk/`:
  deb, rpm, AppImage and SHA256SUMS. Intermediate Docker targets, registry and
  bundler caches live under `target/linux-build-cache/<uid>/`.
  `CULPRIT_BUILD_CACHE` overrides the cache root (the uid is appended).
  `<uid>/nss/` holds generated copies of the image's `/etc/passwd` and
  `/etc/group` with one added build-uid entry; these contain no user data.
  Sidecar staging under
  `app/src-tauri/binaries/` is ignored by git and contains no user data.
- Measurement runs create private isolated data/config/runtime/cache/state roots
  under `target/measurements/desktop-*`. JSON reports contain pid identities,
  memory, CPU and write counters, GPU runtime state, tray registration and CLI
  benchmark output. They are local files, never uploaded by these scripts.
- Windows development is on hold. Planned Windows layout: data in `%LOCALAPPDATA%\com.noumenonai.culprit`,
  config in `%APPDATA%\com.noumenonai.culprit`. The per-user
  `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` value `Culprit`
  contains the quoted agent path and `--from-autostart`. StartupApproved is
  respected. The installer removes the Run value; its explicit data-deletion
  checkbox removes both identifier folders. These Windows operations are not
  implemented or validated by Phase 2d-4.

## Fan control, Phase F2

- `<config>/fans.json` (0600, parent 0700) stores schema version 1, the fan
  choice, an off-by-default restart preference, DMI maker/board/product strings,
  and acknowledgements with choice, Culprit profile id/version and time. Writes
  use a private temporary file, fsync and atomic replacement. Unknown versions
  remain untouched; a different or unidentified machine starts on Automatic.
  Removing Culprit leaves this local preference file, like other settings.
- Read-only snapshots may contain fan channel ids and observations of vendor fan
  settings. The agent reports the power-saver entry's platform driver, offered
  choices, current hold and fallback reason. No new hardware values are invented.
- SQLite `sys_min` records `fan_setting` (0 Automatic, 1 Uses less power/Quieter,
  2 Maximum cooling, 3 curve), with existing retention and rollups. Mixed buckets
  keep their maximum, so a forced-cooling period is excluded from the natural
  fan peak. Database schema version remains 2. The live hour of fan attribution
  and return to Automatic remain free.
- Power-profile cookies, session identity and sleep inhibitor handles exist only
  in agent memory. Local D-Bus calls use the existing power-profiles and logind
  interfaces, with no added permissions, helper, network request or popup.
  Closing the hold connection gives control back even if a release call fails.


## Optional Linux fan add-on, Phase F3

The separately installed add-on owns `/var/lib/culprit-fand/` (root, 0700).
Its files are 0600 and never leave this computer:

- `journal.json`: version, boot id, compiled profile id, hardware identity and
  each changed control's original and written values (journal version 2). File and directory are synced before
  any control write. `dirty` records that recovery is required. Only a verified
  restore clears the marker; a damaged journal uses documented defaults and
  leaves controls without a known default alone. A valid journal for another
  identity retains unresolved originals under that identity until its matching
  driver returns; transient read errors never discard them.
- `writes.log`: the newest 50 attempted changes, with monotonic time, control,
  old/new values and reason. No uid, session, user name, command line or license
  enters this file. Active local users can view these through Status and the
  advanced Fan control card. Atomic replacement uses a sibling `.new` file.
- `safety.json`: boot id, recent heat/anomaly times, cooldown and latch. These
  survive an idle helper exit or crash within the same boot; no lease survives
  a helper restart. `unhealthy` stores the boot id after a write/read-back fault
  so restarting the helper cannot clear that block.
- `lock`: the empty process coordination file. Its OS lock serializes the
  helper and one-shot recovery. `purge-safe`: the package removal script's
  successful-restore receipt. Purge refuses deletion while recovery is pending.
- `/run/culprit-fand/fand.sock` (0666 in a 0755 directory) accepts only the
  bounded intent protocol. Kernel peer credentials, live local-session checks
  and polkit protect operations; its mode does not authorize a control write.
  Peer pid/start time and lease uid/session exist only in memory. Safe Status
  and Release do not require the control polkit permission or a license.
- Local system-bus calls check polkit and logind and hold a sleep delay
  inhibitor. A watchdog, stop hook and boot restore service provide recovery.
  The helper has no Internet connection or HTTP/TLS dependency.
- Package files are a helper in `/usr/libexec/culprit/`, three systemd units,
  the polkit action, copyright notice and a udev rule that requests restoration
  when the driver appears. Static sandbox grants cover exact MSI controls and
  only the stable hp-wmi hwmon directory; there is no generator. No module,
  driver, outside profile or user configuration is installed by the add-on.
- Removal restores before removing the helper. deb purge removes state only
  after successful restoration; ordinary deb/rpm removal retains the state
  folder. Main-package removal is covered by lease expiry. The per-user
  `fans.json` file remains the agent's preference and acknowledgement record;
  the root helper never opens it.

Windows fan state (`%ProgramData%\Culprit\Fans`, administrator-only journal,
write log and imported profiles) remains planned for F4/F5 and is not created by
this Linux add-on. No fan information is uploaded on either planned lane.

- GTK window: `$XDG_CONFIG_HOME/culprit/window-gtk.json` stores only welcome dismissal (mode 0600), kept until the user deletes it and never sent anywhere. Snapshot data stays in memory only.

Native window preferences: `window-gtk.json` version 1 also stores `advanced` (boolean, missing defaults to false), alongside `welcome_dismissed`. It stays local in the same private configuration file.
