pleach
Reference

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.

BumpTriggers
PATCHBug fixes, performance improvements, internal refactors with no API surface change
MINORNew typed config fields, new optional plugin hooks, new subpaths, new SQL files in the schema bundle, new StreamEvent variants, new error codes
MAJORRemoval 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

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

SurfaceWhy
Internal channel names (__planning__, __synth__, etc.)Internal; not part of the public contract
Loader-seam keys under setHarnessModuleLoaderSurface 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: trueDeterminism 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:

  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

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

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:

FieldPurpose
versionThe wire version this entry describes
shippedInThe @pleach/core package version that introduced it
summaryShort prose description
addedFieldsField names added on this version
removedFieldsField 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:

Future version migrations (@pleach/core@2.x3.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

On this page