{"record":{"id":"652644237a6462bb","repo":"gastownhall/beads","slug":"invalid-graph-plan-w","errorCode":null,"errorMessage":"invalid graph plan: %w","messagePattern":"invalid graph plan: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/create_proxied_server.go","lineNumber":345,"sourceCode":"\n\tcommitMsg := plan.CommitMessage\n\tif commitMsg == \"\" {\n\t\tcommitMsg = fmt.Sprintf(\"bd: graph-apply %d nodes\", len(plan.Nodes))\n\t}\n\n\tres, err := uow.RunTxResult(ctx, uowProvider, func(ctx context.Context, uw uow.UnitOfWork) (map[string]string, string, error) {\n\t\tcctx, err := uw.ConfigUseCase().LoadCreateContext(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", fmt.Errorf(\"load create context: %w\", err)\n\t\t}\n\n\t\t// validateProxiedGraphPlan enforces a uniform storage class, so its\n\t\t// resolved useWisp decides which table the whole plan routes to. The\n\t\t// collision preflight runs inside this transaction, so it cannot race\n\t\t// a concurrent create of the same explicit ID.\n\t\tuseWisp, err := validateProxiedGraphPlan(&plan, in, cctx, uowIssueExists(ctx, uw))\n\t\tif err != nil {\n\t\t\treturn nil, \"\", fmt.Errorf(\"invalid graph plan: %w\", err)\n\t\t}\n\n\t\tvar result domain.GraphApplyResult\n\t\tvar applyErr error\n\t\tif useWisp {\n\t\t\tresult, applyErr = uw.IssueUseCase().ApplyWispGraph(ctx, domainPlan, in.createdBy)\n\t\t} else {\n\t\t\tresult, applyErr = uw.IssueUseCase().ApplyIssueGraph(ctx, domainPlan, in.createdBy)\n\t\t}\n\t\tif applyErr != nil {\n\t\t\treturn nil, \"\", fmt.Errorf(\"graph create: %w\", applyErr)\n\t\t}\n\n\t\treturn result.IDs, commitMsg, nil\n\t})\n\tif err != nil {\n\t\treturn HandleError(\"%v\", err)\n\t}","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/create_proxied_server.go#L327-L363","documentation":"validateProxiedGraphPlan rejected the graph plan inside the transaction. The wrapped error says what is invalid: mixed storage classes, nonexistent dep targets, ID collisions, or malformed nodes. Nothing is committed — the transaction rolls back.","triggerScenarios":"runCreateProxiedGraph applies a plan whose nodes fail validation: inconsistent useWisp routing, dep on a nonexistent ID, duplicate explicit IDs, or invalid node fields (checked via uowIssueExists preflight).","commonSituations":"Hand-written or generated graph JSON referencing IDs not in the plan or DB; plan mixing wisps and issues; alias/dep typo in the graph file.","solutions":["Fix the wrapped validation error in the graph file","Ensure every dep target exists in the plan or the database","Use a uniform storage class (all wisps or all issues) per plan","De-duplicate explicit IDs across plan nodes"],"exampleFix":"// before: dep target not in plan or DB\n{\"nodes\":[{\"id\":\"bd-1\",\"deps\":[{\"target\":\"bd-404\"}]}]}\n// after: reference an existing node\n{\"nodes\":[{\"id\":\"bd-1\",\"deps\":[{\"target\":\"bd-2\"}]},{\"id\":\"bd-2\"}]}","handlingStrategy":"validation","validationCode":"// client-side pre-check before graph-apply\nids := map[string]bool{}\nfor _, n := range plan.Nodes {\n    if ids[n.ID] { return fmt.Errorf(\"duplicate id %s\", n.ID) }\n    ids[n.ID] = true\n}\nfor _, n := range plan.Nodes {\n    for _, d := range n.Deps {\n        if !ids[d.Target] && !existsOnServer(d.Target) {\n            return fmt.Errorf(\"dep target %s not found\", d.Target)\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep one storage class (wisp or issue) per plan","Validate dep targets against the plan + server before applying","Validate generated graph files in CI before shipping them"],"tags":["validation","graph-plan","proxied-server","transaction"],"backgroundTag":"invalid-graph-plan","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}