Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.11.9 (2026-09-04)
Comment thread
leongdl marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hand-written prose will be destroyed the next time a release is cut.

.semantic_release/CHANGELOG.md.j2:1 opens with:

{% for version, release in context.history.released.items() %}

The template iterates every released version, not just the new one — so it renders the entire CHANGELOG.md from scratch on each run. Combined with the config in pyproject.toml:210-211:

[tool.semantic_release.changelog]
template_dir = ".semantic_release"

there is no mode = "update" and no insertion_flag, so python-semantic-release stays in its default init mode: full regeneration, overwriting the file. The next release (0.11.10) will re-derive the 0.11.9 section from commit.commit.summary and restore exactly the six near-duplicate maintainer-facing bullets this commit set out to remove, discarding both carefully written entries.

So the improvement here is real but has a lifetime of one release. Worth considering one of:

  1. Set mode = "update" with an insertion_flag, so PSR only prepends new sections and leaves prior hand-edited ones alone. This makes manual curation durable and is the smallest config change.
  2. Curate at the commit-message level instead — squash each PR to a single fix: commit whose summary is the user-facing sentence, so the generator produces the desired output natively. The six bullets exist because b136693 retained six fix: subject lines through the squash.
  3. Split the files — keep a hand-maintained CHANGELOG.md and point PSR at a generated CHANGELOG.generated.md.

Without one of these, this PR is worth treating as a one-off patch rather than a fix to the release notes. Option 1 or 2 also resolves the root cause noted in the commit message ("The generator emits one line per commit").



### Bug Fixes
* A `LIST[BOOL]` job parameter now holds real booleans in the created job, so a list item accepts every spelling Template Schemas §2.15 allows for a scalar `BOOL`: the case-insensitive strings `true`/`yes`/`on`/`1` and `false`/`no`/`off`/`0`, and the numbers `0` and `1`. A mixed list such as `["yes", 0, true]` previously failed to validate with `List contains incompatible types`, and a uniform list such as `["yes", "no"]` was accepted as a list of strings and then failed only once a boolean operator touched an element. `["yes", "no"]` now interpolates as `true`/`false`. A decoded template is unchanged and still round-trips the spellings its author wrote. (#352)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two accuracy problems in this entry.

1. "failed to validate" attributes the mixed-list failure to the wrong stage.

A mixed list such as ["yes", 0, true] previously failed to validate with List contains incompatible types

That message does not come from validation. List contains incompatible types is raised by ExprValue([...]) construction in the expression engine — see test/openjd/expr/test_lists.py:822-841, which pins it as a TypeError from list construction, and test_unresolved_eval.py:351. The #352 commit message agrees, saying heterogeneous lists "failed expression evaluation with 'List contains incompatible types'".

The distinction matters because it changes when a template author finds out. "Failed to validate" implies decode/create_job rejected the template up front. In reality it got through creation and blew up later, at interpolation — the same late-failure mode the entry correctly describes for the uniform case two clauses later. As written the entry claims the two cases failed at different stages, when they both failed at the same one.

Suggest: ...previously failed at expression evaluation with ..., which also makes the parallel with the uniform case explicit.

2. The accepted-spellings list omits floats.

and the numbers 0 and 1

_coerce_bool_value (src/openjd/model/_bool_coercion.py:22-25) also accepts floats:

if isinstance(value, float):
    if value in (0.0, 1.0):
        return bool(value)
    raise ValueError("BOOL value as a float must be 0.0 or 1.0.")

Since the sentence is framed as "every spelling ... allows", an author reading it would reasonably conclude [0.0, 1.0] is rejected when it is accepted. Suggest the numbers 0/1 and 0.0/1.0.

* A step's template-scope `let` (Template Schemas §3.6) is resolved once, at job creation, using POSIX path format so a created job does not depend on the host that created it. It is no longer merged into the script's own `let`. The merge left the bindings to be evaluated a second time in the host's format when the session ran, and that second value overwrote the first: on Windows, `startswith(path("/foo/bar"), "/foo")` went from true at job creation to false in the session. This is a breaking change for a consumer that calls `StepTemplate.resolve_syntax_sugar()`: the step's resolved bindings now travel in `create_job_with_symbol_tables(...).step_symbol_tables[step_name]` and must be forwarded to the session that runs the step. A consumer that does not forward them loses step-level bindings silently rather than failing. `openjd-cli` forwards them as of OpenJobDescription/openjd-cli#237. (#341)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The failure mode described here is the opposite of what the code documents.

This entry states:

A consumer that does not forward them loses step-level bindings silently rather than failing.

But create_job()'s own docstring (src/openjd/model/_create_job.py:577-583) says the failure is loud, not silent:

So for a template that declares a step-level let and references it from the step's script, this Job alone is not enough to run the step — the session has no binding for the name and the action fails with Undefined variable.

These cannot both be right, and the difference matters a lot to a reader deciding how urgently to migrate. "Silently loses bindings" reads as wrong results with no error — the worst case, requiring an audit of every job already created. "Fails with Undefined variable" reads as a hard error you cannot miss — unpleasant but self-announcing, and safe to discover at runtime.

Given the docstring is specific about the exact error string, the docstring is likely the accurate one and this line is the error. Suggest matching it, e.g.:

A consumer that does not forward them will see the step's action fail at session time with Undefined variable for the binding name.

If instead there really is a silent-loss path (for example when the session happens to define the same name in another scope), it would be worth naming that condition here rather than describing silence as the general case, since it contradicts the primary public API's documented behaviour.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The breaking change is scoped to the wrong audience — it understates who has to migrate.

This is a breaking change for a consumer that calls StepTemplate.resolve_syntax_sugar()

resolve_syntax_sugar() is a niche entry point. Grepping the package, the only in-tree caller is the job-creation hook itself (v2023_09/_model.py:3576); it is not re-exported as a standalone function, so a consumer reaching it directly is rare. A reader who does not call it will read this line and conclude they are unaffected.

But the project's own docs say the affected API is create_job() — the primary public entry point, used in the README's main example. README.md:213-221:

If any of the job's steps declares a template-scope let that the step's script references, then this Job is not sufficient to run the step [...] Use create_job_with_symbol_tables instead and forward the step's entry

and create_job()'s docstring at _create_job.py:585-587 says the same. The commit footer on b136693 also frames it as create_job(), not resolve_syntax_sugar():

create_job() no longer returns a Job carrying evaluated step-level let values.

So the population that must migrate is "anyone who calls create_job() and then runs the resulting job, where a step declares a template-scope let its script references" — which includes openjd-cli and, presumably, the Deadline Cloud worker agent. Naming resolve_syntax_sugar() instead points readers away from the check they actually need to perform.

Suggest leading with create_job() and the precondition, e.g.:

Breaking for a caller that runs a job created with create_job() when a step declares a template-scope let referenced from its script: switch to create_job_with_symbol_tables() and forward step_symbol_tables[step_name] to the session. A caller that only inspects the Job (StepDependencyGraph, StepParameterSpaceIterator, hostRequirements) is unaffected.

The "only inspects the Job is unaffected" carve-out is worth keeping from the docstring — it is what lets most readers stop reading.



## 0.11.8 (2026-09-03)


Expand Down
Loading