Repository navigation
Release 1.33.0 → main (production) - #2578
Merged
Merged
Conversation
…2515) The inline-glossary walker scopes over the whole `.tutorial-steps` subtree, which contains the Vue-mounted question islands (Validation quiz, Challenge, tutorial-branches). When a question's text contained a known term (e.g. "MCP"), the walker replaced the text node with a `<span class="glossary-term">`, injecting an inline element into the Validation legend's `inline-flex` layout — splitting the question into broken columns (see #2515 screenshot) — and racing Vue's vdom over DOM it owns. `shouldSkip` now bails on any ancestor that is an island mount host (class ending `-mount`, the markers emitted by `shortcodes/tutorial-step.html`), so question text is never rewritten. Ordinary tutorial prose is unaffected. Adds `test/unit/glossary.test.ts` (happy-dom) covering: prose still wraps; Validation and Challenge islands are skipped.
…out-2515 fix(glossary): skip Vue question islands so terms don't break layout (#2515)
Authors can put multiple [VALIDATE_N] blocks in one step (the number keys
the STEP, not the question) and the parser already accumulated them into
one array. But every question was assigned the same `validate-${stepNum}`
id, so client-side answer/verdict state (keyed by id) collided across the
questions: only one could hold an answer, MCQ selections appeared stuck,
and the all-or-nothing grader failed the step. The parser's own sibling
maps (`${stepNum}:${q.id}`) also overwrote each other, collapsing AI-graded
specs to one row per step.
Assign `validate-N` to the first question in a step and
`validate-N-<ordinal>` to each subsequent one. First-question id is
unchanged, so single-question steps and all published content are
byte-for-byte identical. The Validation.vue component and grading already
key everything by id and need no change.
Adds rules-multi-question-per-step.test.ts covering distinct ids,
backward-compat, and no sibling-map overwrite for AI-graded blocks.
…ep-2514 fix(validation): unique id per question for multi-question steps (#2514)
Public XSUAA instance + dedicated tutorials-srv-mcp module so mcp-remote authenticates via PKCE (no client secret). Retire srv-qa copied-lib drift tax via a core/content/mcp/kg/channels npm workspace. Approuter login flow unchanged.
Plain npm workspaces don't survive CF/MTA packaging (buildpack npm install inside packed gen/<module> can't resolve file:/symlinked sibling packages). Consume shared packages via per-module esbuild bundle (mirrors existing parsers-bundle). P1 proves the mechanism on DEV before other packages ride on it; add R0.
17 tasks across 5 phases: workspace+core+bundling harness (proved on DEV), carve content/mcp/kg/channels, srv-mcp module + public XSUAA + mta-mcp.yaml, retire srv-qa cp block, PR to DEV. PKCE no-secret connect is the acceptance test.
- Fix T2 skeleton defect: packages/core/package.json type commonjs → module
- Add exports map: { ".": "./index.js", "./legacy-id.js": "./legacy-id.js" }
- git mv srv/lib/legacy-id.js → packages/core/legacy-id.js (preserves history)
- Replace srv/lib/legacy-id.js with ESM re-export shim:
export { getNextLegacyId, resetCounters } from '@tutorials/core/legacy-id.js'
- Update packages/core/index.js to ESM barrel re-exporting both symbols
- Verified: boundary lint OK, core subpath resolves, shim resolves, 20 test-file
failures are all pre-existing (srv-qa port-3000 + MediaPipe + Vite browser issues)
…eploy Adds the bundling harness (Task 4) that packages @tutorials/core into a self-contained ESM artifact for CF/MTA deployments where npm workspace symlinks do not survive packaging. - scripts/bundle-shared.cjs: esbuild bundles @tutorials/core → srv/lib/_shared/core.bundle.mjs (ESM, format:esm, with createRequire banner for CJS interop; @sap/* @cap-js/* node:* external) - srv/lib/_shared/ gitignored (generated artifact) - package.json: adds prebuild:shared-bundle + chains into prebuild before parsers-bundle - .deploy/mta.yaml before-all: runs bundle before cds build --production - srv/lib/legacy-id.js: TLA shim prefers CF bundle, falls back to workspace package in local dev - test/unit/shared-bundle.test.js: 3 vitest tests verifying bundle creation + exports
Devtoberfest leaderboard improvements (#2510): shared competition ranks with stable tie order, prev/next paging, participant counter, and personal standing (highlighted + sticky "you" row, rank #N of M). Pairs with gameboard-srv V2 endpoint + communityUrl caller-match, already on gameboard-srv main. PRs: #2511 (feature), #2512 (you-row communityUrl match fix).
…rface (#2517) - Move 5 rank test cases from srv/lib/kg-explore-data.test.js into the existing test/unit/srv/kg-explore-data.test.js (correct home for this module's unit tests); add a mission-node test asserting rank is never stamped on non-tutorial/concept types. - Delete srv/lib/kg-explore-data.test.js (stray, would duplicate coverage). - Revert srv/lib/**/*.test.{js,ts} addition in vitest.config.ts (policy change not needed once tests live in test/unit/srv/). - Revert ExploreNode interface added to hugo-apps/src/related-graph/types.ts (dead code — nothing in related-graph consumes ExploreNode; rank? stays only in app/explore/src/types.ts where it is used).
…raph-2517 feat(explore): opt-in 3D KG view (#2517)
Repo is ESM (type:module); packages/shims/bundles are ESM (.mjs, format:esm, createRequire banner), not the CJS shown in the original snippets. Verified in T3/T4.
…ckage MCP auth modularization — foundation (workspace + core + esbuild bundling)
…core (ESM) Moves 27 core files from srv/lib/ (and srv/jobs/job-lock.js) into packages/core/, preserving subdirectory structure (feature-flags/, runtime-config/, branch/, jobs/). Replaces each original path with an ESM shim using the bundle-first/workspace-fallback pattern established in Task 4. Changes: - git mv all 27 files to packages/core/<same-relative-path> - Write ESM shims at each old srv/lib/ (and srv/jobs/) path - packages/core/package.json: wildcard subpath export "./*": "./*" - packages/core/index.js: barrel re-exports full moved surface (test helpers and FLAG_TTL_MS duplicate omitted from barrel to avoid export-name conflicts; available via subpath imports) - page-fallback.js: probe-both-candidates path resolution for the srv/page-fallback/ directory (works from both package and bundle) Verified: - node scripts/lint-package-boundaries.cjs -> OK - node scripts/bundle-shared.cjs -> 82 keys bundled - @tutorials/core/metrics.js + shim both resolve 7 exports - srv/developer-service.js loads OK - npm test: 65 failed | 1335 passed (same 65 pre-existing failures)
…l test mocks (T6 fix)
… 2 more test files (T6 fix-2) mail-client-credstore.test.js and rebuild-trigger.test.js both mocked '../../srv/lib/credstore.js' (the shim). After the T6 carve the real consumer path is packages/core/secret-resolver.js → ./credstore.js (package-relative), which bypasses the shim entirely, so Vitest's mock never intercepts the readSecret call. Fix: change every vi.mock / dynamic import target in both files from the shim path to '@tutorials/core/credstore.js' — matching the module instance that secret-resolver.js actually resolves. Affected-suites result: 2 passed / 27 tests passed
The MCP public-PKCE token from IAS carries a VERIFIED email (sub) but NO
I-number — its user_uuid is a SCIM UUID, not the SAP ID (decoded live from
atxgsg7zi 2026-10-01). The existing resolveUserSapId XSUAA chain would wrongly
return that SCIM UUID as a bogus sapId. This adds the IAS resolution path:
- packages/core/resolve-db-user.js:
- isIasToken(user) — detects IAS via iss ~ *.accounts.ondemand.com
- iasEmailFromToken(user) — verified email from sub, else email claim,
lowercased (fixes a sub||email short-circuit found by TDD)
- resolveIasSapId(user) — async email → Users.email → sapId join. Fetches ALL
matches (not SELECT.one) and deterministically PREFERS the real I-number over
a SCIM-UUID-shaped sapId: a live DB check found thomas.jung@sap.com maps to
BOTH I809764 AND a stray SCIM-UUID row (fbf099e2-…) auto-provisioned by an
earlier IAS login. Without the preference the join was order-nondeterministic.
Fail-closed (null when non-IAS / no email / no row); never fabricates an id.
- pinIasSapId(user) — pins the resolved I-number onto authInfo.token.userId so
the SYNCHRONOUS resolveUserSapId (and every handler) returns it via branch #1.
XSUAA path untouched.
- srv-mcp/developer-service.js: this.before('*') calls pinIasSapId once per
request (after CAP auth, before handlers; non-fatal on error).
- packages/core/index.js + srv/lib shim: re-export the 4 new names.
Tests: 29/29 (resolve-db-user + resolve-ias-sapid incl. email-collision cases).
Email verification is enforced IAS-side, so the email join is spoof-resistant.
Follow-up (data op, not code): 6 stray SCIM-UUID-keyed Users rows exist from
pre-fix IAS logins; harmless now the resolver prefers the I-number, cleanable
separately. core.bundle.mjs is a gitignored artifact rebuilt at deploy.
Refs #2550, #2551.
…r-2550 feat(mcp-auth): resolve IAS MCP identity via verified-email join (#2550)
The deployed srv-mcp served DeveloperService as a GENERIC ApplicationService with none of McpDeveloperService's on-handlers → every MCP tools/call returned 'Service "DeveloperService" has no handler for "<tool>"'. The MCP tools never worked over HTTP on any deploy (local/hybrid worked, masking it). Root cause: CAP factory.js _sibling resolves a service's handler via its @source dir (srv-mcp/developer-service.cds → probes srv-mcp/developer-service.js) but cds build nests handlers under gen/srv-mcp/srv/ (cds.env.folders.srv='srv/'). @source dir (srv-mcp/) != gen nesting (srv/) → sibling miss → generic service, no handlers. The main srv module only works because its folder IS named 'srv'. Fix (build-only): scripts/patch-mcp-impl.cjs runs in mta-mcp.yaml before-all after cds build and injects @impl='srv/developer-service.js' into the baked csn. A bare (non-'./') path resolves against cds.root → gen/srv-mcp/srv/developer- service.js (the real file). Never touches source/hybrid, where auto-discovery already works. Verified: @impl set, target present. This is independent of the #2550 IAS identity work — it was blocking its acceptance test. Structural follow-up (rename module folder to the srv/ convention; also affects srv-qa) tracked in #2569. Refs #2550, #2569.
…ding fix(srv-mcp): bind DeveloperService impl in production build
…er (#2552) Approved design: switch the durable identity key from Users.sapId (SAP employee ID) to an (issuer, subject) linkage layer so IAS and future social logins — which carry no P/S/I number — resolve correctly. Keep sapId as an NGDS-only attribute (carve-out is a structural no-op). Email stays an attribute, never a key (PROD: 97.6% null, non-unique). Grounded in live PROD/DEV data probes + a full blast-radius audit (sapId is the universal per-user key via one chokepoint; ~40 callsites stay unchanged via the pin pattern). Supersedes the unsafe #2550 email-join, which this build removes. 5 sections approved: schema (UserIdentities), 3-tier resolver, provisioning + no-sapId seam, NGDS carve-out, scope/ktt-fix/migration/testing. Refs #2552, #2550.
…for no-sapId users (#2552) Both row-resolvers now honor the Users.ID pin set by pinResolvedUser: - resolveDbUser: checks attr.dbUserId first (fast-path for pinned users), falls back to sapId-based WHERE, returns null when neither is present. - resolveDbUserId: same priority — attr.dbUserId cache sets user.__dbUserId directly without a DB round-trip; then sapId path; then null. This carries social/IAS users (no canonical sapId) through all ~40 downstream callsites without per-callsite changes.
) Test fix: update Users-row-miss scenario in author-service.test.js to use a SCIM-UUID-shaped userId (unprovisionable under the new provisionDbUser rules from Task 7) instead of a canonical SAP ID (I999999) which the new get-or-create path now provisions. The miss guard still fires for SCIM UUIDs with no email/name claims.
…er-2552 feat(identity): (issuer,subject) identity-linkage layer (#2552)
The #2552 UserIdentities entity shipped as a plain .hdbtable but was NEVER created in the DEV HANA container: this project's db/src/.hdiconfig maps ONLY the hdbmigrationtable plugin (com.sap.hana.di.table.migration) — there is NO plain-hdbtable plugin — so a non-journaled entity's .hdbtable artifact is packaged in the mtar but silently never applied by HDI. Confirmed live: Users (journaled) has 816k rows in the container; COM_SAP_DEVELOPERS_IMS_USERIDENTITIES did not exist after the first full deploy. Every one of the other 55 persisted IMS tables is journaled via @cds.persistence.journal in db/persistence.cds; UserIdentities was missed. The design spec's 'additive .hdbtable, no ALTER' guidance was wrong for this HDI container type. Fix: annotate ims.UserIdentities with @cds.persistence.journal; cds build then emits a clean version=1 .hdbmigrationtable (fresh CREATE, no migration steps) instead of the unused .hdbtable. Committing the generated migration source + updated last-dev/csn.json as required (migration sources are version-controlled). Still additive — a brand-new empty table; no ALTER on the 816k-row Users. Refs #2552.
…l-2552 fix(identity): journal UserIdentities so it deploys to HANA (#2552)
…able transition (#2552) The journal fix (#2572) added a .hdbmigrationtable for UserIdentities, but the FIRST deploy had already registered the plain .hdbtable design-time artifact in the DEV HDI container. HDI then refused the make with: db://COM_SAP_DEVELOPERS_IMS_USERIDENTITIES: the object cannot be provided more than once — the .hdbmigrationtable would provide it; the deployed .hdbtable already provides it — Could not compose a consistent make graph [8212012] So the make FAILED and rolled back → table still absent. This is the CAP- documented hdbtable→hdbmigrationtable transition rule: the old .hdbtable must be on the HDI undeploy allowlist so HDI drops it as the migration table takes over. Every other IMS table that made this transition is already listed in db/undeploy.json (Users, Tutorials, Steps, …); UserIdentities was missing. Fix: add src/gen/com.sap.developers.ims.UserIdentities.hdbtable to db/undeploy.json. Next deploy: HDI undeploys the stale .hdbtable and deploys the v1 migration table cleanly, creating the (empty) table. Refs #2552.
…oy-stale-hdbtable fix(identity): undeploy stale UserIdentities.hdbtable for migration-table transition (#2552)
#2539) Adds docs/end-users/api-consumption.md: the map of all API surfaces (MCP anon/PAT/OAuth, Admin OData, GraphQL, A2A), which are headless-capable vs browser-only, and how to authenticate each — cross-linking the MCP/A2A quickstarts rather than duplicating. Documents DJ Adams' use case (#2539) honestly: his /admin/Tutorials OData query is valid, but admin OData is BROWSER-SESSION-ONLY today — no PAT or client-credentials token can carry the Admin scope (Admin is a named-user role, not in xs-security authorities; PAT is /mcp-pat/* only). So the supported paths are: browser for ad-hoc admin OData, MCP PAT tier for scripted per-user data. Also links the previously-unlinked MCP + A2A quickstarts from the end-users index (new 'APIs & programmatic access' section). Gaps filed as future work: #2574 (headless admin OData access — capability + security decision), #2575 (public api-docs portal restructure). Docs-only; no code/config change. Refs #2539, #2574, #2575.
docs(api): API consumption guide + honest auth/gaps (#2539)
The 'Deploy Docs to GitHub Pages' CI job failed on the #2576 merge: Unregistered pages (on disk but not in sidebar): /end-users/api-consumption docs:build enforces that every page on disk is in the sidebar config. The new api-consumption.md was linked from the README but not added to docs/.vitepress/config.ts. Added it to the End Users sidebar (after the MCP/A2A quickstarts). Verified: npm run docs:build completes clean locally. Refs #2539.
…umption fix(docs): register api-consumption in VitePress sidebar (#2539)
42 user-facing changes since 1.32.0 (12 features, 24 fixes, 3 performance, 3 docs). Highlights: - Identity: more reliable account matching across login providers via an (issuer,subject) identity-linkage layer (#2552), so progress stays attached to the right profile even without an SAP employee ID. - MCP: secretless MCP OAuth via IAS public-PKCE (#2550), MCP developer tools now respond over HTTP (#2570), and a new programmatic API-consumption guide (#2539). - Real-time: WebSocket sticky sessions + Redis adapter for cross-instance fan-out (#2524). - Content/learning: randomized quiz answer order (#2558), opt-in 3D knowledge- graph Explore view (#2517), tutorial parser/heading fixes (#2555), faster content publish (#2462/#2463/#2464) and Concepts page (#2532). - Devtoberfest: scoring-correctness fixes and admin Semaphore taxonomy sync (#2477/#2479). Release files only: .deploy/mta.yaml version bump + hugo/data/whats_new.json digest entries (generated via the whats-new skill). srv snapshot is gitignored, rebuilt at build time.
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.
Production release 1.33.0
Promotes
DEV→mainfor the next PROD deploy. Version1.32.0→1.33.0(minor — ships new features).Release commit:
chore(release): 1.33.0— bumps.deploy/mta.yamland adds 42 user-facingwhats_new.jsonentries (generated via the whats-new skill: merged PRs #2451–#2576 since 1.32.0, release-plumbing/CI/boot-fix PRs excluded).Changelog breakdown (42 entries)
Scope
main(all oforigin/DEV+ the release commit).mainis fully contained inDEV— clean merge, no divergence..deploy/mta.yaml+hugo/data/whats_new.json(the srv snapshot is gitignored, rebuilt at build time).Deploy
Merge does not auto-deploy. PROD deploy is a separate, explicitly-authorized step: deploy from a fresh
origin/mainafter merge,cf targetchecked, sap-devs scheduler paused.