Public API Stability

Copy Markdown View Source

This document defines the supported caller contract for the Obscura 0.1.x release line. The machine-readable baseline is priv/obscura/public_api.exs. Contract tests verify that its functions, struct fields, behaviours, profiles, operators, and error codes remain available.

Elixir makes every exported function callable, but callability does not make a module public API. Obscura supports only modules classified as stable or experimental in the baseline. All other application modules are internal. ExDoc filters internal modules from the published reference.

Stability Classes

ClassCompatibility promise
StableProtected by the 0.1.x compatibility policy and contract tests
ExperimentalDocumented for evaluation, but signatures, options, structs, and behavior may change
InternalNot supported for direct caller use and may change without notice
DeprecatedStill supported during the documented transition period

There are currently no deprecated APIs. The stable product profiles are :fast, :balanced, and :accurate.

Stable Module Inventory

The following table lists stable caller entry points. Functions not listed in the machine-readable baseline remain internal even when their module is stable. Default-argument functions are listed at every exported arity.

AreaStable modules and entry points
Primary APIObscura.analyze/1,2, analyze_many/1,2, anonymize/2,3, redact/1,2, rehydrate/1,2
Analyzer facadeObscura.Analyzer.analyze/1,2, analyze_many/1,2
Analyzer resultsObscura.Analyzer.Result, Obscura.Analyzer.Explanation
AnonymizationObscura.Anonymizer.anonymize/2,3, validate_options/1; result, item, and error structs
ProfilesObscura.Profile query, preflight, validation, description, and preparation functions; Obscura.Profile.Runtime values
DiagnosticsObscura.Diagnostic codes, constructors, normalization, formatting, map conversion, and remediation
Recognizer extensionObscura.Recognizer callbacks and Obscura.Recognizer.PatternDefinition.new!/1
OperatorsObscura.Operator.Custom callback and Obscura.Operator.Hash.verify/2
Structured dataObscura.Structured.analyze/1,2, redact/1,2; structured result and item structs
ProtocolObscura.Redactable.redact/2 and the documented derive contract
Logging and PlugObscura.Logger helpers and the Obscura.Phoenix.Plug Plug callbacks
LLM workflowsAll Obscura.LLM redaction and rehydration functions
StreamingObscura.Stream.Rehydrator.new/1, feed/2, and flush/1
VaultsObscura.Vault; Obscura.Vault.Memory and ETS startup/child specs; Obscura.Vault.Backend; Obscura.Vault.Entry
RehydrationObscura.Rehydrator and Obscura.Rehydrator.Structured
LanguageObscura.Language and the Obscura.Language.Detector.detect/2 callback
CapabilitiesCapability loading and profile/asset lookup functions in Obscura.Capabilities

Stable Mix commands are:

Evaluation, fixture, report-generation, benchmark-promotion, and development Mix tasks are project tooling rather than application API.

Experimental Module Inventory

The following entry points are intentionally outside the 0.1.x compatibility promise:

Stable Profile Contracts

The :balanced and :accurate names, requirements, behavior, and preparation contracts are stable under the 0.1.x compatibility promise. Their optional third-party model assets are not bundled or licensed by Obscura; stable API classification is not a grant of checkpoint rights. See docs/model-asset-licensing.md.

Experimental Profile Contracts

:hybrid_gliner_urchade and :openmed_pii remain experimental. :hybrid_gliner_urchade is the public CPU-only alternative with a clearer provenance chain, but lower measured accuracy. :openmed_pii has specialist evidence but unresolved precision, licensing, tail-latency, and retained-memory concerns.

All Obscura.Eval.*, Obscura.Fixtures.*, engine, registry, routing, normalization, validator, model-math, and Mix-task implementation modules not listed above are internal. Their exported functions are not supported caller contracts.

Function And Return Contracts

Stable functions preserve:

  • their module, name, and arity;
  • documented accepted input categories;
  • the outer success/error tuple shape;
  • documented stable result and error structs;
  • byte-offset semantics for detection and anonymization;
  • input-order preservation for batch APIs;
  • the rule that ordinary analysis does not prepare or download models.

Adding a new function or accepting additional inputs is compatible. Tightening an accepted stable input, changing {:ok, value} into another outer shape, or replacing a documented error struct is breaking.

Configuration failures at caller boundaries return error tuples. Functions whose established Elixir convention is construction-time failure, such as PatternDefinition.new!/1, retain their explicit bang semantics.

Struct Contracts

The baseline records stable fields for:

  • analyzer result and explanation;
  • anonymizer result, item, and error;
  • structured result and item;
  • profile descriptor and prepared runtime;
  • diagnostic;
  • vault entry.

Removing or renaming a stable field, changing its documented meaning, or changing its fundamental type is breaking. Adding a field is compatible. Callers must pattern-match only fields they consume and must not compare Map.from_struct/1 output for exact key equality.

Obscura.Profile.Runtime and Obscura.Stream.Rehydrator are state-carrying values. Callers may pass them back to documented functions but must not mutate their implementation fields. Runtime resource and backend metadata contents are informative and additive, not stable schemas.

All metadata maps are additive extension points. Their presence is stable; individual keys are stable only when explicitly documented elsewhere.

Error Contracts

Obscura.Anonymizer.Error guarantees these fields:

  • code;
  • operator;
  • field;
  • reason;
  • metadata.

Its stable codes are recorded in the baseline. New codes may be added. Existing codes will not be removed or repurposed in 0.1.x.

Obscura.Diagnostic similarly guarantees its documented fields and the codes returned by Obscura.Diagnostic.codes/0. Diagnostic code, component/profile identifiers, and the sanitized to_map/1 shape are machine-readable.

Human-readable exception messages, diagnostic messages, remediation wording, inspection formatting, nested causes, and metadata ordering are not stable. Applications must branch on codes and fields, never rendered text.

Errors, diagnostics, telemetry, and reports must not contain source PII, replacement values, callback exception messages, credentials, or salts. Stable text boundaries reject invalid UTF-8 with controlled error tuples. Default inspection of raw-bearing result/state structs is deliberately value-safe; explicit fields and caller-requested serialization retain their documented data.

Profile Contracts

The stable profile names are:

ProfileStable intentOptional requirements
:fastDependency-light structured PII detectionOptional phone parser
:balancedPractical deterministic plus general-NER profileNx, Bumblebee, backend, pinned TNER assets
:accurateHighest measured general accuracy with conditional location recoveryNx, Bumblebee, backend, two pinned model/tokenizer pairs

The names, classification, dependency/asset reporting, explicit preparation, and no-implicit-download rule are stable. Downloads require allow_download: true; cache-only behavior is the default and offline: true forbids network access. Preparation returns a reusable runtime or a structured diagnostic and supports bounded timeouts and safe progress events. Benchmark values may change as new evidence is promoted. Changing a profile's intended category, silently adding a network operation, or requiring new assets without a release note is breaking.

Obscura.Profile.Preparer is the stable supervised ownership path. It prepares once, retains the runtime, exposes readiness/failure, and supports waiting and progress subscriptions. Subscriber message payloads and progress maps are additive; applications should match only the documented event/status keys they consume.

Model licenses are not granted by Obscura. TNER and OpenMed require independent license review, as documented in docs/known-limitations.md.

The experimental product aliases are:

ProfileEvaluation purposeWhy it is not stable
:hybrid_gliner_urchadeCPU-only GLiNER alternativeLower measured accuracy and exported local asset contract
:openmed_piiNative OpenMed/Nemotron PII specialistLow precision, high tail latency, unclear licensing, and inconclusive retained memory

Their explicit-preparation and analyzer behavior may change without the stable deprecation window. Applications must branch on Obscura.Profile.classification/1 rather than assuming every accepted alias is stable.

Operator Schemas

Every operator map requires an atom :type. Unknown operators and unknown operator keys return %Obscura.Anonymizer.Error{} before any replacement.

TypeRequired keysOptional keys and defaults
:replace:typevalue: "[REDACTED]"
:redact:typenone
:mask:typechar: "*", keep_last: 0
:hash:typealgorithm: :sha256, mode: :secure; deterministic mode also requires :salt
:pseudonymize:typevault: nil, with a vault required from config or call options
:custom:type, :moduleoptions: %{}

Changing a default, accepting a formerly invalid ambiguous value, changing the versioned hash representation, or changing preflight-before-replacement semantics is breaking.

Behaviour Contracts

Obscura.Recognizer requires name/0, supported_entities/0, and analyze/2. entity/0 and analyze_many/2 are optional. Recognizers return a result list, {:ok, result_list}, or {:error, reason} using byte offsets.

Obscura.Operator.Custom requires apply/3. It receives source text, the safe documented context, and its configured options. Valid results are {:ok, replacement} and {:ok, replacement, metadata}. Callback failures, raises, throws, exits, and malformed returns become sanitized errors.

Obscura.Language.Detector requires detect/2.

Obscura.Vault.Backend defines the GenServer startup and call callbacks used by vault implementations.

Adding a new required callback or changing a callback arity or return shape is breaking. Adding an optional callback is compatible.

Stable Option Schemas

Analyze

Core stable options and defaults are:

OptionTypeDefault
:profilestable alias, experimental alias, implementation atom, or prepared runtime:regex_only compatibility default
:entitieslist of atomsprofile-supported entities
:languagesupported atom/string:en
:score_thresholdnumber from 0 through 10.0
:explainbooleanfalse
:include_textbooleantrue
:built_insbooleantrue
:recognizersrecognizer modules/definitions[]
:deny_listsdeny-list definitions[]
:allow_listallow-list definitionsnil
:contextlist of strings[]
:context_windownon-negative integer30
:context_prefix_count / :context_suffix_countnon-negative integer5
:context_boostnumber from 0 through 10.15
:context_min_scorenumber from 0 through 10.4
:context_match:whole_word or :substring:whole_word
:detect_languagebooleanfalse
:language_detectordetector module or nilnil
:batch_sizepositive integer8
:recognizer_timeouttimeout5_000
:parallel_recognizersbooleanfalse
:phone_parser / :phone_validator / :phone_regionsoptional phone policydisabled / []
:telemetrybooleantrue

Low-level :ner, serving, NLP-engine, and model-routing options are experimental. Stable prepared profiles are the supported application path.

Analyzer keyword lists currently ignore unknown keys. Unknown keys are outside the supported schema and callers must not rely on that tolerance; a future minor release may return a structured configuration error after deprecation.

Anonymize And Redact

anonymize/3 accepts :operators (default built-ins), :conflict_strategy (default :aggressive), :merge_whitespace (default false), :vault, token formatting options, and :telemetry. redact/2 combines analyzer and anonymizer options. Operator collections and operator-specific options are strict; unknown keys are errors.

Structured Data

Structured redaction accepts :field_policies (%{}), :traverse_structs (false), :preserve_structs (true), :max_depth (20), and :dry_run (false) plus analyzer/anonymizer options for string leaves. Unknown structured keys are currently ignored and are not a supported extension mechanism.

Vault And Tokens

Vault token options are :token_prefix ("<<"), :token_suffix (">>"), :token_separator ("_"), :token_width (3), :token_case (:upper), and :token_strategy (:sequential). These options are strictly validated when used by pseudonymization.

LLM redaction adds :vault, :create_vault (false), and :roles ([:user]) and forwards supported redaction options.

Streaming rehydration requires :vault and supports :token_prefix, :token_suffix, :max_token_length (128), :unknown (:keep), and :telemetry (true).

The Plug supports :fields ([:params]), :mode (:assign_redacted, :replace, or :disabled), :assign (:obscura_redacted), and supported redaction options. Other values are outside the stable schema.

Optional Dependencies And Assets

The package's required runtime dependencies are Jason, Telemetry, and Plug. The :fast profile needs no model dependency or asset.

  • parser-backed phone validation uses optional ex_phone_number;
  • broad NER uses optional Nx, Bumblebee, an explicit backend, tokenizers, and model assets;
  • native Privacy Filter uses optional Nx, Safetensors, an explicit backend, and a local checkpoint;
  • GLiNER/Ortex uses optional Ortex and Tokenizers and remains experimental;
  • Emily and EXLA are development/runtime choices and are never selected as proof of GPU execution without backend metadata.

No model weights ship in the Hex package. Ordinary analysis and redaction never download assets. Explicit profile preparation defaults to cache-only, online preparation requires deployer authorization, and scheduled model CI uses pre-provisioned caches.

Versioning Policy Below 1.0

Obscura follows SemVer while making a stronger promise than SemVer requires for the stable 0.1.x surface:

  • patch releases fix bugs and do not intentionally break stable contracts;
  • minor releases may add stable APIs and fields;
  • stable breaking changes require deprecation and migration guidance, even before 1.0;
  • experimental and internal APIs may change in any release;
  • a critical security or correctness issue may require an immediate breaking fix, documented prominently in release notes.

A breaking change includes removing or renaming a stable function, changing an arity, narrowing accepted documented inputs, changing outer return tuples, removing/renaming stable struct fields, removing/repurposing stable codes, changing operator defaults or schemas, changing stable profile identity, or adding a required behaviour callback.

Deprecation And Migration

Normal stable deprecations remain available for at least one subsequent minor release and at least 90 days from the release that announces them. They include a compiler/runtime warning where practical, release-note migration steps, and replacement contract tests.

The only exception is an urgent security or data-corruption issue. In that case, Obscura may remove unsafe behavior immediately, with a security notice and explicit migration instructions.

Before a future breaking release:

  1. add the replacement API;
  2. mark the old API deprecated in code and this inventory;
  3. retain tests for both during the transition;
  4. publish migration examples;
  5. update the machine-readable baseline only in the breaking release.

Contract Evidence

test/obscura/public_api_contract_test.exs verifies the machine-readable API baseline. test/obscura/documented_examples_test.exs exercises the dependency-light examples and verifies the documented-example evidence registry. Optional model examples point to tagged integration tests and never download assets during the base suite.