{"record":{"id":"0456fe785f76093d","repo":"JuliusBrussee/caveman","slug":"native-runtime-invalid-decision-record","errorCode":null,"errorMessage":"native runtime: invalid decision record","messagePattern":"native runtime: invalid decision record","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/nativeruntime/explain.go","lineNumber":58,"sourceCode":"\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}\n\tif err := json.Unmarshal(object.Data, &record); err != nil {\n\t\treturn DecisionExplanation{}, fmt.Errorf(\"native runtime: decode decision: %w\", err)\n\t}\n\tif record.Schema != \"caveman.native.decision.v1\" || record.DecisionID != decisionID || record.Action == \"\" || record.Reason == \"\" {\n\t\treturn DecisionExplanation{}, errors.New(\"native runtime: invalid decision record\")\n\t}\n\treturn DecisionExplanation{\n\t\tSchema:               WhySchema,\n\t\tDecisionID:           record.DecisionID,\n\t\tSessionID:            object.SessionID,\n\t\tTimestampMS:          record.TimestampMS,\n\t\tAction:               record.Action,\n\t\tReason:               record.Reason,\n\t\tInputBasis:           record.InputBasis,\n\t\tAlternativesRejected: record.AlternativesRejected,\n\t\tTaskStateBefore:      record.TaskStateBefore,\n\t\tTaskStateAfter:       record.TaskStateAfter,\n\t\tCurrentness:          record.Currentness,\n\t\tRecoveryRef:          record.RecoveryRef,\n\t}, nil\n}\n","sourceCodeStart":40,"sourceCodeEnd":75,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/nativeruntime/explain.go#L40-L75","documentation":"After fetching the TaskDecision object from CCR, ExplainDecision decodes its Data as JSON and requires schema == \"caveman.native.decision.v1\", decision_id equal to the requested id, and non-empty action and reason. A record failing any of these is corrupt or was written by an incompatible writer, so it refuses to explain it.","triggerScenarios":"A decision record whose payload schema string differs (older/newer writer), whose embedded decision_id doesn't match the object it was found under, or with empty action/reason fields; truncated or hand-edited payload bytes that still decode as JSON.","commonSituations":"Upgrading the runtime across a schema change while reading old stored decisions; two writers recording decisions with different serializers; storage corruption or partial writes.","solutions":["Re-record the decision with the current runtime so the payload carries schema \"caveman.native.decision.v1\" and the matching decision_id","Verify the payload: unescape/pretty-print object.Data and check schema, decision_id, action, reason fields","If reading legacy records, migrate or skip them instead of explaining"],"exampleFix":"// before\nexp, err := nativeruntime.ExplainDecision(store, id) // legacy record with schema \"caveman.native.decision.v0\"\n\n// after\n// detect and skip legacy records:\nobj, _ := store.FindTaskDecision(id)\nif !bytes.Contains(obj.Data, []byte(\"caveman.native.decision.v1\")) {\n    return ErrLegacyDecision // migrate or skip\n}\nexp, err := nativeruntime.ExplainDecision(store, id)","handlingStrategy":"try-catch","validationCode":"obj, err := store.FindTaskDecision(decisionID)\nif err == nil && !bytes.Contains(obj.Data, []byte(\"caveman.native.decision.v1\")) {\n    // legacy record: migrate or skip instead of explaining\n}","typeGuard":null,"tryCatchPattern":"exp, err := nativeruntime.ExplainDecision(store, decisionID)\nif err != nil {\n    if strings.Contains(err.Error(), \"invalid decision record\") {\n        // corrupt/legacy record: skip, migrate, or re-record\n    }\n    return err\n}","preventionTips":["Migrate stored decisions across runtime schema upgrades","Write decisions only through the runtime's own recorder so schema fields are always consistent"],"tags":["nativeruntime","go","schema-mismatch","decision-record","integrity"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}