vectord: Phase 41 gate fixes — 202 ACCEPTED + /profile/jobs/{id} alias

Phase 41 PRD (docs/CONTROL_PLANE_PRD.md:121) gate:
  "Activate a profile → returns 202 in <100ms → job completes in
   background → /vectors/profile/jobs/{id} shows progress"

Two concrete mismatches to PRD:

1. activate_profile returned HTTP 200, not 202. Fix: wrap the Json
   return in (StatusCode::ACCEPTED, Json(...)) so the async semantics
   are visible at the status-code level.

2. The PRD quotes GET /vectors/profile/jobs/{id} but code only exposed
   /vectors/jobs/{id}. Fix: add an alias route — same get_job handler,
   second URL matches what the PRD's polling example documents.

Still open from Phase 41 (flagged for follow-up, bigger scope):
  - crates/shared/src/profiles/ module with ExecutionProfile,
    RetrievalProfile, MemoryProfile, ObserverProfile types — PRD
    claims them, file doesn't exist; ModelProfile still does all
    four roles today. This is a real schema-refactor, not 6-line work.
  - crates/vectord/src/activation.rs with ActivationTracker — the
    activation logic lives inline in service.rs; extracting it is
    a module-structure change.
  - Phase 37 hot-swap stress test in tests/multi-agent/run_stress.ts
    Phase 3 — PRD says it must pass, current state unknown.

Workspace warnings still at 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-04-24 13:21:49 -05:00
parent 24b06d80b2
commit 08cc960115

View File

@ -83,6 +83,10 @@ pub fn router(state: VectorState) -> Router {
.route("/indexes/{name}/bucket", axum::routing::patch(migrate_index_bucket)) .route("/indexes/{name}/bucket", axum::routing::patch(migrate_index_bucket))
.route("/jobs", get(list_jobs)) .route("/jobs", get(list_jobs))
.route("/jobs/{id}", get(get_job)) .route("/jobs/{id}", get(get_job))
// PRD Phase 41 alias — docs/CONTROL_PLANE_PRD.md specifies
// GET /vectors/profile/jobs/{id} for polling profile activations.
// Same handler as /jobs/{id}; the alias just matches the PRD URL.
.route("/profile/jobs/{id}", get(get_job))
.route("/search", post(search_index)) .route("/search", post(search_index))
.route("/rag", post(rag_query)) .route("/rag", post(rag_query))
.route("/hybrid", post(hybrid_search)) .route("/hybrid", post(hybrid_search))
@ -1551,10 +1555,13 @@ async fn activate_profile(
tracker.complete(&job_id, result).await; tracker.complete(&job_id, result).await;
}); });
Ok(Json(json!({ // PRD Phase 41 gate: "Activate a profile → returns 202 in <100ms
// → job completes in background". 202 ACCEPTED signals async-work
// started; clients poll /vectors/jobs/{job_id} for progress.
Ok((StatusCode::ACCEPTED, Json(json!({
"job_id": job_id_for_response, "job_id": job_id_for_response,
"message": format!("profile activation started — poll /vectors/jobs/{} for progress", job_id_for_response), "message": format!("profile activation started — poll /vectors/jobs/{} for progress", job_id_for_response),
}))) }))))
} }
/// Unload this profile's model and clear the active slot. No-op if the /// Unload this profile's model and clear the active slot. No-op if the