ADR-064: Central Privacy Model
- Status
-
Accepted
- Date
-
2026-07
- Authors
-
Netresearch DTT GmbH
Context
The extension is metadata-only by construction almost everywhere. Telemetry
(ADR-058: Telemetry Middleware) stores no prompts or responses — only an exception FQCN on
failure — and already bounds its growth with a retention purge
(
nrllm:). But two tables do persist per-request
content, and until now they did so unconditionally:
tx_— a JSON snapshot of the per-prompt graded model output (ADR-060: Quality evaluation — golden sets, grading and regression detection). This is real model output.nrllm_ eval_ result. details tx_andnrllm_ skill_ audit. scan_ result detail— the prompt-injection scan output and a free-form detail string (ADR-061: Skill trust levels, signed manifests, injection scanning).
Both tables also carry pure-metadata columns (identifiers, counts, checksums, trust level, timestamps) that are not sensitive.
ADR-058 named the gap explicitly:
The central privacy model (retention tiers none/metadata/redacted/full) is a later workstream; this middleware is metadata-only by construction.
So there was no single, operator-configurable answer to "how much per-request content does this extension keep, and for how long?", and no purge for the two content tables — only telemetry had one.
Decision
Introduce one central privacy model, read from the extension configuration and applied at every content sink before a write.
1. Four levels — a backed Privacy enum on a strict-to-loose
scale:
none— drop content, keep metadata.metadata— drop content, keep metadata (the default).redacted— store a bounded, credential-scrubbed copy.full— store content verbatim.
The enum exposes persistsContent() (true for redacted/full),
requiresRedaction() (true for redacted) and a severity() ordering
with strictest(a, b) so a global default and any future per-scope override
combine with "strictest wins" — none is strictest, full loosest.
2. Safe default — Privacy returns
Privacy when the setting is unset or invalid. That is the
behaviour the extension already had by construction, so an un-configured
instance keeps content out of the database, not in it.
3. Two governed sinks. Evaluation and
Skill inject Privacy and pass their
content columns through filterContent() before insert
(details; scan_result and detail). null (dropped content) is
stored as the empty string so the columns stay well-typed. Every metadata column
is untouched.
4. Honest, bounded redaction. Content (the redacted level)
masks a small set of high-signal secrets — credential-bearing URL query
parameters (reusing Error, not a copied regex),
obvious bearer / API tokens, and email addresses — and caps length at 2000
characters. Its doc block states plainly that it is a heuristic, not a
guaranteed PII scrubber; operators who must not store content at all use
none/metadata.
5. Centralised retention. Each content repository gains a
purgeOlderThan(int $timestamp): int mirroring
Telemetry
(run_date for eval results, crdate for the audit trail). A new command
nrllm: (--days, defaulting to the configured
privacy.retentionDays, floor 1) purges all three log tables — eval
results, skill audit and telemetry — and reports the count per table. The
retention window clamps a missing / zero / negative setting to 30 days: 0
must never mean "delete everything immediately". The existing
nrllm: stays for backward compatibility.
6. Configuration. Two settings under a new privacy category in
ext_: privacy.level
(options[None|Metadata|Redacted|Full], default metadata) and
privacy.retentionDays (int+, default 30).
Consequences
- ●● One operator-facing answer to "what is stored". A single setting, read in one place and enforced at every content sink, replaces the previous implicit, per-table behaviour.
- ●● Safe by default. An un-configured instance stays metadata-only — the historical behaviour — so this is not a silent behaviour change for existing installs. It generalises the principle telemetry already followed.
- ● Bounded, purgeable growth for the content tables too. The two content
logs get the retention story telemetry already had;
nrllm:bounds all three from the scheduler.privacy: purge - ● Strictest-wins ordering is ready for per-scope overrides. The
severity()/strictest()API expresses tightening today even though only the global default is wired, so a future per-configuration override composes without a rework. - ◐ Redaction is deliberately shallow.
redactedremoves known secret shapes and caps length; it does not guarantee PII removal. The honest doc block prevents a false sense of safety, andnone/metadataremain the answer when content must not be stored. - ✕ Two purge commands now exist.
nrllm:is kept for backward compatibility alongside the broadertelemetry: purge nrllm:. Operators scheduling the new command can retire the old one.privacy: purge - ◑ Lossy at redacted/full boundaries. At
redactedthe storeddetailsJSON may be truncated or masked and is no longer guaranteed to round-trip; consumers that need the verbatim snapshot must run atfull.
Net Score: +6 (2×●● +4, 2×● +2, 1×◐ +0.5, 1×✕ −1.5, 1×◑ −1).
Alternatives considered
- Per-table settings instead of one model. Rejected: it multiplies operator surface and drifts, and gives no single answer to the compliance question. One enum applied at every sink is the point.
- A comprehensive PII scrubber. Rejected as dishonest for the scope: robust
PII detection is a large, error-prone problem. A bounded heuristic with an
explicit "not a guarantee" contract, plus
none/metadatafor store-nothing, is the truthful design. - Drop content unconditionally (no ``full``/``redacted``). Rejected: the eval snapshot and injection-scan output have legitimate debugging and provenance uses. Making retention a choice — with a metadata-only default — serves both the privacy-first and the diagnostics case.
- Reuse ``nrllm:telemetry:purge`` for all tables. Rejected: overloading a
command named for one table is surprising. A dedicated
nrllm:owns cross-table retention; the old command stays for compatibility.privacy: purge
References
- ADR-058: Telemetry Middleware — Telemetry Middleware (metadata-only by construction; named this workstream as the follow-up).
- ADR-060: Quality evaluation — golden sets, grading and regression detection — Quality Evaluation (owns
tx_).nrllm_ eval_ result - ADR-061: Skill trust levels, signed manifests, injection scanning — Skill Trust, Egress and Audit (owns
tx_).nrllm_ skill_ audit - ADR-017 — SafeCastTrait / ErrorMessageSanitizerTrait (the shared credential redaction reused here).