Skip to content

feat(#2247): re-land HCQL on 5 authenticated services (CAP 10.1.0) - #2257

Merged
jung-thomas merged 16 commits into
DEVfrom
worktree-hcql-reland-2247
Sep 11, 2026
Merged

jung-thomas merged 16 commits into
DEVfrom
worktree-hcql-reland-2247

Conversation

@jung-thomas

@jung-thomas jung-thomas commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Re-lands the CAP 10 HCQL ("CQL over HTTP") beta protocol adapter — reverted in #1004 — scoped to the 5 authenticated services (AdminService, AuthorService, AnalyticsService, ExportsService, ConsolidationService), on a CAP runtime bump to 10.1.0 plus a package.json-wide within-major dependency refresh.

Closes the re-land tracked in #2247 (Option 1). Draft — do not merge until the maintainer runs the hybrid suite on a bound environment (see Validation).

What changed

  • CAP runtime bump — @sap/cds 10.0.3 → 10.1.0, @sap/cds-dk → 10.1.1, and ~25 CAP/non-CAP deps advanced within their current major (maintainer decision: no cross-major jumps in this PR). package-lock.json regenerated. Notable conscious pins held: @cap-js/mcp 1.3.0, @cap-js/ai ~1.1.0, cds-caching 2.1.0, sharp 0.35.3 (rationale in the SDD ledger).
  • HCQL enablement via explicit @protocol lists — each service mounts HCQL on a distinct /hcql/<svc> path, never co-mounted on the OData @path. This is the fix for the revert(#995): remove HCQL enablement — 218 unit tests failing on main #1004 root cause (HCQL greedily parsed OData bodies/URLs as CQN). srv/hcql-enablement.cds (4 services) + srv/admin-service-mcp.cds (AdminService's existing list extended in place). Every OData path verbatim-matches the service's current @path.
  • Approuter routes — 5 /hcql/* routes in approuter/xs-app.json, authenticationType: xsuaa, scopes matching each service's @requires, JWT-forwarded to tutorials-srv, placed before the catch-all. No status prop (would crash approuter v16).
  • Test — test/unit/hcql-enablement.test.js: OData path unchanged, HCQL path works, malformed CQN → 400 + server survives, auth enforced before CQN parse.
  • Docs — docs/developers/reference/hcql-support.md rewritten (distinct paths, authenticated-only, cds >= 10.1.0, DoS-fixed-in-10.1.0); CLAUDE.md gotcha + VitePress sidebar; design spec docs/superpowers/specs/2026-09-11-2247-hcql-reland-design.md.

Why it's safe (the #1004 regression)

#1004 broke 218 tests because plain @hcql co-mounted on each OData @path and parsed OData traffic as CQN. Here HCQL rides its own /hcql/<svc> path via object-form @protocol lists; the OData path rejects CQN bodies (405). The malformed-CQN process-exit DoS present in 10.0.3 is fixed in 10.1.0 (→ 400, server stays up). Auth is enforced before CQN parse. HCQL is documented read-only (writes are explicitly unsupported in the beta).

CSRF on /hcql/* routes — resolved to CSRF-on

The check-csrf-clients static guard (post-#895) bans csrfProtection on any route except the allowlisted /mcp/* and /a2a JSON-RPC sources. The 5 /hcql/* routes originally set csrfProtection: false (matching the MCP M2M pattern); a background security scan flagged that MEDIUM (CSRF), and the guard failed CI.

Decision (maintainer, this PR): flip /hcql/* to the approuter CSRF-on default — the secure choice, no allowlist change, matches the security-bot finding. The csrfProtection: false flag was removed from all 5 routes.

Consequence for callers: HCQL POSTs now require the approuter x-csrf-token: fetch two-step handshake — documented in hcql-support.md (curl intro, token-fetch step, AdminService example, smoke-matrix note). Reverting to the M2M csrfProtection: false pattern later would require adding /hcql/* to CSRF_EXEMPT_SOURCES in scripts/check-csrf-clients.ts and issue-tracker sign-off — both intentionally gated.

Validation

  • Unit suite: green — 9746 passed / 16 skipped / 2 todo, 0 non-environmental failures. The only file-level failures are the known unbound-worktree issues (channel-atlas graphology/sigma). No OData/CQN/HCQL/CAP-10 regression. cds build --production clean.
  • Load-bearing positive: server boots clean under CAP 10.1.0 + HCQL + @cap-js/mcp 1.3.0 + the transitive @hono/node-server 1.x→2.x major (via @modelcontextprotocol/sdk). The MCP boot path is exercised, no crash.
  • Hybrid suite: NOT run here — this fresh worktree was never bind:setup-provisioned, so cds bind --exec has no HANA/XSUAA/credstore bindings (139 binding-guard file-fails are environmental). Maintainer: please run npm run test:hybrid on a bound environment before merging, plus the post-deploy HCQL smoke matrix in hcql-support.md (5 response codes).

Kill switch

Delete srv/hcql-enablement.cds + drop the hcql entry from AdminService's @protocol list in srv/admin-service-mcp.cds, then cds build --production + redeploy. ~15 min.

Re-land HCQL (issue Option 1) scoped to the 5 authenticated services,
on a @sap/cds 10.0.3 -> 10.1.0 bump with explicit per-service @protocol
path isolation (HCQL at /hcql/<svc>, OData path unchanged). Spike verified
the collision that caused the #1004 revert and the clean-landing config.
Phase A CAP ecosystem to latest-compatible-with-cds-10.1 (required),
Phase B broader tree within-major only (majors deferred). Full suite +
build gate each phase. Records maintainer decisions.
Major bump to 3.0.0 deferred to a separate PR per maintainer policy.
2.1.0 is the highest 2.x with peer @sap/cds >=8, compatible with 10.1.
Add { kind: 'hcql', path: '/hcql/admin' } to AdminService's @protocol
list in srv/admin-service-mcp.cds. HCQL mounts on its own path so OData
at /admin is never asked to parse CQN bodies (guards the #1004 regression).

Add test/unit/hcql-enablement.test.js: 5 tests confirm HCQL mounts, OData
path is clean, malformed CQN returns 400, and unauthenticated calls get 401.
Adds srv/hcql-enablement.cds with object-form @protocol lists that mount
HCQL on /hcql/author, /hcql/analytics, /hcql/exports, /hcql/consolidation
while preserving each service's existing OData @path verbatim.

Also cleans the dead no-op assertion from the AdminService test block
and extends the test suite with 12 new cases (3 per service × 4 services):
mount presence, OData-path rejection, and unauthenticated rejection.

All 17 hcql-enablement tests pass; cds build --production succeeds.
consolidation mocked user was already present in .cdsrc.json.
Final-review finding: approuter v16 enables CSRF for non-GET by default,
so the documented Bearer-token curl POSTs to /hcql/* would 403. Match the
sibling programmatic-POST routes (/mcp/*, /a2a) which set csrfProtection:false.
Auth is unaffected — XSUAA scope is still enforced per route. Also corrects
hcql-support.md: there is no .deploy/xs-app.json (MTA builds from ../approuter).
# Conflicts:
#	package-lock.json
#	package.json
cds build --production stamps the cds-dk version into the csn snapshot;
the CAP 10.1.0 runtime bump advances it from v10.0.3. Schema entities
unchanged (HCQL enablement is annotation-only @protocol lists). Clears
the CDS build staging check.

Co-authored-by: Ordinary Tom <thomas.jung@sap.com>
srv-qa is a separate deployable CAP module; the CF buildpack installs it
from srv-qa/package-lock.json. The root CAP 10.1.0 bump left srv-qa on
cds ^10.0.3 / @cap-js/hana ^3.0.1, tripping check-srv-qa-dep-parity.
Regenerated srv-qa/package-lock.json (now resolves cds 10.1.0).

Co-authored-by: Ordinary Tom <thomas.jung@sap.com>
The check-csrf-clients static guard (post-#895) bans csrfProtection on any
route except the allowlisted /mcp/* and /a2a JSON-RPC sources. The 5 /hcql/*
routes set csrfProtection:false, tripping the guard. Per maintainer decision,
remove the flag (approuter CSRF-on default) rather than allowlist HCQL —
the secure default, and matches the security-bot MEDIUM finding.

Consequence: clients must do the x-csrf-token: fetch two-step before each
HCQL POST. Documented the handshake in hcql-support.md (curl intro + token
fetch step + AdminService example + smoke-matrix note). Flipping back to the
M2M csrfProtection:false pattern would require adding /hcql/* to
CSRF_EXEMPT_SOURCES plus issue-tracker sign-off.

Co-authored-by: Ordinary Tom <thomas.jung@sap.com>
@jung-thomas
jung-thomas marked this pull request as ready for review September 11, 2026 23:46
@jung-thomas
jung-thomas merged commit ff35cf7 into DEV Sep 11, 2026
9 checks passed
@jung-thomas
jung-thomas deleted the worktree-hcql-reland-2247 branch September 11, 2026 23:46
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