Skip to content

fix(index): index-generation endpoints report execution wired while no executor exists #174

Description

@waterbro-8

Summary

The versioned index-generation subsystem exposes six mutating HTTP endpoints and
reports "execution_wired": true, but no code path executes a build. A build
with any work to do can never leave building, and a build for an empty
workspace can be activated while containing zero vectors.

Issue #55 (feat(index): versioned index generations for safe model migration)
is closed as completed. The path it exists to serve — re-embedding a corpus
under a new profile and switching over — cannot currently run.

Source reproduction

On main @ 7a194f1eba4167d54bd46cf84cdbe86e00532319:

  1. No executor. The target transitions exist only as definitions:
    ClaimTarget server/internal/indexgeneration/service.go:380,
    SucceedTarget :475, SkipTarget :491, FailTarget :495,
    CleanupExpired :579. Two greps from the repository root, run verbatim:
    call sites —
    grep -rn '\.\(ClaimTarget\|SucceedTarget\|FailTarget\|SkipTarget\|CleanupExpired\)(' server --include='*.go' | grep -v _test
    → no output; all mentions —
    grep -rn 'ClaimTarget\|SucceedTarget\|FailTarget\|SkipTarget\|CleanupExpired' server --include='*.go' | grep -v _test
    → eight lines, the five definitions above plus the comments at :32, :378
    and :576. The comment at :378 calls ClaimTarget "the intended Worker"
    entry point.
  2. No reader. NewSearchResolver (search_resolver.go:22) and
    SetGenerationResolver (server/internal/search/generation_routing.go:30)
    are likewise never called outside tests, so the resolver is never installed.
  3. Routing discards the generation. generation_routing.go:63 and :74
    are _ = gen followed by a call to the legacy scan, with a comment saying
    the vector table is populated by "Worker execution (a later slice)".
  4. Progress can therefore never advance. refreshProgress is called from
    exactly one place, server/internal/indexgeneration/store.go:320, inside
    completeTarget, which is reached only from the three unwired target
    transitions. state = 'ready' for a real corpus is set only at
    store.go:340,347.
  5. Consequence, both directions.
    • Non-empty workspace: the build stays building, so activate
      (store.go:419) fails its stateAllowed gate and returns
      ErrQualityGate (guard at store.go:457-460). Create → permanently stuck.
    • Empty workspace: service.go:208-223 marks the build ready when
      requiredTargets == 0, and the activation arithmetic
      SucceededTargets + SkippedTargets != RequiredTargets is 0 != 0, which
      passes. Activating a generation containing no vectors succeeds.
  6. Retention is not enforced either. CleanupExpired
    (service.go:579) has no non-test caller, so retention_until
    (0019_versioned_index_generations.sql:48) is recorded and never acted on.
  7. The documentation says the opposite of the code.
    docs/INDEX_GENERATIONS.md:120 states the endpoints "expose
    execution_wired=false" and that "the server does not expose create, …";
    server/internal/api/api.go:249,253,257,261,265,269 do expose create,
    cancel, resume, activate, rollback, and discard, and
    server/internal/api/handlers_index_generations.go:40,63,119,184 hard-code
    "execution_wired": true.

Expected vs actual

Expected: a status field describes whether execution is wired, and a mutating
endpoint either works or refuses with a reason an operator can act on.

Actual: the field is a literal true; an operator can POST a create, receive a
success, and watch a build that will never move, with no event explaining why.

Scope boundary

This issue is about the mismatch between advertised and real capability, and
about which half is correct. It is not a request to finish the worker slice.
Two mutually exclusive resolutions:

  • Smallest: make the flag honest — report execution_wired: false, align
    docs/INDEX_GENERATIONS.md, and have create/activate return an explicit
    execution_unavailable so nothing can enter the stuck or empty-activation
    states from the API.
  • Full: add the consumer (ClaimTarget → worker → SucceedTarget), install
    the resolver in cmd/memd/main.go, run CleanupExpired on a schedule, and
    require requiredTargets > 0 or an explicit empty-corpus acknowledgment before
    activation.

The first is a reviewable PR; the second is not, and probably needs its own
issue once a real model migration is on the roadmap.

Acceptance

  • One of the two resolutions above is chosen and recorded here, with the
    reason, by whoever owns index generation.
  • execution_wired cannot report a state the server does not honor: a test
    asserts the flag's value against whether a created build can reach
    active.
  • The empty-workspace activation path is either refused or documented as
    intended, with a test for the chosen behavior.
  • docs/INDEX_GENERATIONS.md matches the routes that exist.
  • If the full resolution is deferred, an issue for the worker executor is
    linked from here rather than left implied.

Evidence level

E2 — source-level and exhaustive for the caller claim (the grep above is
reproducible and its whole result set is "no non-test callers"). Nothing was
executed against a database: the stuck-build and empty-activation behaviors were
derived from the state machine, not observed.

Proposed triage

Applied on filing, per docs/maintainers/triage.md and the precedent in #135:
type:bug, area:server, severity:s2-medium, evidence:e2-source,
status:needs-triage.
Left to a maintainer: priority, and any change to the set below.

The severity is s2-medium rather than higher because no data is lost and no
wrong retrieval is served, but an administrative API reports a capability it
does not have.

Related: #55 (closed completed), #78 and #91 (the merged foundation and
routing slices), docs/INDEX_GENERATIONS.md.

Raised from the data/index audit on 2026-09-08.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:serverGo API, CLI, MCP, storage, or server runtimeevidence:e2-sourceSource or log evidence identifies the likely causeseverity:s2-mediumMedium impact with a practical workaroundstatus:needs-triageAwaiting maintainer classificationtype:bugSomething is broken or behaves incorrectly

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions