Release 1.20.1 → PROD - #2080
Merged
Merged
Conversation
…rrent (QA) Mirrors rebuild-content.yml's force-publish. Needed to re-seed the QA ContentCurrent after the delta flags were toggled (a normal delta publish skips unchanged slugs vs ContentFiles, leaving ContentCurrent stale). One-time seed tool; recurring QA rebuilds dual-write deltas normally when WRITE is on.
…nfig Move the three Content Option-B feature flags (CONTENT_DELTA_WRITE_ENABLED, CONTENT_DELTA_READ_ENABLED, CONTENT_DELTA_SKIP_CARRYFORWARD) off process.env onto ImsConfig key/value rows (content.delta.write / .read / .skipCarryForward), read through a new cached, fail-safe resolver. - srv/lib/content-delta-flags.js: 60s-TTL warm cache (mirrors ngds-autosend), synchronous fail-open getters isDeltaWrite/Read/SkipCarryForward (default false, never block/throw on the hot path), async refreshContentDeltaFlags() plus bustContentDeltaFlagsCache(). - Warm on boot in srv/server.js (fail-open). - Replace all process.env.CONTENT_DELTA_* reads across content-store, content-publish-session, chrome-shell, admin-service, jobs/cleanup, jobs/embedding-reconciliation, embedding-pipeline, embedding-stats. - registry.js: add kind:'db' (ImsConfig-backed); resolve.js resolves it live. - AdminService.setContentDeltaFlags / getContentDeltaFlags actions (busts cache). - srv-qa cp list: add srv/lib/content-delta-flags.js. - Tests: new content-delta-flags.test.js; convert env-setting delta tests to seed ImsConfig plus warm cache.
…db-config fix(content): move Option-B fast-path flags from env vars to DB config (ImsConfig)
ci(rebuild-qa): add force-publish input to re-seed Option B ContentCurrent (QA)
…' if absent on boot Follow-up to #2061. Ensures the content fast path defaults ON and survives every deploy: on boot, ensureContentDeltaDefaults() INSERTs content.delta.write/read/ skipCarryForward = 'true' for any key whose ImsConfig row is absent, BEFORE the warm refresh. Only fills missing keys — an admin-set value (true OR false) has a row and is left untouched, so a deliberate disable still survives deploys. The resolver keeps its fail-safe-false default for the cold-cache/DB-error TRANSIENT (never serve fast-path on a DB fault); steady state is governed by the seeded data. Fixes the class where the fast path silently reverted on deploy.
…t-on feat(content): default Option-B fast-path flags ON (seed ImsConfig if absent)
Move the remaining 14 on/off `kind:'env'` feature flags from process.env to the ImsConfig key/value table, read through ONE generic, registry-driven cached resolver — mirroring the just-merged CONTENT_DELTA migration. - New srv/lib/feature-flags/db-flags.js: warm 60s cache (globalThis-backed so all module instances share it), synchronous fail-safe isFlagEnabled() that returns each flag's DECLARED registry default on a cold cache / DB error (NOT blanket false), ensureFeatureFlagDefaults() (seed-if-absent to declared default), refreshFeatureFlags(), bustFeatureFlagsCache(). - registry.js: flip the 14 entries kind:'env'→kind:'db' with dotted-lowercase imsConfigKey (flag.*), preserving each declared default; drop envVar/envRule. - server.js served block: seed defaults + warm the cache at boot (fail-open). - Replace every env read at the call sites with isFlagEnabled(<KEY>), preserving default semantics (false-disables and true-enables both collapse to the resolver's encoded default). - Generic AdminService.setFeatureFlag(flag, enabled) + getFeatureFlags(). - Remove the now-inert env props from .deploy/mta.yaml + deploy/dev.mtaext. - srv-qa cp list: add db-flags.js + registry.js (imported by copied metrics.js and community-blogs-classifier.js). - Tests: new test/unit/db-flags.test.js; update tests that toggled the 14 env vars to use the DB-flag test helpers.
FeatureFlags is a @readonly/@cds.persistence.skip viewer and the setters were
unbound, so Fiori Elements rendered no toggle control. Add BOUND row actions so
an admin can flip each DB flag from the List Report / Object Page.
- admin-service.cds: add `actions { enable(); disable() }` to FeatureFlags,
each returning FeatureFlags.
- admin-service.js: implement bound enable/disable. Resolve the row key from
req.params, require a kind:'db' flag (else reject 400 "Only DB feature flags
are togglable here"). Upsert the flag's backing ImsConfig row to 'true'/'false'
and dispatch the cache bust to the owning module — content.delta.* flags
(flagMeta null, db-flags.js excludes them) bust content-delta-flags.js; every
other managed flag busts db-flags.js. Return the freshly re-resolved row so FE
refreshes.
- admin-annotations.cds: add UI.DataFieldForAction enable/disable to the
FeatureFlags LineItem and Identification (Object Page header).
- featureFlags i18n: add enableFlag/disableFlag (annotations use inline labels).
- test/unit/admin-feature-flags-toggle.test.js: enable/disable on a generic
flag.* flag, routing of a content.delta.* flag, and 400 for db-setting +
constant flags, plus 401 unauth (6 tests).
feat(feature-flags): migrate remaining 14 env flags to DB config (#2060)
The default-ON boot seed (#2064) runs in the cds.test('serve') harness too, inserting content.delta.write/read/skipCarryForward='true' into the in-memory ImsConfig. That flipped the fast path ON for 21 pre-existing content-store / content-pipeline / carryforward / publish-guard / source-hashes tests written against the resolver's fail-safe-OFF (legacy ContentFiles) default: nav/hashes/catalog reads hit the empty ContentCurrent and rollback replayed ContentHistory instead of clearing. Gate ensureContentDeltaDefaults() behind !process.env.VITEST. VITEST is set only by the test runner (unit + hybrid), never in CF or 'cds watch', so production and local dev still default-ON. The seed function stays exported and directly callable, so content-delta-flags.test.js is unaffected.
…ed-vitest fix(content): skip Option-B delta flag boot-seed under vitest
The Rebuild Content (QA) workflow intermittently failed at the publish commit with gorouter 502→503 (run 33164190492). Root cause: every QA rebuild — even a single-tutorial tutorial-qa-updated dispatch — republished all 2211 slugs in force mode, making the commit a ~35s single request that is fragile to a transient routing blip on the single srv-qa instance; the client's 3 retries fell inside a ~4s window and couldn't ride it out. #2062 was a half-done version of this fix: it added a force-publish input and wired `--channel qa ${force-publish && '--force'}` intending delta-by-default, but never removed the force:true hardcode in resolvePublishConfig, so the config override still forced a full publish and no PR ever wired PUBLISH_SLUG. Option B is now fully active on QA (DB-config delta flags default ON, #2061/#2064), so a slug-scoped delta QA publish is safe. Fix #1 — slug-scoped QA publish: - publish-content.ts resolvePublishConfig qa: force now honors --force (mirrors prod) instead of hardcoded true. - rebuild-content-qa.yml publish step: forward the dispatched slug as PUBLISH_SLUG (empty on force-publish re-seed). Commit-triggered rebuild → one slug, sub-second commit; full rebuild → whole-catalog delta; force-publish → full re-seed. - fetchRemoteHashes/fetchRemoteSourceHashes: send Bearer when apiKey is set — srv-qa gates /content/hashes (verified 401 unauthenticated), unlike public prod route; without this, QA delta would 401. Fix #2 — resilient commit retry: - withRetry: optional ±jitter via computeBackoff (default 0, callers unchanged). - commit call: attempts 3→5, backoff [1s,3s,9s]→[2s,5s,10s,20s]+20% jitter so a brief blip is ridden out instead of failing in ~4s. Fix #3 — release publish lock on commit failure: - On permanent commit failure the client now aborts the session (mirrors the append-failure path) which marks the manifest FAILED and releases the lock, instead of stranding it for the 30-min TTL (which 409'd every QA rebuild in that window). Abort gets its own transient retry; reaper remains the backstop. (Server-side auto-abort deliberately omitted: it would turn the client's commit retries into 409s and defeat retry of a transient internal error.) Docs: correct "QA always --force" in build.md + qa-endpoint-design.md. Tests: publish-content-qa force/delta + workflow-wiring guard; withRetry jitter; fetchRemoteHashes auth header. 50 publish-layer tests pass.
…ContentDeltaDefaults no-ops (legacy path)
fix(qa): slug-scope QA content publish + resilient commit + lock release
fix(test): repair unit-suite fallout from flag→DB migration (WIP)
The approuter serves a lapsed/anonymous session as HTTP 200 + an XSUAA login-redirect HTML page (not 401), and Akamai can serve a cached anon /auth/user to a signed-in browser. The /me/tokens island gated on resp.ok alone, so it never set needsLogin, fell through to loadTokens(), and resp.json() threw on the HTML body -> the page showed "Couldn't load your tokens. Try refreshing." instead of a sign-in prompt. - Gate via isSignedIn(): require ok + JSON content-type + body.authenticated (mirrors homepage-personalizer/coordinator.ts). - Harden loadTokens(): a non-JSON 200 (HTML login page) now falls back to the sign-in prompt, not the generic load error. - Add regression tests for 200+HTML on /auth/user and /pats/MyPATs, and authenticated:false.
fix(tokens): /me/tokens sign-in gate must check /auth/user JSON, not resp.ok
…user JSON Same class as the /me/tokens fix (PR #2071): the approuter serves a lapsed/anonymous session as HTTP 200 + an XSUAA login-redirect HTML page (not 401), and Akamai can serve a cached anon /auth/user to a signed-in browser. Both islands gated on authRes.ok alone, so a lapsed session set isLoggedIn=true, fell through to /api/getMyCompletions(), and json() threw on the HTML body -> a 'Network error' / 'Failed to load' message instead of the sign-in prompt. - Gate via isSignedIn(): ok + JSON content-type + body.authenticated (mirrors homepage-personalizer/coordinator.ts). - Harden the data fetch: a non-JSON 200 (session lapsed mid-flow) sets isLoggedIn=false, not a data error. - Add regression tests for both islands (200+HTML on /auth/user and the data endpoint, and authenticated:false).
fix(me): AllCompletions/RecentActivity sign-in gate must check /auth/user JSON, not resp.ok
…2066 unit regression) #2066 migrated on/off feature flags from env vars to ImsConfig-backed DB rows. The generic resolver srv/lib/feature-flags/db-flags.js exposes a synchronous isFlagEnabled() that, on a cold cache, fires a background refreshFeatureFlags() via scheduleRefresh(). server.js reads isFlagEnabled('MCP_AUTH_ENABLED') inside cds.on('bootstrap') to install the MCP kill switch. At bootstrap time cds.model is not yet linked, so the fire-and-forget scheduleRefresh() called cds.connect.to('db') prematurely. cds.connect caches cds.db eagerly (cds.services.db = the pending promise) and resolves the db service's model from the partial, service-less model that exists mid-bootstrap. That poisoned primary-db instance then persists for the process, so cqn4sql can no longer resolve service-qualified targets (SearchService.SearchableItems etc.), leaving '*' unexpanded and every later service read throwing 500 "Query was not inferred and includes '*' in the columns" — ~205 unit failures. content-delta-flags.js was unaffected only because none of its getters run during bootstrap. Fix: scheduleRefresh() no-ops while cds.model is unset. The bootstrap read then returns the flag's declared registry default (the intended cold-cache behavior, per the server.js comment) and the real warm-up runs in the 'served' handler once the model is fully loaded. Runtime-safe on every dialect: the poison is model resolution, not SQLite-specific rendering. Also update two stale kg-path tests (kg-path-v2-or-v1, kg-path-between-handler) that still toggled the now-DB-driven KG_PATH_V2_ENABLED via vi.stubEnv; switch them to __setFlagForTest/__resetFlagsForTest, matching the pattern #2066 already applied to the other migrated-flag tests.
…-regression fix(feature-flags): don't connect to db before model is loaded (fixes #2066 unit regression + latent boot bug)
The 08-25 release (bundled prompt tightening) shifted checkFreshness to 'prefer reporting NOTHING over a speculative one', which suppressed essentially all findings — the LLM returned 0 raw findings even with grounding present, so the admin action appeared to 'return nothing'. Recalibrate PRECISION by severity: keep High/Medium strict (confidence- first, prefer omitting speculative ones) but treat Low as ADVISORY so cosmetic/dated-style staleness surfaces instead of an empty report. All other guardrails (CONTEXT, OUTPUT vs CODE, RESPECT INTENT, SAP CONVENTIONS, SCOPE) are unchanged. DEV A/B (real grounding, claude-4.6-sonnet): tutorials that returned 0 under the tightened prompt now return 1-3 findings (mix of Medium + Low), including a genuinely useful Medium. Updates the prompt-guard unit test to assert the new severity-calibrated intent.
…-calibration fix(freshness): calibrate prompt by severity so Check Freshness surfaces findings again
…horization-server alias
…uration alias test
…cp-auth 401 Spike findings (Task 3 Step 1): - xs-app.json confirms /mcp-auth/* and /mcp-admin/* are XSUAA routes (authenticationType: "xsuaa", scope: "$XSAPPNAME.Tutorial.MCP") that proxy to srv-api. Without a bearer token the approuter currently 302-redirects to the XSUAA login page with no discovery pointer. - insertMiddleware.first runs before the approuter's built-in XSUAA auth middleware, so short-circuiting no-Authorization-header requests there is safe and the handler never interferes with a real bearer token. - Browser-cookie sessions hitting /mcp-auth (no bearer) are out of scope: this namespace is programmatic MCP-only; noted and proceeding. Implementation: - approuter/lib/mcp-auth-challenge.js: new middleware that fires only on /mcp-auth and /mcp-admin prefixes with no Authorization: Bearer header. Calls resolveBaseUrl(req) + resolveScope() from ./well-known-oauth (no changes to that module — Option A preserved), sets WWW-Authenticate: Bearer resource_metadata="…", scope="…", then responds 401 with a JSON error body. Valid bearer always calls next(). - approuter/server.js: require + wire mcpAuthChallengeHandler into insertMiddleware.first after the well-known handlers and before devtoberfestCspHandler/staticHandler/proxyHandler. - test/unit/mcp-auth-challenge.test.js: 3 unit tests (no-bearer 401, bearer pass-through, unrelated path pass-through); all pass.
…very feat(approuter): complete .well-known MCP discovery surface (openid-config, mcp.json, WWW-Authenticate pointer)
docs: register akamai-well-known-forward in sidebar (fix docs:build)
…ed:thirdparty HomepageShelves kept losing its curated/third-party rows (Prior Labs, Dremio, Reltio, n8n, …) on deploy. Root cause: while the seed CSV lived in db/data it compiled to an .hdbtabledata with include_filter:[] that HDI full-replaced the table from on every deploy whose CSV hash changed, wiping runtime-seeded rows and admin edits. Commit 1404c4c moved the CSV to test/data (stopping the wipe on PROD) but never re-seeded the lost rows, and the manual `npm run seed:thirdparty` promotion was DEV/PROD-ambiguous (it followed the CDS binding, so it could silently target the wrong environment). Replace both mechanisms with an idempotent, non-destructive boot seed: - srv/lib/homepage/homepage-shelves-defaults.js — single inline canonical baseline (SAP entries + third-party links); ships with the srv bundle, no runtime file-read / packaging risk. - srv/lib/homepage/seed-homepage-shelves.js — insert-if-missing on (verb,url), called from cds.on('served'). Never overwrites admin edits, self-heals a deleted baseline row on next restart, non-fatal, runs in every profile (cds watch, unit tests, hybrid, production). - Retire test/data seed CSV, db/data/staging/homepage-thirdparty.json, scripts/seed-thirdparty.js + its two tests, and the seed:thirdparty npm script (now a helpful error stub pointing at the new path). - admin-managed-seed-csvs guard test asserts the CSV is gone from BOTH data dirs. - Docs: new "HomepageShelves seeding" section in homepage.md. Tests: new defaults-validation + seed-behavior unit tests added; homepage-seed, homepage-shelves-crud, homepage-link-health still green (9078 unit tests pass).
…ble-seed fix(homepage): boot-seed HomepageShelves; retire seed CSV + manual seed:thirdparty
chore(release): bump MTA 1.20.0 → 1.20.1 + refresh What's New digest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DEV → main promotion for the 1.20.1 PROD deploy. 39 commits ahead of main; clean merge (nothing on main is missing from DEV, so no sync-main-into-DEV reconcile needed this cycle).
Version
.deploy/mta.yaml1.20.0 → 1.20.1 (patch). PROD is currently on 1.19.0; main's 1.20.0 was never PROD-shipped, so this ships all DEV work since 1.19.0.Highlights since 1.20.0 (main)
.well-knownMCP OAuth discovery surface — external MCP clients (Claude/ChatGPT connectors) auto-discover OAuth endpoints (feat(approuter): complete .well-known MCP discovery surface (openid-config, mcp.json, WWW-Authenticate pointer) #2075)Verified on DEV: v1.20.1 deployed, content published (v237), live tutorial asset hashes match the shipped build. Smoke green except the 3 documented-benign stale-assertion/data-gated checks.
After merge: PROD deploy is a separate manual step (primary tree, order planner→tutorials-ims→gameboard).