automation: Create Proxy queues an output the same way (#2479)

`file.createProxy` builds a Render Queue item exactly as `renderQueue.add`
does, with `[compName]_proxy.[fileExtension]` as its output, so an unsaved
project sent it to the home folder too.

Its `path` / `output` had to join `WRITES` for this: the confinement only
knows an output was named through that table, and it was reading them as a
read before, so a proxy could be written into a read-only root (part of the
F-11 audit in #2131).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Anubhav RaiandClaude Opus 5 committed 2026-10-27 23:24:13 +05:30
1 parent a2c455091f
commit b86bc1d2a9
2 files changed
+7 -2

No files matched your search

+2 -1
View File
@@ -86,13 +86,14 @@ const WRITES: &[(&str, &[&str])] = &[
("essential.exportTemplate", &["path"]),
("shortcuts.export", &["path"]),
("renderQueue.add", &["output", "path"]),
("file.createProxy", &["path", "output"]),
];
/// Commands that write where they are not told: the project's file, the Render Queue's outputs.
const IMPLICIT_WRITES: &[&str] = &["file.save", "file.incrementAndSave", "renderQueue.render", "render.preRender"];
/// Commands that queue a render, whose output defaults to the project's folder when not given.
const QUEUES_AN_OUTPUT: &[&str] = &["renderQueue.add", "render.preRender"];
const QUEUES_AN_OUTPUT: &[&str] = &["renderQueue.add", "render.preRender", "file.createProxy"];
/// What must be looked up before an implicitly writing command may run.
#[derive(Debug, PartialEq, Eq)]
+5 -1
View File
@@ -1396,7 +1396,7 @@ fn automation_roots_refuse_a_queued_output_from_an_unsaved_project() {
s.backend().confine(Confinement::new(&[], std::slice::from_ref(&write)).unwrap());
call_json(&mut s, "execute_command", json!({"command": "comp.new", "params": {"name": "A", "width": 64, "height": 64}}));
// Never saved: neither way of queueing a render may fall back to the home folder.
for command in ["renderQueue.add", "render.preRender"] {
for command in ["renderQueue.add", "render.preRender", "file.createProxy"] {
let (c, err) = call(&mut s, "execute_command", json!({"command": command, "params": {"comp": "A"}}));
assert!(err, "`{command}` without an output should be refused: {c:?}");
let msg = c[0]["text"].as_str().unwrap_or_default();
@@ -1406,6 +1406,10 @@ fn automation_roots_refuse_a_queued_output_from_an_unsaved_project() {
assert_eq!(queue["items"].as_array().map(Vec::len), Some(0), "nothing was queued: {queue}");
// Naming an output inside a write root is how it is done.
call_json(&mut s, "execute_command", json!({"command": "renderQueue.add", "params": {"comp": "A", "output": p("named.mp4")}}));
// A proxy's own `path` is a write, not the read it used to be checked as (#2131 F-11).
let (c, err) = call(&mut s, "execute_command", json!({"command": "file.createProxy", "params": {"comp": "A", "path": "/elsewhere/proxy.mov"}}));
assert!(err, "a proxy outside the roots should be refused: {c:?}");
assert!(c[0]["text"].as_str().unwrap_or_default().contains("cannot write"), "{c:?}");
// Once the project is saved there, the default output (beside it) is queued as before.
call_json(&mut s, "save_project", json!({"path": p("proj.ecproj")}));
call_json(&mut s, "execute_command", json!({"command": "renderQueue.add", "params": {"comp": "A"}}));