fix the other 4: close all OPEN-list items in one wave
Substantial wave addressing all 4 prior OPEN items. Three closed in full, one partially (the speculative half deliberately deferred). OPEN #1 — Periodic fresh→main index merge (FULL): - POST /v1/vectors/index/{src}/merge with {dest, clear_source} - Idempotent on re-runs (existing-in-dest items skipped) - internal/vectord/index.go: new Index.IDs() snapshot method + i.ids tracker field as canonical ID set, independent of meta map's nil-vs-{} sparseness (was a real bug — IDs() backed by meta alone missed items added with nil metadata) - 4 cmd-level integration tests (happy path drain+clear, dim mismatch, dest not found, self-merge rejection) + 1 unit test - DecodeIndex backward-compat: old envelopes restore i.ids from meta keys (best effort; new items going forward use the tracker) OPEN #2 — Distillation SFT export (SUBSTRATE): - internal/distillation/sft_export.go ports the load-bearing half: IsSftNever predicate + ListScoredRunFiles (data/scored-runs/YYYY/ MM/DD walk) + LoadScoredRunsFromFile + partial ExportSft. - Synthesis (instruction/input/response generation) deferred to a separate wave — too big for this session, but the substrate makes the next wave a port-not-design exercise. - TestSftNever_PinsExpectedSet locks the contamination firewall set: if a future commit adds/removes from SftNever, this test fails — forcing the change through review. - 5 new tests; firewall fires end-to-end through the partial port. OPEN #3 — Distribution drift via PSI (FULL): - internal/drift/drift.go: ComputeDistributionDrift via Population Stability Index. Standard finance/risk metric, well-defined verdict tiers (stable < 0.10, minor 0.10–0.25, major ≥ 0.25). - Equal-width bucketing over combined min/max so neither dist falls outside; epsilon-clamping for empty buckets so log doesn't blow up. Per-bucket breakdown for drilldown. - Pairs with the existing ComputeScorerDrift: scorer drift is categorical, distribution drift is continuous. Different shapes, same package. - 7 new tests covering identical-is-stable, hard-shift-is-major, moderate-detected-not-stable, empty-inputs-safe, all-identical- safe, bucket-counts-conserved, num-buckets-clamping. OPEN #4 — Ops nice-to-haves (PARTIAL — wall-clock done, others deferred): - (a) Real-time wall-clock for stress harness: per-phase elapsed time logged to stdout as it runs (`[stress] phase NAME starting (T+12.3s)` + `[stress] phase NAME done — 8.5s (T+20.8s)`). Output.PhaseTimings + Output.TotalElapsedMs in JSON. - (b) chatd fixture-mode S3 mock + (c) liberal-paraphrase calibration: not actioned — no fired trigger, would be speculative. Documented as deferred-until-need rather than ignored. Per the project's discipline ("don't add features beyond what the task requires"). OPEN list now empty / steady-state. Future items will land as production triggers fire. Build + vet + tests green; 18 new tests across the 4 closures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -277,6 +277,43 @@ func TestLookup_ReturnsCopy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestIndex_IDs locks the snapshot semantics: IDs() returns a copy
|
||||
// of the metadata keyset that callers can iterate without holding
|
||||
// the index lock. Underpins the merge endpoint (OPEN #1) — without
|
||||
// IDs(), the merge handler can't enumerate items to drain.
|
||||
func TestIndex_IDs(t *testing.T) {
|
||||
idx, err := NewIndex(IndexParams{Name: "ids_test", Dimension: 4})
|
||||
if err != nil {
|
||||
t.Fatalf("NewIndex: %v", err)
|
||||
}
|
||||
if got := idx.IDs(); len(got) != 0 {
|
||||
t.Errorf("empty index should have no IDs, got %v", got)
|
||||
}
|
||||
// Add with nil meta — the ids tracker is the canonical set, so
|
||||
// these MUST appear in IDs() even though they're not in i.meta.
|
||||
for _, id := range []string{"a", "b", "c"} {
|
||||
if err := idx.Add(id, []float32{1, 0, 0, 0}, nil); err != nil {
|
||||
t.Fatalf("Add %s: %v", id, err)
|
||||
}
|
||||
}
|
||||
got := idx.IDs()
|
||||
if len(got) != 3 {
|
||||
t.Errorf("expected 3 IDs after 3 Adds (nil meta still counts), got %d %v", len(got), got)
|
||||
}
|
||||
got[0] = "MUTATED"
|
||||
got2 := idx.IDs()
|
||||
for _, id := range got2 {
|
||||
if id == "MUTATED" {
|
||||
t.Errorf("IDs() must return a snapshot independent of internal state")
|
||||
}
|
||||
}
|
||||
// Delete updates the tracker.
|
||||
idx.Delete("a")
|
||||
if got := idx.IDs(); len(got) != 2 {
|
||||
t.Errorf("expected 2 IDs after Delete, got %d %v", len(got), got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_Names_Sorted(t *testing.T) {
|
||||
r := NewRegistry()
|
||||
for _, n := range []string{"zoo", "alpha", "midway"} {
|
||||
|
||||
Reference in New Issue
Block a user