{"record":{"id":"53a888deb5e0cc24","repo":"JuliusBrussee/caveman","slug":"native-runtime-invalid-decision-id","errorCode":null,"errorMessage":"native runtime: invalid decision id","messagePattern":"native runtime: invalid decision id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/nativeruntime/explain.go","lineNumber":35,"sourceCode":"// Ledger record. It contains decision basis, never raw prompt or tool output.\ntype DecisionExplanation struct {\n\tSchema               string         `json:\"schema\"`\n\tDecisionID           string         `json:\"decision_id\"`\n\tSessionID            string         `json:\"session_id\"`\n\tTimestampMS          int64          `json:\"timestamp_ms\"`\n\tAction               string         `json:\"action\"`\n\tReason               string         `json:\"reason\"`\n\tInputBasis           map[string]any `json:\"input_basis\"`\n\tAlternativesRejected []string       `json:\"alternatives_rejected\"`\n\tTaskStateBefore      string         `json:\"task_state_before\"`\n\tTaskStateAfter       string         `json:\"task_state_after\"`\n\tCurrentness          string         `json:\"currentness\"`\n\tRecoveryRef          string         `json:\"recovery_ref,omitempty\"`\n}\n\nfunc ExplainDecision(store *ccr.Store, decisionID string) (DecisionExplanation, error) {\n\tif !decisionIDPattern.MatchString(decisionID) {\n\t\treturn DecisionExplanation{}, errors.New(\"native runtime: invalid decision id\")\n\t}\n\tobject, err := store.FindTaskDecision(decisionID)\n\tif err != nil {\n\t\treturn DecisionExplanation{}, err\n\t}\n\tvar record struct {\n\t\tSchema               string         `json:\"schema\"`\n\t\tDecisionID           string         `json:\"decision_id\"`\n\t\tTimestampMS          int64          `json:\"timestamp_ms\"`\n\t\tAction               string         `json:\"action\"`\n\t\tReason               string         `json:\"reason\"`\n\t\tInputBasis           map[string]any `json:\"input_basis\"`\n\t\tAlternativesRejected []string       `json:\"alternatives_rejected\"`\n\t\tTaskStateBefore      string         `json:\"task_state_before\"`\n\t\tTaskStateAfter       string         `json:\"task_state_after\"`\n\t\tCurrentness          string         `json:\"currentness\"`\n\t\tRecoveryRef          string         `json:\"recovery_ref\"`\n\t}","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/nativeruntime/explain.go#L17-L53","documentation":"ExplainDecision validates the decision id against the strict pattern ^dec_[0-9a-f]{24}$ before touching the store. Ids not matching (wrong prefix, wrong length, uppercase, or non-hex characters) are rejected as invalid rather than looked up — malformed ids never become store queries.","triggerScenarios":"Calling ExplainDecision with a decision id that is empty, has a typo, uses uppercase hex, is a full 64-hex sha256 instead of the 24-hex dec_ id, or was re-encoded (base64, quoted) in transit.","commonSituations":"Copying a different object's id (ccr_obj_...) instead of the decision id; case-folding middleware uppercasing ids; truncation of the id in logs or terminal copy-paste.","solutions":["Use the exact dec_-prefixed 24-lowercase-hex id returned when the decision was recorded","Validate client-side with regexp `^dec_[0-9a-f]{24}$` before calling","If ids come through case-insensitive channels, lowercase them first"],"exampleFix":"// before\nexp, err := nativeruntime.ExplainDecision(store, decisionID) // e.g. \"DEC_AABB...\" or \"\"\n\n// after\nvar decIDRe = regexp.MustCompile(`^dec_[0-9a-f]{24}$`)\nif !decIDRe.MatchString(decisionID) {\n    return fmt.Errorf(\"malformed decision id\")\n}\nexp, err := nativeruntime.ExplainDecision(store, decisionID)","handlingStrategy":"validation","validationCode":"var decisionIDRe = regexp.MustCompile(`^dec_[0-9a-f]{24}$`)\nif !decisionIDRe.MatchString(decisionID) {\n    return errors.New(\"malformed decision id\")\n}\nexp, err := nativeruntime.ExplainDecision(store, decisionID)","typeGuard":"func isDecisionID(s string) bool {\n    re := regexp.MustCompile(`^dec_[0-9a-f]{24}$`)\n    return re.MatchString(s)\n}","tryCatchPattern":null,"preventionTips":["Pass decision ids through verbatim from the runtime that issued them","Lowercase ids from case-insensitive channels before validating"],"tags":["nativeruntime","go","validation","decision-id","regex"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}