Skip to content

ADR 0409: Public contracts split durable enforcement from session judgment

Status: accepted on 2026-09-17. Amends ADR 0208 and ADR 0390.

Context

§

discern publishes eight contract artifacts under schema/, each with a $id at https://discern.sh/schema/v1/<name> and a compatibility policy recorded inside the file. Once a v<SemVer> tag exists, a gate guard compares the committed artifacts with those at the highest earlier release tag — a tag on the commit itself is excluded, so a release candidate compares with its predecessor — and fails on anything the policy forbids.

The owner reviewed that regime on 2026-09-16 and 2026-09-17 and found it broader and stricter than the product needs. discern has no auto-updater, so the unit of compatibility is the user's repository: the surfaces that live in repositories — authored configuration, durable Proof records, scripts that speak the published protocols — deserve hard enforcement, while the surfaces an agent re-reads every session — the CLI grammar, the MCP tool catalog, result envelopes — deserve judgment. An adversarial review on 2026-09-17 tightened the boundaries; the decision record at project/map/_private/planning/discern-public-contract.md carries the final form, and this record preserves the decisions.

Decision

§

1. Two enforcement tiers. The durable tier is discern.toml (discern-config.schema.json), the setup config document, the landing Proof note, the conventions manifest, and the release comparison JSON. The session tier is the CLI grammar manifest, the MCP tools manifest, and the result contracts. Releases is durable because every installed binary prints the JSON's URL for external tooling to fetch; the binary itself makes no network request (discern releases reports network_request: false). At launch every publication is enforced by the same gate guard; the session-tier checkpoint with owner variance is post-launch work recorded in project/TODO.md. The manual describes the promise each tier makes and never the enforcement mechanism; the map's compatibility policy page describes the mechanism.

2. Evolving members. A result contract may carry stability: "evolving", which marks its CLI command paths, its MCP tool, and its result schema together; config keys carry the same fact through schema metadata. Evolving means complete and supported: the command works and is documented, and only its flags, inputs, and result shapes may still change in a minor release. Evolving members are exempt from compatibility comparison. Graduating an evolving member to stable is allowed; demoting a stable member is a break. The fact is carried in the artifacts and on the public compatibility page, never in CLI help or MCP descriptions: agents re-read those surfaces every session, so a marker there tells them nothing they can act on. The tier is never written "experimental" — a finished feature must not read as unfinished. Evolving at launch: triangle (a hidden verb), desk, enter, patterns with patterns reset, patterns seal, and patterns archives, improvement, coupling, tidy, worktree park, worktree rename, and the [coupling] config section. Everything else is stable.

3. Enum rule: inputs append-only, outputs open or closed by role. Adding a value to a discern.toml enum, a setup-document enum, a flag's accepted values, or an MCP tool input is compatible; removing one breaks. An output enum is either an open vocabulary — published as type: string with the known members as metadata, so additions are compatible by construction — or a closed decision vocabulary, kept closed because engine code branches on the value when reading, so a new member is a break for that artifact alone: a schema major for the proof note or releases, a recorded retirement on the session tier. Open at launch: error slugs, advisory kinds, step kinds, step dispositions, checkpoint drop reasons, proof status, consent source, and every other output enum the engine only carries or displays. Closed: validation mode, step outcome, diagnostic severity, proposal direction, evidence purpose, requirement kind, checkpoint mode, exception state, landing-authority kind, and the release comparison's two vocabularies, status and publication. A consumer treats an unknown member of an open vocabulary as opaque and keeps going.

4. Conventions manifest content. The manifest freezes repository facts only: public environment variables, bundled skill names, built-in checkpoint and question ids, identity derivation, provider file coordinates (absent capabilities omitted rather than frozen as null), Git conventions without the bounds and no_attribution_effects behavior descriptions, exit_statuses, and the script protocols. Private storage layout, verb membership, and reason prose leave it. MCP resources enrol in the MCP manifest.

5. Deprecation promise. Durable tier: an upgrade never leaves a repository unreadable. A renamed configuration key is migrated by discern upgrade, and the old spelling is refused with its successor named; a proof-note format change bumps the note's own version. A copy of a durable-tier schema pinned at any 1.x release validates every later 1.x document. Session tier: a stable command, flag, tool input, or result field is retired only in a minor release, never a patch, and the release notes name it; a retired command or flag refuses with its successor named. That refusal breaks a script that still uses the old spelling, and the owner accepts that deliberately because agents re-read the surface each session. Evolving members may change in any release. This is a stated departure from strict semantic versioning for the session tier.

6. Script protocols are durable. Standards producers and checkpoint when scripts live in users' repositories, so the conventions manifest freezes what they depend on: the DISCERN_METRIC <name> <number> line, the DISCERN_MATCH <path> line, the DISCERN_CHECKPOINT_INPUT document's version and field names, and the meaning of exit statuses 0 (fires) and 10 (passes).

7. Path semantics. https://discern.sh/schema/v1/<name> serves the current 1.x schema for its surface. A copy pinned earlier validates the stable members it knows; consumers refresh before validating additions. A breaking major adds /v2/ and keeps /v1/ served, unchanged from ADR 0208. Each publication's major is independent of the package version.

8. Version axes stay independent. The package version, each publication's schema major, the install schema [meta].schema_version, the setup document version (which now equals its schema major), the checkpoint when input version, and [meta].managed_version — which records the release whose managed material the project last adopted and is evidence, not a contract — move on their own.

What is not contract, on any tier: descriptive prose everywhere (CLI help, MCP titles and descriptions, JSON Schema documentation keywords), the listing order of commands, tools, and resources, Markdown and terminal output, hint text, the bundled instructions and skill bodies (their names are frozen), the manual, the map, and private on-disk format versions and locations.

Consequences

§
  • The durable tier keeps the hard gate permanently. The session tier keeps it only until the checkpoint mechanism exists; after that, a session-tier break becomes a served question the owner can land with a variance, so a deliberate deprecation carries its record in Proof instead of being impossible.
  • Evolving members buy launch-day freedom: the commands still settling can change flags, inputs, and result shapes in a minor release without a schema major. The cost is that a consumer pinning an evolving member gets no promise; the public compatibility page is where that boundary is published.
  • The comparator must prune evolving members before comparing and must know every output enum's role; an output enum registered in neither the open nor the closed set fails the guard, so a new vocabulary cannot ship unclassified.
  • A session-tier retirement knowingly breaks scripts that hard-code the old spelling. The refusal names the successor, the release notes name the retirement, and the minor-release boundary bounds when it can happen.
  • Two tiers are more to explain than one rule. The manual page carries the promise in user terms; this record and the map page carry the reasoning and the mechanism.

Alternatives considered

§
  • Keep the uniform hard gate for all eight publications. Rejected: it ossifies the session surfaces — every rename becomes a schema major, so the grammar and result shapes freeze at their launch imperfections.
  • Version session schemas by the package version. Rejected: it duplicates the identity ADR 0208 already assigns; the schema $id remains the contract identity callers pin.
  • Launch as 0.x until the surfaces settle. Rejected: users judge stability by whether an upgrade breaks a repository, not by the number.
  • Forward every retired spelling to its successor for a deprecation period. Rejected: it keeps two spellings per concept alive for the rest of the major, against ADR 0365, for consumers that re-read the surface each session anyway.
choose openEsc close