ADR-0146: Activate the dark-launched fail-closed controls on master
Date: 2026-07-27 Status: Accepted Deciders: Adrian (Soft4U), Claude (Opus 4.8)
Revision (Codex #811 review): 3 of the initially-proposed 16 flips were reverted to OFF —
finding_correctness_verification_enabled,approval_insufficient_data_gate_enabled,brand_directed_discovery_enabled— because their prerequisites (subject-context wiring, telemetry review, NorthData key) were unmet. 13 controls activated; the 3 are recorded in the intentionally-off table with their blockers.
Context
~23 controls were merged-but-default=False in app/config.py and enabled only
in the reference .env. So the built fail-closed / never-suppress protections —
the ADR-0127 sanctioned-ownership floor, ADR-0128 immediate re-screen, the
verification gate, the ADR-0125 insufficient-data gate, brand-directed discovery —
were not live on master: a fresh deploy without the reference .env ran with
them off, and the reference .env was the only thing keeping the protections on.
This is the activation-governance debt tracked as #774 (AMLR-readiness epic #528). The guardrail is strict: this pass may turn a fail-closed / never-suppress control ON (or record why one must stay OFF) — it must never flip a control in a direction that reduces scrutiny.
Two dependencies named by #774 were verified before flipping:
- the brand-gate P0 (#762) is merged (#783/#794/#800) — so
brand_directed_discovery_enablednow gates correctly; - the BrightData token is provisioned — so the SERP/social recall flags have a live provider;
- the #763 off-topic subject-relevance guard is merged — so
deep_research_discovery_enabledno longer emits misattributed noise.
Decision
Flip 13 fail-closed / never-suppress / scrutiny-increasing controls to
default=True in config.py; keep the destructive or dependency-blocked controls
OFF with a recorded blocker; and pin every intended on/off state in
tests/test_activation_flag_state.py so a control cannot silently regress.
Activated ON (13)
| Flag | Basis | Fail-closed invariant preserved |
|---|---|---|
golden_record_resolver_enabled | ADR-0123 | identity resolution; losers retained as alternatives |
structured_sanctions_scoring_enabled | #513 | sanctions match-type band is monotonic (can only raise) |
sanctioned_ownership_control_enabled | ADR-0127 | >50% floor is monotonic; unknown-weight edge → not_assessed |
gambling_licence_verification_enabled | #724 | revoked/suspended status surfaces; never a benign "OK" |
brightdata_serp_fallback_enabled | ADR-0077 | recall fallback; provider token set |
brightdata_social_enrichment_enabled | ADR-0030 | recall; two-lane attributed |
deep_research_discovery_enabled | #723 | #763 subject-relevance guard merged; unverified lane |
adverse_media_monitoring_enabled | ADR-0099 | indeterminate → WARNING, never benign |
bo_dataset_currency_monitoring_enabled | AMLR Art. 21 | monitoring; fail-closed marker |
dac_calendar_year_cdd_monitoring_enabled | AMLR | monitoring; fail-closed marker |
immediate_designation_rescreen_enabled | ADR-0128 | new designation → CRITICAL; errored → WARNING |
verification_gate_enabled | ADR-0136 | log-first computation only (enforcement = #772) |
quality_gate_enabled | ADR-0111-adj | low quality → forces review |
Intentionally OFF (recorded blockers)
| Flag | Blocker |
|---|---|
retention_purge_enabled | destructive GDPR purge — needs backup + runbook; not a scrutiny control |
finding_correctness_verification_enabled | Codex #811: verifier gets no subject context (OsintAgentOutput lacks company_name/tenant_id) → searches blind, false FULL_REVIEW, audits under demo tenant. Enable after wiring subject through (#302) |
approval_insufficient_data_gate_enabled | Codex #811 / ADR-0125: stays OFF until the would-block telemetry is reviewed + Calibration sign-off (Phase-2 flip) |
brand_directed_discovery_enabled | Codex #811: needs northdata_api_key (defaults empty) — otherwise every gated brand search returns None and the coverage marker is silently unassessed |
crypto_shred_enabled | #787/#784 — decrypt-on-read only partially wired (ciphertext leak); + KMS pending |
connector_contract_trust_enabled | #779 — live-path drift wiring pending (ADR-0143) |
hash_audited_scoring_enabled | #778 — replay driver + dataset_snapshot_id pending (ADR-0124) |
citation_drift_sweep_enabled | scheduled Temporal cron — enable via a separate ops decision (ADR-0121) |
lex_ejustice_enabled | deliberate per-instrument scraping toggle, off by design (ADR-0120) |
automation_audit_mode | operational mode — forces FULL_REVIEW globally, not a default-on control |
neo4j_enabled | infra-dependent — enabled via .env where Neo4j runs |
*_mock_mode (all) | False = real behaviour; on would serve fixtures (reduces scrutiny) |
Consequences
Positive
- The built fail-closed / never-suppress protections are live on master, not
reliant on a reference
.env. - The pin test makes the intended state a checked invariant; a silent regression
to
Falsenow fails CI. - The activation set matches the recall/scoring fixes shipped this session (#762/#763/#782), so the enabled controls gate on correct signals.
Negative
- Larger blast radius on the next run: 16 controls change default behaviour at once. Live validation (an OB re-run) is the verification step — deliberately done after this lands, not before.
verification_gate_enabledis log-first only; the enforcement wiring (#772) is still open, so a reader must not assume the gate blocks approvals yet.
Neutral
- The reference
.env(which already sets these on) is now redundant with the defaults for the activated set, and remains the override surface for the intentionally-off set during targeted testing.
Alternatives Considered
Alternative 1: Flip everything on, including the destructive/blocked set
- Rejected:
retention_purge/crypto_shredare destructive or have a shipped defect (#787 ciphertext leak). Flipping them would violate the "never reduce scrutiny / never destroy data unsafely" bar.
Alternative 2: Leave all defaults off; keep relying on the reference .env
- Rejected: that is the exact debt #774 exists to close — a fresh deploy would silently run with the protections off.
Decision context
- Latency: none — flag reads only; no new call.
- Dependency surface: none new; BrightData provider token required for two activated recall flags (already provisioned).
- Debuggability: the pin test names each flag + its intended state; a failing assertion points at the exact regressed control.
- Reversibility: single-line per flag; a control can be turned back off in one edit (which the pin test then flags for review).
- Blast radius: 16 controls change default; all additive fail-closed / never-suppress or monotonic — none reduce scrutiny.
- Alternative considered: flip-all (rejected — destructive/blocked set).