Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d4e992e
chore: remove btp-ea-onboard-* slugs from discovery-baseline (retire …
jung-thomas Nov 1, 2026
f25d93b
chore: remove btp-ea-onboard-* hashes (retire btp-onboarding repo)
jung-thomas Nov 1, 2026
2c2584f
docs: add retirement plan for btp-onboarding repo
jung-thomas Nov 1, 2026
8dd3a43
Merge pull request #2965 from sap-tutorials/worktree-retire-btp-onboa…
jung-thomas Nov 1, 2026
347e040
docs: add CAP 10.2 upgrade implementation plan
jung-thomas Nov 1, 2026
b694d45
chore(deps): bump @sap/cds and cds-dk to ^10.2.0
jung-thomas Nov 1, 2026
0181679
chore(deps): bump @cap-js/* plugins to latest 10.2-compatible versions
jung-thomas Nov 1, 2026
8be69ec
chore(deps): sync srv-qa CAP deps to root (cds 10.2, hana 3.1.2)
jung-thomas Nov 1, 2026
b9e78fc
Merge pull request #2966 from sap-tutorials/worktree-cap-10-2-upgrade
jung-thomas Nov 1, 2026
b4c6185
docs: add draft_admin_integrity (#2909) implementation plan
jung-thomas Nov 1, 2026
76f2e3c
feat(cap): enable cds.fiori.draft_admin_integrity for optimized draft…
jung-thomas Nov 1, 2026
963f47f
fix(admin): reconcile Mission/Group rollups on draft DISCARD (draft_a…
jung-thomas Nov 1, 2026
cf1a476
docs: draft_admin_integrity migration runbook + Top Gotchas entry (#2…
jung-thomas Nov 1, 2026
5cdc5f6
Merge pull request #2967 from sap-tutorials/worktree-draft-admin-inte…
jung-thomas Nov 1, 2026
1fe4527
fix(test): apply draft_admin_integrity cleanup to 2 missed tests (#2909)
jung-thomas Nov 1, 2026
f28b706
Merge pull request #2969 from sap-tutorials/worktree-fix-draft-delete…
jung-thomas Nov 1, 2026
21deed7
test(hybrid): draft DISCARD reconcile on real HANA (#2909)
jung-thomas Nov 1, 2026
e41adcb
Merge pull request #2970 from sap-tutorials/worktree-hybrid-discard-t…
jung-thomas Nov 1, 2026
f3f9471
feat(serve): draft-integrity-preflight — WARN on missing HANA cascade…
jung-thomas Nov 1, 2026
617538f
chore(serve): wire draft-integrity-preflight startup check (#2968)
jung-thomas Nov 1, 2026
cdbf577
docs: startup preflight check in draft_admin_integrity runbook (#2968)
jung-thomas Nov 1, 2026
0659d6d
Merge pull request #2971 from sap-tutorials/worktree-draft-integrity-…
jung-thomas Nov 1, 2026
6f0cf28
chore(release): bump MTA version to 1.42.0
jung-thomas Nov 1, 2026
f635bf5
Merge pull request #2972 from sap-tutorials/worktree-release-1.42.0
jung-thomas Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .deploy/mta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ ID: tutorials-ims
# Bump this on each release you deploy — it's the version shown by `cf mtas`
# and in the mtar filename (tutorials-ims_<version>.mtar). Deploy is manual:
# `cd .deploy && mbt build && cf deploy mta_archives/tutorials-ims_<version>.mtar -e ../deploy/<env>.mtaext -f`.
version: 1.41.8
version: 1.42.0

# Top-level parameters (overridable per-env via deploy/<env>.mtaext).
parameters:
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,4 @@ The load-bearing few. **Full detail for every relocated item → [tutorials-ims-
- **External channels subsystem** — `Channels` entity (`db/channels.cds`) is the source of truth; re-ingest via `npm run seed-channels`; directory at `/channels` (baked by `fetch-channels` in `build:all`); verb-lane fill via `npm run promote-channels`; community items never land in `START_HERE`. → [channels.md](docs/developers/reference/channels.md).
- **TechEd sessions subsystem (#2312)** — new `TechEd*` entities (`db/external/teched.cds`, NOT reused `CommunityEvents`), loaded via a **`using` side-effect** in `db/external-content.cds` (don't delete the import); RainFocus ingest (`POST https://events.rainfocus.com/api/sessions`, headers `rfapiprofileid`/`rfwidgetid` per venue from `RAINFOCUS_{TE26,TEV26}_PROFILE`/`_WIDGET` env — live-confirmed defaults baked in, IDs may rotate; speakers embedded in `participants[]`, tracks in `attributevalues[attribute=="Track"]`, offset-paginated); idempotent seed via `scripts/seed-teched.cjs`; public `/build/teched` feed; corrected fetcher + delta cron jobs shipped in #2332; `/teched/` page + island, KG projection, embeddings, and Devtoberfest cross-linking are later units. → [teched.md](docs/developers/reference/teched.md).
- **Signed provenance envelope (#2245)** — `PROVENANCE_ENVELOPE_ENABLED` (DB config, default OFF, DEV-first); two anonymous Express endpoints (`/content/tutorials/:slug/provenance` and `/.well-known/tutorial-provenance/jwks.json`); signing key = `PROVENANCE_SIGNING_KEY` credstore secret (Ed25519 PKCS8 PEM); rotate via `/admin-ui/#secrets`. Fail-open. → gotchas.md "Signed provenance envelope".
- **Optimized draft deletion `draft_admin_integrity` ON (#2909, CAP 10.2)** — `cds.fiori.draft_admin_integrity: true` in package.json; draft discard cascade-deletes draft `CompletionPathItems`/`GroupPathItems` at the DB level, so rollup reconcile now fires via `after('DISCARD','Missions.drafts'/'Groups.drafts')` (the `.drafts` target is required), NOT the per-row child `after('DELETE')` hooks (those stay for direct programmatic deletes). Deleting an active entity right after `draftActivate` can 403 ("a draft exists") until the residual draft is discarded. Constraint migration deploys to HANA (`.hdbconstraint`, `NOT VALIDATED`) BEFORE the flag serves. → gotchas.md "Optimized draft deletion".
16 changes: 16 additions & 0 deletions docs/developers/reference/tutorials-ims-gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,19 @@ All default OFF and DEV-only unless noted. Toggles fail-open on every fault path
- **`sourceCommit` plumbing** — `ContentCurrent.sourceCommit` (String(64)) is populated by the publish pipeline via the `source_commit` field in the publish payload; `scripts/fetch-tutorials.ts` threads the HEAD commit SHA through to the publish client. On older published content the column is NULL; the provenance JWS payload will carry `sourceCommit: null` in that case, which is valid.

- **Quarantine + publisher are status-aware for DELETED/INACTIVE (#2585 follow-up)** — a tutorial with `Tutorials.status IN ('DELETED','INACTIVE')` is excluded from the whole publish/quarantine path, so a retired tutorial whose source `.md` still exists upstream no longer gets re-discovered, re-quarantined, or shown in the Admin active-quarantine facet. Mechanism: `GET /content/excluded-slugs` (public-read on prod srv, `hashesAuth` on srv-qa) returns those lowercased slugs; `scripts/fetch-tutorials.ts` (`applyExclusion` after discovery, both main and `--regenerate` paths) and `scripts/validate-tutorials.ts` (skip at the top of the file loop) fetch it and skip those slugs — **fail-open**, an unreachable endpoint means no filtering; and `quarantineIngestHandler` (`packages/content/content-store.js`) drops events for those slugs before persisting, so the slug-only Admin facet join never surfaces them. One-off cleanup of a live snapshot without a full rebuild: `scripts/clear-deleted-from-quarantine.cjs` (`cds bind --exec … -- node scripts/clear-deleted-from-quarantine.cjs --commit`; dry-run by default). Note: the pre-existing `/content/source-hashes` filter excludes only `INACTIVE` — the new `/content/excluded-slugs` query covers **both** statuses.

## Optimized draft deletion — `draft_admin_integrity` (CAP 10.2, #2909)

- **`cds.fiori.draft_admin_integrity: true` is ON in `package.json`** — CAP 10.2 optimized draft deletion. `cds build` generates `ON DELETE CASCADE` constraints on each draft table's `DraftAdministrativeData_DraftUUID` FK; draft activation and root cancellation then delete only the `DraftAdministrativeData` record and let the DB cascade the child draft rows, instead of the runtime issuing a per-table DELETE for each. All 19 draft-enabled entities live in `AdminService`.

- **Draft DISCARD now reconciles rollups via root handlers, NOT child DELETE events** — because the cascade removes draft `CompletionPathItems`/`GroupPathItems` at the DB level, the per-row `before/after('DELETE')` hooks (`srv/admin-service.js:1197-1242`) do **not** fire on a draft discard. `srv/admin-service.js` registers `this.after('DISCARD', 'Missions.drafts', …)` and `…('Groups.drafts', …)` (the `.drafts` target is required — DISCARD on the active entity does not fire) which re-fire `reconcileEntityForUsers` for the active entity's `legacyId`. The existing child DELETE handlers stay — they still fire for **direct programmatic deletes** (e.g. `service.tx()` row deletes), which the cascade does not touch. Fail-open like the SAVE reconcile hooks.

- **Delete-active-immediately-after-activate now 403s until the draft is cleared** — under the flag, `DELETE …(IsActiveEntity=true)` right after `draftActivate` can return `403 — Entity cannot be deleted because a draft exists` (observed on SQLite in tests). Any code or test that deletes an active entity shortly after activation must first discard the residual draft (`DELETE …(IsActiveEntity=false)`), then delete the active. This is the documented 10.2 behavior change, not a bug.

- **Constraint-deploy migration ordering (HANA)** — constraints generate at `cds build` once the flag is on and deploy to HANA as `.hdbconstraint` with `NOT VALIDATED` (so existing orphaned draft rows don't block the deploy). Deploy the constraints to a target env and let them land **before** the flag serves there — once the runtime serves with the flag on, it stops issuing per-table draft deletes and relies on the cascade. Order DEV → test → PROD per the no-main-hotfix model; confirm scope with the maintainer before any PROD step.

- **Startup preflight check (#2968)** — `srv/lib/draft-integrity-preflight.js` runs once at boot (inside `cds.on('served')`, HANA only, fail-open). Queries `SYS.REFERENTIAL_CONSTRAINTS` for `ON DELETE CASCADE` FKs on every draft table and emits `cds.log('draft-integrity-preflight').warn(…)` listing any missing tables. If that warn line appears in CF logs after a deploy, the HDI constraint deploy has not yet landed — wait for HDI to complete and restart the app. Absent = all 26 constraints present and verified.

- **Optional orphan purge** — set `cds.fiori.draft_orphan_cleanup: true` to purge pre-existing orphaned draft rows as background cleanup during draft create/edit, then turn it back off once orphans are cleared. Independent of `draft_admin_integrity`; default OFF. Not enabled in `package.json`.

- **Rollback** — the constraints are additive (`ON DELETE CASCADE` on draft tables only). To revert: flip `draft_admin_integrity` back off (the runtime resumes per-table draft deletes, and the DISCARD reconcile handlers become harmless no-ops relative to the restored child-DELETE path) and optionally drop the generated constraints on the next deploy.
310 changes: 310 additions & 0 deletions docs/superpowers/plans/2026-11-01-cap-10-2-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
# CAP 10.2 Upgrade Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Upgrade all CAP and CAP plugin packages from 10.1.x to 10.2.x, and opt into new 10.2 features where beneficial.

**Architecture:** This is a dependency-bump upgrade. Core changes are: bump `@sap/cds` and `@sap/cds-dk` to `^10.2.0`, bump all `@cap-js/*` plugins to their latest 10.2-compatible versions, relax the `@cap-js/ai` tilde pin to `^1.3.0`, and evaluate three opt-in flags (`draft_admin_integrity`, `draft_orphan_cleanup`, and the new `@cds.autoexpose: false` for internal services). No schema changes are required; no Java dependencies in this project.

**Tech Stack:** Node.js 22.23.4, `@sap/cds` ^10.2.x, CAP plugin ecosystem, SQLite (dev/test), HANA Cloud (hybrid/prod)

**Spec:** https://cap.cloud.sap/docs/releases/2026/oct26

## Global Constraints

- Node.js 22.23.4 — pinned exactly in `engines.node`; do NOT change this
- All CAP Node.js only — no Java migration steps apply
- Tests must pass: `npm test` (in-memory SQLite, fast); hybrid tests are out-of-scope for this PR
- Never write raw SQL — use `cds.ql` or CQL
- PR targets `DEV` branch (never `main`)
- `@cap-js/graphql` is currently pinned to `0.14.0` (exact, no caret); only bump if a new release is available
- `@cap-js/attachments` is currently pinned to `4.0.0` (exact); only bump if a new release is available
- `@cap-js/data-inspector` is currently pinned to `1.0.5` (exact); only bump if `2.0.1` is confirmed for this ecosystem
- The `mcp.per_action_tool: true` config remains in place — the "single `call` tool" 10.2 default does not apply here since we intentionally override it

## Review Focus

- `@cap-js/ai` version jump from `~1.1.0` to `^1.3.0` spans two minor versions: verify AI-author (ValueList recommendations), ONNX model initialization, and `AICore-mocked` local mode still work after bump
- `cds.fiori.draft_admin_integrity: true` changes which delete handlers are called on draft entities — verify that `AdminService` draft entities (`Tutorials`, `Missions`, `Groups`, etc.) still delete cleanly and that any custom `@before DELETE` handlers are not skipped
- `draft_orphan_cleanup` is a new background job; verify it does not interfere with the CAP scheduler / `CronService` under `cds watch` or unit tests
- `@cap-js/change-tracking` bumped to 2.2.3 — verify that change tracking on `TutorialMeta` and related entities still works and that tracked payloads in tests match expected structure
- `@sap/cds` 10.2.x removes `@odata.metadataEtag` from default responses; the `./test/` suite may assert on response shapes — scan for any assertion on `@odata.metadataEtag` and update or remove

---

## Task 1: Bump core CAP packages and run baseline tests

**Files:**
- Modify: `package.json` (dependency version strings only)
- Verify: `package-lock.json` (auto-generated by npm install)

**Interfaces:**
- Produces: updated `package.json` and `package-lock.json` that later tasks depend on

- [ ] **Step 1: Create a new branch off DEV**

```bash
git fetch origin
git checkout -b upgrade/cap-10.2 origin/DEV
```

- [ ] **Step 2: Bump `@sap/cds` and `@sap/cds-dk` in `package.json`**

Change:
```json
"@sap/cds": "^10.1.0"
"@sap/cds-dk": "^10.1.1"
```
To:
```json
"@sap/cds": "^10.2.0"
"@sap/cds-dk": "^10.2.0"
```

- [ ] **Step 3: Run `npm install` and verify resolution**

```bash
npm install 2>&1 | tail -20
node -e "console.log(require('@sap/cds/package.json').version)"
```
Expected: version printed starts with `10.2.`

- [ ] **Step 4: Run baseline unit tests**

```bash
npm test 2>&1 | tail -40
```
Expected: all tests pass (or same failures as before this task)

- [ ] **Step 5: Commit**

```bash
git add package.json package-lock.json
git commit -m "chore(deps): bump @sap/cds and cds-dk to ^10.2.0"
```

---

## Task 2: Bump CAP plugin packages

**Files:**
- Modify: `package.json`
- Verify: `package-lock.json`

**Interfaces:**
- Consumes: branch from Task 1 with `@sap/cds` 10.2 installed
- Produces: all `@cap-js/*` plugins and `@cap-js-community/websocket` bumped to their current latest

**Version targets** (verified available on npm before writing this plan):

| Package | Current | Target |
|---|---|---|
| `@cap-js/ai` | `~1.1.0` | `^1.3.0` |
| `@cap-js/audit-logging` | `^1.3.0` | `^1.3.0` (already at latest; keep) |
| `@cap-js/change-tracking` | `^2.2.2` | `^2.2.3` |
| `@cap-js/hana` | `^3.1.0` | `^3.1.2` |
| `@cap-js/mcp` | `1.3.0` (exact) | `^1.5.0` |
| `@cap-js/ord` | `^1.9.3` | `^1.10.2` |
| `@cap-js/sqlite` | `^3.1.0` | `^3.1.2` |
| `@cap-js/telemetry` | `^2.0.1` | `^2.2.0` |
| `@cap-js-community/websocket` | `^1.11.1` | `^1.11.4` |
| `@cap-js/cds-test` (devDep) | `^1.0.2` | `^1.0.2` (keep; latest is same) |
| `@cap-js/data-inspector` | `1.0.5` (exact) | bump to `2.0.1` (exact) only if no breaking API changes confirmed |
| `@cap-js/graphql` | `0.14.0` (exact) | keep at `0.14.0` (no new release) |
| `@cap-js/attachments` | `4.0.0` (exact) | keep at `4.0.0` (no new release) |

- [ ] **Step 1: Apply version bumps to `package.json`**

Edit the `dependencies` and `devDependencies` blocks per the table above. For `@cap-js/data-inspector`, check `npm info @cap-js/data-inspector versions --json` first — if the `2.0.1` release notes indicate any breaking API changes for this project's usage (search for `data-inspector` in `srv/`), keep it at `1.0.5`.

- [ ] **Step 2: Run `npm install`**

```bash
npm install 2>&1 | tail -20
```
Expected: no peer dependency errors

- [ ] **Step 3: Run unit tests**

```bash
npm test 2>&1 | tail -40
```
Expected: all tests pass

- [ ] **Step 4: Verify AI plugin still initializes**

```bash
node -e "const cds = require('@sap/cds'); const ai = require('@cap-js/ai'); console.log('AI plugin loaded:', ai.version || 'ok')" 2>&1
```
Expected: no error

- [ ] **Step 5: Commit**

```bash
git add package.json package-lock.json
git commit -m "chore(deps): bump @cap-js/* plugins to latest 10.2-compatible versions"
```

---

## Task 3: Evaluate and adopt opt-in 10.2 flags

**Files:**
- Modify: `package.json` (`.cds` block → `fiori` section)

**Interfaces:**
- Consumes: Task 2 (all plugins at 10.2 versions)
- Produces: `package.json` CDS config updated with evaluated opt-in flags

The three 10.2 opt-ins to evaluate:

1. **`cds.fiori.draft_admin_integrity: true`** — draft deletion uses DB cascades; only matters if draft entities have custom delete handlers. Search before adopting.
2. **`cds.fiori.draft_orphan_cleanup: true`** — background cleanup of orphaned draft rows; safe to adopt.
3. **`@cds.autoexpose: false`** annotation — prevents entity duplication in exported APIs; applies to CDS model files, not `package.json`.

- [ ] **Step 1: Check for custom draft delete handlers**

```bash
grep -rn "DELETE\|draft.*delete\|delete.*draft" srv/ --include="*.js" | grep -i "before\|on\|after" | head -20
```

If any handler targets draft entities with custom logic, skip `draft_admin_integrity` for now and note it in the commit message.

- [ ] **Step 2: Adopt `draft_orphan_cleanup`**

In the `cds.fiori` section of `package.json` (create it if absent):

```json
"fiori": {
"draft_orphan_cleanup": true
}
```

- [ ] **Step 3: Conditionally adopt `draft_admin_integrity`**

If Step 1 found no custom draft delete handlers, add to `cds.fiori`:

```json
"draft_admin_integrity": true
```

If custom handlers found, skip and add a comment in the PR description explaining why.

- [ ] **Step 4: Run unit tests to verify draft behavior unchanged**

```bash
npm test -- --grep "draft" 2>&1 | tail -30
```
Expected: tests related to drafts pass; if `draft_admin_integrity` causes failures, revert only that flag.

- [ ] **Step 5: Run the full test suite**

```bash
npm test 2>&1 | tail -40
```
Expected: all tests pass

- [ ] **Step 6: Commit**

```bash
git add package.json
git commit -m "feat(cap): adopt CAP 10.2 draft opt-in flags (orphan_cleanup[, admin_integrity])"
```

---

## Task 4: Check for metadataEtag and CqnSelect.getTransformations impacts

**Files:**
- Modify: any test files in `test/` that assert on `@odata.metadataEtag` (only if found)
- Modify: any service code that calls `CqnSelect.getTransformations()` and mutates the result (only if found)

**Interfaces:**
- Consumes: Task 2 (cds 10.2 installed)
- Produces: tests and code that are compatible with 10.2 breaking changes

- [ ] **Step 1: Scan test files for `metadataEtag` assertions**

```bash
grep -rn "metadataEtag" test/ --include="*.js" --include="*.ts" 2>&1
```

If found: remove or update assertions — the property is no longer in default OData responses; it only appears when `Accept: application/json;odata.metadata=full` is set.

- [ ] **Step 2: Scan service code for `getTransformations()` mutation patterns**

```bash
grep -rn "getTransformations" srv/ --include="*.js" 2>&1
```

If found and the result is mutated (e.g., `.push()`, `.splice()`): update to use `Select.transformations(List)` instead.

- [ ] **Step 3: Run full test suite**

```bash
npm test 2>&1 | tail -40
```
Expected: all tests pass

- [ ] **Step 4: Commit (only if changes were made)**

```bash
git add test/ srv/
git commit -m "fix(cap10.2): remove metadataEtag assertions, use Select.transformations() for mutations"
```

If no changes were needed, skip this commit and note "no code changes required for breaking changes" in the PR.

---

## Task 5: Update CLAUDE.md and open PR

**Files:**
- Modify: `CLAUDE.md` (version references)
- No `docs/` changes needed — this is a version bump, not a new feature

**Interfaces:**
- Consumes: all prior tasks complete, tests green

- [ ] **Step 1: Update version references in CLAUDE.md**

Search for `10.1` in `CLAUDE.md` and update to `10.2` where they refer to the CAP version being used:

```bash
grep -n "10\.1\|cds 10" CLAUDE.md | head -20
```

Only update references to the current CAP version; do not update unrelated version numbers.

- [ ] **Step 2: Run full test suite one final time**

```bash
npm test 2>&1 | tail -40
```
Expected: all tests pass

- [ ] **Step 3: Open PR targeting DEV**

```bash
gh pr create \
--base DEV \
--title "chore(deps): upgrade to CAP 10.2" \
--body "$(cat <<'EOF'
## Summary
- Bumps `@sap/cds` and `@sap/cds-dk` to `^10.2.0`
- Updates all `@cap-js/*` plugins to latest 10.2-compatible versions
- Relaxes `@cap-js/ai` pin from `~1.1.0` to `^1.3.0`
- Adopts CAP 10.2 opt-in flags: `draft_orphan_cleanup` (and `draft_admin_integrity` if no custom handlers found)
- No schema changes; no Java migration steps

## Breaking changes addressed
- `@odata.metadataEtag` removed from default OData responses — scanned and updated if needed
- `CqnSelect.getTransformations()` immutable — scanned and updated if needed

## Test plan
- [ ] `npm test` passes (in-memory SQLite)
- [ ] Dev server starts: `cds watch` (manual check)
- [ ] AI plugin initializes without error on `cds watch` startup
EOF
)"
```
Loading
Loading