{"record":{"id":"940ca05187084d62","repo":"Hmbown/CodeWhale","slug":"fleet-artifact-size-changed","errorCode":null,"errorMessage":"Fleet artifact size changed","messagePattern":"Fleet artifact size changed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/artifacts.rs","lineNumber":58,"sourceCode":"            Ok(())\n        }\n        Err(error) => Err(error).context(\"Publishing Fleet artifact\"),\n    }\n}\n\npub(crate) fn read_verified(\n    workspace: &Path,\n    artifact: &FleetArtifactRef,\n    preview_limit: u64,\n) -> Result<(Vec<u8>, u64)> {\n    let parent = WorkspaceFile::open(workspace, &artifact.path, false)?;\n    let file = parent.open_file()?;\n    let size = file.metadata()?.len();\n    ensure!(\n        size <= MAX_ARTIFACT_BYTES,\n        \"Fleet artifact exceeds the 16 MiB verification limit\"\n    );\n    ensure!(\n        artifact.size_bytes.is_none_or(|expected| expected == size),\n        \"Fleet artifact size changed\"\n    );\n    let checksum = artifact\n        .checksum\n        .as_deref()\n        .context(\"Fleet artifact has no recorded checksum\")?;\n    let mut hasher = Sha256::new();\n    let mut preview = Vec::new();\n    let mut buffer = [0_u8; 8192];\n    let mut total = 0_u64;\n    // The digest and returned preview consume exactly the same bytes from the\n    // same opened file. A changed/replaced pathname is never reopened for data.\n    let mut reader = (&file).take(size + 1);\n    loop {\n        let count = reader.read(&mut buffer)?;\n        if count == 0 {\n            break;","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/fleet/artifacts.rs#L40-L76","documentation":"read_verified compares the artifact's actual on-disk size with the size_bytes recorded in the FleetArtifactRef receipt before reading. If the recorded expectation exists and differs from the current file length, verification fails — the artifact changed after its receipt was issued and can no longer be trusted as the evidence that was published.","triggerScenarios":"Calling read_verified with a FleetArtifactRef whose checksum/size receipt was computed for an earlier version of the file, but the file has since been rewritten, appended to, or truncated by another process.","commonSituations":"A run republished to the same path after the receipt was captured (defeating immutability by external write); concurrent writers racing on one artifact path; a stale receipt referenced after a workspace regeneration.","solutions":["Re-capture the FleetArtifactRef (re-run publication) so size and checksum match the current file.","Find and stop the process that rewrote the artifact after publication — publication is meant to be immutable.","Restore the original file bytes from a backup if the current file is the corrupted copy.","If the receipt is stale from an old run, discard it and reference the receipt from the latest run."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let actual = fs::metadata(ws.join(&artifact.path))?.len();\nif let Some(expected) = artifact.size_bytes {\n    if actual != expected {\n        return Err(\"artifact changed since receipt; re-publish before verifying\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Freeze the workspace (no writers) before running verification.","Reference receipts from the same run that produced the file.","Monitor for processes writing into the Fleet workspace."],"tags":["fleet","artifacts","integrity","concurrency"],"backgroundTag":"checksum-mismatch","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}