fix(pdf): the renderer resolved every artifact path against the wrong root
`render_one` joined `missions_root()/<mission_id>/` before the artifact path, producing `<root>/<mission>/_outputs/<mission>/<phase>/...` — the mission id twice, and no such file. Artifact paths are relative to the MISSIONS ROOT. All three registration sites write `_outputs/<mission>/<phase>/...`, and `_outputs` is deliberately a sibling of the per-mission directories so it survives their reaping; joining the mission id first put the lookup inside the very directory `_outputs` exists to escape. It went unnoticed because until now the only artifacts on the system were `code_diff` rows registered with `render_pdf: false`, which this worker never reads. `produces = ["md","pdf"]` was inert, so nothing ever asked for a render. The first artifacts to ask were the first to find it — both failed with ENOENT on the doubled path. Negative control: restore the extra join and `an_artifact_path_resolves_against_the_missions_root` fails. The worker's error handling is sound and needed no change: it recorded `render_pdf_status = 'failed'` with the full path in `render_pdf_error`, which is how this was diagnosed in one read. 237 lib tests pass.
This commit is contained in:
@@ -37,6 +37,22 @@ fn missions_root() -> PathBuf {
|
|||||||
.unwrap_or_else(|_| PathBuf::from("/var/lib/clawmates-missions"))
|
.unwrap_or_else(|_| PathBuf::from("/var/lib/clawmates-missions"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve `mission_artifacts.path` to a file on disk.
|
||||||
|
///
|
||||||
|
/// Artifact paths are relative to the MISSIONS ROOT, not to the per-mission
|
||||||
|
/// directory: every registration site writes `_outputs/<mission>/<phase>/...`,
|
||||||
|
/// and `_outputs` is deliberately a sibling of the per-mission dirs so it
|
||||||
|
/// survives their reaping.
|
||||||
|
///
|
||||||
|
/// This used to join `missions_root()/<mission_id>/` first, producing
|
||||||
|
/// `<root>/<mission>/_outputs/<mission>/<phase>/...` — the mission id twice and
|
||||||
|
/// no such file. It went unnoticed because the only artifacts that existed were
|
||||||
|
/// `code_diff` rows registered with `render_pdf: false`, which this worker never
|
||||||
|
/// reads. The first artifacts to ask for rendering were the first to find it.
|
||||||
|
fn artifact_abs(rel: &str) -> PathBuf {
|
||||||
|
missions_root().join(rel)
|
||||||
|
}
|
||||||
|
|
||||||
fn chromium_bin() -> String {
|
fn chromium_bin() -> String {
|
||||||
std::env::var("CHROMIUM_BIN").unwrap_or_else(|_| "chromium".to_string())
|
std::env::var("CHROMIUM_BIN").unwrap_or_else(|_| "chromium".to_string())
|
||||||
}
|
}
|
||||||
@@ -91,8 +107,7 @@ async fn render_one(
|
|||||||
artifact: &cm_db::repo::missions::MissionArtifact,
|
artifact: &cm_db::repo::missions::MissionArtifact,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
// 1. Locate the source MD on disk.
|
// 1. Locate the source MD on disk.
|
||||||
let mission_root = missions_root().join(artifact.mission_id.to_string());
|
let src_path = artifact_abs(&artifact.path);
|
||||||
let src_path = mission_root.join(&artifact.path);
|
|
||||||
let md = tokio::fs::read_to_string(&src_path)
|
let md = tokio::fs::read_to_string(&src_path)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("read {}: {e}", src_path.display()))?;
|
.map_err(|e| format!("read {}: {e}", src_path.display()))?;
|
||||||
@@ -133,7 +148,7 @@ async fn render_one(
|
|||||||
// 4. Move next to the source MD so the artifact tree stays self-
|
// 4. Move next to the source MD so the artifact tree stays self-
|
||||||
// contained. Filename derived from the MD path (foo.md → foo.pdf).
|
// contained. Filename derived from the MD path (foo.md → foo.pdf).
|
||||||
let out_rel = pdf_sibling(&artifact.path);
|
let out_rel = pdf_sibling(&artifact.path);
|
||||||
let out_abs = mission_root.join(&out_rel);
|
let out_abs = artifact_abs(&out_rel);
|
||||||
if let Some(parent) = out_abs.parent() {
|
if let Some(parent) = out_abs.parent() {
|
||||||
tokio::fs::create_dir_all(parent)
|
tokio::fs::create_dir_all(parent)
|
||||||
.await
|
.await
|
||||||
@@ -250,6 +265,22 @@ fn pdf_sibling(md_path: &str) -> String {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_artifact_path_resolves_against_the_missions_root() {
|
||||||
|
// Exactly what `mission_delivery` and `mission_outputs` register.
|
||||||
|
let rel = "_outputs/11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222/repo/research/01_summary.md";
|
||||||
|
let abs = artifact_abs(rel);
|
||||||
|
let root = missions_root();
|
||||||
|
assert_eq!(abs, root.join(rel), "{abs:?}");
|
||||||
|
// The regression this exists for: the mission id must appear ONCE.
|
||||||
|
let s = abs.to_string_lossy();
|
||||||
|
assert_eq!(
|
||||||
|
s.matches("11111111-1111-1111-1111-111111111111").count(),
|
||||||
|
1,
|
||||||
|
"the mission id must not be doubled: {s}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pdf_sibling_paths() {
|
fn pdf_sibling_paths() {
|
||||||
assert_eq!(pdf_sibling("research/v3/spec.md"), "research/v3/spec.pdf");
|
assert_eq!(pdf_sibling("research/v3/spec.md"), "research/v3/spec.pdf");
|
||||||
|
|||||||
Reference in New Issue
Block a user