{"record":{"id":"32b0987373fbab35","repo":"AprilNEA/OpenLogi","slug":"asset-must-be-a-non-symlink-regular-file","errorCode":null,"errorMessage":"{asset} must be a non-symlink regular file","messagePattern":"(.+?) must be a non-symlink regular file","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/openlogi-cli/src/cmd/fixture/contribute.rs","lineNumber":495,"sourceCode":"fn read_json<T: serde::de::DeserializeOwned>(path: &Path, asset: &str) -> Result<T> {\n    let bytes = fs::read(path).with_context(|| format!(\"could not read {asset}\"))?;\n    serde_json::from_slice(&bytes).with_context(|| format!(\"could not parse {asset}\"))\n}\n\nfn require_directory(path: &Path, asset: &str) -> Result<()> {\n    let metadata =\n        fs::symlink_metadata(path).with_context(|| format!(\"could not inspect {asset}\"))?;\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        bail!(\"{asset} must be a non-symlink directory\");\n    }\n    Ok(())\n}\n\nfn require_regular_file(path: &Path, asset: &str) -> Result<()> {\n    let metadata =\n        fs::symlink_metadata(path).with_context(|| format!(\"could not inspect {asset}\"))?;\n    if metadata.file_type().is_symlink() || !metadata.is_file() {\n        bail!(\"{asset} must be a non-symlink regular file\");\n    }\n    Ok(())\n}\n\nfn reject_symlink(path: &Path, asset: &str) -> Result<()> {\n    match fs::symlink_metadata(path) {\n        Ok(metadata) if metadata.file_type().is_symlink() => {\n            bail!(\"{asset} output must not be a symlink\")\n        }\n        Ok(_) => Ok(()),\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),\n        Err(error) => Err(error).with_context(|| format!(\"could not inspect {asset} output\")),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use openlogi_fixture::CANONICAL_DEVICE_PROFILE_JSON;","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/AprilNEA/OpenLogi/blob/e846e6f4b4405e33bd6a9aaf949a482ce34cb6d8/crates/openlogi-cli/src/cmd/fixture/contribute.rs#L477-L513","documentation":"`require_regular_file` demands that an asset path be a plain regular file and not a symlink, using `symlink_metadata` so a symlink is rejected even if it targets a regular file. Fixture integrity checks refuse linked files because their content could silently change with the link target.","triggerScenarios":"finish, validate_resumable_layout, or validate_resumable_cases inspects an asset file that is a symlink, or is not a regular file (a directory or missing path — missing paths surface via the `could not inspect {asset}` context instead).","commonSituations":"Dotfile-style setups that symlink fixture JSON into a shared repo; generating fixture files as symlinks to templates; case files hard-linked/symlinked between fixtures.","solutions":["Replace the symlink with a real copy: `rm <asset> && cp <target> <asset>`.","Regenerate the fixture so the file is written in place rather than linked.","Check before running: `[ -f <asset> ] && [ ! -L <asset> ]`."],"exampleFix":"// before\nln -s ../../common/manifest.json out/mx-master/manifest.json\n// after\ncp ../../common/manifest.json out/mx-master/manifest.json","handlingStrategy":"validation","validationCode":"[ -f \"$ASSET\" ] && [ ! -L \"$ASSET\" ] || { echo \"$ASSET must be a regular non-symlink file\" >&2; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Copy fixture files into place rather than symlinking templates.","Exclude fixture dirs from dotfile/symlink managers.","Run `find fixtures -type l` in CI to catch links early."],"tags":["filesystem","symlink","validation"],"backgroundTag":"incompatible-source-type","analyzedSha":"e846e6f4b4405e33bd6a9aaf949a482ce34cb6d8","analyzedAt":"2026-09-13T03:07:16.451Z","contentChangedAt":"2026-09-13T03:07:16.451Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}