Commit Graph
4 Commits
Author SHA1 Message Date
dtourolle ef1154af94 Resolve every base directory in one place, and on Windows
Five sites each read XDG_*_HOME and fell back to $HOME/.local/… on
their own, which is fine on Linux and wrong everywhere else: Windows
sets neither variable, so every one of them degraded to a path
relative to the working directory — for a Start Menu launch,
C:\Windows\System32. The models lookup walked XDG_DATA_DIRS the same
way.

dr_plat::dirs now holds the rule per platform: XDG on Unix, the known
folders on Windows — %APPDATA% for config, which roams, and
%LOCALAPPDATA% for data and state, which do not — and the executable's
own directory as the system data dir, which is where the installer
puts the models. The Android overrides stay where they were; only the
fallback behind them moved. Both rule sets are unit-tested on either
host, and the Windows one was confirmed by running the application
under Wine: its log landed in AppData\Local\darkroom\state and nothing
was written anywhere else.
2026-09-12 07:34:05 +02:00
dtourolleandClaude Opus 5 9994bb4ce7 Regenerate the matrix over the third wave
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 14:02:40 +02:00
dtourolle 509c3a3e96 Merge: a log that survives the process, so the tablet can be debugged
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	apps/darkroom-android/Cargo.toml
#	apps/darkroom-desktop/Cargo.toml
#	platform/dr-plat/src/lib.rs
2026-08-30 13:46:06 +02:00
dtourolleandClaude Opus 5 35954dfa1d Write a panic down where it can still be read, with nothing in it that names the user
NFR-OPS-2 is two sentences — local crash capture always, upload only on
explicit opt-in — and what existed was one `log::error!` in the Android entry
point and nothing at all on desktop. So a panic on desktop went to stderr and
died with the terminal, and a panic on Android went to a logcat ring buffer
that is gone long before anyone reports anything. What the user saw either way
was a job that stopped or a control that went dead, with nothing to send.

That matters more here than it would in most applications, because
NFR-ARCH-4 says no worker error may panic the process and the code is written
that way: errors are typed and attached to the image or job they belong to. A
panic is therefore by construction a bug — an invariant this codebase believed
and got wrong — and it was the one class of failure with no trace.

`dr_plat::crash` writes a record to the XDG state directory: version, time,
os, arch, thread, panic location, message, backtrace. In dr-plat rather than
in either entry point because "where does this platform let an application
keep state" is a platform question, and Android's answer is neither XDG nor
`temp_dir` — `set_state_dir` takes it from `internal_data_path`, the same
place `dr_sync::account::set_data_dir` gets its answer. The hook resolves the
directory when it fires rather than when it is installed, which is what lets
it go in before everything else and cover the startup it would otherwise miss.

**The content rule is the substance of this, not the plumbing.** NFR-SEC-2
forbids credentials in logs and plain files; the same reasoning applies with
more force to what this application is actually about, because a user's
library is private and so is its shape. `/home/anna/Photos/2019 Divorce/` says
something about a person, and a crash record is exactly the file someone
attaches to a bug report while trying to be helpful. So `redact` runs over the
message *and* the backtrace, and is deliberately blunt: anything containing a
slash goes, `content://` and `primary:DCIM/...` included, since SAF names a
library just as precisely as a path does; anything beside a word like
`password` goes; a long opaque run with letters and digits in it goes, which
is the shape of an app password nobody labelled. The one exception is a `.rs`
path, which keeps its basename — a backtrace with no filenames is close to
useless and `library.rs:1270` says nothing about anybody.

Over-redaction costs legibility. Under-redaction costs a user something they
cannot take back. Those are not comparable, so the boundary is not the place
to be clever.

NFR-SEC-5 — face data never in a crash report, under any configuration — is
met structurally rather than by filtering: this module reads no catalog, opens
no image, touches no account. A record is assembled from the panic hook's own
arguments and `std::env::consts`, and there is no code path from here to an
embedding. The message length cap is the backstop for a payload some other
module formatted something large into.

stderr is the one surface that still sees the message unredacted, deliberately:
the previous hook is chained rather than replaced, so a developer watching a
terminal does not lose the panic because the application started writing files.
It is ephemeral, local, and never attached to a report.

**No upload path, and not half of one** — no endpoint, no queue, no "send this
later" flag. Opt-in upload needs a server to receive it and a consent flow
stating what leaves the device (NFR-SEC-4, and the preview-and-consent step
NFR-OPS-1 requires of the diagnostics bundle). Neither exists, and a transport
built ahead of its consent is the shape of thing that later gets switched on
by default.

Leaves NFR-OPS-1 cheaper by three things it will want unchanged: `state_dir`
(the log belongs at `state_dir()/log` beside `crash/`, so the diagnostics
bundle has one directory to collect), `redact` (NFR-OPS-1's "automatic
redaction of credentials and tokens" is this function), and `prune` (a
size-capped rotation is this, counting bytes instead of files).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 10:34:12 +02:00