Skip to content

Handoff: SVC-006-public-route-guard-proof

Scope

  • Dispatch ID: SVC-006
  • Service: classroom-service
  • Task pack: docs/agents/service-tasks/classroom-service.md
  • Next task: SVC-006-public-route-guard-proof
  • Goal: prove classroom route guard behavior while keeping profile and organization references owner-local.
  • Status: accepted bounded slice, not service completion.

Docs Read

  • docs/agents/service-agent-next-slice-queue.json
  • docs/agents/service-agent-next-slice-closeout-ledger.json
  • docs/agents/service-tasks/classroom-service.md
  • docs/agents/service-tasks/classroom-public-adapter-preflight.md
  • docs/agents/handoff-phase-8-classroom-adapter-preflight.md
  • docs/api/classroom-service.md
  • docs/qa/classroom-public-route-guard-proof.md

Legacy Evidence Read

  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/classrooms/classroom.service.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/classrooms/classrooms.controller.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/classrooms/classroom-workspace.spec.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.classrooms-core.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/app-data/app-data.public.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/prisma/schema.prisma

Legacy source was read-only evidence. No file under /Users/velikho/Desktop/WORKING/HOCTAPAZ/ was changed.

Files Changed

  • services/classroom-service/internal/usecase/classroom.go
  • services/classroom-service/internal/http/public_route_guard_test.go
  • services/classroom-service/internal/http/server.go
  • services/api-gateway/internal/gateway/gateway.go
  • services/api-gateway/internal/gateway/gateway_test.go
  • apps/admin-web/src/App.test.tsx
  • apps/admin-web/src/features/classes/admin-classes-gateway.ts
  • apps/admin-web/src/features/classes/admin-classes-panel.tsx
  • docs/agents/admin-web-frontend/06-qa-and-bugfix.md
  • docs/qa/classroom-public-route-guard-proof.md
  • docs/agents/handoffs/SVC-006-public-route-guard-proof.md
  • docs/agents/service-agent-next-slice-closeout-ledger.json
  • docs/agents/service-agent-next-slice-closeout-ledger.md
  • docs/agents/service-agent-accepted-handoffs.md
  • docs/agents/service-tasks/classroom-service.md
  • docs/api/classroom-service.md
  • docs/index.md
  • docs/.vitepress/config.ts
  • docs/qa/service-agent-goal-completion-audit.md
  • docs/qa/service-agent-readiness-smoke.md
  • deploy/gateway/routes.classroom-read-native-example.json
  • deploy/gateway/routes.classroom-read-native-localhost-example.json
  • scripts/test/classroom-read-route-coverage.sh
  • scripts/test/classroom-read-admin-browser-smoke.mjs
  • scripts/test/classroom-read-runtime-smoke.sh
  • scripts/test/classroom-route-guard.sh
  • docs/qa/classroom-read-runtime-smoke.md

Behavior Implemented

  • Non-admin classroom list requests now use the actor organization as the authoritative scope; a query/header organization value cannot widen it.
  • Non-admin classroom detail and manage/read checks enforce the actor organization against the classroom organization reference.
  • Gateway identity is authoritative: caller-supplied X-Actor-Id and X-Actor-Role cannot override authenticated X-User-* headers, and the gateway strips those compatibility headers before forwarding.
  • Non-admin list/detail paths fail closed when organization context is absent; query parameters cannot supply a tenant on behalf of an unauthenticated actor context.
  • Added HTTP regression coverage for cross-tenant denial, PARENT denial, gateway identity headers, owner-local ids, and absence of profile/org display fields.
  • Existing route guard remains the source of truth for default and non-default gateway route tables. No public route was promoted.
  • Added a named non-default candidate for authenticated ADMIN/TEACHERGET /api/classrooms and GET /api/classrooms/{classroomId} reads only. It forwards to the internal /v1/classrooms owner API and retains the broad classroom route as legacy_proxy.

Required Evidence

  • classroom route guard proof: make test-classroom-route-guard keeps public classroom and admin classroom prefixes on legacy_proxy.
  • owner-local profile/org references: the focused HTTP test proves tenant scoping and public-id-only classroom responses without profile/org display hydration.
  • rollback notes: keep deploy/gateway/routes.json active and do not promote native classroom routes from this slice.
  • disposable candidate runtime: make test-classroom-read-routes validates route order, exact list/detail shapes, auth, organization, role, and target constraints. CLASSROOM_READ_RUNTIME_SMOKE_CONFIRM=disposable-runtime make test-classroom-read-runtime proves tenant isolation, RBAC, response fields, and local latency sanity through the candidate Gateway. The opt-in Admin browser variant also proves canonical nested-session tenant propagation, one Gateway request, credentialed CORS/native route headers, and read-only tenant rendering.

Tests And Verification

  • gofmt -w services/classroom-service/internal/usecase/classroom.go services/classroom-service/internal/http/public_route_guard_test.go
  • GOTOOLCHAIN=go1.25.11 go test ./services/classroom-service/... -count=1
  • GOTOOLCHAIN=go1.25.11 go test ./services/api-gateway/... -count=1
  • make test-classroom-route-guard
  • make test-classroom-read-routes
  • CLASSROOM_READ_RUNTIME_SMOKE_CONFIRM=disposable-runtime CLASSROOM_READ_RUNTIME_SMOKE_ARTIFACT_DIR=output/qa/classroom-read-runtime-20260713 make test-classroom-read-runtime
  • CLASSROOM_READ_RUNTIME_SMOKE_CONFIRM=disposable-runtime CLASSROOM_READ_RUNTIME_SMOKE_ADMIN_BROWSER=1 CLASSROOM_READ_RUNTIME_SMOKE_ARTIFACT_DIR=output/qa/classroom-read-admin-browser-runtime-20260713 make test-classroom-read-runtime

All commands passed for this slice. The route guard covers default and route example JSON files; the focused Go test covers the service-side ownership boundary. The isolated runtime used memory-backed processes only and recorded direct and browser list latencies below five seconds; the opt-in browser candidate captured redacted request/response evidence. Both values are local sanity metrics, not production SLOs.

Route State

  • Route/default state: default classroom routes remain legacy_proxy; the named list/detail candidate is non-default pending deployed and browser promotion proof.
  • /api/classrooms*, /api/admin/classrooms*, and /api/classrooms/:id/notify remain legacy_proxy in the default table.
  • Only candidate list/detail reads reach native /v1/classrooms*; all other native contracts remain internal only.

Data Notes

  • Classroom-service stores public ids for organization/profile/document references and classroom-local member rows.
  • It does not own profile PII, IAM roles, permission-bearing organization membership, lessons, materials, tuition, exams, or attempt records.
  • Snapshot lesson/material/tuition/progress fields remain counters until their owner services provide adapters.

Risks / Blockers

  • This is static route/service HTTP plus disposable local Gateway and browser proof, not deployed promotion evidence.
  • Public route promotion still needs legacy response parity, deployed route evidence, and rollback-after-live evidence.
  • IAM/Profile default cutover gates and classroom-adjacent owner slices remain open.

Rollback Notes

These are the rollback notes for the accepted bounded slice:

  • Keep deploy/gateway/routes.json as the active route table.
  • If a rehearsal caller uses native /v1/classrooms*, disable that caller and return traffic to the legacy /api/classrooms* route.
  • Do not expose purge, progress, lessons, materials, or tuition routes through the public gateway from this handoff.

Next Step

Compare the candidate list/detail response against the legacy teacher surface, then collect deployed evidence plus rollback-after-live proof before considering any default public route promotion.

Go-platform documentation is generated from repository Markdown.