Skip to content

Release 1.44.1: DEV to main promotion - #3110

Merged
jung-thomas merged 21 commits into
mainfrom
DEV
Nov 11, 2026
Merged

jung-thomas merged 21 commits into
mainfrom
DEV

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Release 1.44.1 — DEV → main promotion

Promotes DEV to main for the 1.44.1 PROD release. DEV is ahead of main by 21 commits (8 fix PRs + release prep).

⚠️ Merge instruction

Merge commit (NOT squash) per docs/developers/operations/branching-strategy.md §Release — keeps DEV an ancestor of main. A squash here would make DEV show as unmerged forever and replay the whole diff on the next release.

What 1.44.1 ships to PROD (all fixes — patch bump 1.44.0 → 1.44.1)

PR What
#3109 release prep: version bump 1.44.0→1.44.1 + What's New refresh (14 entries)
#3108 tutorial backend deploys reliably again — patch-package → dependencies (unbreaks CF staging)
#3107 quieter caching under multi-instance load — atomic UPSERT in cds-caching
#3106 readable concept/topic/channel pages — container padding + capped relationship walls
#3105 / #3104 friendlier errors — missing-BLOB race degrades to styled page, not raw 500; transient blips not cached
#3101 tutorial file downloads work again — base64url encode fix in render-link hook
#3100 feedback moderation works from the tutorial page — nav-path key resolution
#3097 / #3091–#3094 / #3092 interactive directive-demo tutorials render correctly (code blocks, rich media, content reuse, tested-with panel, hotspots, Joule panel, rebuild pipeline)

Post-merge — deploy is a SEPARATE, explicitly-authorized step

Merging this PR does not redeploy PROD. PROD deploy (full MTA from fresh origin/main) runs separately after go-ahead.

Content-rebuild dependencies to remember at PROD deploy time:

🤖 Generated with Claude Code

…eedbackIrrelevant (#3080)

The markFeedbackIrrelevant / unmarkFeedbackIrrelevant actions are bound to
TutorialFeedback, but the admin UI renders their buttons inside the
feedbackItems LineItem on a Tutorials object page. That invokes them through
the navigation path Tutorials(...)/feedbackItems(<id>)/markFeedbackIrrelevant,
so req.params is [tutorialKey, feedbackKey]. The handler read req.params[0]
(the tutorial key), so the feedback lookup never matched -> consistent
'Feedback entry not found' 404.

Resolve the bound entity's own key from the LAST req.params entry, which
works for both the direct single-key path and the two-key navigation path.
Add regression tests covering the navigation-path invocation for both
actions.
…k-irrelevant

fix(admin): Mark Irrelevant feedback action fails with 404 (#3080)
Hugo's `replace` is `replace INPUT OLD NEW`, so the pipe chain
`$b64 | replace "+" "-" | replace "/" "_" | replace "=" ""`
appended $b64 as the third (NEW) argument at each step, searching the
literal "+"/"/"/"=" and discarding the base64 entirely. The emitted
attachment-source param collapsed to "u=/=/", which the server
base64url-decoded to an invalid URL — ingest failed and every tutorial
download link returned 404 "Attachment unavailable" (old and new
tutorials alike).

Use nested replace() calls with the string as the first positional arg.
Add a behavioral regression test that renders the real hook through Hugo
and asserts the u= param base64url-decodes back to the exact source URL;
the previous string-grep test never rendered Hugo, which is why the bug
shipped.
…-3098

fix(attachments): correct base64url encode in render-link hook (#3098)
…3103)

Content page loads (/concepts/:slug, /tutorials/:slug) intermittently
returned a raw 500 "Internal Server Error". Root cause: serveStoredSlug
reads BLOB metadata and the BLOB in two separate queries (to avoid the
HANA LOB-locator-expiry gotcha). When metadata hits but the separate
BLOB read returns nothing — the inconsistent-read window during a
concurrent publish/delta-write to the mutable ContentCurrent, or a
transient HANA locator blip — blobRow.CONTENT / toBuffer(null) threw,
and serveHandler's catch surfaced raw JSON instead of the styled page.

- serveStoredSlug: guard null/missing/non-Buffer/zero-length BLOB →
  return 'not-found' (styled 404); emit content.blob.missing metric.
  Both HANA and SQLite branches now coerce via toBuffer + share the guard.
- serveHandler catch: serveNotFound(res, slug, 500) (styled page, 500
  kept for monitoring) instead of raw JSON — mirrors the catalog branch
  and the fail-open pages path.
- serveNotFound: no-store on 5xx so a transient blip served as a styled
  500 is never edge-cached for 60s (404s stay cacheable).
- resolveContentBlob: same toBuffer(null) guard in both branches, so the
  error-page/nav resolver can't throw on the identical race.

Covers srv and srv-qa (both shim over @tutorials/content).
Test: publish a slug, null its content column (metadata intact), assert
styled 404 not 500. Red before, green after.
…e-500-resilience

fix(content): degrade missing-BLOB race to styled page, not raw 500 (#3103)
…rror noise (#3102)

KeyvCDS.set did a read-then-create/update (TOCTOU). Under multi-instance
CF two instances both read 'no row', both INSERT the same CacheStore key,
and the second INSERT violates the PK unique index — logged at error level
(the PROD kg:rl:* rate-limiter noise) with the losing write dropped.

Replace that branch with an atomic single-key UPSERT (last-write-wins) via
a committed patch-package patch against cds-caching@2.1.0. Add a regression
test driving the real KeyvCDS against an in-memory CAP fixture, modelling
the TOCTOU window deterministically.
…3102)

Add patch-package devDep + a postinstall hook so the cds-caching@2.1.0
UPSERT patch re-applies on every npm ci / npm install. prepare (git hooks)
is left intact; both hooks fire on install.
…500 (#3103)

Two compounding defects on /tutorials/:slug and /concepts/:slug serve paths:

1. serveStoredSlug read the BLOB metadata and the BLOB itself in separate
   queries (correct — avoids HANA LOB-locator-expiry). During a concurrent
   delta-write to mutable ContentCurrent (overwrite-in-place, no version pin)
   or a transient HANA locator blip the metadata row could survive while the
   BLOB SELECT returned null/undefined, causing toBuffer(null) or
   blobRow.CONTENT dereference to throw.

2. serveHandler's catch returned res.status(500).json({error:'Content retrieval
   failed'}) — raw JSON. All sibling branches (pageServeHandler,
   authorServeHandler, advocateServeHandler) already call serveNotFound; this
   path was the outlier.

Fix:
- Guard the BLOB read: null/undefined/non-Buffer/zero-length contentBuf →
  return 'not-found' (styled 404). Emits content.blob.missing metric for
  observability of the race condition.
- Align serveHandler catch with siblings: serveNotFound(res, slug, 500)
  (styled page, status 500 preserved for monitoring).

Test: publish a slug, null its content column (metadata intact), assert
HTTP 404 not 500. Red before, green after.
…ing-blob-500

# Conflicts:
#	packages/content/content-store.js
…-blob-500

fix(content-serve): degrade missing-BLOB race to styled 404, not raw 500 (#3103)
fix(#3095): concept/topic/channel page layout — container padding + cap relationship walls
…hing-upsert

fix(cds-caching): atomic UPSERT in KeyvCDS.set to stop PK-collision error noise (#3102)
…es prod prune

CF stages tutorials-srv with NODE_ENV=production, which prunes
devDependencies. The postinstall hook runs `patch-package` (added in #3107
to apply patches/cds-caching+2.1.0.patch, a runtime dependency), but
patch-package itself was only in devDependencies — so the prod prune removed
the binary and staging failed with:

  npm error command sh -c patch-package
  npm error sh: 1: patch-package: not found   (code 127)
  **ERROR** Unable to build dependencies: exit status 127
  BuildpackCompileFailed

Moving patch-package to dependencies keeps the binary in the pruned prod
tree so the postinstall completes and the cds-caching patch applies at
staging time. cds-caching is itself a runtime dependency, so the patch must
apply in the production install.

Broke the 2026-11-11 DEV deploy (tutorials-srv staging, all 3 retries).
fix(deploy): move patch-package to dependencies (unbreaks tutorials-srv staging)
Bump .deploy/mta.yaml 1.44.0 -> 1.44.1 (patch; the DEV->main delta is all
fixes, no new features) and refresh hugo/data/whats_new.json with 14 merged
PRs since 1.44.0 (total 1051 entries), summarized via the whats-new skill.

This lands on DEV first; a separate DEV->main promotion PR ships it to PROD.
release 1.44.1 prep: version bump + whats-new refresh
@jung-thomas
jung-thomas merged commit 8b08012 into main Nov 11, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant