Skip to content

release 1.41.8: CF staging fix + event-app improvements - #2964

Merged
jung-thomas merged 37 commits into
mainfrom
DEV
Nov 1, 2026
Merged

jung-thomas merged 37 commits into
mainfrom
DEV

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Summary

Promote DEV → main for the 1.41.8 production release.

The triggering change is the CF buildpack staging fix (#2961): the CVE-2026-111497 Node pin used a >=22.23.4 <23 range that the CF nodejs buildpack rejects as "improper constraint", which broke tutorials-srv staging on DEV. This release pins the exact runtime and adds a build-time cache fallback so a down srv can't deadlock a redeploy.

Note: the 1.41.8 version bump + what's new digest land via release PR #2963 (worktree-release-1.41.8 → DEV). This promotion carries them to main once #2963 is merged into DEV.

Highlights

Fixes

Features

Deploy plan (after merge)

  • Prod deploy runs from the primary checkout on main (deploy-mta Step 0 refuses a worktree); blue-green, operator-gated swap
  • Verify ChatSettings.enabled stays true post-deploy
  • Content publish into PROD after deploy (MTA does not re-bake tutorial BLOBs)

Test plan

  • DEV deploy of 1.41.8 green (done — tutorials-srv staged + smoke 342 passed / 0 failed)
  • Prod blue-green staging succeeds with the exact Node pin
  • Post-deploy smoke diff vs. prod baseline shows no new regressions

architecture-sap-com-fetcher derived the live-site URL purely from the
GitHub file path (.../RA0029/readme), but the Architecture Center
(Docusaurus) routes docs by their `slug:` frontmatter under /docs
(e.g. /docs/ref-arch/98efa0). ~87% of docs carry a slug, so the
path-derived URLs 404 — this is why every "Doc ↗" Architecture Center
link on /concepts/agent-gateway (all 16 RA0029 docs) was broken.

Parse `slug:` from frontmatter and build `${SITE_BASE}/docs${slug}`
when present; keep the path-derived URL as fallback for the no-slug
minority. Verified against the live site: /docs/ref-arch/98efa0 → 200.

Note: stored HelpDocs rows keep their old URLs until a help-docs
re-fetch/re-ingest repopulates them — code fix is forward-correct.
…lug-urls

fix(help-docs): honor Docusaurus slug for Architecture Center URLs
Brotli C decoder out-of-bounds write (CWE-787/CWE-252). Node 22.x is
vulnerable below 22.23.4. Raise engines.node floor so the CF
nodejs_buildpack installs a patched 22.x at staging; cap <23 to stay on
the buildpack-supported 22 line.

Reachable only via undici auto-decompression of Content-Encoding: br
from outbound fetches (all trusted SAP/GitHub hosts over TLS), so this
is a defense-in-depth runtime bump, not an active-exploit hotfix.
…1497)

srv-qa deploys as a separate CAP module from its own package.json +
lockfile; dep-parity guard requires engines.node to match root.
…1497)

The separate tutorials-mcp MTA stages tutorials-srv-mcp from gen/srv-mcp,
whose package.json is copied from committed srv-mcp/. Raise its Node floor
to match so a patched 22.x is installed at staging. No tracked lockfile;
not an npm workspace member, so package.json alone is authoritative.
Navigating into a track only mutated local state, so a page refresh reset
to the track list and the browser back/forward buttons did nothing. Reflect
the selected track in a `track` query param via history.pushState, restore
it on load, and listen for popstate so back/forward move between the list
and a track. Unknown/stale ids fall back to the list. Existing eventId/theme
params are preserved.
fix(app-space): persist selected track in URL for refresh + back/forward
fix(security): pin Node runtime floor to 22.23.4 (CVE-2026-111497 Brotli)
Reduces .hero padding from 2.5rem 2rem 1.5rem to 0.75rem 2rem so that
missions in the lower row are visible without scrolling on the external
monitors used at TechEd app-space workstations.

Also trims .hero-instructions margin-top from 1.5rem to 0.75rem and
updates the mobile (<640px) override to match.

Closes #2941
…nner-padding

fix(app-space): reduce hero banner padding to surface more missions (#2941)
…andarin (#2943)

Arabic: fix hundreds spelling (مية→مائة for 300/400/500/600/700/800/900), fix 2000
  dual (الفا→ألفان), fix 5000 spacing (خمسةالاف→خمسة آلاف).
Hebrew: fix 2000 dual (אלפים→אלפיים), fix ו-connector spacing so it prefixes the
  next element rather than appending to the current one (אלף ו אחת→אלף,ואחת).
English: remove stray comma from thousands separator (one thousand,  one→one thousand,one).
Mandarin: eliminate stray space after 零 by joining zero-suffixed elements with
  their following token (一千零 一→一千零一).
…they self-heal

The event display seeded bucket counts once from getEventBuckets, then only ever
incremented them from socket completionTick/tutorialCompleted events. Any missed
socket event — reconnect gap, multi-instance session flap, dropped long-poll
cycle — was lost permanently, so an all-day kiosk drifted below the true total
and only a full page refresh (which re-runs getEventBuckets) resynced it. This is
the 'numbers do not update live / have to keep refreshing the kiosk' report.

Root cause is client-side: the display had no reconciliation path. The Redis
adapter, Gorouter JSESSIONID/__VCAP_ID__ affinity, and the WS transport are all
working; they just cannot guarantee zero missed events over hours, and the client
had no safety net.

Add reconcileBuckets() (pure, unit-tested) and a 15s periodic getEventBuckets
re-fetch that heals drift WITHOUT rewinding counts the live socket already
advanced past (monotonic merge, avoids visible backward jumps). Socket deltas
keep the display snappy; reconciliation guarantees it converges to truth.

No infra change. Verified: reconcile unit tests (3) + existing event-stream/
display suites (14) green; display-app build clean.
…track completions

The per-hour rate was ALWAYS ZERO at events whose completions didn't resolve to
a mission CompletionPath. Root cause (observed by running the app): the rate
(messageTimestamps -> speedApproximation) was fed ONLY from tutorialCompleted,
which the server emits only for completions that map to a track. completionTick
— emitted for EVERY completion, track or not — did not feed the rate. So when
completions were off-track, tutorialCompleted never fired and per-hour stayed 0
for the whole event, even though the hero total (completionTick) ticked up.

Feed the rate from completionTick instead (every completion); remove the
duplicate feed from applyUpdate to avoid double-counting track completions that
fire both events. Extract completionsPerHour() as a pure, unit-tested function
and lower the compute threshold from 3 to 2 samples.

Verified live: 4 untracked completions now show per-hour=4225 (was 0).
…-2943

fix(display-app): correct number-words for Arabic, Hebrew, English, Mandarin
fix(display): kiosk live updates + per-hour — two root causes found by running the app
When client_payload.slugs contains N tutorials and all of them 404 on
fetch, the guard step was passed the full slug list via PUBLISH_SLUGS but
none of the tutorials rendered (fetch-tutorials skips them and exits 0).
check-approuter-assets.cjs die()s with "no rendered page for slug(s):..."
causing a false infra failure instead of a recoverable author warning.

Root cause: Detect author source errors only checked client_payload.slug
(singular). The multi-slug path (client_payload.slugs) was never filtered
against errors.json, so all 5 unrendered slugs reached the guard.

Fix:
- Extend the detection step to also filter client_payload.slugs against
  errors.json, producing good_slugs (rendered) and bad_slugs (failed).
- Guard, publish, and verify steps use good_slugs instead of the raw
  client_payload.slugs, so they only process slugs that actually rendered.
- All three steps share a new skip condition that covers both the
  single-slug all-fail case (bad_slug != '') and the multi-slug all-fail
  case (good_slugs == '' && client_payload.slugs != '').
- Partial failures (some slugs render, some don't) proceed on the good
  slugs only, with ::warning annotations for the bad ones.

Single-slug (#2384) path is unchanged — bad_slug/source_repo/issue-filing
flow preserved exactly.
…arcode (#2952)

The App Space track page loaded progress only once on mount, so a contestant
who completed a tutorial had to do a full browser refresh to see the track
update and unlock their prize barcode. The WebSocket realtime path fires
confetti/toasts but never re-fetches progress or flips the barcode gate.

Add a Refresh button in both the overview and track-detail headers that
re-calls /api/getAppSpaceProgress, re-applies the payload, and reveals the
checkpoint barcode in place. A refresh nonce cache-busts the barcode PNG so a
newly-unlocked code is re-pulled, the open track is preserved across the
refresh, and a "Progress updated" toast confirms the action.
…g-source-errors

fix(ci): handle multi-slug source errors in QA rebuild (#2951)
…h-2952

feat(app-space): Refresh button to re-check completion & show barcode (#2952)
)

- Add isIrrelevant : Boolean default false and irrelevantReason : String(500)
  to ims.TutorialFeedback entity in db/schema.cds
- TutorialFeedbackAggregate: exclude flagged rows via
  where isIrrelevant is null or isIrrelevant = false
- AuthorSurveyDistribution: extend all 7 UNION ALL legs with the same
  isIrrelevant exclusion predicate alongside existing null guards
- Add isIrrelevant filtering describe block to tutorial-feedback-aggregate.test.js

Note: CDS compiler v7 does not support IS NOT TRUE predicate; used
  (isIrrelevant is null or isIrrelevant = false) which is semantically
  equivalent and handles pre-migration NULL rows correctly.
1. TechEd fixture date-rot: sessions in teched-feed.json were dated
   2026-10-27, now in the past — ?upcoming=true correctly filtered them
   out, breaking the endpoint and all-day tests. Bump dates to 2026-12-15
   (actual TechEd Berlin date). Update matching test comments.

2. Task 11 duplicate describe block: reset-tutorial-progress.test.js had
   two Task 11 blocks — the first (lines 504-524) correctly unit-tests
   computeBuckets() directly. The second block tested getEventBuckets()
   via DisplayService expecting the old per-user histogram shape, which
   was replaced by computePathBuckets() track-bar grouping in commit
   17a3035. The stale second block tested a contract that no longer
   exists and had no startDate/endDate on the fixture event, so
   computePathBuckets() exited early returning []. Remove it; the
   SUPERSEDED behavior is already covered by the first block.
…magpie

feat(feedback): mark entries as irrelevant/inappropriate (#2947)
…ures

fix(test): repair 3 CI unit test failures
When XSUAA session expires, every API call returns 401. Rather than
displaying a cryptic "HTTP 401" or garbled barcode-parse error, detect
the 401 in all three fetch paths (getContestant, getEventStats,
claimPrizeForContestant + its CSRF-token probe) and call
window.location.reload() — the AppRouter then redirects the unauthenticated
request to the IDP login page.
…-redirect

fix(scanner-vue): redirect to login on XSUAA session expiry (401)
…atibility

CF nodejs_buildpack v1.9.6 rejects compound semver ranges (>=x.y.z <n)
with "improper constraint" — it only accepts simple ranges like 22.x or
exact versions. The >=22.23.4 <23 range (set in the CVE-2026-111497 fix)
caused BuildpackCompileFailed on all three deployed modules during the
2026-11-01 DEV deploy.

Exact pin 22.23.4 satisfies the CVE floor requirement and is accepted by
the buildpack resolver. Updated: package.json, srv-qa/package.json,
srv-mcp/package.json.
fix(staging): pin engines.node to exact 22.23.4 for CF buildpack compatibility
…h fails

When CAP_BASE_URL is unreachable (e.g. tutorials-srv is stopped during a
recovery deploy), fetch-advocates now reads from .tutorial-cache/advocates-
roster.json instead of hard-failing. Only exits 1 if neither the live
endpoint nor the cache is available.

This unblocks deploys where the previously-deployed srv is down due to a
prior staging failure — the exact scenario hit during the 2026-11-01 DEV
recovery deploy after the CVE-2026-111497 engine pin broke staging.
fix(build): fall back to cached roster when fetch-advocates live fetch fails
Release the fixes + event-app improvements merged to DEV since 1.41.7:
- #2961 pin engines.node to exact 22.23.4 so the CF buildpack stages cleanly
  (the >=22.23.4 <23 range was rejected as "improper constraint")
- #2962 fetch-advocates falls back to cached roster when the live srv is down
- #2949 raise Node runtime floor for CVE-2026-111497 (Brotli)
- #2959 feat: flag tutorial feedback as irrelevant/inappropriate
- #2957 feat: event-app Refresh button to re-check completion + show barcode
- #2960/#2955/#2954/#2953/#2950/#2948/#2956/#2958 scanner, kiosk-display,
  number-words, app-space, Architecture Center, and CI fixes

Bumps .deploy/mta.yaml 1.41.7 → 1.41.8 (the version in `cf mtas` and the mtar
filename) and refreshes hugo/data/whats_new.json with the merged-PR digest for
this window (15 entries). mta-mcp.yaml intentionally not bumped (no MCP MTA
changes this release).
chore(release): bump MTA version to 1.41.8
@jung-thomas
jung-thomas merged commit 95c1541 into main Nov 1, 2026
14 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