Pulp Engine Document Rendering
Get started
Release v0.24.0

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.errorsresponse.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:

EndpointOld shapeNew 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 updatedValidationResultSchema 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=success ci.yml run 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.__migrations tracking 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.sql onward.
    • Legacy — tables exist but dbo.__migrations does not (database was bootstrapped before versioned migrations were introduced); creates dbo.__migrations and applies only the pending migrations (e.g. 002_add_created_by.sql).
    • Versioneddbo.__migrations already exists with rows; applies only migrations not yet recorded.
  • 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 .json file. 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).
  • useTemplateSave hook — 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: src starts with /assets/ and contains no {{.
  • Dynamic/templated sources (e.g. {{logo.url}}) are not checked.
  • Fix: upload the missing file or correct the src before 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 errors field on POST /templates/:key/validate is 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

  1. Pull v0.24.0.
  2. Apply database schema changes:
    • SQL Server: pnpm --filter @pulp-engine/api db:migrate:sqlserver
    • Postgres: no migration needed
    • File mode: no action needed
  3. Restart the API.
  4. Update any client code that reads response.errors from POST /templates/:key/validate to use response.issues instead.