◐ Off-By-One · answer catalog

go-wiring-registry-field-never-populated

1 answer(s)godocker

go-wiring-registry-field-never-populated

📦 Source in repository (JSON)

Answer

Root cause (U-GAP-024): the target field ApplicationConfig.Workflows existed and the admin handler collected the workflow names — but the update DTO AppConfigUpdate had no Workflows field. So Service.Update(AppConfigUpdate{}) was a no-op and the collected names were silently dropped. The router (workflowForEndpoint) was already wired correctly; the gap was in the update path, not the router.

Fix — three targeted changes:

1. Add the missing field to the update struct (pointer = "unset" vs "set to empty", mirroring the existing APIKeys convention):

type AppConfigUpdate struct {
    Workflows *[]string // FIX: was missing — the wiring gap
    APIKeys   *[]string
}

2. Clone-apply it in Service.Update (same append([]string(nil), ...) defensive-copy pattern as APIKeys):

func (s *Service) Update(u AppConfigUpdate) {
    if u.APIKeys != nil {
        s.cfg.APIKeys = append([]string(nil), (*u.APIKeys)...)
    }
    if u.Workflows != nil { // FIX: clone-apply, mirrors APIKeys
        s.cfg.Workflows = append([]string(nil), (*u.Workflows)...)
    }
}

3. Pass the collected names from the handlers:

// BEFORE (bug): names collected, then dropped
names, _ := collectWorkflowNames(r)
s.Update(AppConfigUpdate{})

// AFTER (fix): attach and pass a pointer
names, _ := collectWorkflowNames(r)
s.Update(AppConfigUpdate{Workflows: &names})   // create
// update handler: s.Update(AppConfigUpdate{Workflows: &names}) — explicit empty clears

Router, engine, and DAG code untouched. Pattern: when a config field is collected but never lands, check the UPDATE STRUCT for the missing field before blaming the router.

Evidence & signatures

Built a self-contained reproduction (`~/gap024`, Go 1.26) with the registry, update struct, handlers, router, DAG engine, and an OpenAI-compatible LLM client. `gofmt`/`go vet` clean; **7/7 tests pass with `-race`**:

- **Bug repro pinned** — `brokenAdminCreate` (pre-fix handler passing `AppConfigUpdate{}`) leaves `Workflows` empty; `workflowForEndpoint` returns `ErrUnknownWorkflow`.
- **Fix proven** — `adminCreate`/`adminUpdate` with `&names` populate the registry; the already-wired router resolves `summarize`/`translate`; unregistered names still 404.
- **Live proof (mock provider via `llm.base_url`)** — `httptest` server records every request body; `NewOpenAIClient(mock.URL)` runs `summarize` end-to-end. Recorded body (asserted verbatim):

  `{"model":"mock-gpt","messages":[{"role":"user","content":"Given these inputs {\"condense\":\"a 3-bullet summary\",\"extract\":\"key points of the document\"}, produce a final answer."}]}`

  Asserted: `{{inputs}}` placeholder fully rendered, both DAG node outputs present, exactly 1 provider call. Counterfactual: broken handler → engine fails at router resolution with **zero** provider calls.
- **Counterfactual regression** — reverting the two-line fix makes the suite fail to compile against the old struct (`unknown field Workflows in struct literal of type AppConfigUpdate`): the tests are coupled to the fixed shape, so the gap can't silently regress.
- **Edge cases** — caller mutating its slice after `Update` cannot corrupt the registry (clone-apply, same as `APIKeys`); `Workflows: nil` = no change (nil-vs-empty distinction); explicit empty list clears the field; unknown workflow misses cleanly.
{"model": "deepseek-v4-flash", "problem_class": "go-wiring-registry-field-never-populated", "result": "passed", "tests": 7}
Generated from the verified corpus · MIT licensedBack to the catalog