Pulp Engine v0.24.0 — Release Notes
Commercial-readiness improvements
This release is a stability and consistency pass with no new end-user features. It hardens the release process, matures the SQL Server migration system, fixes editor undo/history behaviour, closes a validation API inconsistency, and corrects operator documentation.
The only breaking change is a field rename in the template-validation endpoint.
Breaking change
POST /templates/:key/validate now returns { valid, issues } instead of { valid, errors }.
The errors field has been removed from the HTTP response body.
Migration: rename any reference to response.errors → response.issues.
- const { valid, errors } = await fetch('/templates/my-template/validate', ...).json()
+ const { valid, issues } = await fetch('/templates/my-template/validate', ...).json()
What changed
Unified validation result shape
Previously, the two validation endpoints returned different field names:
| Endpoint | Old shape | New shape |
|---|---|---|
POST /render/validate | { valid, issues } | { valid, issues } (unchanged) |
POST /templates/:key/validate | { valid, errors } | { valid, issues } |
Both endpoints now return:
{
"valid": true,
"issues": []
}
On failure:
{
"valid": false,
"issues": [
{ "message": "...", "path": "customer.name" }
]
}
path and code are optional in all cases. code is present only on POST /render/validate
items (values: INVALID_TEMPLATE, RENDER_ERROR). path is present on both endpoints
when a specific field is implicated.
OpenAPI spec updated — ValidationResultSchema in the generated spec now reflects
issues on both routes.
CI-gated release enforcement
.github/workflows/release.yml now requires a passing ci.yml run on the exact release
commit before any Docker build or GitHub Release is created.
The check-ci job:
- Verifies the release commit is an ancestor of
origin/main(no detached-ref or local-only releases). - Queries the GitHub Actions API for a
branch=main,event=push,conclusion=successci.ymlrun scoped to the exact commit SHA. - Times out after 10 minutes with a non-zero exit if CI is still running or no run is found.
- Runs before the Docker build and GitHub Release jobs; both are blocked until CI is confirmed.
This replaces the previous convention of tagging only after CI passed manually.
SQL Server migration maturity
The db:migrate:sqlserver script has been rewritten as a fully versioned, transactional
migration system.
- A
dbo.__migrationstracking table records which migration files have been applied. - Each migration runs inside a transaction; rollback occurs automatically on failure and the script exits non-zero, naming the failing file.
- Three-state bootstrap detection handles all upgrade paths:
- Fresh — no tables present; applies all migrations from
001_init.sqlonward. - Legacy — tables exist but
dbo.__migrationsdoes not (database was bootstrapped before versioned migrations were introduced); createsdbo.__migrationsand applies only the pending migrations (e.g.002_add_created_by.sql). - Versioned —
dbo.__migrationsalready exists with rows; applies only migrations not yet recorded.
- Fresh — no tables present; applies all migrations from
- Safe to re-run at any time; already-applied migrations are skipped.
A new migration file, 002_add_created_by.sql, adds the created_by columns to
template_versions and assets for existing SQL Server databases. Fresh installations
with v0.24.0+ get these columns from 001_init.sql directly.
SQL Server operators upgrading from v0.23.0 or earlier must run:
pnpm --filter @pulp-engine/api db:migrate:sqlserver
The runner detects the existing database state and applies only the required migrations.
Rich-text undo/history fix
Internal sync operations between the visual editor and the editor store now bypass the
undo history via a new updateNodeSilent() store mutation.
Previously, switching between the Visual and JSON tabs on a richText node caused the
tab-switch sync to write a spurious entry into the undo stack. This made Ctrl+Z
unpredictable — it would undo the sync rather than the user’s last deliberate edit.
With the session-boundary pattern now applied to richText, only user-initiated changes from the visual editor are recorded in the undo history. Tab switches and other internal syncs are transparent to the history stack.
Template picker freshness fix
Reopening the template picker dialog quickly no longer results in stale data being
applied. A sequence counter (loadSeqRef) is incremented on each open event, and any
API list response that was initiated before the latest open is discarded before it can
update state.
Editor workflow improvements
- TemplateLoaderDialog — a new “Load JSON” dialog allows loading a template from a
pasted JSON object or a local
.jsonfile. The dialog validates the top-level structure (key, name, version, inputSchema, document) before applying the template to the editor. Invalid JSON or missing required fields show an inline error. The template loads in local mode (source badge:local). useTemplateSavehook — the save/publish workflow (create vs. update, conflict detection on 412, duplicate-key detection on 409, status flash messages) is now a shared hook used across all save entry points. This eliminates duplication and ensures consistent conflict handling regardless of which UI action triggers the save.
Operator documentation corrections
The v0.23.0 release notes contained SQL Server upgrade instructions that required
operators to run two ALTER TABLE statements manually. Those instructions have been
corrected: the db:migrate:sqlserver runner handles this automatically via
002_add_created_by.sql.
Named-user-only standalone configuration
EDITOR_USERS_JSON without any shared API keys is now an explicitly supported production
configuration. The auth plugin validates at startup that at least one credential source is
present — either a shared key or a named-user registry entry. Deployments that manage all
editor access via per-user credentials no longer need to set any shared API keys.
Save reconciliation for inactive tabs
useTemplateSave now correctly updates the originating tab’s UI even when the user switches
to a different tab while the save request is in flight. A sequence nonce is captured at the
start of each save; the server response is applied only if no new edits have been made in the
originating tab since the save started. This prevents stale version numbers and status
messages from being written back to the wrong state.
Admin-capable editor session token semantics
When a named user’s role in EDITOR_USERS_JSON is "admin", requests authenticated via
their editor session token are granted admin scope at request time — not embedded in the
token. Role changes take effect immediately after updating EDITOR_USERS_JSON and restarting
the API; no token re-issuance is required to downgrade a user.
This behaviour applies in named-user-only mode and in mixed mode (named users alongside shared keys). The token format is unchanged.
Publish blocked for missing static asset references
Template publish is now hard-blocked (not advisory) when an image node references a static
/assets/… path that does not appear in the uploaded asset list. This check runs in the
PublishGate dialog before the render preflight.
- Static path:
srcstarts with/assets/and contains no{{. - Dynamic/templated sources (e.g.
{{logo.url}}) are not checked. - Fix: upload the missing file or correct the
srcbefore publishing.
This prevents publishing templates that would produce broken images at render time.
validate-deploy.sh scope clarification
The API_KEY argument comment in scripts/validate-deploy.sh has been corrected. It
previously stated “any scoped API key (ADMIN or RENDER scope)”. The accurate requirement is
an admin-scoped key: check 4 (GET /templates) accepts admin or editor scope; check 5
(POST /render/html) accepts render or admin scope — only admin covers both.
Backward compatibility
- All API routes are unchanged. Only the
errorsfield onPOST /templates/:key/validateis removed. - SQL Server migration runner is backward-compatible with existing databases. The three-state bootstrap detection applies only the migrations needed for the current database state.
- Postgres and file-mode deployments require no migration for this release.
- Editor improvements (TemplateLoaderDialog, useTemplateSave hook, save reconciliation, undo fix, picker freshness) are internal changes. They do not affect the API contract or stored data format.
- Named-user-only mode, admin-capable token semantics, and publish blocking for missing assets are additive changes. No existing configuration, API surface, or token format is affected.
Upgrading
- Pull v0.24.0.
- Apply database schema changes:
- SQL Server:
pnpm --filter @pulp-engine/api db:migrate:sqlserver - Postgres: no migration needed
- File mode: no action needed
- SQL Server:
- Restart the API.
- Update any client code that reads
response.errorsfromPOST /templates/:key/validateto useresponse.issuesinstead.