Appearance
Teacher AI Preview Question Bank Solution Runtime Proof
Date: 2026-07-12
Scope
This proof covers the reviewed AI-solution path only:
teacher-web -> exact opt-in Gateway route -> question-bank-service -> replacement question version
AI remains a preview provider. Question Bank owns the canonical question, version, explanation provenance, tenant scope, and idempotency record. The default deploy/gateway/routes.json remains unchanged: its broad /api/questions route is still legacy_proxy.
Automated Evidence
bash
GOTOOLCHAIN=go1.25.11 go test ./services/question-bank-service/internal/usecase \
-run TestQuestionService_ApplyAIPreviewSolutionCreatesVersionAndIsIdempotent -count=1 -v
GOTOOLCHAIN=go1.25.11 go test ./services/question-bank-service/internal/http \
-run TestServer_ApplyAIPreviewSolutionWritesCanonicalVersion -count=1 -v
GOTOOLCHAIN=go1.25.11 go test ./services/api-gateway/internal/gateway \
-run TestGatewayTeacherAIPreviewRoutesForwardOnlyAuthenticatedTeacherScope -count=1
pnpm --filter @hoctapaz/ui typecheck
pnpm --filter @hoctapaz/ui test -- teacher-workspace.test.tsx
make test-teacher-ai-preview-routes
make test-ai-classification-job-route-guardAll commands passed on 2026-07-12. The Gateway test verifies the exact route, authenticated actor headers, role/organization guard, and forged caller-header replacement. The UI test verifies that a logged-in teacher must supply a canonical Question Bank ID and save a reviewed preview through /api/questions/ai-preview-solutions.
Compose Runtime Fixture
The service was rebuilt with the real migration path:
bash
docker compose -f deploy/docker-compose.yml up -d --build question-bank-serviceGET http://127.0.0.1:8089/readyz returned 200 with status ready. Using isolated organization org_ai_solution_runtime_20260712 and teacher teacher_ai_solution_runtime_20260712, the runtime fixture:
- Created canonical question
q_c33a0d3cc3a04872958dc59f81d1a113with original versionqv_ab7bb62e571358706c01313e188d7ea1and source metadata. - Posted the reviewed
solution_runtime_preview_1toPOST /v1/questions/ai-preview-solutions; it returned201and replacement versionqv_3be518fe6e99965686da2752c1f81c5b. - Verified persisted explanation text,
type=ai_preview_solution,source=ai-preview, preview/provider/model fields, two ordered steps, and unchanged original source metadata. - Replayed the exact payload; it returned
200with the same replacement version, so no extra version was added. - Retried as a different teacher in the same organization; it returned
404. - Hard-deleted the unused fixture question successfully (
200), which also removes the companion idempotency record.
Exact Gateway Runtime Fixture
The shared host port 8085 belongs to the local control plane, so this proof used a temporary Compose Gateway on 127.0.0.1:18085 rather than changing the default Gateway or its route table:
bash
docker compose -f deploy/docker-compose.yml build api-gateway
docker compose -f deploy/docker-compose.yml run -d --no-deps \
--name hoctapaz-ai-preview-gateway-proof -p 18085:8080 \
-e GATEWAY_ROUTE_TABLE=/deploy/gateway/routes.teacher-ai-preview-native-example.json \
api-gatewayThe temporary Gateway reported ready and /v1/routes exposed only the exact teacher-ai-preview-solution-write route for this write. A short-lived HS256 teacher token then submitted the reviewed solution with forged X-Actor-* headers. The actual request returned 201 with X-Hoctapaz-Gateway-Route: teacher-ai-preview-solution-write and X-Hoctapaz-Gateway-Route-State: native_write; Question Bank persisted version 2 and the expected preview/step provenance. The identical replay returned 200. The same request using a STUDENT token returned 403 AUTH_FORBIDDEN before it reached Question Bank. The fixture was hard-deleted with 200, then hoctapaz-ai-preview-gateway-proof was removed.
Teacher Browser Surface
Playwright opened the running teacher Vite app at http://127.0.0.1:5202/teacher/questions/ai-solutions. Anonymous navigation redirected to /login?next=%2Fteacher%2Fquestions%2Fai-solutions; a local teacher demo session returned to the protected route. The desktop and 390 x 844 mobile views both rendered the canonical Question Bank ID field, source selector, selected-solution preview, and review action without overlap. Mobile reported clientWidth=390 and scrollWidth=390. The only browser console error was the existing missing favicon.ico 404; no application errors were observed.
Remaining Boundary
This is a non-default, opt-in route rehearsal and direct owner-service runtime proof. It does not promote broad question APIs, does not bypass teacher review, does not let AI write Question Bank data directly, and does not claim a provider-execution cache for charged generation requests.