# Versioning policy (/docs/versioning)



`@pleach/core` ships under semantic versioning with a deliberate
deprecation policy. Three orthogonal version axes matter to
consumers: the package semver, the schema bundle version, and
the `auditRecordVersion` on the audit row.

Each axis has its own evolution rules. This page walks them so
you know what's compatible across upgrades and what isn't.

## Package semver [#package-semver]

Standard semver: `MAJOR.MINOR.PATCH`.

| Bump    | Triggers                                                                                                                                                 |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PATCH` | Bug fixes, performance improvements, internal refactors with no API surface change                                                                       |
| `MINOR` | New typed config fields, new optional plugin hooks, new subpaths, new SQL files in the schema bundle, new `StreamEvent` variants, new error codes        |
| `MAJOR` | Removal of a typed config field, signature change to `executeMessage` / `createSession`, removal of a `StreamEvent` variant, removal of a public subpath |

Practical implications:

* Minor upgrades are always safe — adding a new variant to
  `StreamEvent` doesn't break `switch`-based consumers that have
  a `default` arm.
* Major upgrades come with a migration note in the changelog.
  The substrate has shipped one major (1.0.0) so far; future
  majors will be telegraphed.
* Patch upgrades never change runtime behavior in a way that
  invalidates the cache — same `pleachVersion` field on the
  fingerprint.

## What lives outside the semver contract [#what-lives-outside-the-semver-contract]

A few surfaces are deliberately excluded from the semver
guarantee, because pinning them would prevent the substrate from
fixing bugs.

| Surface                                                        | Why                                                                                         |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Internal channel names (`__planning__`, `__synth__`, etc.)     | Internal; not part of the public contract                                                   |
| Loader-seam keys under `setHarnessModuleLoader`                | Surface is shrinking; key removal isn't a major-version event                               |
| Deep wildcard imports under `@pleach/core/*`                   | Marked unstable; expect rename / removal without major bump                                 |
| Exact bytes of composed prompts under `seedCoreDefaults: true` | Determinism guarantee is "same version, same input"; the version moving is what invalidates |

If you depend on any of these for a contract behavior, the right
fix is to file an issue asking for the surface to be promoted to
a stable subpath. The wildcard-narrowing track in upstream
`pa3prep/` does exactly this when there's external demand.

## Deprecation policy [#deprecation-policy]

A surface marked deprecated stays in for at least:

* **Two minor versions** if it's a typed config field or a
  documented public function.
* **One major version** if it's a structural commitment (a
  channel kind, an export-map subpath, an audit row column).

Deprecation notices land in three places:

1. The package's `CHANGELOG.md` for the version that deprecates.
2. A `@deprecated` JSDoc on the type definition.
3. The relevant docs page on this site.

A deprecated surface still works until removed. The deprecation
notice tells you what to migrate to and which version removes
it.

## Schema bundle versioning [#schema-bundle-versioning]

The schema bundle is versioned by file count, not by any in-row
column. Files are additive:

* New schema files land as `NNN_<table_name>.sql` with the next
  available `NNN`. The bundle never edits existing files.
* File 001 (`harness_sessions`) and file 010 (`harness_auditable_calls`)
  are the load-bearing files; changing either's column shape is
  an `auditRecordVersion` event (next section).
* New files only add tables or columns; they don't drop or
  rename.

Applying an older bundle against a newer database is safe — the
existing tables stay; the new bundle's `IF NOT EXISTS` clauses
skip them. Going the other direction (newer bundle, older
database) installs new tables; the runtime will use them on the
next call.

The `schema_version` column on `harness_sessions` is the
in-row marker. Bumps coordinate with the package's
`SESSION_SCHEMA_VERSION` constant; old rows hydrate through a
migration function inside the runtime.

## `auditRecordVersion` [#auditrecordversion]

The wire-format version of `AuditableCall` rows. Bumps are gated
by an upstream audit (`audit:auditable-call-version`) and
coordinate with consumer adapters.

```typescript
import { AUDIT_RECORD_VERSION_HISTORY } from "@pleach/core/audit";

for (const entry of AUDIT_RECORD_VERSION_HISTORY) {
  console.log(`v${entry.to} (${entry.landedAt}): ${entry.reason}`);
}
```

The history constant lets consumers render "what changed in v7"
at runtime. Each entry carries:

| Field           | Purpose                                               |
| --------------- | ----------------------------------------------------- |
| `version`       | The wire version this entry describes                 |
| `shippedIn`     | The `@pleach/core` package version that introduced it |
| `summary`       | Short prose description                               |
| `addedFields`   | Field names added on this version                     |
| `removedFields` | Field names removed (rare; gated by major bump)       |

Adapters check this constant during migration to know which
payload fields are new. Adding a field to the payload is
non-breaking for readers — old rows simply lack the new field.

## Pre-1.0 and reserved placeholders [#pre-10-and-reserved-placeholders]

<Callout title="What ships today">
  The first-wave publish ceremony cuts every shipping `@pleach/*`
  package at `0.1.0 · FSL-1.1-Apache-2.0`: `@pleach/core`,
  `@pleach/tools`, `@pleach/react`, `@pleach/base-tools`,
  `@pleach/replay`, `@pleach/sandbox`, `@pleach/langchain`,
  `@pleach/compliance`, `@pleach/eval`, `@pleach/gateway`,
  `@pleach/mcp`, `@pleach/coding-agent`, `@pleach/observe`, and
  `@pleach/recipes`. `@pleach/trust-pack` alone remains a
  reserved npm name at `0.0.1 · UNLICENSED`. See
  [Packages](/docs/packages) for per-SKU notes (which packages
  are substrate-complete, which expose a typed contract with one
  method still throwing a sentinel, and which are pure contract).
</Callout>

Pin `0.1.0` exactly. The `0.0.x → 0.1.0` jump and the
`0.1.x → 1.0.0` jump are both non-caretable, and `^0.1.0` will
not pick up later `0.x` releases either. Use
`"@pleach/<name>": "0.1.0"` in `dependencies` — not `^0.1.0`.
For future `1.x` releases, caret ranges become safe.

## License [#license]

`@pleach/*` ships under **FSL-1.1-Apache-2.0** (Functional Source
License with Apache 2.0 as the future license). Source-available,
usable in production, free of charge during the FSL window; auto-
transitions to permissive Apache 2.0 two years after first stable
publish. The license text is in `LICENSE` at each package root.

`@pleach/trust-pack` remains at `0.0.1 · UNLICENSED` as a
reserved npm name with no shipping code. See per-SKU README for
the canonical license declaration.

## Migration guides [#migration-guides]

Each major release ships a migration guide in the upstream
`docs/MIGRATION-vN.md`. The site mirrors the most relevant ones
when they affect typical consumers:

* [Migrating from the AI SDK](/docs/migrating-from-ai-sdk) — not
  a version migration, a tool migration.
* [Migrating from LangChain](/docs/migrating-from-langchain) —
  same.

Future version migrations (`@pleach/core@2.x` → `3.x`, etc.)
will land as `migrating-from-v2.mdx` etc. when they exist.

## Registry-state discipline [#registry-state-discipline]

A CI audit (`audit:package-version-vs-registry`) compares the
in-tree `package.json#version` for every `@pleach/*` package
against the latest tag on npmjs.org. The audit runs on every PR
and on `main`.

For consumers vendoring `@pleach/*` SKUs, this catches
"silent past 1.0.0" drift — when a vendored copy's in-tree
version slides past 1.0.0 without a matching registry publish,
the audit flags it. Vendored trees and the registry stay
aligned, or the build fails.

For day-to-day consumers reading from npm, the gate is
informational. It asserts the public registry matches what the
source tree claims to ship.

## Peer-dependency promotion timing [#peer-dependency-promotion-timing]

Peer-dep ranges on `@pleach/core` and `@pleach/react` tightened
at the first 1.x cut. The ranges are now a contract, not a
suggestion.

Policy: peer-dep ranges move on **minor** bumps only — never on
patch. Consumer code should pin peer deps explicitly and
re-verify ranges at each minor bump. A patch upgrade never
forces a peer-dep churn.

At a major bump, recompute the full peer-dep tree (peer ranges,
peer-meta-dependencies). Major bumps are when wider
compatibility changes land. See
[publishing contract](/docs/publishing-contract) for the broader
packaging conventions.

## Path-alias leaks in published `dist/` [#path-alias-leaks-in-published-dist]

For forkers and vendored installs, one packaging failure mode
warrants its own audit: workspace path aliases leaking into the
published tarball. The class is structural and invisible to most
in-monorepo gates.

The setup that creates the leak is innocuous. A package's source
tree imports a sibling module via a workspace alias —
`@/lib/foo`, `@/components/bar`, or a deep relative path like
`../../../shared/helpers`. The monorepo's `tsconfig.json` resolves
the alias cleanly through its `paths` map. The build emits to
`dist/` carrying the alias verbatim. Every in-monorepo gate
passes — type-check resolves, source-mode audits resolve,
package-build emits artifacts.

A standalone consumer running `npm install @pleach/<pkg>` cannot
reach those aliases. The published tarball has no `tsconfig.json
paths` to resolve them against. The first `import` from the
package fails with `ERR_MODULE_NOT_FOUND`.

Two audits gate this class.

### `audit:package-runtime-deps` [#auditpackage-runtime-deps]

Scans `dist/**/*.{js,cjs,mjs}` for third-party `import`/`require`
specifiers and verifies every package name is declared in
`dependencies`, `peerDependencies`, or `optionalDependencies`.
Catches the case where a runtime import resolves to a sibling
package that's only declared as a `devDependency` — fine in the
monorepo, broken in a consumer install.

### `audit:package-dist-runtime-aliases` [#auditpackage-dist-runtime-aliases]

Scans `dist/**/*.{js,cjs,mjs}` for workspace-internal aliases
(`@/lib/...`, `@/components/...`, `@/hooks/...`, and every other
top-level workspace alias) and bare relative paths that escape
the package root (`../../../...` chains walking above `dist/`).
Either class resolves only inside the monorepo; both surface as
runtime failures in a standalone consumer.

The audit has two modes. Single-package mode (`--package <path>`)
scans one package, used during routine PR-time gating.
Multi-package mode (`--all-packages`) auto-discovers every
`@pleach/*` package under `packages/` and scans each one — used
before any coordinated multi-package release or first-publish
rehearsal.

`:include-dts` extends the scan to `*.d.ts` files. Type-only
leaks break consumer `tsc` even when the runtime build resolves,
since a consumer's TypeScript can't see the workspace alias
either. The runtime sweep and the type sweep are independent —
both must pass for a clean publish.

The remediation pattern, when an alias leak is found, is one of:

* Verbatim relocate the imported module into the package's own
  source tree, eliminating the cross-tree import.
* Dep-invert via a host-supplied factory — the package defines
  an interface; the host implements it at runtime.
* Add the import to the package's per-package allowlist with
  a documented rationale (used for genuine consumer-extension
  hooks where the alias points to an opt-in host integration).

See [Publishing contract](/docs/publishing-contract) for the
broader pre-publish gate set this audit pair sits inside.

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="Subpath exports" href="/docs/subpath-exports" description="Which subpaths are stable, which are unstable wildcards." />

  <Card title="Schema" href="/docs/schema" description="The schema-bundle file list and the additive rule." />

  <Card title="Audit ledger" href="/docs/audit-ledger" description="`AUDIT_RECORD_VERSION_HISTORY` and the version-bump contract." />

  <Card title="FAQ" href="/docs/faq" description="The FSL-1.1-Apache-2.0 license question, distilled." />
</Cards>
