Versioning policy
Semver, deprecation, schema-bundle versioning, and `AUDIT_RECORD_VERSION_HISTORY`.
@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
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
StreamEventdoesn't breakswitch-based consumers that have adefaultarm. - 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
pleachVersionfield on the fingerprint.
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
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:
- The package's
CHANGELOG.mdfor the version that deprecates. - A
@deprecatedJSDoc on the type definition. - 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
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>.sqlwith the next availableNNN. 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 anauditRecordVersionevent (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
The wire-format version of AuditableCall rows. Bumps are gated
by an upstream audit (audit:auditable-call-version) and
coordinate with consumer adapters.
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
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 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).
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
@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
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 — not a version migration, a tool migration.
- 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
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-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 for the broader packaging conventions.
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
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
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 for the broader pre-publish gate set this audit pair sits inside.
Where to go next
First-publish packaging contract
Dual ESM/CJS emit, types-first exports ordering, tsup dir-style entries, tsc-built DTS, sourcemap-free tarballs, peer-dep tightening — the package-build conventions every @pleach/* SKU follows at 1.0.
@pleach/base-tools changelog
Release history for @pleach/base-tools — site-docs entries that materially touched the @pleach/base-tools surface, newest-first. Canonical runtime history lives upstream.