Public API Deprecation Policy¶
OpenMed treats its public Python API as a contract. Downstream clinical integrations pin the package and expect imports and call signatures to keep working across patch and minor releases. This policy defines what "public" means, how the surface is protected in CI, and the process for making a backwards-incompatible change when one is genuinely required.
What counts as public¶
The supported public surface is exactly what the checker captures:
- Every name exported from
openmed.__all__. - Every name exported from the
__all__of the public subpackages listed inscripts/release/check_public_api.py(PUBLIC_SUBPACKAGES):openmed.core,openmed.processing,openmed.utils,openmed.clinical,openmed.eval,openmed.risk,openmed.interop,openmed.structured,openmed.mlx,openmed.multimodal,openmed.ner,openmed.zero_shot, andopenmed.compliance. - Governance and write-back namespaces are checked on every PR too:
openmed.agentand itspermissions,approvals,workflows,security, andtoolssubpackages;openmed.interop.fhir,openmed.interop.omop,openmed.interop.lineage; andopenmed.mcp. - For every public callable (function or class), its parameter names, order, and which parameters are optional.
Anything not reachable through one of those __all__ lists -- private modules, underscore-prefixed names, backend shims, and internal helpers -- is not covered and may change without a deprecation cycle. If you want a symbol to be supported, add it to the relevant __all__.
Compatible vs. breaking changes¶
The checker (scripts/release/check_public_api.py) classifies every diff against the committed baseline (scripts/release/public_api_baseline.json).
Backwards compatible (never fails CI):
- Adding a new exported name.
- Adding a whole new public module.
- Appending a new positional parameter that has a default value, or adding an optional keyword-only parameter.
- Widening a callable with
*args/**kwargs. - Changing a parameter's default value (the baseline records only whether a default exists, not what it is).
Breaking (fails CI unless announced):
- Removing an exported name.
- Changing a member's kind (for example a function becoming a class).
- Removing or renaming a parameter.
- Reordering positional parameters.
- Making a positional-or-keyword parameter positional-only or keyword-only, or inserting an optional positional parameter before an existing one.
- Making a previously optional parameter required, or adding a new required parameter.
Deprecation cycle¶
The v3.1 governance types, approval and permission contracts, workflow operations, and FHIR/OMOP write-back APIs follow this same deprecation window. Their inclusion in the per-PR baseline does not grant a pre-release exception to the compatibility policy. New exports and optional parameters remain compatible additions. Declared exports remain checked when optional backends are unavailable; an unavailable callable's signature is not inferred from a missing extra.
Backwards-incompatible changes are allowed, but only deliberately and with warning. The required cycle is:
- Announce. In the release that first deprecates a symbol, keep it working but emit a
DeprecationWarningand document the replacement in the changelog and the affected docstring. - Wait at least one full minor release and respect SemVer. Give downstream users a complete minor-version warning window. A breaking removal or signature change may ship only in the next major release; the warning window is not permission to break a later minor release. See Release Streams, SemVer, and Channels.
- Record the intentional break. When you finally remove or change the symbol, add an entry to
scripts/release/public_api_allowlist.jsonunderannounced_breaks, keyed by themodule.namelocation the checker reports, with a short justification (for example the deprecation version and the replacement). This is how you tell CI the break is sanctioned. - Regenerate the baseline. Run the update command below so the baseline reflects the new surface. Once the removed name is gone from the baseline, delete its now-stale allowlist entry in the same or a follow-up PR.
Working with the checker¶
Run the check locally exactly as CI does:
It exits non-zero and prints the offending symbols when it finds an unannounced breaking change. Additions are reported for visibility but never fail.
When you have legitimately added public surface, or completed an announced deprecation, regenerate the baseline and commit it:
Review the resulting diff to scripts/release/public_api_baseline.json in your PR: additions should look intentional, and any removals or signature changes must correspond to an announced_breaks allowlist entry.
The checker is stdlib-only (ast / inspect). It reads each namespace's literal __all__ from source, then imports available objects to inspect their callable signatures. Modules that cannot be imported in a given environment (because an optional extra is missing) are skipped rather than reported as removed. A declared export that is temporarily unavailable remains in the snapshot but is not treated as a kind or signature change, so partial installs never produce false breaks.
v3.1 security-remediation exception¶
The maintainer explicitly approved a narrow security exception on 2026-10-20 for v3.1.0, superseding the proposed v4.0 boundary. This release deliberately removes privacy-sensitive fields present in v3.0.0. Preserving their original behavior would preserve the exposure being fixed. These are breaking changes, not backward-compatible aliases; consumers must follow the 3.0-to-3.1 migration guide.
The exception covers only the two remediations below, including their constructor and serialized-record changes. It waives the normal warning window and major-version requirement only for this release. The release gate permits exactly their ten field removals, including re-exports, against v3.0.0; it retains the breaking count, migration hash and named exception in signed evidence. A missing migration entry, additional break, different baseline or different minor/patch target fails closed. v3.2 and all other APIs retain the normal compatibility policy.
openmed.agent.approvals.ApprovalReceiptremovesreviewer_role,consumed_atandexpires_atfrom its public constructor and receipt. These protected attributes belong toApprovalAuthorization, returned throughconsume_authorization; use its approved value-free receipt projection for audit output. The public receipt is not a reusable authorization credential.openmed.risk.AggregateDPReleaseremovesnoiseandseed_digestfrom its constructor and public release record. The exact noise draw permits reversal of the release's differential privacy. Use the releasedvalueand its privacy accounting; obtain releases through the reviewed aggregation factory rather than reconstructing a public record with the hidden draw.
These contracts are also reachable through openmed.agent.approvals.tokens and openmed.risk.aggregate_dp; their field removals must be named in the release migration guide. The per-PR checker reports the two exported constructor locations above. Its reviewed baseline already contains the safe constructors, and the production allowlist is empty. The release-level static diff still compares against the immutable v3.0.0 tag; refreshing a per-PR snapshot does not erase release migration requirements.