Appearance
Handoff: SVC-015-ai-usage-wallet-route-proof ai-service usage wallet route proof
Scope
- Service/area:
ai-serviceviaservices/ai-classifier-service - Task pack:
docs/agents/service-tasks/ai-service.md - Goal: accept the bounded
SVC-015next slice by proving the current usage/wallet ownership boundary, public AI route guard state, and route rollback notes before any AI job/default public route promotion. - Non-goals: default or deployed Gateway route promotion, live provider credential proof, quota enforcement inside AI, wallet credit movement inside AI, provider secret storage, or legacy source edits under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/.
Docs Read
go-platform/docs/agents/service-tasks/ai-service.mdgo-platform/docs/agents/handoffs/SVC-015-queue-worker-batching-semantics.mdgo-platform/docs/agents/service-agent-next-slice-queue.jsongo-platform/docs/agents/service-agent-next-slice-closeout-ledger.jsongo-platform/docs/agents/agent-handoff-format.mdgo-platform/docs/agents/handoff-phase-6-ai-classifier-foundation.mdgo-platform/docs/agents/handoff-phase-6-ai-classifier-pgx-store.mdgo-platform/docs/agents/handoff-phase-6-ai-classifier-provider-adapters.mdgo-platform/docs/api/ai-classifier-service.mdgo-platform/docs/qa/ai-classification-job-live-smoke.mdgo-platform/docs/qa/question-classification-apply-live-smoke.mdgo-platform/services/ai-classifier-service/README.md
Legacy Evidence Read
/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/admin/ai-settings.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/admin/ai-usage.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.queue.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.worker.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-generation.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-generation.service.spec.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-solution.service.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-solution.service.spec.ts/Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/grading/grading.spec.ts- Legacy evidence shows the old generation/solution flows check AZ Credit before provider calls and burn credits after usable results, while the Go migration boundary keeps AI preview/provider telemetry separate from future usage-service and wallet-service consumption APIs.
- Legacy source was read-only evidence only; no file under
/Users/velikho/Desktop/WORKING/HOCTAPAZ/was edited.
Files Changed
go-platform/docs/agents/handoffs/SVC-015-ai-usage-wallet-route-proof.mdgo-platform/docs/agents/service-agent-next-slice-closeout-ledger.jsongo-platform/docs/agents/service-agent-next-slice-closeout-ledger.mdgo-platform/docs/agents/service-agent-accepted-handoffs.mdgo-platform/docs/qa/service-agent-goal-completion-audit.mdgo-platform/docs/qa/service-agent-readiness-smoke.mdgo-platform/docs/qa/ai-generation-usage-wallet-runtime-proof.mdgo-platform/docs/qa/ai-classification-worker-runtime-smoke.mdgo-platform/scripts/test/ai-classification-worker-runtime-smoke.shgo-platform/deploy/gateway/routes.ai-classification-jobs-native-example.jsongo-platform/deploy/gateway/routes.ai-classification-jobs-native-localhost-example.jsongo-platform/scripts/test/ai-classification-job-route-coverage.shgo-platform/scripts/test/ai-classification-job-route-guard.shgo-platform/packages/ui/src/modules/teacher/panels/ai-classification-jobs-panel.tsxgo-platform/packages/ui/src/modules/teacher/teacher-ai-preview-api.tsgo-platform/packages/ui/src/modules/teacher/teacher-workspace.test.tsxgo-platform/services/ai-classifier-service/internal/domain/ai_task_contract.gogo-platform/services/ai-classifier-service/internal/usecase/classifier.gogo-platform/services/ai-classifier-service/internal/usecase/generation_monetization_client_test.gogo-platform/services/ai-classifier-service/internal/repository/{memory.go,postgres.go,memory_test.go,postgres_test.go,postgres_settlement_integration_test.go}go-platform/services/ai-classifier-service/internal/http/{server.go,server_test.go}go-platform/services/ai-classifier-service/internal/http/generation_settlement_fault_injection_test.gogo-platform/services/ai-classifier-service/internal/domain/classifier.gogo-platform/services/ai-classifier-service/internal/provider/{provider.go,provider_test.go}go-platform/services/ai-classifier-service/internal/repository/{memory.go,memory_test.go,postgres.go}go-platform/services/ai-classifier-service/migrations/000006_generation_preview_settlement_worker.sqlgo-platform/services/ai-classifier-service/README.mdgo-platform/scripts/test/ai-generation-settlement-fault-injection.shgo-platform/scripts/test/ai-generation-settlement-local-postgres.shgo-platform/docs/qa/ai-generation-settlement-reconciliation-proof.mdgo-platform/Makefile
Behavior Implemented
- P6-049 adds an opt-in durable generation-settlement worker. It claims only AI-owned pending replay rows, persists lease/attempt/backoff/last-error state, and continues settlement with the existing owner-service idempotency keys. It does not call provider, preflight, Question Bank context, or a canonical Question Bank write.
- The isolated fault-injection runner sends a generation request through the real AI HTTP handler, fails one usage-consume boundary call after the wallet debit and three usage mutations, then starts the production settlement worker loop against disposable HTTP usage/wallet fixtures. The retry reuses the same owner idempotency keys and completes the stored provider result without rerunning quota preflight or provider generation.
- Existing
ai-classifier-servicepreview routes record provider usage telemetry and useChargePolicy: "preview_only_no_consume"for generate, explain, and essay-grade envelopes. - The bounded P6-047 generation adapter is opt-in only. When
AI_GENERATION_USAGE_WALLET_ENABLED=true, it asksusage-serviceto check the four quota keys, askswallet-servicefor balance, then settles a usable result through owner-serviceUSAGE_DEBITand idempotentusage/consumecalls. AI neither stores quota state nor mutates the wallet database. - Existing generation preview results remain handoff payloads for
question-service, explanation preview results do not mutate question data, and essay-grade preview results remain handoff payloads forattempt-service. - The AI-owned
provider_settingsdefault selection now initializes the native provider runtime when an enabled row exists.ADMIN-onlyGET/PUT /v1/admin/ai/providers/settingsvalidates and saves only provider, model, base URL, and an allowlisted secret environment-variable reference; raw provider keys are never persisted or returned. The update switches the active in-process provider only after candidate validation and adds no public/default Gateway route. - Existing public AI job, suggestion, generation, and solution routes remain legacy-proxied. The only native question-classification route example remains the exact
PATCH /api/questions/ai-classify/applycarve-out toquestion-bank-service, notai-classifier-service. - The Teacher route
/teacher/questions/ai-classify-jobsis an actual read-only consumer of the local candidateGET /api/questions/ai-classify/jobsandGET /api/questions/ai-classify/jobs/{id}paths. It obtains the access token from the existing Gateway session, renders owner-provided progress, Question Bank context, and suggestion fields, and deliberately exposes no apply, retry, cancel, or canonical Question Bank write. - Required evidence covered: usage/wallet boundary proof, AI route live proof, no quota or credit ownership in AI.
Tests And Verification
- Command:
GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/... - Result: passed.
- Evidence: ai-classifier-service cmd, domain, HTTP, provider, repository, and usecase packages passed.
- Command:
make test-ai-generation-settlement-reconciliation - Result: passed on 2026-07-14.
- Evidence: a provider result that had already caused one effective wallet debit and a partial usage sequence was recovered by the settlement worker without a second provider or preflight call. The focused tests also prove delayed backoff, lease recovery, and an expired preview that remains claimable while settlement is pending.
- Command:
scripts/test/ai-generation-settlement-fault-injection.sh - Result: passed on 2026-07-14.
- Evidence: a hermetic AI HTTP request ran one quota preflight (four resource checks) and one provider generation, then received an injected
503from thecredits_weeklyusage-consume fixture after one effective wallet debit. The real settlement worker made a second HTTP settlement attempt: owner idempotency reduced two wallet ledger calls to one effective debit and eight usage-consume calls to four effective resource mutations. The stored replay completed in one worker claim; a final identical AI HTTP request was served from the settled replay without any additional provider, usage, or wallet calls. - Command:
GOTOOLCHAIN=go1.25.11 go test -race ./services/ai-classifier-service/internal/http -run '^TestGenerationSettlementWorkerHTTPFaultInjection$' -count=1 -timeout=60s -v - Result: passed on 2026-07-14.
- Evidence: the concurrent usage-consume fixture, worker loop, and HTTP call counters passed the Go race detector with the same
1/4/2/1/8/4/1provider/preflight/wallet-call/effective-debit/usage-call/effective-usage/ worker-attempt proof tuple. - Command:
AI_CLASSIFIER_POSTGRES_DSN=postgres://hoctapaz:[email protected]:15440/hoctapaz_ai_classifier_db?sslmode=disable GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/internal/repository -run TestPostgresGenerationSettlementClaimPersistsAcrossStores -count=1 -v - Result: passed on 2026-07-14 against a new disposable
postgres:16database. - Evidence: all AI migrations through
000006applied; a pending replay whose preview TTL had expired was readable through a new store instance, one worker claim acquired a persisted lease, a second worker could not claim, and the retry timestamp/error survived a separate store read. The container was removed after verification. - Command:
GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/internal/usecase -run 'TestClassifierServiceGeneratePreview|TestClassifierServiceExplainPreview|TestClassifierServiceGradePreview|TestClassifierServiceProcessJob|TestHTTPQuestionContextClient' -count=1 -v - Result: passed.
- Evidence: focused tests covered question context loading, provider batching, generation preview usage plus question-service handoff, explanation preview usage with no write result, essay-grade preview usage plus attempt-service handoff, invalid grade contracts, configured question-context failure, cancelled-running-job safety, retry recovery usage, circuit-open usage, and HTTP question context client behavior.
- Command:
make test-ai-classification-job-route-guard - Result: passed.
- Evidence: all gateway route tables kept AI job/suggestion/generation/solution prefixes off active native AI targets; broad
questionsandfallbackroutes stayedlegacy_proxy; the apply carve-out did not targetai-classifier-service. - Command:
AI_CLASSIFICATION_JOB_SELF_TEST=1 make test-ai-classification-job-live - Result: passed.
- Evidence: hermetic live-smoke harness self-test validated route-table/header assertions for AI classification job routes staying on the legacy
questionsroute. This is not real browser proof or a live deployed gateway call. - Command:
make test-question-classification-apply-routes - Result: passed.
- Evidence: default route table stayed legacy-only; non-default compose and localhost examples expose only exact authenticated
PATCH /api/questions/ai-classify/applytoquestion-bank-service, with broad questions/fallback routes still legacy. - Command:
AI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime make test-ai-classification-worker-runtime - Result: passed on 2026-07-13.
- Evidence: a temporary in-memory service and mock provider accepted one deferred twelve-question job, its enabled worker completed three batches with one attempt, and native job detail, SSE snapshot, queue summary, and worker log agreed on the drained
COMPLETEDstate. The process and temporary binary were removed after the check. Artifacts:output/qa/ai-classification-worker-runtime-20260713/. - Command:
AI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres make test-ai-classification-worker-runtime - Result: passed on 2026-07-13.
- Evidence: a new
postgres:16container received only the AI service migrations. The worker completed the deferred job through the Postgres store; direct SQL verifiedCOMPLETED:1:ai-classifier-worker-1before the service, database container, and ports were removed. Artifacts:output/qa/ai-classification-worker-postgres-20260713/. - Command:
AI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank make test-ai-classification-worker-runtime - Result: passed on 2026-07-13.
- Evidence: a disposable memory-backed Question Bank accepted twelve canonical questions through
POST /v1/questions. The configured AI worker loaded all twelve throughPOST /v1/questions/ai-classify/context, completed the deferred job with native SSE and queue-summary evidence, and retained the twelve canonical prompt rows inscope.questionContext. Artifact:output/qa/ai-classification-worker-question-bank-20260713-rerun/. - Command:
AI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank make test-ai-classification-worker-runtime - Result: passed on 2026-07-13.
- Evidence: the same isolated Question Bank fixture loaded all twelve prompt rows into a fresh Postgres-backed AI job. Direct SQL verified
COMPLETED:1:ai-classifier-worker-1before removing the AI service, Question Bank process, database container, and temporary ports. Artifact:output/qa/ai-classification-worker-question-bank-postgres-20260713/. - Command:
make test-ai-classification-job-routes && make test-ai-classification-job-route-guard - Result: passed on 2026-07-13.
- Evidence: only the two designated non-default tables may activate exactly three authenticated
GETroutes for native classification job list, detail, and{id}/events; their broad questions/fallback routes remain legacy. The default route table has no active job route. - Command:
AI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank AI_CLASSIFICATION_WORKER_SMOKE_GATEWAY=job-read make test-ai-classification-worker-runtime - Result: passed on 2026-07-13.
- Evidence: a fresh Postgres-backed AI job
aiclsjob-83f63573d0d70099loaded all twelve Question Bank prompt rows, completed once, and was read through a disposable Gateway using the localhost candidate table. List/detail/SSE responses carried their expectednative_readroute headers; a locally signed teacher JWT was accepted and a student JWT was rejected with403 AUTH_FORBIDDEN. Artifacts:output/qa/ai-classification-worker-question-bank-gateway-postgres-20260713-final/. - Command:
pnpm exec vitest run packages/ui/src/modules/teacher/teacher-workspace.test.tsx - Result: passed on 2026-07-13 (90 tests).
- Evidence: the exact Teacher route stored an authenticated Gateway session, fetched
GET /api/questions/ai-classify/jobs?limit=20, auto-loaded the first selected detail, rendered a worker suggestion, and fetched a second detail after the operator selected another job. - Command: held disposable Postgres/Question Bank/Gateway worker smoke followed by the in-app browser at
http://127.0.0.1:15202/teacher/questions/ai-classify-jobs. - Result: passed on 2026-07-13.
- Evidence: the browser loaded one completed job
aiclsjob-8adf2b5038c91f21through the local candidate Gateway, displayed12/12processed, three batches,12/12Question Bank context and three result suggestions, thenTải lại jobissued the exact list/detail reads. There were no console errors or warnings. The locally signed disposable Teacher session was cleared before browser finalization and the worker smoke cleaned all temporary processes/ports. Artifacts:output/qa/ai-classification-worker-teacher-browser-postgres-20260713/. - Command: isolated Compose runtime proof with
AI_GENERATION_USAGE_WALLET_ENABLED=true, followed by a temporary Gateway usingroutes.teacher-ai-preview-native-localhost-example.json. - Result: passed on 2026-07-13.
- Evidence: the fixture requested two mock previews, settled one six-credit wallet debit, consumed the four owner-service quota counters, and replayed without a second debit or counter increase. The Gateway request returned
teacher-ai-generation-preview/native_writeplususage_wallet_settled; aSTUDENTtoken was rejected with403 AUTH_FORBIDDENbefore upstream execution. Artifacts:output/qa/ai-generation-usage-wallet-runtime-20260713/. - workflowVerification:
GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/...scripts/test/ai-generation-settlement-fault-injection.shGOTOOLCHAIN=go1.25.11 go test -race ./services/ai-classifier-service/internal/http -run '^TestGenerationSettlementWorkerHTTPFaultInjection$' -count=1 -timeout=60s -vGOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/internal/usecase -run 'TestClassifierServiceGeneratePreview|TestClassifierServiceExplainPreview|TestClassifierServiceGradePreview|TestClassifierServiceProcessJob|TestHTTPQuestionContextClient' -count=1 -vmake test-ai-classification-job-route-guardmake test-ai-classification-job-routesAI_CLASSIFICATION_JOB_SELF_TEST=1 make test-ai-classification-job-liveAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank AI_CLASSIFICATION_WORKER_SMOKE_GATEWAY=job-read make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank AI_CLASSIFICATION_WORKER_SMOKE_GATEWAY=job-read make test-ai-classification-worker-runtimepnpm exec vitest run packages/ui/src/modules/teacher/teacher-workspace.test.tsxmake test-question-classification-apply-routes
- requiredVerification:
GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/...scripts/test/ai-generation-settlement-fault-injection.shGOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/internal/usecase -run 'TestClassifierServiceGeneratePreview|TestClassifierServiceExplainPreview|TestClassifierServiceGradePreview|TestClassifierServiceProcessJob|TestHTTPQuestionContextClient' -count=1 -vmake test-ai-classification-job-route-guardmake test-ai-classification-job-routesAI_CLASSIFICATION_JOB_SELF_TEST=1 make test-ai-classification-job-liveAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank AI_CLASSIFICATION_WORKER_SMOKE_GATEWAY=job-read make test-ai-classification-worker-runtimeAI_CLASSIFICATION_WORKER_SMOKE_CONFIRM=disposable-runtime AI_CLASSIFICATION_WORKER_SMOKE_STORE=postgres AI_CLASSIFICATION_WORKER_SMOKE_CONTEXT=question-bank AI_CLASSIFICATION_WORKER_SMOKE_GATEWAY=job-read make test-ai-classification-worker-runtimepnpm exec vitest run packages/ui/src/modules/teacher/teacher-workspace.test.tsxmake test-question-classification-apply-routes
Route State
- Legacy route: public
/api/questions/ai-classify/jobs*,/api/questions/ai-classify/suggestions,/api/questions/ai-generation*,/api/questions/ai-solutions*, and attempt submit stay behind existing legacy routes. - Native route: internal
/v1/ai-classifier/*,/v1/ai/generation/preview,/v1/ai/explanation/preview,/v1/ai/essay-grade/preview, and owner admin queue/provider APIs remain service/native routes only. - Gateway state: no default gateway route table changed. The 2026-07-13 proof exercised only non-default teacher generation preview and AI job-read candidates; broad
questionsand fallback routes remainedlegacy_proxy. The Teacher job page is a local read-only candidate consumer. Non-defaultquestion-classification-applyexamples targetquestion-bank-service, notai-classifier-service. - Route default state: AI job/public route promotion requires usage/wallet boundary proof and route live proof.
- Rollback: keep
deploy/gateway/routes.jsonas the active default route table. If a non-default AI job route rehearsal is added later, rollback must returnGATEWAY_ROUTE_TABLEtodeploy/gateway/routes.jsonor remove the native AI job entries, then rerunmake test-ai-classification-job-route-guardand the relevant live/browser smoke before promotion review.
Data Notes
- Tables touched: the isolated Compose project wrote only service-owned fixture rows for AI provider telemetry, usage events/counters, and wallet ledger entries. It used no legacy database.
- The settlement fault-injection runner uses only memory-backed AI state and disposable in-process HTTP fixtures. It reads no shared credentials, starts no container, and touches no usage-service, wallet-service, legacy, or shared database. Fixture state is discarded when the focused Go test exits.
- Migration run: service-owned Compose migration containers completed for
ai-classifier-service,usage-service, andwallet-servicebefore the proof calls. - Validation report: the fixture showed one six-credit debit, four settled owner-service counters, and replay idempotency. No canonical question, solution, attempt, or public default-route data was written.
- Boundary proof:
provider_usage_eventsremains AI-owned telemetry;usage-serviceowns quota decisions/counters andwallet-serviceowns credit balance/ledger movement. - Provider selection data stays AI-owned in
provider_settings; it contains no provider secret value. The local PostgreSQL runner seeds only a disposablemockselection and verifies native startup reportsconfigurationSource=databasebefore and after the settlement restart.
Risks / Blockers
- The owner-local worker smoke proves disposable memory and fresh Postgres storage paths, the isolated Question Bank prompt-context path, and a local non-default Gateway candidate for selected read/SSE routes. The separate Teacher proof validates the local read-only browser consumer. Neither proves concurrent Postgres
SKIP LOCKEDclaims, Question Bank Postgres or taxonomy data, or a live deployed Gateway. - P6-049 now recovers a partial owner sequence forward with durable claim, lease, and backoff state. It intentionally has no distributed transaction or compensating debit, so keep the charged path opt-in and rely on the wallet/usage owner APIs' idempotency keys.
- The new fault injection proves the AI worker and HTTP client against contract-shaped in-process owner fixtures; it does not execute the real usage-service/wallet-service binaries, their databases, or a process restart.
- Public AI job route promotion remains blocked on provider-backed execution, disposable real-owner persistence proof, default-route rollback review, and deployed-Gateway evidence.
- The database-backed selection proof uses the mock provider and verifies only configuration resolution. It does not prove a live provider credential, network execution, billing behavior, or a deployed/default Gateway route.
Next Step
- Keep the Teacher job surface read-only. Before a charged provider-backed public route, repeat the injected failure with disposable real usage/wallet service processes and stores, then retain default-route rollback evidence. A separate owner-service apply handoff must be idempotent and audit-backed; it must not be added as a side effect of the AI read surface.