Key Design Decisions ​
Source: extracted from project README, 2026-05-25.
This page is the fast-scan aggregate — a one-liner per architectural rule the platform runs under. For the why behind an individual decision, its trade-offs, and what you'd have to redo to change it, see the Architecture Decision Records. ADRs are the canonical form for decisions with dedicated records; this page remains the quick tour.
Architecture ​
- Tutorial HTML lives in HANA, not on disk. Hugo builds HTML,
publish-content.tsgzip-compresses + SHA-256-hashes per slug, then uploads only the changed slugs as BLOBs toContentFiles+ContentManifest. AppRouter rewrites/tutorials/{slug}→/content/tutorials/{slug}on the srv, which decompresses and serves with ETag + bounded LRU. Consequence:approuter/static/tutorials/is explicitly removed during build — there is no static fallback. If nothing has been published,/tutorials/*returns 404. - QA channel is a parallel srv + HDI, not just a route flag.
tutorials-srv-qabinds totutorials-hana-qa, runs the same handlers, and exposes preview content gated by theTutorial.AuthorXSUAA scope. The router sends/tutorials-qa/*to the QA destination. Authors get prod-shaped previews with zero risk of cross-tenant data leakage; prod queries can never accidentally hit QA tables. - Public Hugo + lazy login. The catch-all
/*isauthenticationType: "none"— anyone can read tutorials without an OAuth bounce. Login is triggered explicitly when the user clicks the profile icon (the/loginroute is the only authenticated GET on a non-API path). API calls under/api/*enforce XSUAA at the router and return 401 if the user hasn't signed in yet. - Optional service bindings degrade gracefully.
tutorials-audit-log,tutorials-cloud-logging, andtutorials-aicoreareoptional: truein the MTA. The srv detects missing bindings at boot: chat returns 503, audit logging falls through to the console sink, OTLP export is no-op. This makesmbt build && cf deploysucceed in fresh sandbox subaccounts that haven't been entitled to AI Core. - 4-tier GitHub discovery resilience. Live GitHub → on-disk cache →
RepoCatalogbaseline (HANA) → degrade. CI is the canonical writer ofRepoCatalog— author pushes update the baseline so a GitHub outage at build time doesn't break the build.
CAP runtime ​
bootstrapvs.servedevent split. Custom Express routes (/api/qrcode,/build/*,/feedback/*,/content/*,/auth/user,/health,/chat/stream) register onbootstrap— before CDS auth middleware — so unauthenticated routes can opt out cleanly. Jobs and the Socket.IO plugin register onserved, after entities and services exist.- Socket.IO via
@cap-js-community/websocket, not raw WebSocket.@protocol: ['websocket', ...]annotations onDisplayService+EventStreamServicemap CDS events to Socket.IO messages on/ws/displayand/ws/event-streamnamespaces. Scope check happens at namespace-join time (the router can't enforce XSUAA on a Socket.IO upgrade without breaking the handshake). - Never SELECT a HANA BLOB alongside metadata in one CDS QL query. The LOB locator expires before the stream is consumed when mixed with non-BLOB columns.
srv/lib/content-store.jsandsrv/lib/embedding-query.jsuse rawdb.run()SQL on HANA and CDS QL on SQLite (unit tests). This is a HANA-only quirk that SQLite silently tolerates. AnalyticsService.runSelectQueryis gated by allowlist + parser, not just@requires.srv/lib/analytics-sql-validator.cjsrejects anything that isn't a singleSELECTagainst the@analytics.exposedtable set, then wraps withLIMIT 5001so a runaway query can't OOM the srv. The exposed entity surface for theAnalyticsServiceis governed by the same@analytics.exposedannotations on CDS views.
Data + identity ​
- JWT-only identity on CAP (vs. the Java IMS's SCI lookup). User attributes come from
xs.user.attributeson the XSUAA JWT — no synchronous network hop for profile enrichment. See docs/developers/architecture/authentication.md. @PersonalData+@cap-js/audit-loggingdrives audit events onUsers/UserMetaData/TaskRecordsautomatically. Plus a manualSecurityEventon user anonymization. No hand-written audit calls.@changelog+@cap-js/change-trackingon admin-managed entities (Events, Missions, Groups, Accomplishments, Prizes, ImsConfig, FeaturedTasks, ChatSettings) for the changelog UI.- Legacy ID sequences (HANA
.hdbsequence) on every entity that exposes an integer ID to legacy IMS consumers. Used during parallel operation; remains a public contract until the cutover deprecation window closes. - Slug fields are required for
Missions.slugandCompletionPaths.slug, populated byscripts/setup-dev-data.cjsfrom.migration-data/slug-mapping.jsonafter a fresh DB deploy. Without slugs,/build/catalogreturns numeric IDs and Hugo cannot generate mission/group URLs.
Operational defaults ​
publish-contentalways runs with--forcein production. Default delta detection silently drops slugs from the manifest because the server treats every publish as a full snapshot —--forcebypasses delta and republishes the full set. (See memory: publish-content needs --force.)- Daily content GC at 03:00 prunes
SUPERSEDED/ROLLED_BACKcontent versions older than 7 days (keeps 3 most recent for rollback). Never touchesACTIVEorPUBLISHING. - Notification toggle gates the scheduled job only. The manual
sendContributorNotificationsadmin action always sends regardless — operators need to be able to recover from a misconfigured cron without disabling and re-enabling the toggle. FailedEmails+NGDSFailedMessagesretry queues keep the integration paths idempotent. Transport failures are persisted, not raised, so a missing SMTP in dev is graceful, not fatal. Retry job replays with exponential backoff.