{"record":{"id":"673f1be4f0c2eccb","repo":"JuliusBrussee/caveman","slug":"ccr-typed-object-byte-lengths-cannot-be-negative","errorCode":null,"errorMessage":"ccr: typed object byte lengths cannot be negative","messagePattern":"ccr: typed object byte lengths cannot be negative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/ccr/store.go","lineNumber":139,"sourceCode":"\t\tobj.CreatedAt = time.Now().UTC()\n\t} else {\n\t\tobj.CreatedAt = obj.CreatedAt.UTC()\n\t}\n\tdataSum := sha256.Sum256(obj.Data)\n\tcomputedHash := \"sha256:\" + hex.EncodeToString(dataSum[:])\n\tif obj.ContentHash == \"\" {\n\t\tobj.ContentHash = computedHash\n\t} else if obj.ContentHash != computedHash {\n\t\treturn Object{}, errors.New(\"ccr: typed object content_hash does not match data\")\n\t}\n\tif obj.OriginalByteLength == 0 {\n\t\tobj.OriginalByteLength = len(obj.Data)\n\t}\n\tif obj.StoredByteLength == 0 {\n\t\tobj.StoredByteLength = len(obj.Data)\n\t}\n\tif obj.OriginalByteLength < 0 || obj.StoredByteLength < 0 {\n\t\treturn Object{}, errors.New(\"ccr: typed object byte lengths cannot be negative\")\n\t}\n\tif obj.ID == \"\" {\n\t\tidentity := strings.Join([]string{string(obj.Type), obj.SessionID, obj.Source, obj.RepositoryState, obj.ContentHash}, \"\\x00\")\n\t\tsum := sha256.Sum256([]byte(identity))\n\t\tobj.ID = \"ccr_obj_\" + hex.EncodeToString(sum[:16])\n\t}\n\tobj.Dependencies = append([]string(nil), obj.Dependencies...)\n\tobj.Data = bytes.Clone(obj.Data)\n\treturn obj, nil\n}\n\n// sameImmutableObject compares fields PutObject promises never to change.\n// CreatedAt is assigned at first storage, while currentness and lifecycle have\n// explicit mutation methods, so repeat puts intentionally do not compare them.\nfunc sameImmutableObject(left, right Object) bool {\n\treturn left.ID == right.ID &&\n\t\tleft.Type == right.Type &&\n\t\tleft.ContentHash == right.ContentHash &&","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/engine/ccr/store.go#L121-L157","documentation":"prepareObject defaults zero byte-length fields to len(obj.Data) but rejects explicitly negative OriginalByteLength or StoredByteLength. Negative lengths would corrupt budget accounting and storage stats, so they fail closed. Only values < 0 are rejected; zero means 'fill in for me'.","triggerScenarios":"Setting OriginalByteLength or StoredByteLength to -1 or any negative value (often as a sentinel for 'unknown'); arithmetic that underflows (e.g. len(data) - overhead where overhead > len(data)); deserializing a record with corrupt length fields.","commonSituations":"Using -1 as an 'unset' sentinel in a producer system; integer underflow when computing stored length after subtracting an estimate; hand-edited or externally produced JSON records with negative numbers.","solutions":["Leave the length fields at 0 so the store derives them from Data","If you track pre-transform size, ensure OriginalByteLength is a non-negative count of the original bytes","Clamp/validate lengths at your boundary before building the Object"],"exampleFix":"// before\nobj := ccr.Object{OriginalByteLength: -1, Data: data} // -1 as 'unknown'\n\n// after\nobj := ccr.Object{Data: data} // lengths default to len(Data)\n// or, when tracking the pre-compression size:\nobj := ccr.Object{Data: packed, OriginalByteLength: len(original), StoredByteLength: len(packed)}","handlingStrategy":"validation","validationCode":"if obj.OriginalByteLength < 0 || obj.StoredByteLength < 0 {\n    return errors.New(\"byte lengths must be >= 0\")\n}\nstore.PutObject(obj)","typeGuard":"func validByteLengths(o ccr.Object) bool {\n    return o.OriginalByteLength >= 0 && o.StoredByteLength >= 0\n}","tryCatchPattern":null,"preventionTips":["Use 0 for 'unset', never -1 sentinels","Guard subtraction with max(0, a-b) when deriving lengths","Validate external records' numeric fields before building objects"],"tags":["ccr","go","validation","putobject","byte-length"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}