Skip to main content

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_enabled now 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_enabled no 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)

FlagBasisFail-closed invariant preserved
golden_record_resolver_enabledADR-0123identity resolution; losers retained as alternatives
structured_sanctions_scoring_enabled#513sanctions match-type band is monotonic (can only raise)
sanctioned_ownership_control_enabledADR-0127>50% floor is monotonic; unknown-weight edge → not_assessed
gambling_licence_verification_enabled#724revoked/suspended status surfaces; never a benign "OK"
brightdata_serp_fallback_enabledADR-0077recall fallback; provider token set
brightdata_social_enrichment_enabledADR-0030recall; two-lane attributed
deep_research_discovery_enabled#723#763 subject-relevance guard merged; unverified lane
adverse_media_monitoring_enabledADR-0099indeterminate → WARNING, never benign
bo_dataset_currency_monitoring_enabledAMLR Art. 21monitoring; fail-closed marker
dac_calendar_year_cdd_monitoring_enabledAMLRmonitoring; fail-closed marker
immediate_designation_rescreen_enabledADR-0128new designation → CRITICAL; errored → WARNING
verification_gate_enabledADR-0136log-first computation only (enforcement = #772)
quality_gate_enabledADR-0111-adjlow quality → forces review

Intentionally OFF (recorded blockers)

FlagBlocker
retention_purge_enableddestructive GDPR purge — needs backup + runbook; not a scrutiny control
finding_correctness_verification_enabledCodex #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_enabledCodex #811 / ADR-0125: stays OFF until the would-block telemetry is reviewed + Calibration sign-off (Phase-2 flip)
brand_directed_discovery_enabledCodex #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_enabledscheduled Temporal cron — enable via a separate ops decision (ADR-0121)
lex_ejustice_enableddeliberate per-instrument scraping toggle, off by design (ADR-0120)
automation_audit_modeoperational mode — forces FULL_REVIEW globally, not a default-on control
neo4j_enabledinfra-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 False now 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_enabled is 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_shred are 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).