Cairntir’s v1.0 contract is a promise: the protocol surface re-exported
from cairntir.__init__ will not shift underneath you without warning.
This document defines warning and without.
Everything in cairntir.__all__ is public and covered by this
policy:
Store, EmbeddingProvider, HypothesisProposer,
ExperimentRunner, BeliefStore, MemoryGatewayDrawer, Layer, Hypothesis, Experiment,
Outcome, BeliefUpdatecairntir.errors, plus
CairntirDeprecationWarning__version__Anything under cairntir.impl.* (DrawerStore,
HashEmbeddingProvider, SentenceTransformerProvider, Retriever,
ReasonLoop, SCHEMA_VERSION) is not covered. These concrete
implementations reserve the right to change between minor releases.
If you want stability, depend on the protocol surface.
Anything with a leading underscore — module, attribute, or method — is not covered, full stop.
A change to a public name is breaking if it would cause any of the following in a caller that was previously correct:
ImportError — the name no longer exists, was renamed, or moved
to a different module.TypeError at the call site — a method’s positional or
keyword-only signature changed in an incompatible way.TypeError or silent misbehavior at the value boundary — a field
on a frozen dataclass was removed, renamed, or changed type.CairntirError is
no longer a subclass, or vice versa.Adding a new optional parameter with a safe default, adding a new method to an impl class (not the protocol), adding a new value to an open enum, or adding a new exception subclass is not breaking.
Every breaking change to a public surface must:
CairntirDeprecationWarning on use.
The warning message must name the replacement and link to a
migration note in CHANGELOG.md.1.3.0,
it cannot be removed before 1.5.0.Patch releases (1.4.0 → 1.4.1) must not introduce, remove, or
retarget any public name. Patches are bugfixes and internal cleanups
only.
The warning is a DeprecationWarning subclass, so Python’s standard
filtering applies:
import warnings
from cairntir import CairntirDeprecationWarning
# Fail loudly on any Cairntir deprecation — recommended for CI.
warnings.filterwarnings("error", category=CairntirDeprecationWarning)
Filtering narrowly on CairntirDeprecationWarning (rather than
DeprecationWarning) lets you opt in to Cairntir’s warnings without
touching the noise from other libraries.
When a deprecation lands:
CairntirDeprecationWarning at every call site of the old
surface, with stacklevel=2 so the warning points at your code,
not ours.CHANGELOG.md entry under the deprecating release that names
the old surface, the replacement, and the earliest release in
which the old surface may be removed... deprecated:: note that repeats the
migration guidance.cairntir CLI command or a
sed recipe in the CHANGELOG entry.| Change | Policy |
|---|---|
| Renaming a public protocol method | Two-minor deprecation |
| Adding a required method to a public protocol | Two-minor deprecation |
| Removing a public field from a frozen dataclass | Two-minor deprecation |
| Changing the type of a public field | Two-minor deprecation |
Moving a name out of cairntir.__all__ |
Two-minor deprecation |
Renaming / removing a name under cairntir.impl.* |
Allowed in any minor |
| Changing internal SQL schema layout | Allowed in any minor |
Changing the HashEmbeddingProvider output |
Allowed in any minor |
| Fixing an incorrect type annotation | Patch release |
| Fixing a docstring | Patch release |
Cairntir uses three-segment Semantic Versioning numbering with one deliberate, documented deviation: a deprecated public surface may be removed in a MINOR release once it has served the two-minor warning window above. Strict SemVer would require a MAJOR bump for that removal. Cairntir does not take one.
The deviation is safe because the warning window does the work a major
bump would otherwise do. Nothing public disappears without a
CairntirDeprecationWarning, a named replacement, a CHANGELOG.md
migration note, and at least two minor releases of overlap. You will
never be surprised; you will be told, in code and in the changelog,
with time to move.
What 1.x.y therefore guarantees is not permanent surface
compatibility with 1.0.0. It guarantees the process: no public name
is removed or retargeted without the full window. Pin a minor version
and read the changelog before upgrading it, exactly as you would for
any dependency.
2.0.0 is reserved for a revolutionary change in what Cairntir is,
not for an accumulation of breaking changes. It may never happen, and
the minor segment is unbounded — 1.24.0 is an ordinary version. See
Release Cadence for the full versioning rules and
the reasoning behind the reservation.
If you hit a change you think violates this policy, open an issue with a minimal reproduction. The project treats policy violations as bugs — the fix is either to restore the old surface and land a proper deprecation, or (if the policy was genuinely ambiguous) to clarify this document.