Pre-1.0 compatibility
Increment is alpha software. This section says what may change between prereleases, what is written to disk or a warehouse, and how to migrate. It is a software-compatibility policy, not scientific certification: a stable interface says nothing about whether a method’s assumptions hold for your experiment. Method assumptions and known gaps live in Statistical limitations, whose path table remains the single statement of what runs where.
Promised surface
Section titled “Promised surface”These are the interfaces this policy covers:
- documented root-package workflow and configuration imports (
from increment import ...); - the documented receive-only types in
increment.resultsand the solvers inincrement.power; - result schemas: field names, types, row sets, null reasons and the reference/guarantee labels that qualify an interval;
- stable refusal codes and the named fields of their context;
- each versioned portable or artifact format listed below.
Underscore modules and private research kernels are not public APIs and may change without notice.
Alpha semantics
Section titled “Alpha semantics”- Pin the exact prerelease and the versions of its dependencies (Narwhals, Ibis, Arrow, DuckDB, pandas/Polars as used) for any decision you need to reproduce.
- Breaking changes are permitted between 0.x prereleases. Each is listed in the changelog with the affected paths and a migration route. Nothing here implies 1.0 stability or a deprecation window.
- A released bug fix may change a numerical answer that was wrong. The changelog names the correction; the incorrect value is not preserved for compatibility.
Machine-readable contracts
Section titled “Machine-readable contracts”- A refusal code’s meaning is never reassigned to a different condition.
Message prose is not stable; branch on
.codeand the context fields. - A code that is retired keeps its entry in
increment.errors.RETIRED_CODES, which maps it to its replacement code, a tuple of replacements when it split, orNonewhen it was removed with no replacement. Retiring a code is a contract change and is listed in the changelog. - Consumers of results must preserve numeric nulls and the reason, guarantee and reference metadata that accompany a number. Dropping a null reason or reading a value without its reference label changes what the number claims.
- Removing a result field or changing its type requires an explicit migration notice.
Persisted formats
Section titled “Persisted formats”Moments cubes. Analysis.export writes a moments_format stamp on every
row; Analysis.from_moments reads it. There is one format number per kind of
writer:
| Format | Written by | Read by from_moments |
|---|---|---|
| 7 | no longer written | yes (fixed horizon) |
| 8 | fixed-horizon export | yes |
| 9 | registered sequential export (one typed checkpoint envelope) | yes |
| below 7 | no longer written | refused, moments.format.unsupported_legacy |
| above 9 | not written by this release | refused, moments.format.unsupported_future |
A cube must use one format (moments.format.mixed) and repeat no
(metric, group_id) row (moments.rows.duplicate). Format 8 files from a
sequential analysis cannot resume the sequential process: they are refused with
sequential.continuation.legacy. Re-export from the raw definitions with the
current release, or pin the release that wrote the file. Nothing is
downgraded or filled in silently.
Unit-day artifacts. Artifact context format 2 is the only supported
context. It carries no fact, dimension or exposure SQL; source recipes are
represented by domain-tagged SHA-256 digests. Opening or publishing with a
format-1 context is refused with artifact.format.unsupported, which names the
received and supported versions. The refusal does not upgrade an old artifact:
republish from trusted definitions. Copies of a format-1 manifest that you
already stored or shared still contain the SQL they were written with. See
Canonical unit-day artifacts.
Republish and re-register after the window-day and label fixes. An
artifact context now binds window_days, the boundary-local day of each
declared window edge. A context without it is refused with
artifact.context.mismatch (missing window_days) before any manifest,
relation or pin is used; republish from trusted definitions. A native
sequential registration made before this change binds a different source
recipe and refuses with sequential.source.invalid; register again. Frame
registrations are unaffected. Artifacts published earlier also label Boolean
breakout and factor values True/False; republishing yields
true/false/__null__.
Names added in this release. increment.semantics.models.local_day and
window_days; Experiment.start_day, Experiment.end_day and
Experiment.observation_horizon_day;
increment.query.artifact_contract.open_trusted_manifest_snapshot;
WarehouseArtifactStore.abandon_generation(artifact_id, generation_id), which
durably hides a generation that may or may not have a manifest row yet (safe to
repeat); the roles= argument of increment.impute.pooled_mean. A
pooled_mean call without roles= still fills but now emits the
impute.pooled_mean_role_undeclared warning.
Site-volume coverage ends at end. An experiment that declares
observation_end after end now publishes site-volume coverage through end,
the window its rows and the estimand use, instead of through observation_end.
Its context and site-volume extension digests change, so republish such
artifacts. Experiments whose observation_end is not on a later local day than
end are byte-identical.
New refusal codes. query.session.warehouse_artifact.publication_cleanup_incomplete
(relations could not be dropped before the manifest insert began) and
query.session.warehouse_artifact.publication_state_unknown (cleanup was
incomplete after it began: the tombstone could not be written, or relation
erasure failed after it was) are raised when a publication block exits normally
without a manifest and cleanup could not finish. A propagating error is re-raised
unchanged with a note instead. Both codes carry the qualified relation names and a
recovery route in their context.
Sequential checkpoints and segment labels. A saved checkpoint is evidence
for one monitoring process and is never relabelled or rehashed. Frame segment
labels are canonical strings: true/false for Booleans and __null__ for
missing values. A checkpoint captured under an earlier spelling (True,
None) still replays unchanged, but continuing it from a source that labels
the same units canonically is refused with sequential.continuation.rewrite.
That refusal is not permission to restart monitoring and spend alpha again:
keep the earlier checkpoint as the record, or register a new protocol you can
defend. See Sequential inference.
Encodings are format-specific. Aliases, tuple encoding, collection order, duplicate rejection and which values are runtime-only are defined by each format’s own contract, not by a general promise that a value is “JSON compatible”.
Hashes and pickle
Section titled “Hashes and pickle”A digest proves that bytes match the encoding that was hashed. It does not prove who wrote them or that a result is statistically valid, and it does not hide a secret that can be guessed. The caller-pinned reference and the store’s own authorization are the authenticity boundary for artifacts.
Portable files (moments parquet/rows, JSON, artifacts) are the interchange formats above. Python pickle is not: it is a trusted-input-only convenience for objects you produced yourself, and carries no versioned-interchange guarantee across releases. Never unpickle data from an untrusted source.
TabularPolicy persistence
Section titled “TabularPolicy persistence”TabularPolicy accepts any hashable context key, but only string-keyed tables
roundtrip through JSON. A table with any other key type refuses at dump time
with logged_policy.policy.json_context_key, rather than have JSON turn 1
into "1" and change the policy. Persist typed keys with
model_dump(mode="python") or trusted pickle. The built-in integer-keyed
TARGET_POLICY_V1 is in the Python-only group.
from increment import TabularPolicy
policy = TabularPolicy( policy_id="p", version="v1", probabilities={"web": {"A": 0.5, "B": 0.5}}, default={"A": 1.0},)assert TabularPolicy.model_validate_json(policy.model_dump_json()) == policy
typed = TabularPolicy( policy_id="p", version="v2", probabilities={1: {"A": 1.0}}, default={"A": 1.0})assert TabularPolicy.model_validate(typed.model_dump(mode="python")) == typedtry: typed.model_dump_json()except Exception as exc: assert exc.code == "logged_policy.policy.json_context_key"