{"record":{"id":"fc66cc594c456096","repo":"gastownhall/beads","slug":"w-remove-dependency-requires-an-actor","errorCode":null,"errorMessage":"%w: remove dependency requires an actor","messagePattern":"%w: remove dependency requires an actor","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependency_editor.go","lineNumber":54,"sourceCode":"\t\tif edge.IssueID == \"\" || edge.DependsOnID == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: add dependencies edge %d requires both endpoints\", storage.ErrValidation, i)\n\t\t}\n\t\tif !edge.Type.IsValid() {\n\t\t\treturn fmt.Errorf(\"%w: add dependencies edge %d requires a dependency type (max %d chars)\",\n\t\t\t\tstorage.ErrValidation, i, types.MaxDependencyTypeLen)\n\t\t}\n\t\tif edge.IssueID == edge.DependsOnID {\n\t\t\treturn fmt.Errorf(\"%w: %s cannot depend on itself\", domain.ErrSelfDependency, edge.IssueID)\n\t\t}\n\t}\n\treturn nil\n}\n\n// ValidateRemoveDependencyRequest applies the request rules every\n// DependencyEditor implementation shares for a removal.\nfunc ValidateRemoveDependencyRequest(request publicops.RemoveDependencyRequest) error {\n\tif request.Actor == \"\" {\n\t\treturn fmt.Errorf(\"%w: remove dependency requires an actor\", storage.ErrValidation)\n\t}\n\tif request.IssueID == \"\" || request.DependsOnID == \"\" {\n\t\treturn fmt.Errorf(\"%w: remove dependency requires both endpoints\", storage.ErrValidation)\n\t}\n\treturn nil\n}\n\n// AddDependenciesCommitMessage is the history entry an edge assertion records.\n//\n// Unlike CloseBatchCommitMessage it is composed from the REQUEST rather than\n// the result, and that difference is the contract's: the request is\n// all-or-nothing, so what was asked for is exactly what landed or nothing did.\n// A batch close has to name what landed because it can skip an id; this cannot.\n//\n// The two spellings are the two the CLI already wrote — one edge names both\n// endpoints, several name their count — because the role is what both `bd dep\n// add <a> <b>` and `bd dep add --file` now go through, and `bd dolt log` should\n// keep reading the way it did. The count spelling is keyed on the EDGE COUNT","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependency_editor.go#L36-L72","documentation":"ValidateRemoveDependencyRequest rejects a RemoveDependencyRequest whose Actor field is empty, wrapping storage.ErrValidation. Every dependency mutation is attributed to an actor so the change is recorded durably in history; a removal without an actor cannot be audited, so the request is refused before any database work. The %w wrap lets callers match errors.Is(err, storage.ErrValidation).","triggerScenarios":"Calling any DependencyEditor implementation's remove-dependency path (which runs ValidateRemoveDependencyRequest) with a publicops.RemoveDependencyRequest where request.Actor == \"\" — e.g. a struct literal that only sets IssueID and DependsOnID, or a caller that drops the actor value when translating from a CLI/API layer.","commonSituations":"Hand-constructing RemoveDependencyRequest in tests or scripts and forgetting the Actor field; a CLI command that has no --actor flag configured so the value defaults to empty; refactors that renamed or removed an actor parameter upstream leaving the zero value.","solutions":["Set request.Actor to the acting user or agent identity (e.g. the 'bd' configured user or requesting principal) before invoking the remove-dependency operation.","If the caller genuinely has no identity, use the deployment's default actor (e.g. the value bd uses for local unattended changes) rather than an empty string.","Check upstream translation code (CLI flags, HTTP handlers) to ensure the actor is propagated into the request struct instead of being dropped."],"exampleFix":"// before\nreq := publicops.RemoveDependencyRequest{IssueID: \"bd-42\", DependsOnID: \"bd-7\"}\nerr := editor.ExecuteRemoveDependency(ctx, req)\n\n// after\nreq := publicops.RemoveDependencyRequest{IssueID: \"bd-42\", DependsOnID: \"bd-7\", Actor: \"alice\"}\nerr := editor.ExecuteRemoveDependency(ctx, req)","handlingStrategy":"validation","validationCode":"if req.Actor == \"\" {\n    return fmt.Errorf(\"remove dependency: actor is required (set the acting user/agent)\")\n}\n// proceed with req","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always construct requests through a helper that requires an actor parameter, never a bare struct literal.","In CLI/API layers, default the actor from config (e.g. bd's configured user) instead of leaving it empty.","Add a unit test asserting the actor-propagation path for every remove-dependency entry point."],"tags":["go","validation","dependencies","auditability"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}