Skip to content

Deployment

Source: extracted from project README, 2026-05-25.

Single MTA deployment to SAP BTP Cloud Foundry:

bash
mbt build
cf deploy mta_archives/tutorials-ims_1.0.0.mtar

MTA Modules

The deployment in .deploy/mta.yaml defines five modules — the prod and QA channels share an AppRouter and XSUAA instance but each gets its own srv app and HDI container.

ModuleTypeSourceRequiresPurpose
tutorials-db-deployerhdbgen/dbtutorials-hana, tutorials-cloud-loggingProd HANA schema + indexes (one-shot HDI deploy). Cloud Logging binding forwards deployer stdout for ~30-day forensic retention (#257).
tutorials-db-qa-deployerhdbgen/db-qatutorials-hana-qa, tutorials-cloud-loggingQA HANA schema (peer of db/, namespace com.sap.developers.ims.qa). Cloud Logging binding mirrors prod for QA forensic parity.
tutorials-srvnodejsgen/srvhana, xsuaa, destination, mail, audit-log, cloud-logging, aicoreCAP backend (9 services + jobs + Socket.IO + content store + RAG)
tutorials-srv-qanodejsgen/srv-qahana-qa, xsuaaQA-channel CAP srv (re-renders author drafts via srv-qa/lib/parsers.bundle.mjs)
tutorials-approuterapprouter.nodejsapprouter/xsuaa, srv-api (destination), srv-qa-api (destination)XSUAA login + static delivery + reverse proxy to both srv apps

The AppRouter routes ^/tutorials-qa/(.*), ^/qa-search/(.*) to the srv-qa-api destination and everything else (/api/*, /admin/*, /display/*, /content/*, etc.) to srv-api. WebSocket paths (^/socket\.io/, ^/ws/) are authenticationType: 'none' because the scope check happens at namespace join.

BTP Service Bindings

ResourceService / planRequired byNotes
tutorials-hanahana / hdi-sharedtutorials-srv, tutorials-db-deployerProd HDI container (com.sap.xs.hdi-container)
tutorials-hana-qahana / hdi-sharedtutorials-srv-qa, tutorials-db-qa-deployerQA-channel HDI container — separate from prod, no cross-foreign-keys
tutorials-xsuaaxsuaa / applicationall srv apps + approuterConfigured from .deploy/xs-security.json (Admin, MobileApp, DisplayApp, Tutorial.Author, ConsolidationScope, DeveloperApp scopes)
tutorials-destinationdestination / litetutorials-srvNGDS + SCI remote endpoints
tutorials-mailmail / standardtutorials-srvSMTP for notification escalation emails
tutorials-audit-logauditlog / standard (optional)tutorials-srv@cap-js/audit-logging sink for @PersonalData events
tutorials-cloud-loggingcloud-logging / standard (optional)tutorials-srv, tutorials-db-deployer, tutorials-db-qa-deployerOTLP ingest enabled; backs the cfLogsUrl virtual on PipelineLog / JobExecutionLog. Deployer bindings (#257) capture HDI deploy stdout for ~30-day forensic retention.
tutorials-aicoreaicore / extended (optional)tutorials-srvBacks ChatService + embeddings + RAG (getRelevantSteps tool)

optional: true resources let mbt build && cf deploy succeed in a subaccount that hasn't entitled them yet (e.g., a fresh sandbox without AI Core). The srv app degrades gracefully when bindings are missing — chat returns 503, audit logging falls through to the console sink, OTLP export is no-op.

Route Architecture

The AppRouter (approuter/xs-app.json) evaluates routes top-to-bottom on first match — so order matters. There are ~28 active routes; canonical reference for auth/scope per route is testing-endpoints.md.

Static UIs (XSUAA + scope, served from approuter/static/<route>/)
PatternBacked byAuth
^/admin-ui/(.*)$app/admin-shell/dist/ (TNT shell + 13 Fiori Elements components)XSUAA + Admin
^/analytics-ui/(.*)$app/analytics-explorer/dist/ (Vue 3 + Monaco)XSUAA + Admin
^/scanner-ui/(.*)$app/scanner/webapp/ (UI5 BarcodeScanner)XSUAA + MobileApp
^/scanner-vue/(.*)$hugo-apps/src/scanner-vue/ islandXSUAA + MobileApp
Authenticated API proxies (XSUAA + scope, → srv-api destination → tutorials-srv)
PatternServiceScope
^/admin/exports/(.*)$ExportsServiceAdmin
^/admin/analytics/(.*)$AnalyticsService (gated entity surface + runSelectQuery)Admin
^/admin/(.*)$AdminServiceAdmin
^/display/(.*)$DisplayServiceDisplayApp
^/api/v1/(.*)$ConsolidationServiceConsolidationScope
^/api/(.*)$DeveloperService + /api/qrcode, /api/recommendationsXSUAA (any)
^/chat/(.*)$ChatService + /chat/stream (SSE)XSUAA (any)
^/scanner/(.*)$ScannerService OData functionsXSUAA + MobileApp
^/auth/user$Identity probeXSUAA (any)
^/login(\?.*)?$OAuth2 entry (XSUAA redirect)XSUAA
QA channel (XSUAA + Tutorial.Author, → srv-qa-api destination → tutorials-srv-qa)
PatternTarget on srv-qaNotes
^/tutorials-qa/_nav\.json$/content/navQA-only navigation metadata
^/tutorials-qa/search/?(.*)$static /qa/search/$1search page shell from Hugo QA build
^/qa-search/(.*)$/search/$1QA-only SearchService proxy
^/tutorials-qa/(.*)$/content/tutorials/$1preview HTML from tutorials-hana-qa BLOBs
Public / unauthenticated (no session required)
PatternPurpose
^/api/ChatConfig(.*)$Chat client bootstrap — config only, no PII
^/search/(.*)$SearchService — public tutorial search
^/content/(.*)$Tutorial HTML serve + /content/hashes, /content/nav (writes are bearer-token gated by CONTENT_API_KEY at the srv)
^/build/(.*)$Build pipeline catalog/navigator/repo-catalog endpoints (CI consumers)
^/feedback/(.*)$/feedback/submit (rate-limited, IP hashed via SUBMISSION_SALT_SECRET)
^/health(/.*)?$Liveness + DB connectivity
^/.well-known/(.*)$, ^/ord/(.*)$ORD discovery
^/rest/(.*)$Custom REST escape hatch (server-defined sub-routes)
^/tutorials/_nav\.json$/content/navProd navigation metadata
^/tutorials/(.*)$/content/tutorials/$1Prod HTML rewrite to HANA-backed serve
WebSocket transport (auth: none at the router; scope enforced at namespace join)
PatternNamespaceScope check
^/socket\.io/(.*)$Socket.IO upgrade transport(none — namespace-level)
^/ws/(.*)$/ws/displayDisplayApp enforced inside @cap-js-community/websocket plugin
^/ws/(.*)$/ws/event-streamanonymous (kiosk monitors)

The router is intentionally authenticationType: "none" for ^/socket\.io/ and ^/ws/ because the WebSocket plugin runs scope checks at namespace-join time — adding XSUAA at the router would force an OAuth dance the Socket.IO client can't complete cleanly. See authentication.md for the full token flow.

Catch-all (last)

^/(.*)$localDir: static/ (Hugo build output: homepage, /tutorials/{slug} shells, /missions/{slug}, /groups/{slug}, /me/, /event-display/, /app-space/, plus the compiled Vue islands in hugo/static/js/). authenticationType: "none" — public Hugo content with lazy login on demand.