Release v0.79.0 — Audit remediation consolidation + contract evolution
Date: 2026-05-30
Tag: v0.79.0
Summary
Minor release consolidating the full v0.78.4 post-hardening quality-audit remediation (eight phases) plus a test & evidence-hardening pass, cut from the development line. It closes the audit’s adoption gates — funnel integrity, evidence-vs-claim CI gating, licence observability, and supply-chain/legal — and ships the user-approved pre-1.0 API contract evolution with backward-compatible deprecation aliases. One critical security fix (SEC-1), a broadened credential-collision check (SEC-3), and a DNS-rebinding TOCTOU fix (XC-3) are included.
This is a minor bump (not a patch): a new canonical route, a new consumer-visible batch error-envelope shape, and the deprecation of an existing route are contract evolution under SemVer even with backward-compatible aliases.
Version-line note
The previous tags v0.78.5 and v0.78.6 were cut from the release/v0.78 maintenance branch and were never merged back into the development line, so this release advances from v0.78.4. The only development-relevant content unique to those patch tags — #50’s evaluator/auth documentation corrections (including a wrong templateKey → template field in a runbook curl example) — is forward-ported into this release. The container-image license label and the public-pull guardrail introduced during the audit make this line strictly ahead of v0.78.6 on those surfaces.
API contract evolution (pre-1.0, backward-compatible)
These are the only consumer-visible contract changes. Both are additive with one-version deprecation windows; existing callers keep working.
- Canonical
POST /render/pdf. PDF rendering now has a dedicated path matching every other format (/render/docx,/render/pptx, …). The barePOST /renderis retained as a deprecated alias (operationIdrenderPdfLegacy,deprecated: trueinopenapi.json) for one version. Migrate toPOST /render/pdf. - Unified batch sub-item error envelope. A failing item in any batch surface — sync PDF/DOCX/PPTX results, NDJSON streaming lines, async job results, and the webhook payload — now carries a single shape:
{ success: false, error: { code?, message, details? } }(the sharedBatchItemErrorSchema), wherecode, when present, is aRenderErrorCodeSchemavalue (it is optional because not every failure maps to a render-error code). Previously these were bare scalars that differed per surface. Both the TypeScript and Python SDK batch result types were updated to match.
OpenAPI request/response schemas also gained example payloads (previously none), improving generated-client and evaluator ergonomics.
Security
- SEC-1 (critical) — per-template rate-limit fail-open is honored. The Redis-failure branch in the per-template limiter read object truthiness instead of the
RATE_LIMIT_FAIL_OPENflag, so an operator who explicitly opted into fail-open still got fail-closed429s during a Redis outage. Corrected to read the flag, with a mocked-Redis-failure regression test asserting requests are allowed when fail-open is set. - SEC-3 — credential-collision detection broadened. The startup “named-user key must be distinct from all API keys” check omitted several real verification secrets. It now covers
API_KEY_SUPER_ADMIN, bothAPI_KEYS_JSONandAPI_KEYS_JSON_FILEforms, the verify-only rollover secrets (API_KEY_EDITOR_PREVIOUS/API_KEY_ADMIN_PREVIOUS), and — when OIDC is configured (OIDC_DISCOVERY_URL+OIDC_COOKIE_SECRET) — the secret derived fromOIDC_COOKIE_SECRET. The shared loader/parser was relocated to a neutral module to avoid an import cycle;getActiveApiKeys()keeps its signature. - XC-3 — DNS-rebinding TOCTOU closed on outbound fetch. Webhook delivery and scheduled URL data sources validated the target’s resolved IPs at submission time, then later re-resolved the hostname inside a retry loop, reopening the rebinding window. Both now route through a shared
safeFetchthat resolves the hostname once, rejects any private/internal resolved IP, and pins the connection to the validated IP (via a custom dispatcher) while preserving the originalHostheader and TLS SNI.
What landed (by audit gate)
Funnel integrity
The dead releases link was repointed to the public mirror; the broken TypeScript-SDK snippet was fixed (a typed client.templates.generate(...) now exists); and the advertised-but-unpublished SDKs carry honest “ships in-repo only — not yet on npm/PyPI” caveats (see the standing residual below).
Evidence-vs-claim CI gating
The published Python SDK is now gated in CI (pytest + ruff + mypy, run with the package as the working directory) instead of only syntax-checked; the DOCX/PPTX/chart renderers gained test:coverage scripts with explicit thresholds wired into the coverage gate; and a tenant-propagation CI check now runs the check-tenant-propagation script the docs already advertised.
Licence observability
Two Prometheus gauges — pulp_engine_licence_valid and pulp_engine_licence_days_until_expiry — are refreshed at scrape time (not only on readiness), with valid=1 only for a genuine unexpired commercial licence and a sentinel -1 for evaluation/invalid so an expired or removed licence can never latch a stale positive. PromQL for an expiry alert (gated on validity so evaluation deployments never page) is documented in docs/licence-key-format.md.
Supply-chain / legal
A root LICENSE carrying the commercial/evaluation terms; a generated THIRD-PARTY-NOTICES.md (Permissive / Weak-copyleft / Copyleft / Unknown buckets); and a CI license-compliance gate that fails the build on copyleft (GPL/AGPL/SSPL) or any unclassified third-party dependency, scoped to external deps only. Container images now stamp org.opencontainers.image.licenses=LicenseRef-PulpEngine-Commercial.
Test & evidence hardening (Phase 8)
- Branch-aware coverage of the editor’s
tree-utilsimmutable tree ops (40 cases), including thetransplantNodeancestor guard. - A SQL Server ↔ Prisma schema-parity guard (25 cases) that builds the effective SQL schema across migrations (CREATE + ALTER in order) and asserts column/NOT-NULL/unique/FK-onDelete parity against the canonical Prisma schema — drift surfaces at build time instead of at runtime for SQL Server customers.
- The previously-parked
plugin-storage-activationtest un-parked and made deterministic. - The SQL Server v8-coverage exclusion formalized as by-design (gated behaviorally by the real-mssql CI job + the new parity test); the API coverage floor held at 73/63, which the CI-measured 73.66/64.01 confirms with safe margin.
Verification
Local pre-tag verification on the release commit:
check-version.mjs(in the pre-tag-release-head mode CI uses,PULPENGINE_ALLOW_UNTAGGED_RELEASE_HEAD=1) — all seven manifests aligned at0.79.0, with the[0.79.0]CHANGELOG section + link and this release note present. The plain off-tag invocation intentionally requires an[Unreleased]top section; the strict tagged-path check runs at tag time.pnpm typecheck— 46/46.pnpm lint— 22/22.- Full CI on the constituent merges was green on
main, including the Linuxci/Test, Linuxtest-file-mode,CI — Windows,Docker build + smoke,test-sqlserver, and both e2e jobs. Two CI flakes surfaced post-merge and were fixed: the PreviewPanel async-capability render race (synchronousgetByRole→ asyncfindByRole) and the plugin-storage/health/readyrenderer-warmup coupling (assert the plugin-owned check, not the aggregate 200).
Known residuals
- SDK trusted-publisher registration is still pending on npm and PyPI. No
@pulp-engineSDK has yet published to a public registry; the publish workflows now fail loudly at the registry-auth step (the earlier silent-masking behavior was fixed). This is a one-time operator-side configuration on npmjs.org and pypi.org — code is fine, and publication can be dispatched retroactively against an existing tag once the trusted publishers are registered. The in-repo SDK caveats remain in place until a registry lookup confirms the packages are live. - HA nightly Check 7 (single-replica outage failover) measures availability below its 99% threshold during the replica’s own restart — a mis-calibrated bar for a single-replica topology rather than an API/failover regression. Tracked separately from the per-commit CI gate.