{"record":{"id":"c2f9a6dd5900a37c","repo":"gastownhall/beads","slug":"formula-validation-failed-s","errorCode":null,"errorMessage":"formula validation failed:\n  - %s","messagePattern":"formula validation failed:\n  - (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/formula/types.go","lineNumber":677,"sourceCode":"\t\t\tif bp.BeforeStep != \"\" {\n\t\t\t\tif _, exists := stepIDLocations[bp.BeforeStep]; !exists {\n\t\t\t\t\terrs = append(errs, fmt.Sprintf(\"compose.bond_points[%d] (%s): before_step references unknown step %q\", i, bp.ID, bp.BeforeStep))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor i, hook := range f.Compose.Hooks {\n\t\t\tif hook.Trigger == \"\" {\n\t\t\t\terrs = append(errs, fmt.Sprintf(\"compose.hooks[%d]: trigger is required\", i))\n\t\t\t}\n\t\t\tif hook.Attach == \"\" {\n\t\t\t\terrs = append(errs, fmt.Sprintf(\"compose.hooks[%d]: attach is required\", i))\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(errs) > 0 {\n\t\treturn fmt.Errorf(\"formula validation failed:\\n  - %s\", strings.Join(errs, \"\\n  - \"))\n\t}\n\n\treturn nil\n}\n\n// collectChildIDs recursively collects step IDs from children.\n// idLocations maps ID -> location where first defined (for better duplicate error messages).\nfunc collectChildIDs(children []*Step, idLocations map[string]string, errs *[]string, prefix string) {\n\tfor i, child := range children {\n\t\tchildPrefix := fmt.Sprintf(\"%s.children[%d]\", prefix, i)\n\t\tif child.ID == \"\" {\n\t\t\t*errs = append(*errs, fmt.Sprintf(\"%s: id is required\", childPrefix))\n\t\t\tcontinue\n\t\t}\n\t\tif firstLoc, exists := idLocations[child.ID]; exists {\n\t\t\t*errs = append(*errs, fmt.Sprintf(\"%s: duplicate id %q (first defined at %s)\", childPrefix, child.ID, firstLoc))\n\t\t} else {\n\t\t\tidLocations[child.ID] = childPrefix","sourceCodeStart":659,"sourceCodeEnd":695,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/formula/types.go#L659-L695","documentation":"The formula Validate method aggregates all structural problems it finds (missing IDs, missing compose hooks' attach fields, unknown references, etc.) into a single error listing each problem prefixed with \"  - \". It is the top-level validation failure for a formula document, not a single specific defect.","triggerScenarios":"Calling Validate (public method on the formula type) on a formula whose checks appended entries to errs — e.g. a compose hook with no `attach` field — and at least one problem exists.","commonSituations":"Loading a formula after a schema/version change, hand-editing a formula and omitting required fields, or importing a formula written for an older format.","solutions":["Read each bullet in the error message; each names the offending path (e.g. compose.hooks[0]).","Fix the indicated fields — e.g. add `attach` to each compose hook.","Re-run validation until the error disappears; the list regenerates fresh each time.","Compare against a known-good example formula for the current schema."],"exampleFix":"// before (TOML)\n[[compose.hooks]]\ncommand = \"setup\"\n// after\n[[compose.hooks]]\ncommand = \"setup\"\nattach = \"step-1\"","handlingStrategy":"validation","validationCode":"if err := formula.Validate(); err != nil {\n    // err lists every problem, one per line prefixed by \"  - \"\n    lines := strings.Split(err.Error(), \"\\n\")\n    for _, l := range lines[1:] { log.Printf(\"formula problem:%s\", strings.TrimPrefix(l, \"  - \")) }\n    return err\n}","typeGuard":null,"tryCatchPattern":"if verr := f.Validate(); verr != nil && strings.Contains(verr.Error(), \"formula validation failed\") { /* iterate bullets; fix each named path */ }","preventionTips":["Run Validate on every formula at load time and in CI.","Require attach on all compose hooks via a pre-commit check.","Keep formulas aligned with the current schema after upgrades."],"tags":["formula","validation","aggregate-errors"],"backgroundTag":"schema-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}