fix(model): implement PR #383 review follow-ups for resolved-value checks - #397
Conversation
7887218 to
9f66402
Compare
… resolved-value checks Two early-detection gaps flagged in the PR OpenJobDescription#383 review: - Single-valued allOf literal count (gate 1): the single-valued standard-attribute check fired only for all-literal lists. A literal element always contributes exactly one resolved element, so more than one literal violates the rule under every possible resolution — now gated on the literal count instead of all-literal, rejecting e.g. allOf: [linux, windows, '{{ Param.X }}'] at decode instead of at job creation. - Job-name control characters (gate 2): §1.1.1 constrains the resolved name, but create_job only re-applied the length and emptiness checks. An interpolated name resolving to a value with Cc characters passed both gates; create_job now rejects it alongside the emptiness check. Signed-off-by: Mark <399551+mwiebe@users.noreply.github.com>
9f66402 to
e364d4e
Compare
…sors Two small accessors for reasoning statically about resolution shape: - segment_count(): a format string with more than one segment always concatenates to a single string (Expression Language §1.3.2) — only a whole-field single-expression format string can resolve to null or to a list. is_literal() distinguishes the two single-segment cases. - literal_segments(): the literal text runs, which appear verbatim in every possible resolution; expression source text is excluded since it never appears in a resolved value. Signed-off-by: Mark <399551+mwiebe@users.noreply.github.com>
Review follow-ups:
- The single-valued allOf count now includes multi-segment format-string
elements alongside literals: a multi-segment element always
concatenates to exactly one string, so it contributes exactly one
resolved element — only a whole-field single-expression element can
null-skip or flatten. allOf: [linux, 'windows{{ Param.X }}'] on
attr.worker.os.family is now rejected at decode.
- The decode-time job-name control-character check now runs on the
name's literal segments instead of the whole raw text. Literal text
appears verbatim in every resolution, so a control character there is
still a certain violation even in an interpolated name — but
expression source text never appears in a resolved value, so a
control character inside an expression (e.g. a tab in a string
literal that resolution removes) no longer over-rejects. What an
expression resolves to is checked on the resolved value, statically
when fully static and at job creation otherwise. Comments and specs
now describe all three checks accurately.
Signed-off-by: Mark <399551+mwiebe@users.noreply.github.com>
What it does and what it fixesThree check sites, all following one principle from the #383 review: enforce a rule at the earliest checkpoint where the violation is certain, defer the rest to where the value is actually known. Site 1, Site 2, Site 3, The two new Data flow and call stackClaims verified rather than taken on trustThe The deferral is not a hole. The job-creation re-check exists at The relaxation opens no hole. Four routes carrying an expression-derived control character to a resolved name were probed (parameter default with
|
What was the problem/requirement? (What/Why)
An OpenJD job template goes through two checkpoints before it runs on a
worker: template validation (what
openjd checkdoes — the templatealone, with no parameter values) and job creation (when a user
submits the template with actual parameter values). A guiding principle
from the review of #383: when we can already tell at an earlier
checkpoint that a template can never be valid, we should fail there,
instead of letting the problem surface later.
That review found two places where we report a problem later than we
could:
Single-valued host requirements. A template can declare host
requirements like "this step must run on Linux". Two of these
attributes — the OS family and the CPU architecture — are
single-valued: a host has exactly one OS, so an
allOflist(conditions that must all hold) with more than one value can never
be satisfied. Template validation enforced this, but only when every
list entry was plain text. As soon as one entry was an expression
like
"{{ Param.X }}", the whole check was skipped until jobcreation — even for a list like
["linux", "windows", "{{ Param.X }}"],where the two plain-text entries already make the list impossible on
their own, no matter what the expression turns out to be.
Control characters in job names. The spec forbids control
characters (newlines, tabs, etc.) in the final job name. When the
name contains an expression — e.g.
"render-{{ Param.Suffix }}"—the final name is only known at job creation, once
Param.Suffixhas a value. But job creation only re-checked the name's length and
non-emptiness, not control characters. So submitting with
Suffix = "a\nb"produced a job whose name contains a newline, andnothing ever rejected it.
What was the solution? (How)
In template validation (
structure.rs), the single-valued rule nowcounts the plain-text entries instead of requiring the whole list to
be plain text. A plain-text entry always contributes exactly one
value to the final list (only expressions can drop out or expand), so
two or more plain-text entries violate the rule under every
possible outcome — it is safe to reject immediately. Lists with at
most one plain-text entry still defer to the existing job-creation
re-check. For all-plain-text lists the new condition is identical to
the old one, so no previously-valid template is affected.
In job creation (
create_job/mod.rs), the resolved job name is nowrejected if it contains control characters, right next to the
existing emptiness re-check, using the same error type
(
ModelError::DecodeValidation).What is the impact of this change?
allOfis impossible regardless ofexpression values now fail at
openjd checkinstead of at jobsubmission. The error message and path are unchanged — it just
arrives earlier.
now fail with
Validation error: Job name must not contain control characters. Previously they silently succeeded; per the spec such aname was never valid.
How was this change tested?
standard (failure tests assert the full path + message):
test_attr_os_family_all_of_two_literals_plus_expr— mixed listwith two literals rejected at decode.
test_attr_os_family_all_of_one_literal_plus_expr_deferred— oneliteral + one expression still passes decode (deferred to job
creation, which may resolve it to a single value).
test_create_job_resolved_name_with_control_chars_rejected—interpolated name with a newline in the parameter value rejected at
job creation (exact error asserted).
test_create_job_resolved_name_without_control_chars_ok— passingcontrol.
cargo test --workspace: passes. (Two pre-existingopenjd-sessionsWindows cross-user test failures on the devmachine are environmental — domain-joined host changes the
LogonUserWerror code — and reproduce identically onorigin/main.)cargo clippy --all-features --all-targets --workspace -- -D warningsand
cargo fmtclean.Was this change documented?
specs/model/validation.md: documented the literal-count rule forsingle-valued attributes in the pass-6 deferral discussion, and the
job-name control-character re-check in the resolved-value
consequences section.
specs/model/job-creation.md: documented the full set of resolvedjob-name checks (length, non-empty, no control characters).
public-api.mdis untouched; codecomments added at both check sites.
Is this a breaking change?
No.
Does this change impact security?
No.
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.