Skip to content

chore(deps): Bump openjd-* Rust crates to the 0.6.0 release - #349

Merged
leongdl merged 6 commits into
OpenJobDescription:mainlinefrom
leongdl:chore/bump-openjd-rs-0.6.0
Sep 3, 2026
Merged

chore(deps): Bump openjd-* Rust crates to the 0.6.0 release#349
leongdl merged 6 commits into
OpenJobDescription:mainlinefrom
leongdl:chore/bump-openjd-rs-0.6.0

Conversation

@leongdl

@leongdl leongdl commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Moves the bindings crate onto the openjd-rs release published by OpenJobDescription/openjd-rs#357.

crate from to
openjd-expr 0.5.0 0.6.0 (breaking)
openjd-model 0.5.4 0.6.0 (breaking)
openjd-sessions 0.5.4 0.5.5

What the breaking part is

Both minor bumps are breaking because of openjd-rs#354, "Keep the decimal places of a floatstring range element" — the Rust counterpart of #345 on this side. TaskParameter::Float now carries Vec<Float64> rather than Vec<f64>, because a <floatstring> range element has to keep the scale it was written with: '02.50' renders 2.50, not 2.5 (Template Schemas §7.5).

That reaches Python through TaskParameterValue, which renders the preserved spelling verbatim, so the per-task value a command line receives from openjd.model._v1 now matches the pure-Python reference.

openjd-model 0.6.0 also carries openjd-rs#355 (two chunking parity gaps) and openjd-rs#358 (the 512-character cap on a let binding identifier). openjd-sessions 0.5.5 carries openjd-rs#361 (persist a resolved symbol table supplied by argument).

Parity with openjd-rs#354

Every input/expected pair in that PR's own assertions — its value.rs unit tests, the ranges.rs leading-zero tests, and test_step_param_space_iter.rs — was run through both implementations this repo ships. 26/26 agree, v0 and v1, including -0.00.0, -0.000.00, 0e5, 00e50e5, -0.0E+20.0E+2, 0.0000001, 1e-400, a 403-character 0.000…1, " 2.50 "2.50, 5.5. and .5.5.

Every fix in the release, and how it was verified

The release's claimed changes were reconciled against a source diff of the old and new crates, so an unlisted change could not hide. openjd-expr touched only value.rs; openjd-model touched ranges.rs, job/mod.rs, step_param_space.rs and template/validate_v2023_09/format_strings.rs; openjd-sessions touched only session.rs. Every one maps to a listed fix — nothing unclaimed.

upstream fix verified through result
openjd-rs#354 — keep a floatstring's decimal places decode_job_template + create_job + StepParameterSpaceIterator, all 26 input/expected pairs from that PR's own assertions 26/26 agree, v0 and v1
openjd-rs#355 — two chunking parity gaps StepParameterSpaceIterator indexing and chunks_* getters both gaps closed; the strict xfails that pinned them were promoted to real tests
openjd-rs#358 — 512-char cap on a let identifier decode_job_template on both implementations 512 accepted, 513 rejected, with EXPR alone and with FEATURE_BUNDLE_1; no test existed on the v1 side, so one was added
openjd-rs#361 — persist a resolved symtab supplied by argument not reachable from this repo — see below forwarding confirmed by reading the call site

openjd-rs#361 is an openjd-sessions change, and this repo has no sessions tests: test/ holds expr, model_v0 and model_v1 only, and the Python wrapper plus its suite live in openjd-sessions-for-python. What is confirmed here is that the binding reaches the fixed path — rust-bindings/src/sessions/session.rs:494 forwards the argument as session.enter_environment(&env, resolved.as_ref(), Some(&env_id), env_ref), which is exactly the resolved_symtab argument route the fix made equivalent to the field. The fix is internal to the crate, so it needs no binding change. End-to-end coverage of a wrap environment's Param.* reaching its RFC 0008 hooks belongs to the sibling repo and is not verified by this PR.

Changes beyond the version pins

FloatTaskParameter conversion. Float64::value() where the binding wants a number. FloatTaskParameter.range stays list[float] — it is the numeric introspection view, not the render path.

A FLOAT range element built from Python keeps its spelling (review finding). StepParameterSpace(taskParameterDefinitions={"F": {"type": "FLOAT", "range": ["1.50"]}}) rendered 1.5 where the template rendered 1.50; measured, 5 of 6 elements disagreed. The cause was re-emitting the element as a JSON number, which Float64 reads as a bare value. A Python str is now forwarded as a JSON string, reproducing the template's own <float> | <floatstring> distinction. Not made unconditional: a Python float must not acquire a spelling from str(), and both routes already agree on 1.5, 1000.0 and 0.5.

The adaptive indexing refusal is enforced, not incidental (review finding). __getitem__ resolved a negative index against self.len, the count __len__ withholds for an adaptive space, so it[-1] declined only because get declined downstream. It now rejects an adaptive space up front, with the pure-Python reference's message so "unknown count" is distinguishable from "past the end". The type stays IndexError rather than moving to v0's bare LookupError; IndexError is a LookupError, so callers catching either are served by both implementations, and changing the type would break except IndexError. The divergence is recorded in the spec.

Two chunking gaps close, so tests move. openjd-rs#355 resolved both divergences test_known_gaps.py recorded as strict xfails, which xfail_strict reports as failures. Following that file's own rule, both were promoted to test_step_param_space_iter.py:

  • A CONTIGUOUS chunked space now supports random access, observing chunks_task_count_override, with one past the end still an IndexError. Replaces test_a_contiguous_space_refuses_indexing_with_or_without_the_override, which asserted the refusal and named this swap as its counterpart.
  • chunks_parameter_name and chunks_default_task_count now report for any chunked space, not only an adaptive one, which is what v0 has always done.

Nothing here is a chunking regression — every assertion moved toward the v0 reference.

THIRD-PARTY-LICENSES.txt regenerated; the diff is exactly the three crate versions, no transitive dependency moved. That needed a portability fix in scripts/check_third_party_licenses.sh: sed -i 's/\r//' is GNU-only and killed the script on macOS before it wrote anything. sed now folds into the redirect that already builds the file, which also removes a temp file that was not registered with the trap (review finding).

Verification

  • Python: 5567 passed, 24 skipped, 3 xfailed, with the 94% coverage gate enforced. ruff, black --check, mypy clean.
  • Rust: cargo build --all-targets, cargo clippy --all-targets -- -D warnings, cargo fmt --check, cargo test, cargo test --doc all pass.
  • scripts/check_third_party_licenses.sh reports the file up to date, cargo-about 0.9.2, the version CI installs.
  • Mutation-checked, both new behaviours. Reverting the float-spelling branch to the numeric path fails 10 of the 16 new spelling tests; the 2 survivors are labelled in the test as not pinning the fix. Removing the adaptive guard fails test_an_adaptive_space_still_refuses_indexing and nothing else.
  • The two promoted chunking assertions are falsifiable by the dependency version alone: they were strict xfails passing at openjd-model 0.5.4, and CI on 007f212 reports them xpassing at 0.6.0.

Not verified locally: Windows and Linux. Everything above was run on macOS; CI covers the other two legs. One Windows leg failed earlier on test_pwsh_string_simple_roundtrip, a pre-existing PowerShell-startup subprocess.TimeoutExpired unrelated to this change.

Move the bindings crate onto the openjd-rs release published by
OpenJobDescription/openjd-rs#357:

  openjd-expr      0.5.0 -> 0.6.0  (breaking)
  openjd-model     0.5.4 -> 0.6.0  (breaking)
  openjd-sessions  0.5.4 -> 0.5.5

The breaking part of both minor bumps is openjd-rs#354, "Keep the decimal
places of a floatstring range element", the Rust counterpart of OpenJobDescription#345 on this
side. `TaskParameter::Float` now carries `Vec<Float64>` rather than `Vec<f64>`,
because a `<floatstring>` range element has to keep the scale it was written
with: '02.50' renders `2.50`, not `2.5` (Template Schemas §7.5).

That reaches Python through `TaskParameterValue`, which renders the preserved
spelling verbatim, so the per-task value a command line receives now matches
the pure-Python reference. `FloatTaskParameter.range` stays `list[float]` --
it is the numeric introspection view of the resolved definition -- so the
conversion takes `Float64::value()` there.

openjd-model 0.6.0 also carries openjd-rs#355 (two chunking parity gaps) and
openjd-rs#358 (the 512-character cap on a let binding identifier), and
openjd-sessions 0.5.5 carries openjd-rs#361 (persist a resolved symbol table
supplied by argument).

Verified: cargo build --all-targets, cargo clippy --all-targets -D warnings,
cargo test and cargo test --doc all pass. The Python suite is 5546 passed with
5 failures that are all gap markers this release closes; they are addressed in
the following commit.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
@leongdl
leongdl requested a review from a team as a code owner September 3, 2026 02:31
Comment thread rust-bindings/Cargo.toml
Comment thread rust-bindings/src/model/task_parameter.rs
openjd-rs#355 closed both divergences that
`test/openjd/model_v1/test_known_gaps.py` recorded as strict xfails, so with
`xfail_strict = true` they now fail as xpasses. That file's own rule is to
promote a resolved gap to its proper home rather than drop the marker in place,
so both move to `test_step_param_space_iter.py` beside the rest of
`TestChunksTaskCountOverride`.

  - A CONTIGUOUS chunked space supports random access. `it[0]`, `it[1]` and
    `it[-1]` answer, indexing observes `chunks_task_count_override`, and one
    past the end is still an IndexError. This replaces
    `test_a_contiguous_space_refuses_indexing_with_or_without_the_override`,
    which asserted the refusal and named this exact swap as its counterpart.
  - `chunks_parameter_name` and `chunks_default_task_count` report for any
    chunked space, not only an adaptive one, which is what v0 has always done.

Added `test_an_adaptive_space_still_refuses_indexing` as the negative control
and the remaining limitation: an adaptive space has no knowable count, so
`len()` raises ValueError and every index is out of range, while iteration
still yields. Measured, along with everything asserted above, against
openjd-model 0.6.0 before the assertions were written.

Both promoted assertions are falsifiable by the version alone: they were strict
xfails passing on mainline at openjd-model 0.5.4, so they failed there, and the
CI run on 007f212 reports them xpassing at 0.6.0.

`specs/python-model-interface.md` claimed both limitations and pointed at the
xfails by name; it now states the random access that works and the one adaptive
limitation that remains. The `Optional[str]`/`Optional[int]` signatures are
unchanged -- both getters still answer None for a space that is not chunked.

Verified: 5551 passed, 24 skipped, 3 xfailed with the 94% coverage gate
enforced; ruff, black and mypy clean.

`test_known_gaps.py` is now down to one test, which is a passing regression
test rather than a gap. Left where it is rather than widen this change.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
`scripts/check_third_party_licenses.sh` fails on a Cargo.lock change alone, and
the three crate bumps are exactly what the diff contains: openjd-expr 0.5.0 ->
0.6.0, openjd-model 0.5.4 -> 0.6.0, openjd-sessions 0.5.4 -> 0.5.5. No other
line moves, and no transitive dependency changed.

Regenerating it needed a portability fix first. `sed -i 's/\r//'` on the EOL
normalization line is GNU-only: BSD sed reads the next argument as the backup
suffix, so on macOS the script died with `sed: 1: "/var/folders/...": invalid
command code f` before writing anything. Rewriting through a temp file behaves
identically on both. The failure was not specific to this change -- the script
could not be run on macOS at all -- and CI regenerates with the same script, so
the committed file and the check stay in agreement.

Verified: `scripts/check_third_party_licenses.sh --update` then
`scripts/check_third_party_licenses.sh` reports the file up to date, with
cargo-about 0.9.2, the version CI installs.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
@leongdl
leongdl enabled auto-merge (squash) September 3, 2026 02:56
Comment thread test/openjd/model_v1/test_step_param_space_iter.py Outdated
Comment thread test/openjd/model_v1/test_step_param_space_iter.py
Comment thread specs/python-model-interface.md
Comment thread scripts/check_third_party_licenses.sh Outdated
`StepParameterSpace(taskParameterDefinitions={"F": {"type": "FLOAT", "range":
["1.50"]}})` rendered the task parameter value `1.5`, while the same element
decoded from a template rendered `1.50`. Review on OpenJobDescription#349 found it; measured, 5 of
6 elements disagreed between the two routes: '1.50'->1.5, '1e3'->1000.0,
'5.'->5.0, '.5'->0.5, '02.50'->2.5.

`task_param_def_from_dict` read every element through `v.str()` and re-emitted
the float variant as a JSON *number*. `Float64` reads a JSON number as a bare
value and a JSON string as a preserved spelling, so the number form threw the
decimal places away -- the thing openjd-rs#354 added `Float64` to carry.

The template makes this distinction with the `<float> | <floatstring>` union, so
the binding now makes it the same way: an element that arrives as a Python `str`
is forwarded as a JSON string, anything else keeps the numeric path. `v.str()`
collapsed the two, which is why the fix is a branch rather than a cast.

Deliberately not widened:

  - A Python `float` must not acquire a spelling from `str()`. Measured, both
    routes already agree on 1.5, 1000.0 and 0.5, and forwarding '1000.0' as
    text would have been a new divergence in the other direction.
  - An unparseable element still fails, in `Float64`'s deserializer rather than
    here, exactly as it did when this re-emitted a number.
  - `FloatTaskParameter.range` stays `list[float]`. It is the numeric
    introspection view, so '2.50' still reports 2.5 there while rendering 2.50.
    Two of them can compare equal by `.range` and render differently; the spec
    now says so rather than the API changing shape.
  - A redundant leading zero is still not stripped here. That is a `create_job`
    normalization (openjd-model `create_job/ranges.rs`), not part of reading a
    resolved value, and constructing the Rust `TaskParameter` directly behaves
    the same way -- `Float64`'s deserializer trims whitespace and unsigns zero
    but does not strip. So '02.50' given straight to the resolved type keeps its
    zero. Pinned by name.

`TestFloatRangeSpellingIsPreserved` covers it: 10 string spellings, 3 float
negative controls, agreement with the template path, the leading-zero
divergence, and the rejection path. Mutation-checked by flipping the new branch
back to the numeric path, which fails 10 of the 16. The two survivors are
labelled in the test as not pinning the fix: '2.5' renders the same either way,
and '  2.50  ' already took the string path because Rust's f64 parse rejects the
spaces.

Separately measured against openjd-rs#354 itself: all 26 input/expected pairs
from that PR's own assertions render identically through both this repo's
implementations, v0 and v1 -- including '-0.0'->0.0, '0e5', '-0.0E+2'->0.0E+2,
'1e-400', a 403-character 0.000...1, '5.' and '.5'.

Verified: 5567 passed, 24 skipped, 3 xfailed with the 94% coverage gate
enforced; cargo clippy --all-targets -D warnings, cargo fmt --check, cargo test,
ruff, black and mypy all clean.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
Four findings from review on OpenJobDescription#349, all against bfa28bc.

`__getitem__` now rejects an adaptive space explicitly. Review pointed out that
it resolved a negative index against `self.len` — the count `__len__` refuses to
report for an adaptive space, on the grounds that it is not knowable — and that
`it[-1]` therefore declined only because `get` happened to decline downstream.
Confirmed by reading it: `self.len` is captured unconditionally at construction
and `__getitem__` had no adaptive check. Since 0.6.0 just extended `get` to
contiguous chunked spaces, a later extension to adaptive would have made `it[-1]`
answer against a count `len()` still withholds. The guard makes the documented
behaviour the enforced one and lets the message name the reason.

The message is now the pure-Python reference's, verbatim. The type stays
`IndexError` rather than moving to v0's bare `LookupError`: `IndexError` is a
`LookupError` subclass, so a caller catching either type is served by both
implementations, while changing the type would break anyone catching
`IndexError`. Full type parity is a breaking change and is not smuggled into a
dependency bump. The spec now records the divergence, which review noted the
rewritten text had made read as though the area were at parity.

`test_indexing_observes_the_override` had a docstring the previous commit
falsified: it said random access needs a non-sequential space and that a
contiguous chunked space is always sequential, then pointed at "the test below"
— which is now the test proving both clauses wrong. Replaced with the reason the
NONCONTIGUOUS choice actually still holds, which review identified correctly: it
renders a single-task chunk as a bare `1` where CONTIGUOUS renders `1-1`, and
that is what the assertions depend on.

`scripts/check_third_party_licenses.sh` no longer creates a second temp file.
The `$generated.eol` sibling the previous commit introduced was not registered
with the `trap`, so it leaked if the script died between the redirect and the
`mv`. Folding `sed` into the redirect that already builds `$generated` drops the
file, the `mv` and the portability caveat at once. Review suggested `tr -d`;
kept `sed 's/\r//'` because `tr -d` also deletes a CR that is not a line ending,
and the point here is EOL normalization. The script still reports the file up to
date, so the output is byte-identical.

Verified: 5567 passed, 24 skipped, 3 xfailed with the 94% coverage gate;
cargo clippy --all-targets -D warnings, cargo fmt, ruff, black, mypy clean;
scripts/check_third_party_licenses.sh reports up to date. Removing the new guard
fails `test_an_adaptive_space_still_refuses_indexing` and nothing else, from a
green baseline of 18 in that class.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
AlexTranAmz
AlexTranAmz previously approved these changes Sep 3, 2026
openjd-model 0.6.0 carries openjd-rs#358, which enforces Template Schemas
§3.6.1's 512-character cap on a `let` binding's `<UserIdentifier>`; before it a
513-character name was accepted. The v0 side has covered this since OpenJobDescription#348, in
`test/openjd/model_v0/v2023_09/test_let_bindings.py`. The v1 path had nothing, so
the bump brought the enforcement with no test on this side to hold it.

Measured through `decode_job_template` on both implementations before writing the
assertions. 512 characters is accepted and 513 rejected, identically, with `EXPR`
alone and with `EXPR` plus `FEATURE_BUNDLE_1`.

The accept at 512 with `EXPR` alone is the case worth having. The cap is flat,
not §7.1's `<Identifier>` cap of 64 rising to 512 under `FEATURE_BUNDLE_1`, so a
fix built on the wrong constant would reject a template the spec permits. A
65-character control covers the same ground from the other side: over the §7.1
cap, under §3.6.1's, and accepted.

The two implementations' messages differ in wording, so the assertion is on the
error path and the phrase `exceeds 512 characters`, not on the whole string.

Verified: 5572 passed, 24 skipped, 3 xfailed with the 94% coverage gate; ruff,
black and mypy clean.

Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com>
@leongdl
leongdl merged commit c6f7805 into OpenJobDescription:mainline Sep 3, 2026
31 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 3, 2026
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.

4 participants