Skip to content
commission

Compatibility policy

What commission promises not to break, per surface, and what “break” means for each one. This document is the contract behind the version number; the entries that exercise it are in ../CHANGELOG.md CHANGELOG.md, and the process that enforces it at release time is release-checklist.md.

The version number

Commission ships one version string, single-sourced in src/version.ts and derived from package.json. Every surface that reports a version reports that one:

SurfaceHow you read it
CLIcommission --version
MCPserverInfo.version in the initialize result
HTTPGET /v1/version{ commission, schema, config }
Packagethe version field in package.json

tests/version.test.ts asserts all four agree, and fails if any source file outside src/version.ts reintroduces a hardcoded version literal.

Commission follows semantic versioning across the CLI surface and the HTTP and MCP contracts taken together. There is one number, not one per surface: a breaking change to any of them is a major bump. This is deliberately conservative. Splitting the version per surface would let a “minor” release break the HTTP API for anyone who reads only the CLI’s number, and commission’s whole argument is that one truth beats several that drift.

Two things carry their own independent version, because they describe stored state rather than a calling convention, and both are reported by GET /v1/version:

  • schema — the database schema level (PRAGMA user_version).
  • config — the .commission.json format version.

Pre-1.0

Until 1.0.0 there is no compatibility promise. 0.x is the development line; surfaces move. The policy below describes what 1.0 onward guarantees, and Commission is being built to it now so that RC1 can adopt it without a redesign.

For the release candidates: 1.0.0-rc.N speaks the 1.x contracts and is held to them. Breakage between release candidates is possible but must be listed under ### Breaking in the changelog with its migration, same as any major.

Per-surface definitions of “breaking”

CLI

The CLI is a machine interface first. Agents parse its output and branch on its exit codes, so cosmetic changes are not automatically safe.

Breaking:

  • Removing or renaming a command, subcommand, or flag.
  • Removing a flag’s short form, or repointing it at a different flag.
  • Changing a flag from optional to required, or adding a required flag or positional argument to an existing command.
  • Narrowing an accepted value: fewer enum members, a stricter format, a lower limit.
  • Changing a default in a way that changes what an existing invocation does.
  • Changing an exit code for an existing outcome (see below).
  • Making a command that succeeded refuse, or exit non-zero where it exited 0.

Not breaking:

  • Adding a command, subcommand, or optional flag.
  • Adding a new accepted value to an input that already accepts several.
  • Changing human-readable prose: help text, descriptions, refusal wording, progress output. Refusal messages are documentation, not contract — what is guaranteed is that a refusal exits 2 and, under --json, carries missing and hint.
  • Changing the layout, ordering, colour, or wrapping of non---json output. If you parse commission’s human output, you are on your own — use --json.

JSON output shapes

Everything printed under --json, and every HTTP and MCP response body.

Breaking:

  • Removing a field, or renaming one.
  • Changing a field’s type, including stringnumber, scalar → array, and making a previously always-present field nullable or optional.
  • Changing the meaning or units of an existing field while keeping its name and type. This is the worst kind, because nothing fails loudly.
  • Removing a member from an enum-valued field (status, error, evidence kind), or repurposing an existing member.
  • Changing a top-level response from an object to an array, or vice versa.

Not breaking:

  • Adding a field. Consumers must ignore unknown fields. This is the one obligation the policy places on clients, and everything else depends on it.
  • Adding a new enum member to a field that is documented as extensible. New status values are not covered by this — the status set is closed and adding one is breaking.
  • Changing key order, or whitespace and indentation.

Exit codes

Commission’s exit codes are load-bearing: they are how an agent decides whether to retry, fix, or stop. The mapping is fixed for the life of 1.x.

CodeMeaning
0Success
1Error — something went wrong that is not the caller’s fault to fix
2Refusal — the operation is understood and declined; the message says what is missing and how to fix it
3Not found — the named project, task, or binding does not exist

Breaking: changing any of these meanings, moving an existing outcome from one code to another, or introducing a new code in the 0–3 range.

Not breaking: adding a code above 3 for an outcome that has no existing code. Note that a new refusal on an operation that used to succeed is a CLI break even though the code itself is unchanged — the caller’s script still stops working.

Database schema

The database is the user’s data. The promises here are stronger than elsewhere, and none of them are optional.

Commission migrates forward automatically. src/db/ddl.ts holds MIGRATIONS, an ordered, append-only array of DDL statements; src/db/client.ts compares PRAGMA user_version against it on every open and applies the missing ones, each inside a transaction that bumps user_version with it. Migrations ship inside the binary — there is no migration folder to deploy, and no separate migrate command to forget.

The promises:

  1. Forward migration is automatic and transactional. Any commission command opening an older database upgrades it. Each step commits atomically with its version bump, so an interrupted upgrade leaves a consistent database at a known version, never a half-migrated one.
  2. Commission never silently downgrades. A binary that meets a database newer than the migration chain it carries does not touch it and does not guess at the schema. It refuses with the upgrade instruction. Downgrading a database is not a supported operation at all — restore a backup instead.
  3. Migrations are append-only. A released migration is never edited or reordered. Fixing a shipped migration means adding another one; editing it in place would give two users the same user_version with different schemas, which is unrecoverable.
  4. No data-destroying migration without an explicit gate. A migration that cannot preserve existing rows does not run unattended.
  5. The database is copied before it is migrated. The first open that has migrations to apply copies the file — consistently, via VACUUM INTO — and prints the copy’s path with the exact command that restores it. Migrations are not reversible (v5 drops columns), so this copy is the only way back. If the copy cannot be written, Commission refuses to migrate rather than proceeding without one.
  6. Every migration is exercised against real data before it ships. tests/migrations.test.ts builds a v1 database with rows in every table, walks it through the whole chain one migration at a time, and asserts after each step that row counts, referential integrity, and the meaning of any moved data all survived. A migration added without an entry there fails the suite.

The schema level is MIGRATIONS.length and nothing else — there is no second constant declaring it, because a second copy of that number can only drift from the array it describes.

Breaking: dropping a table or column that held user data; changing a column’s type or meaning in place; any change requiring manual intervention before an upgrade; tightening a constraint such that existing valid rows become invalid.

Not breaking: adding a table, adding a nullable column or one with a default, adding an index, or widening a constraint. A schema version bump on its own is routine and is not a breaking change — it is the expected outcome of almost every feature.

commission doctor reports the current schema level against the expected one, and commission migrate --check reports what is pending without touching the database, exiting 2 when a database is behind the binary or ahead of it. The operational side — what the backup is called, how to restore it, what to do when commission refuses to open a database — is guides/upgrades.md.

.commission.json config version

See reference/config.md for the format itself.

Version 1 is supported for the life of 1.x. A .commission.json written today against "version": 1 will be read, unchanged and without warnings, by every 1.x commission. If a version 2 becomes necessary, 1.x binaries continue to accept version 1 files; commission config migrate performs the upgrade, non-destructively and idempotently, and prints the diff before writing anything.

Two rules make this safe in both directions:

  • A config declaring a version the binary does not speak is refused, with the upgrade command — never partially applied. A newer config meeting an older commission fails loudly instead of silently ignoring the keys it cannot parse.
  • Unknown keys are refused, not ignored. This is why adding a key to the schema is a compatible change but the file cannot be “forward compatible” by accident: a typo that silently does nothing is precisely the failure the strictness exists to prevent.

Breaking: removing a key, renaming one without accepting the old name, changing a key’s type or default in a way that changes behaviour, or requiring a key that was previously optional.

Not breaking: adding an optional key, adding a value to an extensible enum, or adding a default for something previously unset.

A file with no version at all is the pre-versioned shape and is read as such — project and integrations only — with a notice pointing at commission config migrate. That compatibility path predates versioning and is scheduled for removal; the deprecation policy is relay-32.

What is explicitly not covered

  • Human-readable output. Layout, wording, and colour change freely. Parse --json.
  • The web view. It is a rendered view over the same API, with no stability promise of its own.
  • Anything importing commission as a TypeScript library. src/core is the internal core the CLI, HTTP, and MCP surfaces consume. It is not a published API and changes without notice. If you need programmatic access, use the HTTP API.
  • Adapter behaviour against third-party systems. Jira and GitHub change their own APIs; adapters follow. Commission’s promise is that the interop layer is opt-in and inert until configured, not that a given Jira instance keeps working forever.
  • The event log’s payload internals. Event kind values and the envelope are contract; the shape inside payload varies per kind and is documented per kind, not guaranteed uniformly.

Deprecation

Nothing is removed without first being deprecated in a release:

  1. The deprecation ships in a minor release, listed under ### Deprecated in the changelog, naming the replacement.
  2. Using the deprecated thing warns on stderr — once per invocation, never on stdout, and never in --json output, so warnings cannot corrupt a parsed response.
  3. The removal happens no earlier than the next major.

A deprecation that has no working replacement on the day it ships is not a deprecation, it is a break with a longer fuse. Do not ship one.

This page is docs/compatibility.md in the Commission repository, rendered in place — the site keeps no copy of it. The repository is private, so there is no edit link to follow.