Skip to content

feat(challenge-widget): grading loop + spec cache + cost sizing (#2441) - #2444

Merged
jung-thomas merged 2 commits into
DEVfrom
worktree-challenge-widget-2441
Sep 20, 2026
Merged

jung-thomas merged 2 commits into
DEVfrom
worktree-challenge-widget-2441

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Closes #2441. Clears the three activation gates from the #2362 challenge-widget POC writeup. The widget still ships dark — this PR does NOT flip flag.challengeWidget or trigger a rebuild; those need the editorial review + recorded greenlight from the issue.

The whole subsystem mirrors the existing AI-quiz validate-answer path ~1:1.

Gate 1 — freeText + MCQ grading

  • db: ChallengeAnswers entity — server-only freeText reference answers, keyed (tutorial, stepNumber, nodeId). Anti-leak: the reference never ships to clients (MCQ answerIndex stays public/client-graded).
  • publish: POST /content/challenge-answers (REPLACE-per-slug, contentAuthMiddleware) + a CLI uploader wired non-fatally into publish-content.ts (skipped for channel=qa, same as validate-answer validate-answer-specs publish returns HTTP 500 {error:internal} for 23 slugs on QA full rebuild #1375).
  • runtime: POST /api/challenge-grade — authenticated, rate-limited (30/hr, 5/5min per step), gated on the flag.challengeWidget ImsConfig kill switch (fail-open). Reuses validate-answer-prompt.js schema/redaction and defaultCallModel verbatim.
  • client: ChallengeRenderer.vue now submits — MCQ client-graded against the public answerIndex (no server call); freeText AI-graded via /api/challenge-grade. slug+stepNumber threaded through main.ts; isPreview never POSTs.

Gate 2 — build-time spec cache

scripts/lib/challenge-spec-cache.ts (mirrors ai-quiz-cache.ts) + wiring in fetch-tutorials.ts. A cache hit reconstructs step.challenge + the reference sidecar with no model call (hashKey over stepBody + PROMPT_VERSION + modelName). Turns repeat flag-on rebuilds from cold-cost into cache-miss-only.

Gate 3 — AI Core cost sizing

docs/developers/reference/challenge-widget-cost-sizing.mdanalytical estimate (cold full pass ~11M tokens across ~1400 tutorials; warm rebuild ~50× less). Flagged as an estimate pending a live per-call DEV measurement — cf was locked to PROD during a concurrent operation, so the live pass was deferred; the doc includes the exact steps to run it on DEV.

Verification

  • 65 new/extended unit tests across the 8 touched files, all green.
  • cds compile srv clean; existing ai-challenge-spec.test.js still passes.
  • Full npm test: 10,437 tests pass. The 20 failing test files are pre-existing fresh-worktree missing-optional-dep issues (sigma/mediapipe graph+selfie islands, srv-qa suites needing a bound model) — none are in this change.

Not in scope (human/ops follow-ups)

  • Flip flag.challengeWidget on PROD.
  • Flag-on full PROD rebuild.
  • Editorial review of AI-authored challenges + recorded greenlight.
  • Live per-call cost measurement on DEV (deferred; steps in the sizing doc).

…izing (#2441)

Clears the three activation gates from the #2362 challenge-widget POC writeup.
The widget still ships dark (flag.challengeWidget OFF on PROD); this PR does NOT
flip the flag or trigger a rebuild — those need editorial review + greenlight.

Gate 1 — grading loop (mirrors the AI-quiz validate-answer trio):
- db: ChallengeAnswers entity (server-only freeText reference answers, keyed
  tutorial/stepNumber/nodeId; anti-leak — reference never ships to clients).
- POST /content/challenge-answers publish endpoint (REPLACE-per-slug,
  contentAuthMiddleware) + CLI uploader wired into publish-content.ts
  (non-fatal, channel=qa skipped).
- POST /api/challenge-grade runtime endpoint (authenticated, rate-limited,
  gated on flag.challengeWidget ImsConfig, fail-open). Reuses the
  validate-answer prompt/schema/redaction and defaultCallModel verbatim.
- client: ChallengeRenderer.vue now submits — MCQ client-graded vs public
  answerIndex (no server call), freeText AI-graded via /api/challenge-grade;
  slug+stepNumber threaded through main.ts (isPreview never POSTs).

Gate 2 — build-time spec cache (mirrors ai-quiz-cache):
- scripts/lib/challenge-spec-cache.ts + wiring in fetch-tutorials.ts: a hit
  reconstructs step.challenge + the reference sidecar with no model call
  (hashKey over stepBody+PROMPT_VERSION+modelName). Turns repeat flag-on
  rebuilds from cold-cost into cache-miss-only.

Gate 3 — AI Core cost sizing:
- docs/developers/reference/challenge-widget-cost-sizing.md — analytical
  estimate (cold ~11M tokens across ~1400 tutorials; warm rebuild ~50x less).
  Flagged as estimate pending a live per-call DEV measurement (cf was locked
  to PROD during a concurrent op).

Tests: 65 new/extended unit tests across the 8 touched files, all green;
cds compile clean; existing ai-challenge-spec test still passes.
…2441)

check-srv-qa-route-drift flagged the new publish route. It is intentionally
srv-only — the QA model (com.sap.developers.ims.qa) does not load
ChallengeAnswers, srv-qa has no /api/challenge-grade reader, and the publish
CLI skips the step for channel=qa. Same rationale as validate-answer-specs.
@jung-thomas
jung-thomas merged commit 8c17e8d into DEV Sep 20, 2026
8 checks passed
@jung-thomas
jung-thomas deleted the worktree-challenge-widget-2441 branch September 20, 2026 21:22
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