Skip to content

Handoff: SVC-014-search-legacy-owner-rebuild-proof search-service legacy owner rebuild proof

Scope

  • Service/area: search-service
  • Task pack: docs/agents/service-tasks/search-service.md
  • Goal: accept the bounded SVC-014 next slice by proving the current legacy route ownership mapping, rebuild/projection proof, and route rollback notes before any public search route rehearsal or promotion.
  • Non-goals: default gateway route promotion, synthetic /api/search compatibility routes, public catalog ownership changes, live browser parity, owner-service canonical writes, or legacy source edits under /Users/velikho/Desktop/WORKING/HOCTAPAZ/.

Docs Read

  • go-platform/docs/agents/service-tasks/search-service.md
  • go-platform/docs/agents/handoffs/SVC-014-projection-worker-runtime-fixtures.md
  • go-platform/docs/agents/service-agent-next-slice-queue.json
  • go-platform/docs/agents/service-agent-next-slice-closeout-ledger.json
  • go-platform/docs/agents/agent-handoff-format.md
  • go-platform/docs/architecture/legacy-api-map.md
  • go-platform/docs/contracts/legacy/legacy-route-snapshot.json
  • go-platform/docs/qa/search-route-rehearsals.md
  • go-platform/docs/api/search-service.md

Legacy Evidence Read

  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/questions.controller.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/exams/exams.controller.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/courses/courses.controller.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/storage/storage.controller.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/public/public-catalog.controller.ts
  • 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-014-search-legacy-owner-rebuild-proof.md
  • go-platform/docs/agents/service-agent-next-slice-closeout-ledger.json
  • go-platform/docs/agents/service-agent-next-slice-closeout-ledger.md
  • go-platform/docs/agents/service-agent-accepted-handoffs.md
  • go-platform/docs/qa/service-agent-goal-completion-audit.md
  • go-platform/docs/qa/service-agent-readiness-smoke.md
  • go-platform/Makefile
  • go-platform/services/search-service/internal/usecase/projection_freshness_worker.go
  • go-platform/services/search-service/internal/usecase/projection_freshness_worker_test.go
  • go-platform/scripts/test/search-projection-pull-worker-postgres-isolated.sh
  • go-platform/docs/qa/search-projection-freshness-latency-proof.md
  • go-platform/docs/qa/search-projection-pull-worker-postgres-proof.md

Behavior Implemented

  • The optional freshness worker records latencyMs for each owner source from sync-state lookup through owner fetch, copied-index mutation, and checkpoint persistence. Its initial and interval ticks emit those per-source fields in structured logs.
  • A disposable restart/resume harness builds the real search-service binary, applies only search-service migrations to a fresh PostgreSQL container, and supplies deterministic local Question Bank, Course, Exam, and Document owner fixtures. The first process stops with four PARTIAL cursors; the replacement process resumes each cursor and ends at four SYNCED checkpoints.
  • Existing search-service rebuild APIs remain copied-index/projection paths for QUESTION, COURSE, EXAM, and DOCUMENT; owner services keep canonical writes and authorization decisions.
  • The legacy route ownership mapping remains explicit: docs/contracts/legacy/legacy-route-snapshot.json contains /api/public/catalog and /api/public/catalog/:type/:slug under PublicCatalogController, and docs/architecture/legacy-api-map.md maps the current public catalog paths to course-service, not search-service.
  • search-service has native /v1/search and /v1/rebuild/... routes for service tests, migration tooling, and future adapters only; no public gateway route is accepted in this slice.

Tests And Verification

  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/search-service/... -count=1
  • Result: passed.
  • Evidence: search-service command/server, domain, HTTP, repository, and usecase packages passed; HTTP/repository/usecase packages executed tests.
  • Command: make test-search-rebuild
  • Result: passed.
  • Evidence: SEARCH_REBUILD_SELF_TEST=1 python3 scripts/migration/rebuild-question-search-index.py indexed paginated course, document, exam, and question-bank projection pages and ended with ok search rebuild self-test.
  • Command: make test-search-routes
  • Result: passed.
  • Evidence: scripts/test/search-route-rehearsal-guard.sh confirmed default /api/public remains legacy-proxied, no routes.search*.json file exists, no gateway route targets search-service, and no synthetic /api/search route is introduced.
  • Command: SEARCH_REBUILD_SELF_TEST=1 python3 scripts/migration/rebuild-question-search-index.py
  • Result: passed.
  • Evidence: direct self-test repeated paginated rebuild loops for course, document, exam, and question-bank sources and ended with ok search rebuild self-test.
  • Command: GOTOOLCHAIN=go1.25.11 go test -race ./services/search-service/internal/http ./services/search-service/internal/usecase ./services/search-service/internal/repository -count=1
  • Result: passed.
  • Current rebuild recheck: the current external self-test rebuilt two pages for each course, document, exam, and question-bank owner projection, carried cursor-1 to cursor-2, and ended each source with hasMore=false. Captured output and assertions are in output/qa/search-legacy-owner-rebuild-proof-20260713/.
  • Current route-boundary recheck: make test-search-routes still confirmed /api/public is legacy_proxy, no routes.search*.json or /api/search route exists, and no gateway target points at search-service. No Gateway or browser runtime was started because there is still no real legacy public route mapped to search-service.
  • Command: make test-search-projection-freshness-latency
  • Result: passed.
  • Evidence: the focused worker test held an owner response for 25 ms and asserted that latencyMs includes the delay; the HTTP configuration test still started all four owner fixtures concurrently.
  • Command: SEARCH_PROJECTION_PULL_WORKER_POSTGRES_CONFIRM=disposable-postgres make test-search-projection-pull-worker-postgres
  • Result: passed.
  • Evidence: artifact output/qa/search-projection-pull-worker-postgres-29568-1784032579/summary.json records four first-start PARTIAL checkpoints/four documents, then four post-restart SYNCED checkpoints/eight documents with COURSE:2,DOCUMENT:2,EXAM:2,QUESTION:2. Native /v1/search and /v1/sync-state/* reads passed; worker logs include latencyMs.
  • Current runtime recheck (2026-07-15): the full source suite, rebuild and route guards, freshness-latency tests, and the disposable PostgreSQL event replay/fence integration passed again. The fresh pull-worker artifact output/qa/search-projection-pull-worker-postgres-53184-1784092988/summary.json records the same four PARTIAL first-start checkpoints/four copied rows and four SYNCED post-restart checkpoints/eight rows with COURSE:2,DOCUMENT:2,EXAM:2,QUESTION:2. The independent disposable Question Bank -> NATS JetStream -> Search smoke also passed durable restart recovery, duplicate replay, stale-order rejection, and DELETE tombstone protection. All containers used dynamic loopback ports and were removed by their harnesses; no Gateway route or caller database was used.
  • workflowVerification:
    • GOTOOLCHAIN=go1.25.11 go test ./services/search-service/... -count=1
    • make test-search-rebuild
    • make test-search-routes
    • SEARCH_REBUILD_SELF_TEST=1 python3 scripts/migration/rebuild-question-search-index.py
    • make test-search-projection-freshness-latency
    • SEARCH_PROJECTION_PULL_WORKER_POSTGRES_CONFIRM=disposable-postgres make test-search-projection-pull-worker-postgres
  • requiredVerification:
    • GOTOOLCHAIN=go1.25.11 go test ./services/search-service/... -count=1
    • make test-search-rebuild
    • make test-search-routes
    • SEARCH_REBUILD_SELF_TEST=1 python3 scripts/migration/rebuild-question-search-index.py
    • make test-search-projection-freshness-latency
    • SEARCH_PROJECTION_PULL_WORKER_POSTGRES_CONFIRM=disposable-postgres make test-search-projection-pull-worker-postgres

Route State

  • Legacy route: /api/public/catalog and /api/public/catalog/:type/:slug remain legacy/public catalog routes mapped to course-service; question, exam, course, and storage controller routes remain mapped to their owner services.
  • Native route: search-service native /v1/search and /v1/rebuild/... routes remain internal/native service routes, not public compatibility routes.
  • Gateway state: no default gateway route table changed, no non-default search route table was added, and no gateway route targets search-service.
  • Route default state: search route promotion requires exact legacy owner mapping plus rebuild/projection proof.
  • Rollback: keep the current default deploy/gateway/routes.json broad /api/public legacy proxy. If a future non-default search route rehearsal is added, rollback must remove the non-default route table and prove default traffic still goes through the legacy/public catalog owner before promotion review.

Data Notes

  • Tables touched: only search_documents and search_sync_states in one fresh disposable PostgreSQL container; the fixture then removed that container.
  • Migration run: the harness applied the Up portions of search-service's own migrations only to that disposable database.
  • Validation report: output/qa/search-projection-pull-worker-postgres-29568-1784032579/summary.json; no live/shared data was migrated.
  • Required evidence covered: legacy route ownership mapping, rebuild/projection proof, route rollback notes.
  • Current artifact: output/qa/search-legacy-owner-rebuild-proof-20260713/ contains only disposable self-test output; no database, default gateway table, or legacy source was changed.

Risks / Blockers

  • Public search/catalog promotion remains blocked until a real legacy controller route is selected and mapped to search-service.
  • Browser/runtime parity and rollback evidence remain required before any default route cutover.
  • The new runtime proof is intentionally disposable; it is not a shared or production PostgreSQL integration run.
  • Copied indexes must not bypass owner-service visibility rules, authorization decisions, or canonical source-of-truth writes.

Next Step

  • Assign a real legacy route mapping for a non-default search route rehearsal with browser/runtime parity and rollback proof; do not create a synthetic /api/search route merely to promote this internal worker proof.

Go-platform documentation is generated from repository Markdown.