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:
- 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.
- 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.
- 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)".
- 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.
- 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.
- 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.
- 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
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.
Summary
The versioned index-generation subsystem exposes six mutating HTTP endpoints and
reports
"execution_wired": true, but no code path executes a build. A buildwith any work to do can never leave
building, and a build for an emptyworkspace 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 corpusunder a new profile and switching over — cannot currently run.
Source reproduction
On
main @ 7a194f1eba4167d54bd46cf84cdbe86e00532319:ClaimTargetserver/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,:378and
:576. The comment at:378callsClaimTarget"the intended Worker"entry point.
NewSearchResolver(search_resolver.go:22) andSetGenerationResolver(server/internal/search/generation_routing.go:30)are likewise never called outside tests, so the resolver is never installed.
generation_routing.go:63and:74are
_ = genfollowed by a call to the legacy scan, with a comment sayingthe vector table is populated by "Worker execution (a later slice)".
refreshProgressis called fromexactly one place,
server/internal/indexgeneration/store.go:320, insidecompleteTarget, which is reached only from the three unwired targettransitions.
state = 'ready'for a real corpus is set only atstore.go:340,347.building, soactivate(
store.go:419) fails itsstateAllowedgate and returnsErrQualityGate(guard atstore.go:457-460). Create → permanently stuck.service.go:208-223marks the buildreadywhenrequiredTargets == 0, and the activation arithmeticSucceededTargets + SkippedTargets != RequiredTargetsis0 != 0, whichpasses. Activating a generation containing no vectors succeeds.
CleanupExpired(
service.go:579) has no non-test caller, soretention_until(
0019_versioned_index_generations.sql:48) is recorded and never acted on.docs/INDEX_GENERATIONS.md:120states the endpoints "exposeexecution_wired=false" and that "the server does not expose create, …";server/internal/api/api.go:249,253,257,261,265,269do expose create,cancel, resume, activate, rollback, and discard, and
server/internal/api/handlers_index_generations.go:40,63,119,184hard-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 asuccess, 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:
execution_wired: false, aligndocs/INDEX_GENERATIONS.md, and have create/activate return an explicitexecution_unavailableso nothing can enter the stuck or empty-activationstates from the API.
ClaimTarget→ worker →SucceedTarget), installthe resolver in
cmd/memd/main.go, runCleanupExpiredon a schedule, andrequire
requiredTargets > 0or an explicit empty-corpus acknowledgment beforeactivation.
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
reason, by whoever owns index generation.
execution_wiredcannot report a state the server does not honor: a testasserts the flag's value against whether a created build can reach
active.intended, with a test for the chosen behavior.
docs/INDEX_GENERATIONS.mdmatches the routes that exist.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.mdand 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-mediumrather than higher because no data is lost and nowrong retrieval is served, but an administrative API reports a capability it
does not have.
Related: #55 (closed
completed), #78 and #91 (the merged foundation androuting slices),
docs/INDEX_GENERATIONS.md.Raised from the data/index audit on 2026-09-08.