Aller au contenu principal

ADR 004 — Detection: Hybrid Go-native + Python Presidio Sidecar

Status: Accepted Date: 2026-04-23 Deciders: Jerome Related: prd-detection-tiering, detection-multitier-architecture, nlp-strategy, ml-benchmark-plan, onnx-pipeline

Context

Current detection stack is pure Go (noxys-api/internal/classification/). This covers structured PII (NIR, IBAN, BIC, RIB, RPPS, FINESS, Carte Vitale, LEI, AWS/Slack/Anthropic tokens…) very well: RE2 is linear-time, zero ReDoS risk, <1 ms per pattern match, trivially deployable, memory-cheap.

Two problems push us beyond pure Go:

  1. No NER in Go. Go has no mainstream, battle-tested NER library. Embedding a transformer in Go via ONNX runtime is technically possible but the tokenizer + post-processing ecosystem is immature compared to Python. Spacy + Presidio is the de-facto standard.
  2. Multi-language EU expansion. DE/ES/IT/NL structured IDs can be added in Go (regex + context). But the free-text NER (person names, addresses, DOB in prose) requires language-specific models. Five of those is spaCy territory.

We must also ship a moat between Starter (8€/u) and Business (15€/u) tiers. "Better NER" is a natural, defensible tier-2 feature.

Decision

Hybrid architecture. Three detection layers composed per tier.

  1. Go-native structured PII, all tiers. Existing Go recognizers + new recognizers_de.go / _es.go / _it.go / _nl.go. Fast, cheap, no ML runtime. Covers 80% of CISO use cases.
  2. CamemBERT ONNX in Web Worker, Starter+. FR-only NER, runs in the browser extension, ~45 MB INT8 model, WASM+SIMD backend. Solves FR free-text detection without introducing a server-side ML runtime for Starter customers. <15 ms p95.
  3. Python Presidio sidecar, Business+. Full 5-lang NER (fr/de/es/it/nl spaCy lg), Presidio recognizer engine, custom recognizer CRUD, tenant-aware cache. Separate process, mTLS + JWT, gRPC from noxys-api.

noxys-api composes them: always runs Go recognizers; if user's tier includes sidecar_access and sidecar is healthy, it calls sidecar and merges entity spans; deduplicates by span overlap + higher score wins.

Consequences

Positive

  • Starter gets real FR NER without any server-side Python runtime — keeps Starter cost low, protects margin
  • Business tier has a clear technical moat (5-lang server NER + custom recognizer API)
  • Graceful degradation: sidecar outage falls back to Go-only with a counter + alert
  • Python stays isolated in its own repo / container / team — no Python in noxys-api
  • CI/CD independent (sidecar ships on its own cadence)
  • Air-gap story tractable: one Go binary + one signed Python container

Negative

  • Two runtimes to maintain. Go + Python. Ops complexity up. Mitigated by treating sidecar as a black-box service with a stable gRPC contract.
  • Distributed tracing needed. Request now spans two services. We must correlate via request_id in gRPC metadata and propagate OpenTelemetry.
  • Latency penalty. Sidecar hop adds ~20–60 ms p95. Acceptable inside our 250 ms end-to-end budget for Business tier.
  • RAM cost. 5 spaCy lg models + Presidio engines ≈ 2 GB. Mitigated by LRU eviction + lazy load.
  • Quantization risk. CamemBERT INT8 may drop F1 by 1–3%. Benchmark in DT-2, accept if drop <2%, fallback to FP16 otherwise.

Operational

  • New repo noxys-presidio with its own CI, versioning, release cadence
  • Noxys-api gains a Go gRPC client + circuit breaker (DT-4)
  • Helm chart adds a new deployment + NetworkPolicy
  • PlanGate gains sidecar_access feature flag (DT-6)

Alternatives Considered

Alternative A — Go-only forever (add DE/ES/IT/NL regex, skip NER)

  • Pros: single runtime, minimal ops, lowest latency
  • Cons: no NER ever → person name / address / DOB in free prose missed → loses parity with Presidio-based competitors (Nightfall, Harmonic); no tier-2 differentiation; EU multi-country sale still works for structured but fails for unstructured prose
  • Rejected: unstructured detection is non-negotiable for Business tier; competitors already ship it

Alternative B — Python Presidio sidecar for everything, deprecate Go recognizers

  • Pros: single detection layer, one language (Python), Presidio ecosystem, easier custom recognizer story
  • Cons: throws away working, tested Go code; every classify call hits Python (bad p99); heavier ops; Python cold start not acceptable for Starter pricing; breaks air-gap story for low-end self-host
  • Rejected: Go recognizers are a performance and cost moat we want to keep; Starter tier cannot carry a Python runtime at 8€/u margins

Alternative C — WASM-only NER for all languages (no Python at all)

  • Pros: no Python process, everything runs in browser or edge; uniform deploy model
  • Cons: 5× ~45 MB models to ship to every user is prohibitive; browser RAM budget blown; custom recognizer hot-reload impossible in extension context; tenant-level rules cannot live on client (trust boundary); spaCy-equivalent quality not reachable with distil-models alone
  • Rejected: browser is not the right home for multi-lang NER or tenant-specific custom recognizers

Alternative D — Third-party API (AWS Comprehend, GCP DLP, Azure Cognitive Services)

  • Pros: zero infra, immediate coverage
  • Cons: sends customer data to non-EU cloud, kills sovereign pitch, per-call cost makes 15€/u unviable, vendor lock-in, no air-gap path
  • Rejected: fundamentally incompatible with our positioning

Follow-up Actions

  • DT-1: ship Go recognizers EU (structured)
  • DT-2: ship CamemBERT Web Worker
  • DT-3: create noxys-presidio repo, MVP
  • DT-4: integrate sidecar in noxys-api with PlanGate + circuit breaker
  • DT-5: deploy manifests
  • DT-6: tier gating + Stripe

References

  • prd-detection-tiering
  • detection-multitier-architecture
  • nlp-strategy
  • ml-benchmark-plan
  • onnx-pipeline