agenthropic

ADR-0012: CD-10 — Scope, secrets & retention: MVP discipline for a solo owner

As-built update — 2026-07-30

Verdict: partially built. This decision bundles three separate risks, and they shipped at three different depths. Taking them in order:

Scope — held. No vector-DB feed, no fleet, no multi-tenancy. The instance/host_id hedge is present and NOT NULL on every orchestration_edges row, exactly as the cheap-hedge criterion requires. The API surface is small (sessions, DAG, cost, events, stream). The “< 30s time-to-understand a session” criterion is unmeasured — no one has timed it, so it is neither met nor missed; it is untested.

Redaction — shipped, in the strong position. apps/server/src/hooks/redact.ts scrubs hook payloads at the ingest boundary, before persistence and — critically — before the idempotency key is computed, so a redelivered event redacts identically and still dedupes. Two independent rules run: key-based (any field whose normalized name matches token, secret, password, apikey, authorization, bearer, privatekey, accesskey, … is replaced wholesale, whatever its value type) and value-based (string values are scanned for credential shapes — sk-, ghp_, xox, AKIA, JWTs, Bearer <…> — and each match is masked in place). An explicit allowlist keeps token-count fields (input_tokens, output_tokens, …) intact, because token counts are observability data, not credentials — the one place where a naive “redact anything called *token*” rule would have destroyed the project’s core dataset.

Two caveats stated plainly: the redaction policy implements the recommended resolution of OPEN-3 as a default and is pending Ivan’s sign-off; and it can only ever grow on sign-off, never relax.

Retention — NOT implemented. There is no TTL sweeper, no prune, no purge, and no retention configuration anywhere in apps/server, packages/core or packages/shared. WP-D10 shipped its redaction half and not its retention half. The Decision below says “Retention TTL + payload redaction from Phase 1”; half of that sentence is true.

This is blocked, and on a named person: the retention policy depends on the still-open OPEN-1 / OPEN-2 / OPEN-3 decisions (open-decisions.md), which are Ivan’s to make. Building a sweeper before those land would mean choosing a data-destruction policy by default — the precise failure mode this project was built to avoid. So the gap is deliberate, but it is still a gap: the “unbounded local storage growth” risk this ADR names in its Context is, as of today, not mitigated. A long-running instance grows without bound. (Superseded 2026-09-10: mitigated for events; see below.)

Telegram token_ref — not built, because there is nothing to secure yet. No token_ref resolver exists (WP-A3), because alerting is post-1.0 and no Telegram secret is handled by any code path. The >0600-dotfile-rejected criterion has nothing to run against. Separately, ANTHROPIC_API_KEY does stay out of the dashboard env entirely — it appears nowhere in the server source, and the server has no outbound network call that could use one.

As-built update — 2026-08-15

Verdict: still partially built, but the shape of the gap has changed. The 2026-07-30 sentence “there is no TTL sweeper, no prune, no purge, and no retention configuration anywhere” is superseded. apps/server/src/retention/ now holds policy.ts, prune.ts, journal.ts, backup-files.ts, runner.ts and port.ts, with db/retention-queries.ts behind them. What has not changed is the reason WP-D10 is still not done: the policy numbers are blank, and they are not an agent’s to fill in. (They were filled in by the owner on 2026-09-08 and wired on 2026-09-10 — see the next update.)

The separation being maintained here is mechanism versus policy, and it is the whole point. The mechanism can express either branch of OPEN-1; the policy that selects a branch awaits Ivan’s ratification. Concretely:

What is wired, and what is not. Backup-file pruning is live: the daily backup scheduler in apps/server/src/index.ts runs a keep-minimum-floored pass after each write, so backup files on disk are bounded today. The row-level runner (createRetentionRunner) is constructed only by its tests — no bootstrap path calls it. That is consistent with the policy being unset, since wiring it up would run a no-op, but it means the row half of the mechanism has never executed outside a test and should not be described as running in production.

So the risk named in Context is still not mitigated. “Unbounded local storage growth” now has a bounded, tested tool pointed at it and no instruction to fire. A long-running instance still grows without bound in events and token_usage. The blocker remains OPEN-1 / OPEN-2 / OPEN-3 in open-decisions.md, and it remains Ivan’s. Building the mechanism was the part that could be done without choosing on his behalf; choosing is not. (He chose on 2026-09-08 — the next update records it.)

Redaction, secrets and scope are unchanged. The redaction default is still pending sign-off and still may only grow, never relax; token_ref still does not exist because alerting does not; ANTHROPIC_API_KEY still appears nowhere in the server source; and ”< 30s time-to-understand a session” is still UNMEASURED — no one has timed it, so it is neither met nor missed.

As-built update — 2026-09-10

Verdict: the retention half is built and wired; WP-D10 closes. The 2026-08-15 sentence “the policy numbers are blank, and they are not an agent’s to fill in” is superseded by a decision, not by an agent: on 2026-09-08 the owner accepted the closing plan’s default D3 — events 90 days; token_usage never pruned in v1.0; backup files 30 days with the newest 7 always kept — and lane L9 wired it on 2026-09-10.

Redaction, secrets and scope are unchanged from 2026-08-15, and the “< 30s time-to-understand a session” gate is still UNMEASURED: its preparation section was written on 2026-09-09, and the stopwatch run is the owner’s.

Context

Three separate risks compound if left undecided together: scope creep (a solo owner out-building a 28.4k★ incumbent on five axes plus a coverage gate plus a docs site is, in the Holistic lens’s words, “the exact hoangsonww enterprise-cosplay-over-solo-project trap,” §4.6); secret handling (the Telegram bot token must never reach SQLite, the SSE stream, or the browser); and unbounded local storage growth (a local-first tool that never prunes its own history eventually chokes on its own data).

Decision

Acceptance criteria

From concept-analysis-v2.md §6 (“Product / business”) and §3 (CD-10 row):

Supporting evidence, development-plan.md:

Consequences

Alternatives considered