Pass runtime table as run-fn argument
The executor now calls run-fns with the runtime table instead of ().
Lua silently discards extra arguments, so zero-arg (fn [] ...) still
works — but run-fns can now destructure the runtime directly:
(fn [{: sh : secret : jobs}] ...).
Removes the ArityViolation check that rejected one-arg run-fns at
registration time, since the argument is now always provided.
Assisted-by: GLM-5.1 via pi
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index bfb64eb..6cde4ed 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -413,9 +413,11 @@ fn run_pipeline(
.clone();
runtime.enter_job(job_id);
+ let rt =
+ RuntimeHandle::runtime_table(runtime.lua()).expect("runtime table should be installed");
let result: Result<(), RuntimeError> = match run_fn {
RunFn::Lua(f) => f
- .call::<mlua::Value>(())
+ .call::<mlua::Value>(rt)
.map(|_| ())
.map_err(RuntimeError::from),
RunFn::Rust(f) => f(&runtime),
diff --git a/quire-core/src/ci/pipeline.rs b/quire-core/src/ci/pipeline.rs
index 67fc196..2a3e352 100644
--- a/quire-core/src/ci/pipeline.rs
+++ b/quire-core/src/ci/pipeline.rs
@@ -46,15 +46,6 @@ pub enum DefinitionError {
#[label("duplicate registration")]
span: SourceSpan,
},
-
- #[error(
- "Job '{job_id}' run-fn takes an argument — run-fns must be zero-arg `(fn [] …)`; bind the runtime via `(local runtime (require :quire.runtime))` or destructure it from `(require :quire.ci)` instead of destructuring a handle."
- )]
- ArityViolation {
- job_id: String,
- #[label("here")]
- span: SourceSpan,
- },
}
/// A post-graph structural error found after all jobs have been
diff --git a/quire-core/src/ci/registration.rs b/quire-core/src/ci/registration.rs
index 2086cf8..8242ac6 100644
--- a/quire-core/src/ci/registration.rs
+++ b/quire-core/src/ci/registration.rs
@@ -198,26 +198,6 @@ fn register_job(
return Ok(());
}
- // Arity check: one-arg run-fns use the old `(fn [{: sh}] …)`
- // pattern. Reject them so users get a clear message instead of
- // a runtime-nil panic when the ambient runtime is absent.
- //
- // Fail open on `debug.getinfo` errors — if the debug library is
- // unavailable or the call shape changes, treat the function as
- // zero-arg and let the user surface any real arity mismatch at
- // execution time.
- let nparams: u32 = lua
- .load("return debug.getinfo(...).nparams")
- .call(&run_fn)
- .unwrap_or(0);
- if nparams != 0 {
- let span = pipeline::span_for_line(&r.source, line);
- r.errors
- .borrow_mut()
- .push(DefinitionError::ArityViolation { job_id: id, span });
- return Ok(());
- }
-
match Job::new(id, inputs, RunFn::Lua(run_fn), line, &r.source) {
Ok(job) => r.add_job(job, line),
Err(e) => r.errors.borrow_mut().push(e),
diff --git a/quire-core/src/ci/runtime.rs b/quire-core/src/ci/runtime.rs
index 3053338..c83d0c3 100644
--- a/quire-core/src/ci/runtime.rs
+++ b/quire-core/src/ci/runtime.rs
@@ -380,6 +380,13 @@ pub struct RuntimeHandle {
}
impl RuntimeHandle {
+ /// The Lua table at `package.loaded["quire.ci"].runtime`.
+ /// Populated by [`install`](Self::install) and cleared by
+ /// [`uninstall`](Self::uninstall).
+ pub fn runtime_table(lua: &Lua) -> mlua::Result<mlua::Table> {
+ runtime_stub(lua)
+ }
+
/// Install the runtime on the Lua VM. Hold the returned guard for
/// the duration of the run; dropping it tears the install down.
pub fn install(runtime: Rc<Runtime>, lua: &Lua) -> mlua::Result<Self> {
@@ -656,6 +663,13 @@ mod tests {
/// tears down the install). Tests in this module exercise the
/// `RunFn::Lua` path; if the first job turns out to be a `Rust`
/// variant the test setup is wrong.
+ /// Call `run_fn` with the runtime table as its argument, matching
+ /// how the executor invokes Lua run-fns.
+ fn call_fn(runtime: &Rc<Runtime>, run_fn: &mlua::Function) -> mlua::Result<mlua::Value> {
+ let rt = runtime_stub(runtime.lua()).expect("runtime table");
+ run_fn.call(rt)
+ }
+
fn rt(
source: &str,
secrets: HashMap<String, SecretString>,
@@ -762,7 +776,7 @@ mod tests {
}));
*runtime.current_job.borrow_mut() = Some("go".to_string());
- let _: mlua::Value = run_fn.call(()).expect("sh call");
+ let _: mlua::Value = call_fn(&runtime, &run_fn).expect("sh call");
let calls = received.borrow();
assert_eq!(calls.len(), 2, "expected 2 events, got: {calls:?}");
@@ -784,7 +798,7 @@ mod tests {
let log_dir = runtime.log_dir().to_path_buf();
*runtime.current_job.borrow_mut() = Some("go".to_string());
- let _: mlua::Value = run_fn.call(()).expect("sh call");
+ let _: mlua::Value = call_fn(&runtime, &run_fn).expect("sh call");
let log_path = log_dir.join("jobs").join("go").join("sh-1.log");
assert!(log_path.exists(), "expected sh-1.log at {log_path:?}");
@@ -810,7 +824,7 @@ mod tests {
}));
// No enter_job — current_job stays None.
- let _: mlua::Value = run_fn.call(()).expect("sh call");
+ let _: mlua::Value = call_fn(&runtime, &run_fn).expect("sh call");
assert_eq!(*count.borrow(), 0, "callback should not fire outside a job");
}
@@ -820,7 +834,7 @@ mod tests {
/// table as ShOutput.
fn run_sh_via_job(source: &str) -> ShOutput {
let (runtime, run_fn, _guard) = rt(source, HashMap::new());
- let value: mlua::Value = run_fn.call(()).expect("sh call should return a value");
+ let value: mlua::Value = call_fn(&runtime, &run_fn).expect("sh call should return a value");
runtime.lua().from_value(value).expect("decode ShOutput")
}
@@ -843,7 +857,7 @@ mod tests {
// assertion by writing the field directly.
*runtime.current_job.borrow_mut() = Some("go".to_string());
- let value: mlua::Value = run_fn.call(()).expect("sh call");
+ let value: mlua::Value = call_fn(&runtime, &run_fn).expect("sh call");
let returned: ShOutput = runtime.lua().from_value(value).expect("decode");
// The Lua caller still sees the raw value — echo printed it.
@@ -1066,8 +1080,8 @@ mod tests {
git_dir = bare.display(),
);
- let (_runtime, run_fn, _guard) = rt(&source, secrets);
- let _: mlua::Value = run_fn.call(()).expect("mirror should succeed");
+ let (runtime, run_fn, _guard) = rt(&source, secrets);
+ let _: mlua::Value = call_fn(&runtime, &run_fn).expect("mirror should succeed");
// Tag landed in the target repo, pointing at the head SHA.
let resolved = git(&["rev-parse", "refs/tags/v1"], &target);