ADR-0030 — A model classifier may drop a candidate only if the drop is recorded
- Status: Proposed — awaiting founder approval on #2324. The framing is founder direction; the definitions, the prerequisites, the admissible window in D4 and the laundering closures in D5 are the architect's, and they are the substance being approved. Decided-only, not implemented. Nothing here changes shipped behaviour today, and §D6 argues that this is precisely why it should be settled now. Revision note: D4 was reworked after review — the first draft's "evaluate the drop after redaction" remedy does not hold, and D4 records why it fails rather than deleting it. D8 (retention + disclosure) was added in the same pass.
- Date: 2026-08-11 (proposed)
- Deciders: Principal Architect (author + technical recommendation). Founder sign-off required — this narrows a carve-out that was inside a founder-approved ruling (#2322, approved 2026-07-31), and D8 widens the P0.1.3 DPA line.
- Refs: #2324 — the architect ruling that surfaced the contradiction and escalated exactly this one item (comment 5189179257) · #2322 — the ruling whose §(d)
confidencecarve-out is narrowed here (comment 5115083871) · #2371 — the N% sampled human audit of auto-activations, which this ADR reuses as its enforcement footing rather than inventing a second one · #2037 (PRD) / tracker #2063 / LLD #2069 · #2297 (importance) and #2395 (high_stakes) — the two governance gates, deliberately untouched · #2095 — quarantine-on-hit, whose escalate-only rule D4 is built to preserve · #2499 / #2505 — class-tiered mutation, whose low tier is why D4's first draft failed · #2508 — the prior instance of an ADR-0029 premise proving narrower than it read · #2546 — the external-tenant gate that D8 touches but does not move · ADR-0029 (at-rest posture for memory content; D4 and D8 both sit against it). This ADR does not address #2324, which stays open for its own accepted work items.
Context
The contradiction
Two rulings, both mine, both coherent, and they cannot both hold.
#2322 §(d) carved confidence out of the doctrine that had just been applied to high_stakes:
confidenceis a drop gate (precision filter), not a governance gate — it decides whether a row is written at all, not whether a human is consulted. #2297 and this ruling say the extractor gets no vote on governance. Confidence is not governance, so it may stay in the contract.
#2324 attacked exactly that distinction:
Used as a drop, it decides by model vote that no human ever sees the row. That is a stronger form of the thing #2322 forbade, not a weaker one — #2322 forbade a model vote deciding a human is not consulted, and this deletes the row outright.
The disagreement is over where the system's boundary sits. #2322 separates entering the corpus from being escalated once in, and governs only the second. #2324 observes that if the row never enters, no human ever sees it, so the drop is a decision about human consultation — and one with no appeal, because unlike an escalation it leaves nothing behind to appeal against.
What actually separates them: the harm is invisibility, not the vote
#2324's own reason for rejecting option 2a names it:
A drop filter's false positives are invisible by construction — the memory is never written, so nothing counts what was lost. That is the SILENT-DROP class and it is the highest-cost error mode we have.
That sentence indicts invisibility, not model authorship. A model vote whose errors are countable, attributable and inspectable is a measurable component with a knowable false-positive rate. A model vote whose errors leave no trace is an unfalsifiable claim. The founder's direction narrows the prohibition onto the property that does the damage, and on inspection the narrowing is right: it forbids the class of failure #2324 actually described, and permits the mechanism #2324 only described incidentally.
This is the repo's dominant recent defect wearing another costume. A required status check that reports success because it skipped (#2473, #2218). A guard that samples where it claims to prove (#2491). Six byte-identical guard bodies that agree with one another and constrain nothing (#2541 F1). An audit hash chain that verifies clean over a corpus excluding the 122 rows it cannot see (#2493). A control whose failure is invisible is not a control. The register is apt — with one distinction worth keeping rather than flattening: a control that fails loudly and wrongly is self-reporting. ADR-0029's destructive redaction false positive was a bad outcome that produced the evidence of its own error, and that evidence is why the class got fixed. Invisibility is a worse failure than wrongness, which is exactly why the drop is treated here as more dangerous than the escalation, even though the escalation is the one that wastes human attention.
Why the rule is scoped to model classifiers, and why that scoping survives attack
evaluate() (internal/context/memory/extraction/gate.go:88) has three drop reasons — confidence (model-supplied), redaction (deterministic regex, fail-closed), and downstream dedup (content hash). Only the first is a model vote. Scoping the rule to model classifiers is not a preference; it follows from what can be known without a record:
- A deterministic gate's decision function is inspectable. For any input you can predict the verdict, and you can measure its false-positive rate offline against a synthetic corpus with no production record whatsoever. ADR-0029's Context is that exercise, done: four detector configurations built from one implementation, false positives walked 9 → 5 → 0. A counter is genuinely sufficient there, because the counter is the only thing you don't already know.
- A model classifier's decision function is not inspectable. Its false-positive rate can be estimated only from decisions it actually made, on inputs it actually saw. Strip the record and the rate is unknowable in principle, not merely unmeasured.
Recording deterministic drops as well would be defensible but would swamp the control in volume it cannot use. The scoping stands. It should not be "generalised" later.
(One adjacency: ADR-0029 D2's low tier contemplates "any future NER class below a confidence threshold". An NER model is a model classifier — but a sub-threshold hit there routes to flag-and-quarantine, not to a drop, so ADR-0030 does not bind it. If a sub-threshold NER hit is ever made a drop, it does.)
What exists today, measured
Verified on the dev corpus and against origin/main at babe1f09, not taken on report:
| Property | State today |
|---|---|
| Countable | Partly. memmetrics.IncCandidateDropped(ctx, orgID, reason) (memmetrics.go:618) carries {org_id, reason} and no extractor_version. The denominator is present and correct: observeConfidenceDistribution runs at consumer.go:303, before the gates, over every emitted candidate — #2322 (d.2) did this deliberately. |
| Attributable | No. const ExtractorVersion = "v1" (enqueue.go:27) is a compile-time constant. Measured: 72 of 72 agent_memory rows are v1. infra/litellm/config.yaml maps model_name: "*" onto ollama_chat/qwen2.5:3b, so MEMORY_EXTRACTION_MODEL: gemini-2.5-flash-lite in compose is decorative — the compose comment says so outright. |
| Samplable | No, and not by degree. The dropped candidate's content is discarded at consumer.go:412 and stored nowhere. Nothing exists to sample. |
So a bare counter — the thing the word "recorded" most cheaply denotes — already exists, and its existence changes nothing about #2324's objection. That is the first thing this ADR has to rule out.
The gate is inert today
Confidence on written rows is 1.0 × 69, 0.9 × 3 of 72; the bars are 0.5 (work_artifact) to 0.75 (team_preference). No observed value has been near a bar. The gate has never demonstrably dropped anything, and this ADR changes nothing operationally on the current deployment. §D6 argues that the inertness is the reason to settle it now rather than a reason to defer.
Decision
A model classifier may remove a candidate before write only if the removal is recorded — countable, attributable to a model version, and samplable. A drop with no record is forbidden.
D1 — What "recorded" requires. Three conditions, all of them, and "content" is the load-bearing word.
Countable. A monotonic counter from which both the drop rate and its denominator are derivable, labelled at minimum {org_id, reason, extractor_version} from a bounded vocabulary. Today's IncCandidateDropped is missing extractor_version; the denominator (ObserveCandidateConfidence, pre-gate over all candidates) already holds and must not regress behind the gate. A count with no denominator is the same defect #2324 §4b found on agent_memory — negatives that cannot be turned into a rate — and it would be its third location.
Attributable to a model version. The record names the (model, endpoint class, prompt revision) that made the decision, resolved at decision time. A build constant is not a model version. See D3.
Samplable. The content of the dropped candidate is durably retained, org-scoped, addressable by a human, under a bounded retention clock. Content is what makes this condition non-trivial: a record from which a human cannot reconstruct what was lost does not make the loss visible, and #2324's objection survives it entirely. A {reason, count, version} triple is a measurement of the filter; it is not an appeal against it.
D2 — Samplability is not enough. A non-zero sample rate is mandated, on #2371's footing.
A record nobody reads is not review. Samplability is a property of data; a control is an act. Every failure in the Context section above was a control that existed as data and never as an act. If this ADR mandated only retention, the predictable outcome is that the store ships, the sampling does not, and the drop is once again invisible — now with a paper trail asserting otherwise, which is strictly worse than the status quo because it is harder to notice.
The rate is therefore mandated, and it reuses #2371's existing machinery rather than a second knob:
- The sample is routed over the governance surface as a distinct
action_type— as #2371 did formemory_audit(review/sampledaudit.go:67, admitted by migration 191's CHECK) — never into a bespoke table with a bespoke reader. - The rate resolves through
memory_audit_sampling_config(migration 191) with #2371's asymmetry preserved verbatim: a missing org row resolves to the process default, never to 0. Only an explicit0disables sampling for an org, and that is an operator act with a row and anupdated_byto show for it. - The outcome of each sampled review is recorded, so the drop-audit has a denominator of its own. Otherwise D2 reproduces inside itself the defect D1 forbids.
Anything weaker than #2371 would be indefensible: an auto-activated memory that a sampled audit rejects can still be tombstoned, whereas a dropped candidate is unrecoverable. A strictly more final decision cannot carry a strictly weaker control.
D3 — Real provenance is a hard prerequisite, not a follow-up.
extractor_version must become a genuine (model, endpoint class, prompt revision) stamp resolved at run time before any model drop is enabled. Two reasons, and the second is the stronger:
- A drop record stamped
v1names a build constant. It is not attributable, so D1 is unsatisfiable by construction. confidenceThresholdsis keyed byextractor_version(candidate.go:136) and has exactly one key. Wiring a calibrated model without the provenance fix applies v1's bars to a different model and stamps the resulting dropsv1. The record would then attribute the decision to a model that did not make it. A misattributed record is worse than no record, because it is evidence-shaped.
This is architect-callable and already accepted as "lands first" in the #2324 disposition — no new dependency, no new secret, no new egress. It is recorded here as a gate, not a recommendation.
D4 — A model drop is admissible only on a candidate whose redaction class set is EMPTY. Redaction is an admissibility test, not a sanitiser.
THE INVARIANT. An implementer must not miss this, so it is stated as a predicate and not as prose.
classes := merge(content classes, rationale classes) // gate.go:108, redact.RedactResult.Hit() == len(Classes) > 0if len(classes) > 0 {// MODEL GATE IS UNREACHABLE. Not "runs and is overridden" — unreachable.// Row proceeds to status='pending' (escalate-only, #2095). A human sees it.}if len(classes) == 0 {// Model gate MAY drop. Drop record retains content verbatim.}This must be structural — the model gate cannot be called with a non-empty class set — never a comment or an
ifa later refactor can invert. It needs a test that a below-threshold candidate carrying a class is WRITTEN (status='pending'), asserted as a count of rows written, not as "a pending row exists". A presence assertion here is satisfied by any other pending row and cannot see this one vanish.
An earlier draft of this ADR got this wrong, and the way it was wrong is worth keeping on the record. It said: move the model gate after redaction, so the drop record holds redacted content. That remedy fails in both directions.
- It sanitises nothing for the low tier.
applyTierPolicy(redact/tier.go:199) returnsscrubbedonly whenHighTierHit(classes); otherwise it returnsoriginal.phone,ip, bare digit runs and any future sub-threshold NER class therefore pass throughRedactContentverbatim, by design — that is the founder-approved point of class-tiered mutation (#2505 / #2499 / ADR-0029 D2). "Evaluate after redaction" would have stored un-redacted PII at rest anyway. The remedy addressed the high tier and quietly assumed the low tier. - It introduces a worse violation than the one it repairs. A redaction hit forces
status='pending', and #2095's rule is escalate-only. Evaluating a model drop after redaction lets a model vote delete a row that redaction has already flagged for a human. That is a strictly worse instance of the thing this ADR exists to forbid, manufactured by its own remedy.
Why the empty-class-set window is the right answer. The mistake was treating redaction as a sanitiser. It is not reliably one — but it is a reliable classifier, because RedactResult.Hit() is len(Classes) > 0 (redact/adapter.go:55), which is class-based and completely independent of whether anything was mutated. That is exactly the predicate needed. Using it as an admissibility test rather than a cleanup step resolves both conflicts at once: a flagged row can never be model-dropped (conflict 2 is not mitigated but eliminated — the escalate-only rule is strictly preserved, and in fact strengthened, because today a below-threshold candidate carrying PII is dropped before redaction ever runs), and the drop store never receives content the detector flagged (conflict 1 no longer depends on mutation semantics at all).
The honest bound on the at-rest claim — zero-hit is not PII-free. The detector is fail-open on classes it does not know; ADR-0029 names +44 20 7946 0958 as a deliberate, known false negative. Unknown-class PII can therefore land in the drop store. The correct bound is comparative, and it is the reason this is acceptable: the drop store's at-rest PII profile is no weaker than the status='active' corpus we already keep, which is stored unquarantined, is subject to the same fail-open residual, and is read by agents — which the drop store never is. This creates no new exposure class; it extends an existing, accepted one to a new table, under ADR-0029 D3's two premises (org scoping with an already-authorised reader; unreachable from any agent context), which must be re-examined if either stops holding.
Cost. Candidates that are both below-threshold and carrying a class now escalate instead of dropping, which is reviewer load. Today that intersection is empty: the confidence gate is inert, and ADR-0029 records exactly one redaction hit ever firing in this deployment. The cost is real, bounded, and currently zero.
Why ADR-0029's premises keep proving narrower than they read
This is the third time. findBySnapshot under D5.2, governance_approvals.metadata under D3 (#2508), and now class-tiered mutation under this ADR's first draft. The pattern is one thing, and it is worth writing down because it will happen again: each premise was established by reading one code path and was then written as though it were a statement about the property. "Redaction sanitises content" was verified on the high tier and recorded unqualified; "pending rows are not agent-reachable" was verified on four recall paths and recorded unqualified. The habit that catches it is to state the premise with the enumeration that established it attached — "…on the four recall paths listed", "…for high-tier classes" — so the next reader inherits the boundary along with the claim instead of having to rediscover it by tripping over it.
D5 — Recording is necessary, never sufficient. Four laundering paths; three closed, one named.
A rule of the form "you may do X if you record it" invites relabelling. Closed explicitly:
| Move | Closure |
|---|---|
| (a) Relabel a governance gate a "precision filter" and record its drops. | Recording does not change what class a gate is. high_stakes (#2395) and importance (#2297) remain forbidden as model-assigned whether or not they are recorded. ADR-0030 grants governance gates no relaxation of any kind. Recording is a necessary condition on a permitted drop, never a sufficient condition that converts a forbidden one. |
| (b) Record to a sink nobody monitors. | D2.1 — the sample lands on the shared governance surface with a distinct action_type, not in a private table. A drop store with no reader is non-compliant, not minimally compliant. |
| (c) Set the sample rate to 0. | D2.2 — #2371's asymmetry. Missing config is not exemption; only an explicit operator row with an updated_by turns it off, and that row is itself the audit evidence. |
| (d) Move the drop into the prompt, so no candidate ever exists to record. | Not closed. Named. |
(d) is real and it is already open. The extractor prompt (extractor.go:306) says "Only include memories that will matter in FUTURE sessions" and "Skip chit-chat". Those are model drops executed inside the model — uncounted, unattributable, unsamplable, and outside the reach of any rule that binds a post-emission stage. ADR-0030's rule therefore governs post-emission drops only, and a future precision effort that responds to it by tightening the prompt instead of adding a gate is compliant in letter and defeats it in substance.
The only instrument that can see non-emission is the offline eval harness (#2324 §4a) measuring recall against a labelled corpus — and #2324 deliberately did not buy recall labelling, on the reasoning that #2371's audit stream gives precision for free while recall does not. That reasoning stands; the consequence is that the prompt's own suppression stays unmeasured. Recorded as an accepted, named residual so that nobody discovers it later and calls it a loophole. The honest statement of this rule's reach is: it makes the filters we build visible; it does not make the model's own silence visible.
D6 — Status of #2322 §(d): narrowed, not overturned. What changes for confidence, precisely.
Upheld: confidence is a precision filter and not a governance gate. Its classification, its place in the extractor contract, and (d.1)/(d.2)'s per-version keying and inertness telemetry all stand.
Withdrawn: the implicit inference that being a precision filter is sufficient licence to drop unrecorded. Recording is now a condition of that licence.
What changes in practice, today: nothing. The gate is inert (72 rows, confidence ∈ {1.0, 0.9}, bars 0.5–0.75), and a gate that has never dropped needs no drop record. What changes is the condition on future change:
Adding a calibrated
extractor_versionrow toconfidenceThresholdsis now a gated change. That key may not be enabled until D1–D4 are satisfied for it.
And the transition is already observable rather than left to vigilance. #2322 (d.2) shipped confidenceVerdict (confidence.go:75), which emits inert when the per-run variance is below epsilon and varying when the model produces a real spread. A varying verdict is exactly the signal that a calibrated model has landed and the gate has become load-bearing. A varying verdict on an extractor_version with no drop recording in place is an alertable condition — the same doctrine (d.2) applied one step further: an inert gate must be observable as inert, and a gate becoming live must be observable as becoming live.
That is the argument for settling this now. The rule costs nothing today, the tripwire that makes it bind already exists in shipped code, and the alternative is renegotiating it under delivery pressure on the day someone wires a better model.
D7 — What this unblocks, and the cost question that is deliberately not pre-approved.
#2324 rejected option 2b (a second cheap LLM pass over candidates) on architecture, before cost — precisely because it drops by model vote. Under this ADR, 2b becomes admissible in principle if it satisfies D1–D4: recorded drops with content, mandated sampling, real provenance, and confinement to the empty-class-set window. Note what D4 costs 2b specifically: a second pass may only ever adjudicate candidates the detector flagged nothing on, so it can never be the thing that removes a PII-carrying row.
This ADR pre-approves no spend. When 2b returns as a costing decision, the terms are larger than the token bill, and the dominant one is not tokens:
- second-pass inference cost per extraction run;
- drop-record storage plus a retention obligation and a widened DPA line (D8);
- human sampling time on the drop queue — and this term is coupled to the drop rate. A pass dropping 30% of candidates at a 10% sample rate adds reviewer load proportional to what it removes. #2371's own rationale is that a queue nobody can keep up with trains rubber-stamping, and a rubber-stamped audit measures nothing. Sample rate and drop rate must be costed together, not separately.
A second pass that is cheap in tokens and expensive in attention is not obviously worth it. That judgement is the founder's, and it is a later decision than this one.
D8 — Retained drop content is a new data-class scope. The mechanism exists; three things are unpriced and are named here rather than left implicit.
The mechanism is not the problem. internal/compliance/classregistry already supplies exactly what a new retained-content store needs — a Scope carrying Class, Sensitivity, OrgIDColumn, RetentionKey, and Retention{Default,Floor,Ceiling}Days, plus a Deleter for right-to-deletion — and Data-Class Registry Coverage is a required status check, so a store that ships without a registry entry fails CI. That is the structural guard this ADR wants and it is already wired; the implementer does not build it, they register into it. Note the context_shares precedent in scopes.go: leaving OrgIDColumn empty silently opts a scope out of the generic deleter. Dropped candidates must carry org_id and a working Deleter, or erasure passes over them while the registry reports coverage.
Three items are genuinely open and must not be inherited by default:
- The retention number. Unset, and it may not be copied from
agent_memory(registeredRetentionDefaultDays: 180, floor 30). A drop record exists to be sampled, not to be kept. The one hard architectural constraint on whatever number is chosen: retention must exceed the audit sampling interval. A window shorter than the cadence empties the queue before anyone reads it, and the control then reports healthy while sampling nothing — this ADR's own failure mode, one layer up. - The data class, which does not follow from its parent.
agent_memoryis registeredClassOperational. Never-promoted content is plausiblyClassPersonal("GDPR Art.4(1) personal data. RTD DELETEs rows.",types.go:35), because it is retained for platform audit and has no operational purpose to the tenant at all — the tenant's agent never sees it. If that reading is right, the drop store is a stricter class than the table it derives from, which is unusual enough to be stated explicitly rather than discovered during implementation. The class must be an explicit decision, not an inheritance. - Disclosure reach. Retaining content the platform decided not to keep is a new purpose and a new category, not a variation on an existing one, so P0.1.3 (the DPA line) widens. Subject-access and erasure requests must reach dropped candidates — which is the
Deleter+OrgIDColumnpoint above, restated as an obligation rather than a mechanism. P0.1.4 (the sub-processor list) is unaffected: nothing here adds a third party. #2546 step 1 stays XS–S and does not become a new gate.
Consequences
Positive.
- The doctrine becomes self-consistent. "The model gets no vote on what a human sees" and "
confidencemay stay in the contract" stop contradicting each other, because the second is now conditioned on the property that made the first worth saying. - The highest-cost error mode in the memory pipeline acquires a measurable rate. A drop's false-positive rate stops being an assumption and becomes a number, on the same footing as #2371's audit of auto-activations.
- Precision work at the extraction stage is unblocked as a class, not just 2b. Any future filter — heuristic, model, or hybrid — now has a known admission test instead of a case-by-case ruling.
- The rule binds a decision before it is under delivery pressure, at a moment when its operational cost is zero.
- D4 preserves #2095's escalate-only rule exactly, and tightens it. A candidate the detector flagged can never be removed by a model vote — where today a below-threshold flagged candidate is dropped before redaction even runs.
- The compliance mechanism is reused, not rebuilt: the drop store registers into
classregistrybehind an existing required CI check (D8).
Negative / accepted cost.
- New machinery: a drop-record store, a retention clock, and content at rest. This is close to the machinery ADR-0029 declined, and the resemblance should be stated rather than glossed. The distinction: ADR-0029 declined it because a strictly cheaper fix existed (stop mutating low-tier hits), and here no cheaper fix exists — a drop cannot be made visible without retaining something. The object stored is also different: under D4 it is zero-hit content, of the same PII profile as the
activerows already kept, not ADR-0029's rejected pre-redaction original. - Zero-hit is not PII-free (D4). The detector's fail-open residual on unknown classes reaches the drop store, bounded only by being no worse than the active corpus.
- Some junk now escalates instead of being dropped — below-threshold candidates carrying a redaction class consume a reviewer slot. Bounded, and currently zero.
- Recurring human cost. Sampled drop review is permanent reviewer load, as #2371's audit is. It is the price of the rate being real.
- Reach is limited to post-emission drops (D5(d)). The extractor's own suppression stays invisible and unmeasured.
- Retention is a new obligation over content that was previously discarded within milliseconds — a genuine increase in data held, and it needs a defined window rather than "keep it".
- A new gated change: adding a
confidenceThresholdskey stops being routine.
Security. No new external surface, no new dependency, no new secret, no new egress. The posture item is D4: the drop store holds only content on which the detector flagged nothing, which is the same PII profile as the active corpus and strictly better than any variant that stored flagged content. It inherits ADR-0029 D3's two premises explicitly — org-scoped with an already-authorised reader, and unreachable from any agent context. The second premise is the one to probe: any read path over dropped candidates must not repeat the PullContext defect of ADR-0029 D5.2, where a recall query without a status predicate returned exactly the row that was supposed to be withheld. The fail-closed proof is a test that a below-threshold flagged candidate is written to review rather than dropped, asserted by count.
Metrics. memory_candidates_dropped_total gains extractor_version; the label vocabulary stays bounded and enum-sourced (never a raw model string from config). The pre-gate confidence distribution at consumer.go:303 must remain pre-gate — it is the denominator, and moving it behind the gate would silently make the drop rate uncomputable while every test stayed green.
Alternatives rejected
A1 — Forbid model drops outright: every model filter becomes escalate-only. This is #2324's position taken to its conclusion, and it is the alternative with the strongest claim. Rejected on three grounds. It converts every precision problem into reviewer load, and #2371's own approved reasoning is that an unkeepable queue trains rubber-stamping — so it degrades the control it is trying to protect. It makes extraction-stage precision unimprovable, which is #2324's actual complaint. And it does not even achieve invisibility-elimination, because D5(d)'s prompt-level suppression is untouched: it would bind the visible half of the drop surface and leave the invisible half alone. A rule that binds only what is already observable is the weaker rule, not the stricter one.
A2 — Keep #2322 §(d) as written: precision filters are simply exempt. Rejected because the exemption is defined by a label the filter's own author chooses. Any drop can be described as a precision filter; "governance" and "precision" are not properties the system can distinguish. An exemption with a self-assigned key is not an exemption, it is a formality.
A3 — Counter-only recording, no content retained. Rejected because this is the state today (IncCandidateDropped, shipped) and it is the exact state #2324's objection describes. Adopting it would rename the status quo as compliance and close the escalation without changing anything — the single worst outcome available here, because it would also foreclose re-raising it.
A4 — 100% human review of drops. Rejected on #2371's ground (rubber-stamping at volume) and on a structural one: if a human sees every drop, it is not a drop, it is an escalation with extra steps. That option already exists and is #2324's accepted disposition for 2a.
A5 — Retain the pre-redaction candidate in an access-controlled store. Rejected — this is ADR-0029's rejected alternative reappearing one stage earlier, with the same costs (more at-rest secret surface, its own retention clock, an L5-gated read path). D4 reaches the same visibility by narrowing which candidates may be dropped at all, so the store never receives flagged content and needs no privileged read path.
A6 — Move the model gate after redaction so the drop record holds scrubbed content. This ADR's own first draft, and it is recorded as rejected rather than silently corrected, because it is the intuitive answer and someone will propose it again. It fails twice: applyTierPolicy does not scrub the low tier at all (tier.go:199), so nothing is sanitised; and it lets a model vote delete a row that redaction already flagged for a human, breaking #2095's escalate-only rule. See D4.
Founder decision points
- Ratify the rule and D1's three-part definition, including that content retention — not a counter — is what satisfies "samplable".
- Ratify D2's mandated non-zero sample rate on #2371's footing. The alternative is "samplable but not sampled", which the author does not recommend and which the Context section argues is worse than the status quo.
- Note D4 — model drops are confined to the empty-class-set window. The rule itself is an architect call, but two things in it touch posture the founder escalated on ADR-0029 and are recorded for explicit notice: the drop store holds zero-hit, un-mutated content at rest, and "zero-hit" means the detector flagged nothing, not contains no PII — the fail-open residual on unknown classes reaches this store, bounded by being no worse than the
activecorpus already kept. - Note D5(d) — prompt-level suppression stays out of reach and is an accepted, named residual.
- NEW — D8's three unpriced items. These widen what is being approved beyond the original framing and are flagged as such rather than left to ride: (a) the retention number is unset and may not be inherited from
agent_memory's 180/30 band; (b) the data class is an explicit decision — never-promoted content is plausiblypersonalwhere its parent table isoperational; (c) P0.1.3 (DPA) widens — retaining content the platform decided not to keep is a new purpose and a new category, and DSAR/erasure must reach dropped candidates. P0.1.4 is unaffected (no third party is added) and #2546 step 1 stays XS–S without becoming a new gate.
The cost of option 2b is not on this list and is not decided here (D7).