Skip to content

feat!: Bump openjd-* Rust crates to the openjd-model 0.11.0 release - #375

Merged
leongdl merged 2 commits into
OpenJobDescription:mainlinefrom
leongdl:bump/openjd-rs-crates
Oct 5, 2026
Merged

leongdl merged 2 commits into
OpenJobDescription:mainlinefrom
leongdl:bump/openjd-rs-crates

Conversation

@leongdl

@leongdl leongdl commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes: n/a (dependency bump for OpenJobDescription/openjd-rs#416 and #420)

BREAKING CHANGE: openjd.model._v1 SimpleAction.script is an openjd.expr.FormatString instead of str, and SimpleAction(script=...) requires one. Read the template text with .raw(). LIST[PATH] defaults must be relative, stay inside the template directory, and are joined with it; relative submitted PATH values are normalized.

What was the problem/requirement? (What/Why)

openjd-rs released openjd-expr 0.10.1, openjd-model 0.10.1 and openjd-sessions 0.7.2 on 2026-09-30 (openjd-rs#416), then openjd-model 0.11.0 and openjd-sessions 0.7.3 on 2026-10-03 (openjd-rs#420). This package pinned 0.10.0 / 0.10.0 / 0.7.1. openjd-sessions has no source change in either release.

One public Rust API change breaks compilation: openjd-rs#419 makes template::SimpleAction::script a FormatString. The PySimpleAction constructor and getter were the two errors.

What was the solution? (How)

Bump the three pins and adapt the two call sites. Following #373's capability-name precedent, the Python-facing SimpleAction.script is now an openjd.expr.FormatString in both directions, matching its args / timeout siblings and the EmbeddedFile.data it desugars to.

The new Rust-side SimpleActionKind / simple_actions / simple_action helpers are not exposed: nothing here consumes them, and step.bash .. step.node already cover introspection. resolve_syntax_sugar changed signature, but the bindings never call it.

Cargo.lock moves only the three crates. THIRD-PARTY-LICENSES.txt was regenerated with scripts/check_third_party_licenses.sh --update and changed only the three version lines.

pyo3 0.29.2 → 0.29.3 (second commit). Rust 1.99.0, stable since 2026-10-01, makes clippy flag clone_on_copy inside pyo3 0.29.2's #[pyclass(from_py_object)] expansion on our Copy enums. That produced 13 errors and failed Rust Quality's clippy -D warnings step on every PR, including unrelated dependabot runs. pyo3 0.29.3 fixes the generated code (PyO3/pyo3#6309). The floor in rust-bindings/Cargo.toml is raised to 0.29.3 so a fresh resolve cannot pick the affected release. Only the four pyo3 lines in THIRD-PARTY-LICENSES.txt changed.

What is the impact of this change?

Every behaviour change below was measured through this package's Python API on both the old and new pins, with expectations copied from the upstream tests' assertions rather than their prose.

Upstream Python-visible effect Measured
openjd-rs#419 bash / python / cmd / powershell / node steps are validated at decode, with errors at the authored path (steps[0] -> bash -> args[1]). A malformed script now fails at parse (DecodeValidationError). Non-ASCII step names no longer raise PanicException. 28/28 match upstream. On 0.10.0, 17 of 21 upstream rejection cases decoded: 9 failed later at create_job at the desugared path, 8 were never rejected.
openjd-rs#419 (named only in the squash body) The comprehension-shadows-let check now covers a step script's authored embeddedFiles[].data. Decoded on 0.10.0; now rejected at steps[0] -> script -> embeddedFiles[0] -> data. v0 agrees 4/4.
openjd-rs#421 LIST[PATH] defaults must be relative and stay inside the template directory, and are joined and normalized element by element. Relative submitted PATH / LIST[PATH] values are joined and normalized. Defaults are constraint-checked after the join, including environment-template parameters. Absolute submitted values are returned as written. 27/27 POSIX rows match. create_job without a preceding preprocess is unchanged (7 cases identical on both pins).
openjd-rs#410 A job environment's let failure reads like a step script's (script let binding 'q': ...). The missing-extension message lists extensions sorted. 2/2
openjd-rs#410, #417, #418 openjd.expr memory accounting. Some expressions that fit now exceed the limit (coercion to the target type, string slices, attribute base lookups) and some that failed now fit. Every upstream byte figure reproduces, 21/21. No existing Python test pinned a moved figure.

v0 divergence. v0 preprocess_job_parameters returns LIST[PATH] defaults unjoined and does not normalize a submitted ... openjd-specifications#191 (Template Schemas §2.2, §2.12) makes v1's behaviour normative, so v0 is the non-conformant side. Recorded as two strict xfails in test_known_gaps.py and in reports/model-bindings-quality-evaluation-report.md; fixing v0 is a separate change. On the #419 decode cases v0 agrees with v1 on outcome in 22/24 and on field path in 16/24. The two outcome differences are v0 rejecting Task.File.<step>_script, which predates this bump.

How was this change tested?

  • Have you run the unit tests? Yes.

hatch run test: 6323 passed, 26 skipped, 5 xfailed, coverage 94.22%. The +2 skipped are the Windows-only #421 tests; the +2 xfailed are the v0 gaps above. No strict xfail flipped. hatch run lint and hatch run typing clean. cargo build --all-targets, cargo clippy --all-targets -- -D warnings, cargo test and cargo test --doc against rust-bindings/ clean. License verify mode passes.

The pyo3 fix was checked against the toolchain CI uses: rustup run 1.99.0 cargo clippy --manifest-path rust-bindings/Cargo.toml --all-targets -- -D warnings reports the same 13 clone_on_copy errors as CI with pyo3 0.29.2, and 0 with 0.29.3.

New tests:

  • test/openjd/model_v1/test_parse.py: TestSimpleActionValidation (all 21 upstream rejection rows, 3 caller-limit rows, the brace-mismatch parse failure, controls) and the embedded-file data comprehension cases. Full-message assertions at the authored path.
  • test/openjd/model_v1/test_create_job.py: SimpleAction caps, LIST[PATH] defaults, submitted-path normalization, post-join constraints, Windows rows, let-failure messages, sorted missing extensions.
  • test/openjd/model_v1/test_template_types.py: script type, str refusal, pickle.
  • test/openjd/model_v1/test_known_gaps.py: two v0 strict xfails.
  • test/openjd/expr/test_memory.py, test_slicing.py: memory-accounting figures, extreme slice step.

Mutation check, each mutant rebuilt from source, bytecode cleared, restored by checksum:

Mutant Result
pins and the adaptation reverted to 0.10.0 / 0.10.0 / 0.7.1 62 of 79 new or edited cases fail; the 17 survivors are controls whose behaviour 0.10.0 shares
script getter returns str 4 failed
constructor accepts str 4 failed

What could NOT be verified

  • The three-auditor review of the new tests was not run. The mutation results above are the author's own. A separate semantic review read every added test for vacuous patterns and found none, but it did not run independent mutants.
  • The Windows #421 rows were not run locally; they run on the windows CI lane only. The Python 3.9 lane was not run locally either.
  • URI-allowed LIST[PATH] rows are unreachable from Python: the binding hard-codes allow_uri_path_values=false at rust-bindings/src/model/create_job_fns.rs:158. Upstream test_create_job.rs covers them.
  • TestExtremeSliceSteps pins the openjd-rs#418 overflow fix only in a build with overflow checks. 'hello'[1::9223372036854775807] panicked on 0.10.0 only in a dev build; a hatch editable install of 0.10.0 already returned e. Its docstring says so.
  • src/openjd/_openjd_rs.pyi was hand-edited, because scripts/generate_stubs.sh does not run on macOS (precedent ffd3f22). A stub regeneration on Linux should reproduce the edit, but nobody has checked.

Was this change documented?

  • Are relevant docstrings in the code base updated? Yes.
  • specs/python-model-interface.md: SimpleAction.script as FormatString, the SimpleAction decode-validation rules, and the PATH / LIST[PATH] preprocessing rules (including that walk-up also permits absolute defaults).
  • src/openjd/_openjd_rs.pyi: the script type on SimpleAction.
  • reports/model-bindings-quality-evaluation-report.md: the v0 PATH divergence.

Is this a breaking change?

Yes. SimpleAction.script is openjd.expr.FormatString instead of str. A caller reading the template text uses .raw(); a caller constructing one wraps in FormatString(...).

Templates that decoded before may now be rejected: SimpleAction steps that only failed at create_job (or never), embedded-file data with a comprehension variable shadowing a let binding, and LIST[PATH] defaults that are absolute or escape the template directory.

Does this change impact security?

In the direction of enforcement. LIST[PATH] defaults can no longer be absolute or escape the template directory unless allow_job_template_dir_walk_up=True, and defaults are constraint-checked after the join. SimpleAction scripts are validated at decode rather than at job creation.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

openjd-rs released openjd-expr 0.10.1, openjd-model 0.10.1 and
openjd-sessions 0.7.2 on 2026-09-30 (OpenJobDescription/openjd-rs#416),
then openjd-model 0.11.0 and openjd-sessions 0.7.3 on 2026-10-03
(#420). This package pinned 0.10.0 / 0.10.0 / 0.7.1. Cargo.lock moves
only the three crates; no transitive dependency changed. openjd-sessions
has no source change in either release.

One public Rust API change breaks compilation: #419 makes
template::SimpleAction::script a FormatString. The constructor and the
getter of PySimpleAction were the two errors. Following OpenJobDescription#373's
capability-name precedent, the Python-facing SimpleAction.script is now
an openjd.expr.FormatString in both directions, like its args/timeout
siblings and the EmbeddedFile.data it desugars to. The new Rust-side
SimpleActionKind / simple_actions / simple_action helpers are not
exposed: nothing here consumes them, and step.bash..node already cover
introspection. resolve_syntax_sugar changed signature but the bindings
never call it.

Behaviour changes, each measured through this package's Python API
against both pins, with expectations copied from upstream assertions:

- #419: bash/python/cmd/powershell/node steps are validated at decode
  with errors at the authored path (steps[0] -> bash -> args[1]). On
  0.10.0, 17 of 21 upstream rejection cases decoded; 9 failed later at
  create_job at the desugared path, 8 were never rejected. A malformed
  script now fails at parse (DecodeValidationError). Non-ASCII step
  names no longer raise PanicException (ASCII-only sanitizer, named in
  the squash body but not the changelog).
- #419, also named only in the squash body: the comprehension-variable
  check now covers a step script's authored embeddedFiles[].data. A
  comprehension over a let-bound name in data decoded on 0.10.0 and is
  now rejected at steps[0] -> script -> embeddedFiles[0] -> data. The
  same check on command and args is unchanged. v0 agrees on outcome and
  path in 4/4 cases.
- #421: LIST[PATH] defaults must be relative and stay inside the
  template directory, are joined to it and normalized, element by
  element; relative submitted PATH / LIST[PATH] values are joined and
  normalized; defaults are constraint-checked after the join, including
  environment-template parameters. Absolute submitted values are
  returned as written. create_job without a preceding preprocess is
  unchanged (measured, 7 cases identical on both pins).
- #410: a job environment's let failure now reads like a step script's
  ("script let binding 'q': ..."); the missing-extension message lists
  extensions sorted.
- #410/#417/#418: openjd.expr memory accounting; every upstream byte
  figure reproduces. Some expressions that fit now exceed the limit
  (coercion to the target type, string slices, attribute base lookups)
  and some that failed now fit. No existing Python test pinned a moved
  figure.

Unreachable from Python: URI-allowed LIST[PATH] rows (the binding
hard-codes allow_uri_path_values=false, create_job_fns.rs:158; upstream
test_create_job.rs covers them). The Windows rows of #421 are added as
win32-only tests and were not run locally.

v0 divergence: v0 preprocess_job_parameters returns LIST[PATH] defaults
unjoined and does not normalize submitted '..'. openjd-specifications#191
(Template Schemas 2.2, 2.12) makes v1's behaviour normative. Recorded as
two strict xfails in test_known_gaps.py and in the model report; fixing
v0 is a separate change. On the #419 decode cases v0 agrees with v1 on
outcome in 22/24 and on field path in 16/24; the two outcome
differences are v0 rejecting Task.File.<step>_script, which predates
this bump.

Verified: 6323 passed / 26 skipped / 5 xfailed, coverage 94.22%. The +2
skipped are the Windows-only tests, the +2 xfailed the v0 gaps; no
strict xfail flipped. ruff, black, mypy, cargo fmt, build, clippy
-D warnings, test and test --doc clean. THIRD-PARTY-LICENSES.txt
regenerated (three version lines) and verify mode passes.

Mutants, each rebuilt, bytecode cleared, restored by checksum: reverting
the pins and the adaptation kills 62 of 79 new or edited cases (the 17
survivors are controls whose behaviour 0.10.0 shares); the getter
returning str kills 4; the constructor accepting str kills 4.

Not done: the three-auditor review of the new tests
(regression-workflow section 3) was not dispatched; the mutation
results above are the author's own. TestExtremeSliceSteps pins the #418
overflow fix only in a build with overflow checks; a hatch env install
of 0.10.0 already returned the right answer.

BREAKING CHANGE: SimpleAction.script is an openjd.expr.FormatString
instead of str, and SimpleAction(script=...) requires one. LIST[PATH]
defaults must be relative and are joined with the job template
directory; relative submitted PATH values are normalized.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
Rust 1.99.0 (stable since 2026-10-01) makes clippy flag
clone_on_copy inside pyo3 0.29.2's #[pyclass(from_py_object)]
expansion on Copy enums: 13 errors, which fail Rust Quality's
clippy -D warnings step on every PR. pyo3 0.29.3 fixes the
generated code (PyO3/pyo3#6309).

Raise the floor in rust-bindings/Cargo.toml to 0.29.3 so a fresh
resolve cannot pick the affected release. THIRD-PARTY-LICENSES.txt
regenerated; only the four pyo3 version lines changed.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
@leongdl
leongdl merged commit 32fed4b into OpenJobDescription:mainline Oct 5, 2026
66 of 94 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants