From 48e4a41cc23c7009b65b658cff072e109a72e4cc Mon Sep 17 00:00:00 2001 From: David Leong <116610336+leongdl@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:19:18 -0700 Subject: [PATCH 1/6] test: Add post-resolution conformance fixtures for chunks and host requirements Three fields that resolve at job creation had no fixture asserting the resolved value: chunks.defaultTaskCount, chunks.targetRuntimeSeconds, and hostRequirements.amounts[].min. All three are annotated @fmtstring without [host], so a format string in the template must be resolved before the value is used. No fixture in the suite wrote any of them as a format string. This is not hypothetical. A service reading chunks.defaultTaskCount out of the pre-resolution template rejected valid task-chunking jobs in production, because it saw the literal "{{Param.ChunkSize}}" where the resolved job carries an int. Added to the live suite, passing both reference implementations: - TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml asserts the resolved value through the chunk boundaries it produces. - TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml resolves to 0, where section 3.4.1.5 fixes the scheduler's behavior, so the boundaries are deterministic. - FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number .invalid.test.yaml covers a min that resolves to a non-numeric string. Added under proposed/, believed spec-correct and currently failing the Rust implementation. Both are the same defect shape: a bound that cannot be checked at decode because the value is a format string, deferred correctly, then never re-checked after resolution. - TASK_CHUNKING/jobs/proposed/ covers a defaultTaskCount that resolves to 0 against the documented minimum of 1. - base/jobs/proposed/ covers an anyOf element that resolves to a value violating the section 3.3.2.2 pattern. Each fixture was mutation-checked: change the resolved value, leave the expectations, confirm it fails. The targetRuntimeSeconds fixture was also mutated to a non-zero resolved value, which engages adaptive chunking and changes the boundaries, so it pins that field specifically rather than only defaultTaskCount. Not covered, and not writable today: the positive case asserting a resolved amounts[].min, .max or anyOf value is correct. No openjd CLI surfaces resolved host requirements, and the runner asserts only on stdout and task status, so those values have no observable effect on a single-host run. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com> --- ...g-resolves-to-non-number.invalid.test.yaml | 30 ++++++++++++ ...default-task-count-format-string.test.yaml | 42 +++++++++++++++++ .../TASK_CHUNKING/jobs/proposed/README.md | 30 ++++++++++++ ...-string-resolves-to-zero.invalid.test.yaml | 33 +++++++++++++ ...et-runtime-seconds-format-string.test.yaml | 46 +++++++++++++++++++ ...esolves-to-invalid-value.invalid.test.yaml | 34 ++++++++++++++ .../2023-09/base/jobs/proposed/README.md | 34 ++++++++++++++ 7 files changed, 249 insertions(+) create mode 100644 conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml create mode 100644 conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml create mode 100644 conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md create mode 100644 conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml create mode 100644 conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/proposed/README.md diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml new file mode 100644 index 00000000..494c1922 --- /dev/null +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml @@ -0,0 +1,30 @@ +# hostRequirements.amounts[].min may be a format string under FEATURE_BUNDLE_1 (Template +# Schemas L958). The numeric constraint cannot be checked at decode, so it must be checked +# after resolution at job creation. Here the parameter resolves to a non-numeric string and +# the run must fail. +# +# This is the negative half of the amount-resolution gap. The positive half, asserting that +# min and max resolve to the CORRECT numbers, is not writable today: no openjd CLI surfaces +# resolved host requirements, in summary --output json or anywhere else. +template: + specificationVersion: jobtemplate-2023-09 + extensions: + - FEATURE_BUNDLE_1 + name: TestJob + parameterDefinitions: + - name: CpuMin + type: STRING + default: "abc" + steps: + - name: Step1 + hostRequirements: + amounts: + - name: amount.worker.vcpu + min: "{{Param.CpuMin}}" + script: + actions: + onRun: + command: python + args: + - -c + - print(r'RAN') diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml new file mode 100644 index 00000000..98cd276f --- /dev/null +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml @@ -0,0 +1,42 @@ +# chunks.defaultTaskCount is annotated @fmtstring (Template Schemas L1263), so it may be +# a parameter reference and must be resolved at job creation. This case pins the resolved +# value by its observable effect: ChunkSize=3 over the range 1-8 can only produce the +# boundaries 1-3, 4-6, 7-8. An implementation that reads the pre-resolution template value +# fails here rather than at some later stage. +# +# Status-only by design: a 3-chunk run cannot self-assert, because each task sees only its +# own output (see the conformance-tests README). +template: + specificationVersion: jobtemplate-2023-09 + extensions: + - TASK_CHUNKING + name: ChunkTest + parameterDefinitions: + - name: ChunkSize + type: INT + default: 3 + steps: + - name: Step1 + parameterSpace: + taskParameterDefinitions: + - name: Frame + type: CHUNK[INT] + range: 1-8 + chunks: + defaultTaskCount: "{{Param.ChunkSize}}" + rangeConstraint: CONTIGUOUS + script: + actions: + onRun: + command: python + args: + - -c + - print(r'CHUNK:{{Task.Param.Frame}}') +expected: + output: + - 'CHUNK:1-3' + - 'CHUNK:4-6' + - 'CHUNK:7-8' + forbidden: + - 'CHUNK:1-8' + - 'CHUNK:9' diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md new file mode 100644 index 00000000..5ba4483e --- /dev/null +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md @@ -0,0 +1,30 @@ +# Proposed TASK_CHUNKING job fixtures: post-resolution bound checks + +These fixtures are believed spec-correct and currently fail at least one reference +implementation. They live here rather than in `../` because the runner discovers test +files with a non-recursive glob, so `proposed/` is excluded and the suite stays green. +Promotion is `git mv` up one directory with no edit to the fixture. + +| Fixture | Construct | Observed | +|---|---|---| +| `default-task-count-format-string-resolves-to-zero.invalid.test.yaml` | `chunks.defaultTaskCount: "{{Param.ChunkSize}}"` where the parameter default is `0`, against the documented minimum of 1 (Template Schemas L1276) | openjd-model Python rejects at job creation. The Rust CLI reports `Template ... passes validation checks` and then runs, logging `Frame(CHUNK[INT]) = 1-1`, so the resolved `0` is treated as `1` | + +## Classification + +Implementation fix. + +The minimum-of-1 bound cannot be checked at decode when the value is a format string, +because the value is not yet known, and `TASK_CHUNKING/job_templates/default-task-count-zero.invalid.yaml` +already covers the literal `0` case at decode. The bound must therefore be re-checked +after resolution at job creation. The Python path does this; the Rust path defers the +check and never resumes it. + +Cross-check: in `openjd-model/src/template/validate_v2023_09/task_chunking.rs` the +`>= 1` comparison is guarded on the chunk count being a literal integer, which is +correct for decode-time validation. No equivalent check appears on the job-creation +path, so a format-string value reaches the scheduler unvalidated. + +A green twin ships in `../default-task-count-format-string.test.yaml`, which asserts +that a format-string `defaultTaskCount` resolves and produces the expected chunk +boundaries. That case passes both implementations, so the divergence recorded here is +specific to the bound check rather than to resolution itself. diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml new file mode 100644 index 00000000..81cee56e --- /dev/null +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml @@ -0,0 +1,33 @@ +# chunks.defaultTaskCount has a minimum of 1. When the value is written as a format string +# the bound cannot be checked at decode, because the value is not yet known, so it must be +# checked after resolution at job creation. This case supplies a parameter that resolves to +# 0 and must therefore fail. +# +# .invalid.test.yaml rather than .invalid.yaml: the template is statically valid and the +# error only fires once the expression is evaluated. +template: + specificationVersion: jobtemplate-2023-09 + extensions: + - TASK_CHUNKING + name: ChunkTest + parameterDefinitions: + - name: ChunkSize + type: INT + default: 0 + steps: + - name: Step1 + parameterSpace: + taskParameterDefinitions: + - name: Frame + type: CHUNK[INT] + range: 1-8 + chunks: + defaultTaskCount: "{{Param.ChunkSize}}" + rangeConstraint: CONTIGUOUS + script: + actions: + onRun: + command: python + args: + - -c + - print(r'CHUNK:{{Task.Param.Frame}}') diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml new file mode 100644 index 00000000..ab946995 --- /dev/null +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml @@ -0,0 +1,46 @@ +# chunks.targetRuntimeSeconds is annotated @fmtstring (Template Schemas L1264), so it may +# be a parameter reference and must be resolved at job creation. +# +# The resolved value is 0, and section 3.4.1.5 says that when targetRuntimeSeconds is 0 a +# scheduler should ignore it and use defaultTaskCount for every chunk. That makes the +# assertion deterministic: the boundaries must be exactly those defaultTaskCount implies, +# with no adaptive resizing. A non-zero resolved value would let a conforming scheduler +# adjust the chunk size, so this case pins the one resolved value whose effect is fixed. +# +# An implementation that reads the pre-resolution template value sees the literal +# "{{Param.TargetRuntime}}" instead of 0 here. +template: + specificationVersion: jobtemplate-2023-09 + extensions: + - TASK_CHUNKING + name: ChunkTest + parameterDefinitions: + - name: TargetRuntime + type: INT + default: 0 + steps: + - name: Step1 + parameterSpace: + taskParameterDefinitions: + - name: Frame + type: CHUNK[INT] + range: 1-6 + chunks: + defaultTaskCount: 2 + targetRuntimeSeconds: "{{Param.TargetRuntime}}" + rangeConstraint: CONTIGUOUS + script: + actions: + onRun: + command: python + args: + - -c + - print(r'CHUNK:{{Task.Param.Frame}}') +expected: + output: + - 'CHUNK:1-2' + - 'CHUNK:3-4' + - 'CHUNK:5-6' + forbidden: + - 'CHUNK:1-6' + - 'CHUNK:7' diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml new file mode 100644 index 00000000..ef07e9e1 --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml @@ -0,0 +1,34 @@ +# hostRequirements.attributes[].anyOf is annotated @fmtstring in base 2023-09 (Template +# Schemas L1014), with no extension gate. Its element type +# (section 3.3.2.2) constrains the value to the identifier-like pattern, and that constraint +# cannot be checked at decode when the element is a format string. It must therefore be +# checked after resolution at job creation. +# +# Here the parameter resolves to "not valid!", which contains a space and an exclamation +# mark, so the run must fail. +# +# NOTE ON CURRENT STATE: this case is expected to FAIL against the Rust CLI, which accepts +# the resolved value and runs the job. The Python CLI rejects it with "Value not valid! is +# not a valid attribute capability value." That divergence is the defect this case exists to +# expose; it is not a broken test. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + parameterDefinitions: + - name: Software + type: STRING + default: "not valid!" + steps: + - name: Step1 + hostRequirements: + attributes: + - name: attr.custom.software + anyOf: + - '{{Param.Software}}' + script: + actions: + onRun: + command: python + args: + - -c + - print(r'RAN') diff --git a/conformance-tests/2023-09/base/jobs/proposed/README.md b/conformance-tests/2023-09/base/jobs/proposed/README.md new file mode 100644 index 00000000..bb606eb7 --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/proposed/README.md @@ -0,0 +1,34 @@ +# Proposed base job fixtures: post-resolution constraint checks + +These fixtures are believed spec-correct and currently fail at least one reference +implementation. They live here rather than in `../` because the runner discovers test +files with a non-recursive glob, so `proposed/` is excluded and the suite stays green. +Promotion is `git mv` up one directory with no edit to the fixture. + +| Fixture | Construct | Observed | +|---|---|---| +| `3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml` | `hostRequirements.attributes[].anyOf` element written as `"{{Param.Software}}"`, resolving to `not valid!`, against the `` pattern in section 3.3.2.2 | openjd-model Python rejects at job creation with `Value not valid! is not a valid attribute capability value.` The Rust CLI accepts the resolved value and runs the job to completion | + +## Classification + +Implementation fix. + +`anyOf` is annotated `@fmtstring` in base 2023-09 at Template Schemas L1014, with no +extension gate, and its element type is constrained to the identifier-like pattern in +section 3.3.2.2. That pattern cannot be checked at decode when the element is a format +string, so it must be re-checked after resolution at job creation. The Python path does +this; the Rust path defers the check and never resumes it. + +The value used here, `not valid!`, violates the pattern twice, on the space and on the +exclamation mark, so no reading of the pattern admits it. + +This is the same defect shape as +`../../../TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml`: +validation correctly deferred for a format string, never resumed. Reviewers may prefer +to treat the two as one issue. + +Not covered by any fixture, here or in `../`: the positive case asserting that a +resolved `anyOf` value is *correct*. No `openjd` CLI surfaces resolved host +requirements, in `summary --output json` or anywhere else, and the runner asserts only +on stdout and task status, so the resolved value has no observable effect on a +single-host run. The negative case above is the reachable half. From e52315b751e7a1e0673790267abc691339b7b613 Mon Sep 17 00:00:00 2001 From: David Leong <116610336+leongdl@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:11:51 -0700 Subject: [PATCH 2/6] test: Extend post-resolution fixtures to the remaining coercible fields The first commit covered the fields implicated in one production defect. This covers the rest of the surface: every 2023-09 field where the raw template and the resolved job can hold different values. Two classes were missing entirely. The and unions. JobIntParameterDefinition and JobFloatParameterDefinition declare default, allowedValues, minValue and maxValue as ` | ` in base 2023-09 with no extension gate, and IntRangeList and FloatRangeList do the same for their elements. The suite had template-level accept coverage and no job-level coverage of any string-form definition field, so nothing asserted what a string form resolves to. Across 2,417 IntRangeList elements and 1,044 FloatRangeList elements in the suite, not one used the string form. Fields whose resolved value a CLI does not print. cancelation .notifyPeriodInSeconds written as a format string was covered only by a fixture asserting a constant, because neither `openjd run` nor `openjd summary` surfaces the field. WrappedAction.Cancelation.NotifyPeriodInSeconds from RFC 0008 closes it: the reflection variable carries the effective value after resolution, so a wrap script reads back what the job actually got. Added to the live suite, passing both reference implementations: - WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved - base/jobs/1.1.1--resolved-job-name-value, asserting the resolved name field at base level through the implementation's own report of the job it ran. Existing base fixtures assert a parallel substitution into a task argument instead. - base/jobs/1.1--path-default-joined-with-template-dir - base/jobs/2.3--int-param-intstring-default-resolves - base/jobs/1.1--int-intstring-bounds-satisfied and its .invalid pair - base/jobs/1.1--int-intstring-allowedvalues-violation.invalid - FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number .invalid, the max counterpart to the min case in the previous commit Strengthened one existing fixture. 1.1--validation-valid-path-default-within -template-dir asserted the substring /output against a default of ./output, and /output is a substring of ./output, so an implementation that skipped the template-directory join passed it. Verified by running it with the task hardcoded to print the un-joined value: it passed. Two forbidden entries close that, and the same mutant now fails. Added under proposed/, believed spec-correct and currently failing the Rust implementation: - FEATURE_BUNDLE_1 amounts[].max resolving to 0, against . The only assertion in the suite that distinguishes max semantics from min. - base attributes[].allOf resolving to an invalid , the allOf counterpart to the anyOf case in the previous commit. - base IntRangeList and FloatRangeList string-form elements. These carry a spec question rather than a clear defect: and are defined only as base-10 string representations, with nothing said about normalization, and the two implementations disagree in opposite directions on different fields. EXPR/jobs/expr1.3.4--float-passthrough already pins the verbatim reading for a parameter default, so a ruling is needed before either it or the float fixture here can be called conformant. Every fixture was run against both CLIs and mutation-checked. Nine mutants, nine caught. The notifyPeriodInSeconds and job-name fixtures were additionally mutated by removing their parameter overrides, so a fixture satisfied by a template default rather than a resolved submitted value would fail. Not covered, and not writable: positive assertions on resolved host-requirement values. No CLI surfaces them, and WRAP_ACTIONS reflection is scoped to fields, so the eight reflection variables cannot reach host requirements. Also not written: capability name as a format string, which needs a spec decision first, since the spec declares "A string" while one implementation resolves it and the other rejects it. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com> --- ...g-resolves-to-non-number.invalid.test.yaml | 30 +++++++++ ...-string-resolves-to-zero.invalid.test.yaml | 34 ++++++++++ .../FEATURE_BUNDLE_1/jobs/proposed/README.md | 37 +++++++++++ ...notify-period-fmtstring-resolved.test.yaml | 66 +++++++++++++++++++ ...-allowedvalues-violation.invalid.test.yaml | 29 ++++++++ ...--int-intstring-bounds-satisfied.test.yaml | 35 ++++++++++ ...tstring-bounds-violation.invalid.test.yaml | 28 ++++++++ ...default-joined-with-template-dir.test.yaml | 35 ++++++++++ ...path-default-within-template-dir.test.yaml | 14 ++++ .../1.1.1--resolved-job-name-value.test.yaml | 39 +++++++++++ ...param-intstring-default-resolves.test.yaml | 32 +++++++++ ...esolves-to-invalid-value.invalid.test.yaml | 34 ++++++++++ ...ge-intstring-elements-normalized.test.yaml | 35 ++++++++++ ...-floatstring-elements-normalized.test.yaml | 39 +++++++++++ .../2023-09/base/jobs/proposed/README.md | 31 ++++++++- 15 files changed, 516 insertions(+), 2 deletions(-) create mode 100644 conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml create mode 100644 conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml create mode 100644 conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md create mode 100644 conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml create mode 100644 conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml new file mode 100644 index 00000000..f8786f22 --- /dev/null +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml @@ -0,0 +1,30 @@ +# hostRequirements.amounts[].max may be a format string under FEATURE_BUNDLE_1 (Template +# Schemas L960), the same as `min` at L958. The numeric constraint cannot be checked at +# decode, so it must be checked after resolution at job creation. Here the parameter resolves +# to a non-numeric string and the run must fail. +# +# Companion to 3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml. +# `min` and `max` carry different types in the spec, against +# , so they are covered separately rather than assumed symmetric. +template: + specificationVersion: jobtemplate-2023-09 + extensions: + - FEATURE_BUNDLE_1 + name: TestJob + parameterDefinitions: + - name: CpuMax + type: STRING + default: "abc" + steps: + - name: Step1 + hostRequirements: + amounts: + - name: amount.worker.vcpu + max: "{{Param.CpuMax}}" + script: + actions: + onRun: + command: python + args: + - -c + - print(r'RAN') diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml new file mode 100644 index 00000000..c7957bee --- /dev/null +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml @@ -0,0 +1,34 @@ +# hostRequirements.amounts[].max is typed (Template Schemas L959-960), and +# section 3.3.1 confirms it separately from min, which is . So 0 is legal for +# min and illegal for max. +# +# Under FEATURE_BUNDLE_1 max may be a format string, and the positivity constraint cannot be +# checked at decode because the value is not yet known. It must be re-checked after resolution +# at job creation. Here the parameter resolves to 0. +# +# This is the only assertion in the suite that distinguishes max semantics from min semantics +# for a resolved value. A literal max: 0 is already rejected at decode by +# base/job_templates/3.3.1--amount-max-zero.invalid.yaml, so only the resolved path is +# unguarded. +template: + specificationVersion: jobtemplate-2023-09 + extensions: + - FEATURE_BUNDLE_1 + name: TestJob + parameterDefinitions: + - name: CpuMax + type: INT + default: 0 + steps: + - name: Step1 + hostRequirements: + amounts: + - name: amount.worker.vcpu + max: "{{Param.CpuMax}}" + script: + actions: + onRun: + command: python + args: + - -c + - print(r'RAN') diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md new file mode 100644 index 00000000..da26c94c --- /dev/null +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md @@ -0,0 +1,37 @@ +# Proposed FEATURE_BUNDLE_1 job fixtures: post-resolution bound checks + +These fixtures are believed spec-correct and currently fail at least one reference +implementation. They live here rather than in `../` because the runner discovers test +files with a non-recursive glob, so `proposed/` is excluded and the suite stays green. +Promotion is `git mv` up one directory with no edit to the fixture. + +| Fixture | Construct | Observed | +|---|---|---| +| `3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml` | `amounts[].max: "{{Param.CpuMax}}"` where the parameter default is `0`, against the `` type at L959 | openjd-model Python rejects at job creation. The Rust CLI accepts the resolved `0` and runs the job to completion | + +## Classification + +Implementation fix. + +`max` is `` while `min` is ``, so `0` is legal for one and +not the other. A literal `max: 0` is already rejected at decode by +`../../job_templates/3.3.1--amount-max-zero.invalid.yaml`. Under FEATURE_BUNDLE_1 the value +may be a format string, in which case the positivity constraint cannot be checked at decode +and must be re-checked after resolution. The Python path does this; the Rust path defers the +check and never resumes it. + +This is the same defect shape as the two fixtures in +`../../../TASK_CHUNKING/jobs/proposed/` and `../../../base/jobs/proposed/`: validation +correctly deferred for a format string, never resumed. Reviewers may prefer to treat all of +them as one issue. + +The green twin, +`../3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml`, covers a +`max` that resolves to a non-numeric string. Both implementations reject that, which is why +it sits in the live suite. The divergence recorded here is specific to the positivity bound +rather than to numeric parsing. + +Not covered by any fixture, here or in `../`: the positive case asserting a resolved `max` +is the correct number. No `openjd` CLI surfaces resolved host requirements, and the runner +asserts only on stdout and task status, so a host requirement has no observable effect on a +single-host run. The negative cases are the reachable half. diff --git a/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml b/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml new file mode 100644 index 00000000..83ec3992 --- /dev/null +++ b/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml @@ -0,0 +1,66 @@ +# cancelation.notifyPeriodInSeconds is annotated @fmtstring under FEATURE_BUNDLE_1 +# (Template Schemas L1776), so it may be a parameter reference. Neither `openjd run` nor +# `openjd summary` surfaces the field directly, so the resolved value would normally be +# unobservable and only assertable at level 2. +# +# WrappedAction.Cancelation.NotifyPeriodInSeconds (RFC 0008) closes that: the reflection +# variable carries the EFFECTIVE value, after schema defaults and after format-string +# resolution, so a wrap script can read back what the job actually got. +# +# The parameter's own default is 120 and the submitted value is 47. Both are forbidden as +# outputs alongside the unresolved literal, so the fixture fails if the implementation +# resolves nothing, resolves to the default, or reflects a stale value. +# +# Companion to WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-injected.test.yaml, which +# covers the same reflection path for a literal notifyPeriodInSeconds. +template: + specificationVersion: jobtemplate-2023-09 + extensions: + - FEATURE_BUNDLE_1 + name: WrapNotifyPeriodFmtstringResolved + parameterDefinitions: + - name: NotifyPeriod + type: INT + minValue: 1 + maxValue: 600 + default: 120 + steps: + - name: Step1 + script: + actions: + onRun: + command: echo + args: ["placeholder"] + cancelation: + mode: NOTIFY_THEN_TERMINATE + notifyPeriodInSeconds: "{{Param.NotifyPeriod}}" +parameters: + NotifyPeriod: 47 +environments: +- specificationVersion: environment-2023-09 + extensions: + - WRAP_ACTIONS + - EXPR + environment: + name: WrapEnv + script: + actions: + onWrapEnvEnter: + command: bash + args: ["-c", "{{ repr_sh(WrappedAction.Command) }} {{ repr_sh(WrappedAction.Args) }}"] + onWrapTaskRun: + command: bash + # `<...>` sentinel wrap so an empty or null value renders unambiguously as `NP=<>`. + args: ["-c", "echo \"NP=<{{WrappedAction.Cancelation.NotifyPeriodInSeconds}}>\""] + onWrapEnvExit: + command: bash + args: ["-c", "{{ repr_sh(WrappedAction.Command) }} {{ repr_sh(WrappedAction.Args) }}"] +runOn: +- posix +expected: + output: + - "NP=<47>" + forbidden: + - "NP=<{{" + - "NP=<>" + - "NP=<120>" diff --git a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml new file mode 100644 index 00000000..c28ee2bc --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml @@ -0,0 +1,29 @@ +# .allowedValues is `[ | , ... ]` in base +# 2023-09 (Template Schemas L307). Membership must be decided numerically when the list holds +# the string form and the submitted value arrives as a number. +# +# The list is ['1', '2', '4'] as strings, and the submitted value is the number 3, which is +# absent. The job must be rejected at job creation. The paired positive case, submitting a +# member, is covered at template level by +# base/job_templates/2.3--allowed-values-intstring.yaml. +# +# '04' is used for the third element so an implementation comparing string forms rather than +# parsed integers would also fail to match a submitted 4, which the mutation check exercises. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + parameterDefinitions: + - name: Count + type: INT + allowedValues: ['1', '2', '04'] + steps: + - name: Step1 + script: + actions: + onRun: + command: python + args: + - -c + - print(r'COUNT:{{Param.Count}}') +parameters: + Count: 3 diff --git a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml new file mode 100644 index 00000000..b6f0145d --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml @@ -0,0 +1,35 @@ +# .minValue and .maxValue are ` | ` in base +# 2023-09 (Template Schemas L308-309). When the bound is written as a string and the submitted +# value arrives as a number, the comparison must be numeric. A naive implementation that +# compares the string bound against the value lexically accepts and rejects the wrong things. +# +# Bounds here are the strings '10' and '100', and the submitted value is the number 50, which +# satisfies both. The paired negative is +# 1.1--int-intstring-bounds-violation.invalid.test.yaml. +# +# '010' is used for the lower bound so a lexical comparison against '50' would order them +# differently from the numeric comparison, making the two paths distinguishable. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + parameterDefinitions: + - name: Count + type: INT + minValue: '010' + maxValue: '100' + steps: + - name: Step1 + script: + actions: + onRun: + command: python + args: + - -c + - print(r'COUNT:{{Param.Count}}') +parameters: + Count: 50 +expected: + output: + - 'COUNT:50' + forbidden: + - '{{Param.' diff --git a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml new file mode 100644 index 00000000..a4801073 --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml @@ -0,0 +1,28 @@ +# Negative pair for 1.1--int-intstring-bounds-satisfied.test.yaml. +# +# minValue and maxValue are written as the strings '010' and '100', and the submitted value is +# the number 200. The bound must be interpreted numerically and the value rejected at job +# creation. An implementation that fails to parse a string bound, or that compares it +# lexically, admits this job. +# +# .invalid.test.yaml rather than .invalid.yaml: the template is statically valid and the error +# only fires once the submitted value is bound. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + parameterDefinitions: + - name: Count + type: INT + minValue: '010' + maxValue: '100' + steps: + - name: Step1 + script: + actions: + onRun: + command: python + args: + - -c + - print(r'COUNT:{{Param.Count}}') +parameters: + Count: 200 diff --git a/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml new file mode 100644 index 00000000..f774bda0 --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml @@ -0,0 +1,35 @@ +# Template Schemas L2014 states that at job creation "PATH parameter defaults are joined +# with the job template directory". That join is a value transform, so the raw template and +# the resolved job differ, and a consumer reading the raw default gets a relative path. +# +# The existing fixture 1.1--validation-valid-path-default-within-template-dir.test.yaml +# cannot catch a missing join: its default is `./output` and it asserts the substring +# `/output`, which the un-joined `./output` also contains. An implementation that skipped the +# join entirely passes it. +# +# This fixture removes that escape. The default carries no leading `./`, so `RESULT:inputs/` +# appears only if the value was not joined, and it is forbidden. Only the path suffix is +# asserted, because the absolute prefix legitimately differs between implementations: one +# resolves symlinks and reports /private/var/..., the other reports /var/... +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + parameterDefinitions: + - name: InputDir + type: PATH + default: inputs/scene + steps: + - name: Step1 + script: + actions: + onRun: + command: python + args: ["-c", "print(r'RESULT:{{Param.InputDir}}')"] +runOn: +- posix +expected: + output: + - "/inputs/scene" + forbidden: + - "RESULT:inputs/" + - "RESULT:./" diff --git a/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml index 374dbd1d..fddce4dc 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml @@ -1,3 +1,14 @@ +# Strengthened: the expected substrings alone could not distinguish a joined path from an +# un-joined one. The default is `./output`, and the POSIX assertion `/output` is a substring of +# the literal `./output`, so an implementation that skipped the template-directory join +# entirely still passed. Verified by running this fixture with the task hardcoded to print +# `OUTPUT:./output`, which passed. +# +# The forbidden entries close that. `OUTPUT:./` and `OUTPUT:output` can only appear if the +# value reaching the task was not joined with the job template directory. +# +# See also 1.1--path-default-joined-with-template-dir.test.yaml, which asserts the same +# behaviour with a default carrying no `./` prefix. template: specificationVersion: jobtemplate-2023-09 name: TestJob @@ -21,3 +32,6 @@ expected: - /output output_windows: - "\\output" + forbidden: + - 'OUTPUT:./' + - 'OUTPUT:output' diff --git a/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml b/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml new file mode 100644 index 00000000..4eec320e --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml @@ -0,0 +1,39 @@ +# The job template `name` is annotated @fmtstring (Template Schemas L38) and resolves at +# job creation. Existing base fixtures assert a PARALLEL substitution of the same parameter +# into a task argument, which proves the parameter resolved somewhere but says nothing about +# the name field. EXPR/jobs/7.3.1--job-name-step-name.test.yaml asserts the resolved name +# properly, but needs the EXPR extension to read {{ Job.Name }}. +# +# This asserts the name field itself at base level, through the implementation's own report +# of the job it ran. Both reference CLIs emit the resolved name twice, once as +# `Running job ''` and once as `Job: ` in the results block, so the assertion is +# written with anyOf and either satisfies it. +# +# The parameters block overrides both defaults. The default-derived name is forbidden, so a +# fixture that resolved against defaults instead of submitted values fails. +template: + specificationVersion: jobtemplate-2023-09 + name: Job-{{Param.Project}}-{{Param.Version}} + parameterDefinitions: + - name: Project + type: STRING + default: TemplateDefault + - name: Version + type: STRING + default: '9.9' + steps: + - name: Step1 + script: + actions: + onRun: + command: echo + args: ["ran"] +parameters: + Project: StudioX + Version: '1.0' +expected: + output: + - anyOf: ["Job: Job-StudioX-1.0", "Running job 'Job-StudioX-1.0'"] + forbidden: + - "Job-TemplateDefault-9.9" + - "{{Param." diff --git a/conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml b/conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml new file mode 100644 index 00000000..8ea685fb --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml @@ -0,0 +1,32 @@ +# .default is ` | ` in base 2023-09 (Template +# Schemas L306) with no extension gate. The string form is a distinct coercion surface: a +# consumer reading the raw template sees "007" where the resolved job carries 7. +# +# The suite has template-level coverage of the string form +# (base/job_templates/2.3--int-param-with-default-string.yaml) and no job-level coverage of +# any string-form definition field, so nothing asserts what the string resolves TO. +# +# `007` is chosen over `7` so the assertion distinguishes a parsed integer from a passed-through +# literal. `COUNT:007` is forbidden. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + parameterDefinitions: + - name: Count + type: INT + default: '007' + steps: + - name: Step1 + script: + actions: + onRun: + command: python + args: + - -c + - print(r'COUNT:{{Param.Count}}') +expected: + output: + - 'COUNT:7' + forbidden: + - 'COUNT:007' + - '{{Param.' diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml new file mode 100644 index 00000000..636479c0 --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml @@ -0,0 +1,34 @@ +# hostRequirements.attributes[].allOf is annotated @fmtstring in base 2023-09 (Template +# Schemas L1015), the same as anyOf at L1014, with no extension gate. Its element type +# is constrained to the identifier-like pattern in section 3.3.2.2, +# and that pattern cannot be checked at decode when the element is a format string, so it must +# be re-checked after resolution at job creation. +# +# Here the parameter resolves to "not valid!", violating the pattern on both the space and the +# exclamation mark. +# +# Companion to 3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml in +# this directory. anyOf and allOf are separate spec fields with separate validators, so they +# are covered separately rather than assumed symmetric, but the observed divergence is the same +# and reviewers may prefer to treat both as one issue. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + parameterDefinitions: + - name: Feature + type: STRING + default: "not valid!" + steps: + - name: Step1 + hostRequirements: + attributes: + - name: attr.custom.features + allOf: + - '{{Param.Feature}}' + script: + actions: + onRun: + command: python + args: + - -c + - print(r'RAN') diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml new file mode 100644 index 00000000..c4db82fe --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml @@ -0,0 +1,35 @@ +# elements are ` | ` in base 2023-09 (Template Schemas +# L1110), with no extension gate. The suite has 2,417 IntRangeList elements and not one uses +# the string form, so nothing asserts what a string element resolves to. +# +# is defined as "a string whose value is the string representation of an integer +# value in base-10", so '02' and '003' represent the integers 2 and 3, and the task parameter +# should carry those integers rather than the source text. +# +# This is the value that reaches a task command line, so a wrong answer here means a renderer +# is invoked with --frame 02 instead of --frame 2. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + steps: + - name: Step1 + parameterSpace: + taskParameterDefinitions: + - name: Frame + type: INT + range: ['1', '02', '003'] + script: + actions: + onRun: + command: python + args: + - -c + - print(r'FRAME:{{Task.Param.Frame}}') +expected: + output: + - 'FRAME:1' + - 'FRAME:2' + - 'FRAME:3' + forbidden: + - 'FRAME:02' + - 'FRAME:003' diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml new file mode 100644 index 00000000..113bcfd9 --- /dev/null +++ b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml @@ -0,0 +1,39 @@ +# elements are ` | ` in base 2023-09 (Template Schemas +# L1184), with no extension gate. The suite has 1,044 FloatRangeList elements and not one uses +# the string form. +# +# Same question as the IntRangeList case in this directory: is a string +# representation of a base-10 number, so '02.50' represents 2.5 and the task parameter should +# carry that number rather than the source text. +# +# Note for reviewers: the direction of normalization is not consistent across the two +# implementations today, and it is not consistent across FIELDS either. For range-list elements +# one implementation preserves the literal and the other normalizes; for a FLOAT parameter +# DEFAULT the two swap sides. An already-landed fixture, +# EXPR/jobs/expr1.3.4--float-passthrough.test.yaml, pins the verbatim behaviour for a parameter +# default by asserting PARAM:3.500 from default "3.500". So a spec ruling is needed on whether +# normalizes, and if it does, the landed fixture and this one cannot both be +# right. This fixture states the normalizing reading; it should not merge until that is settled. +template: + specificationVersion: jobtemplate-2023-09 + name: TestJob + steps: + - name: Step1 + parameterSpace: + taskParameterDefinitions: + - name: Weight + type: FLOAT + range: ['1.5', '02.50'] + script: + actions: + onRun: + command: python + args: + - -c + - print(r'W:{{Task.Param.Weight}}') +expected: + output: + - 'W:1.5' + - 'W:2.5' + forbidden: + - 'W:02.50' diff --git a/conformance-tests/2023-09/base/jobs/proposed/README.md b/conformance-tests/2023-09/base/jobs/proposed/README.md index bb606eb7..38568ef2 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/README.md +++ b/conformance-tests/2023-09/base/jobs/proposed/README.md @@ -7,9 +7,12 @@ Promotion is `git mv` up one directory with no edit to the fixture. | Fixture | Construct | Observed | |---|---|---| -| `3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml` | `hostRequirements.attributes[].anyOf` element written as `"{{Param.Software}}"`, resolving to `not valid!`, against the `` pattern in section 3.3.2.2 | openjd-model Python rejects at job creation with `Value not valid! is not a valid attribute capability value.` The Rust CLI accepts the resolved value and runs the job to completion | +| `3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml` | `attributes[].anyOf` element written as `"{{Param.Software}}"`, resolving to `not valid!`, against the `` pattern in section 3.3.2.2 | Python rejects at job creation with `Value not valid! is not a valid attribute capability value.` The Rust CLI accepts the resolved value and runs the job | +| `3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml` | Same, for `allOf` at L1015 | Same. Swept across four invalid resolved values, a space, an exclamation mark, 120 characters and a leading digit: Python rejects all four, Rust runs all four | +| `3.4.1.1--int-range-intstring-elements-normalized.test.yaml` | `` elements in the `` string form, `['1', '02', '003']` | Rust substitutes `1`, `2`, `3`. Python substitutes `1`, `02`, `003`, so a task command line receives `--frame 02` | +| `3.4.1.2--float-range-floatstring-elements-normalized.test.yaml` | `` elements in the `` string form, `['1.5', '02.50']` | Rust substitutes `1.5`, `2.5`. Python substitutes `1.5`, `02.50` | -## Classification +## Classification: the two capability-value fixtures Implementation fix. @@ -32,3 +35,27 @@ resolved `anyOf` value is *correct*. No `openjd` CLI surfaces resolved host requirements, in `summary --output json` or anywhere else, and the runner asserts only on stdout and task status, so the resolved value has no observable effect on a single-host run. The negative case above is the reachable half. + + +## Classification: the two range-element fixtures + +Spec decision needed, then an implementation fix on one side or the other. + +`` and `` are defined only as "a string whose value is the string +representation of an integer / floating point value in base-10". Nothing is said about +whether the string form normalizes, so `['02']` yielding `2` and yielding `02` are both +defensible readings of the text as written. + +Two facts make this worth a ruling rather than a shrug. First, the value reaches a task +command line, so the two readings produce different renderer invocations for the same +template. Second, the implementations do not merely disagree, they disagree in opposite +directions on different fields: for range-list elements Python preserves the literal and +Rust normalizes, while for a FLOAT parameter `default` Python normalizes and Rust preserves. + +There is also a conflict with a landed fixture. `EXPR/jobs/expr1.3.4--float-passthrough.test.yaml` +asserts `PARAM:3.500` from `default: "3.500"`, which pins the verbatim reading for a parameter +default. If the spec rules that `` normalizes, that fixture and +`3.4.1.2--...` here cannot both be correct. + +These two fixtures state the normalizing reading. They should not be promoted out of +`proposed/` until the spec says which reading is conformant. From 28db39b0fe25e5f54510142e2ff1b22899906e04 Mon Sep 17 00:00:00 2001 From: David Leong <116610336+leongdl@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:14:37 -0700 Subject: [PATCH 3/6] test: Compose the intstring and format-string coercions in one fixture Every fixture whose subject is a field annotated @fmtstring wrote its parameter value as a native integer, so it exercised the format-string layer and left the layer untouched. Those are two coercions in series, and the seam between them was untested: an parameter default must first parse to a number, and only then can the format string on the field resolve to it. Writing the parameter value in the string form covers both layers with the same assertion, so this is strictly stronger at no cost rather than extra coverage. - chunks.defaultTaskCount now resolves from default '03'. The boundaries 1-3/4-6/7-8 hold only if the string parsed to 3 and the format string then resolved to it. - chunks.targetRuntimeSeconds resolves from default '00'. - The two proposed bound-check fixtures use '0', so the bound must be enforced after both coercions rather than after one. - The wrapped action's notifyPeriodInSeconds takes a submitted '047', chaining three coercions: the string parses to 47, the format string on the action resolves to it, and the reflection variable reports the effective value. Leading zeros are deliberate. A bare '3' would be indistinguishable from a passed-through literal, whereas '03' resolving to 3 can only be a parsed integer, and the fixtures already forbid the un-normalized text. Re-verified on both reference CLIs, unchanged results. Four new mutants, four caught: '03' to '04' fails, '03' to 'abc' fails at parse rather than passing quietly, '00' to '03' engages adaptive chunking and shifts the boundaries, and '047' to '048' fails through the reflection variable. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com> --- ...ax-format-string-resolves-to-zero.invalid.test.yaml | 3 ++- .../jobs/default-task-count-format-string.test.yaml | 10 +++++++++- ...nt-format-string-resolves-to-zero.invalid.test.yaml | 3 ++- .../target-runtime-seconds-format-string.test.yaml | 4 +++- ...celation-notify-period-fmtstring-resolved.test.yaml | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml index c7957bee..a4e113d1 100644 --- a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml @@ -18,7 +18,8 @@ template: parameterDefinitions: - name: CpuMax type: INT - default: 0 + # form, so the positivity check must survive both coercions. + default: '0' steps: - name: Step1 hostRequirements: diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml index 98cd276f..1927ace9 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml @@ -4,6 +4,10 @@ # boundaries 1-3, 4-6, 7-8. An implementation that reads the pre-resolution template value # fails here rather than at some later stage. # +# The parameter default is written in the string form deliberately. A native +# `default: 3` would exercise only the format-string layer; the string form chains both, and +# the assertion is identical, so it is strictly stronger at no cost. +# # Status-only by design: a 3-chunk run cannot self-assert, because each task sees only its # own output (see the conformance-tests README). template: @@ -14,7 +18,11 @@ template: parameterDefinitions: - name: ChunkSize type: INT - default: 3 + # form, with a leading zero. INT parameter defaults are + # ` | ` in base 2023-09 (L306), so this composes the two + # coercions the fixture cares about: the string default must parse to 3, and the + # format string below must then resolve to it. The boundaries assert both at once. + default: '03' steps: - name: Step1 parameterSpace: diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml index 81cee56e..5f63609b 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml @@ -13,7 +13,8 @@ template: parameterDefinitions: - name: ChunkSize type: INT - default: 0 + # form, so the bound check must survive both coercions. + default: '0' steps: - name: Step1 parameterSpace: diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml index ab946995..cbf08c1b 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml @@ -17,7 +17,9 @@ template: parameterDefinitions: - name: TargetRuntime type: INT - default: 0 + # form: the string must parse to 0 before the format string can resolve to + # it, so this fixture covers both coercion layers rather than only the second. + default: '00' steps: - name: Step1 parameterSpace: diff --git a/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml b/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml index 83ec3992..90aefb30 100644 --- a/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml +++ b/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml @@ -35,7 +35,10 @@ template: mode: NOTIFY_THEN_TERMINATE notifyPeriodInSeconds: "{{Param.NotifyPeriod}}" parameters: - NotifyPeriod: 47 + # form on the SUBMITTED value, not just the definition default. Three + # coercions chain here: the string parses to 47, the format string on the action resolves + # to it, and the reflection variable reports the effective value. + NotifyPeriod: '047' environments: - specificationVersion: environment-2023-09 extensions: From 8224dd0bea745a8290730a5559a45f06df3ea647 Mon Sep 17 00:00:00 2001 From: David Leong <116610336+leongdl@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:37:39 -0700 Subject: [PATCH 4/6] docs: Cite the exact spec line, with a link, in every fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each fixture asserted a spec requirement without giving a reviewer a way to check it. References were bare line numbers such as "Template Schemas L1263", which are not clickable, are not the convention the suite already uses, and appear in no pre-existing fixture. Three fixtures cited nothing at all. Every fixture and every proposed/README.md now carries a Spec references block listing each claim it depends on, with the section number and a line-anchored link: §3.4.1.5 L1263 chunks.defaultTaskCount is ` | ` and @fmtstring https://github.com/.../2023-09-Template-Schemas.md?plain=1#L1263 Three details behind the format: `?plain=1` is required. GitHub renders markdown, so a bare `#L1263` on a .md file does not anchor; only the plain view has line anchors. Both the section and the line are given. The section is the durable reference if the spec is re-flowed, and the line is where the claim was actually verified. Prose now uses §X.Y throughout, matching the 32 existing fixtures that already cite sections that way. Citations are verified, not asserted. A checker walks every §X.Y L### pair in the diff, confirms the cited line really sits under the cited heading by parsing the spec's headings, and requires a keyword supporting the claim within three lines of the target. 71 citations, 0 problems. It also confirms every `?plain=1#L` link has a matching section reference in the same file, so a link can never drift away from the claim it supports. One reference was repointed rather than reformatted. The wrap-actions fixture cited L1776, which sits in the generic §5.3 `` block; the precise citation for a NOTIFY_THEN_TERMINATE notifyPeriodInSeconds is §5.3.2 L1835. It now cites that, plus §4.3.1 L1630 for the reflection variable and RFC 0008 for the wrap hook variable table. All 12 live fixtures re-run on both reference CLIs after the rewrite, unchanged results. Comment-only change; no assertion, template or expectation was touched. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com> --- ...g-resolves-to-non-number.invalid.test.yaml | 20 ++++++++----- ...g-resolves-to-non-number.invalid.test.yaml | 19 ++++++++----- ...-string-resolves-to-zero.invalid.test.yaml | 14 ++++++++-- .../FEATURE_BUNDLE_1/jobs/proposed/README.md | 11 +++++++- ...default-task-count-format-string.test.yaml | 28 ++++++++++++------- .../TASK_CHUNKING/jobs/proposed/README.md | 11 +++++++- ...-string-resolves-to-zero.invalid.test.yaml | 20 +++++++++---- ...et-runtime-seconds-format-string.test.yaml | 22 ++++++++++----- ...notify-period-fmtstring-resolved.test.yaml | 26 +++++++++++------ ...-allowedvalues-violation.invalid.test.yaml | 11 +++++--- ...--int-intstring-bounds-satisfied.test.yaml | 15 ++++++---- ...tstring-bounds-violation.invalid.test.yaml | 10 +++++-- ...default-joined-with-template-dir.test.yaml | 18 +++++++----- ...path-default-within-template-dir.test.yaml | 12 +++++--- .../1.1.1--resolved-job-name-value.test.yaml | 24 ++++++++++------ ...param-intstring-default-resolves.test.yaml | 14 ++++++---- ...esolves-to-invalid-value.invalid.test.yaml | 26 +++++++++++------ ...esolves-to-invalid-value.invalid.test.yaml | 27 ++++++++++-------- ...ge-intstring-elements-normalized.test.yaml | 14 +++++++--- ...-floatstring-elements-normalized.test.yaml | 10 +++++-- .../2023-09/base/jobs/proposed/README.md | 16 +++++++++-- 21 files changed, 254 insertions(+), 114 deletions(-) diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml index f8786f22..428a54f2 100644 --- a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-max-format-string-resolves-to-non-number.invalid.test.yaml @@ -1,11 +1,17 @@ -# hostRequirements.amounts[].max may be a format string under FEATURE_BUNDLE_1 (Template -# Schemas L960), the same as `min` at L958. The numeric constraint cannot be checked at -# decode, so it must be checked after resolution at job creation. Here the parameter resolves -# to a non-numeric string and the run must fail. +# hostRequirements.amounts[].max may be a format string under FEATURE_BUNDLE_1 (§3.3.1 L960), +# the same as `min` at §3.3.1 L958. The numeric constraint cannot be checked at decode, so it +# must be checked after resolution at job creation. Here the parameter resolves to a non-numeric +# string and the run must fail. # -# Companion to 3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml. -# `min` and `max` carry different types in the spec, against -# , so they are covered separately rather than assumed symmetric. +# Companion to 3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml. `min` +# and `max` carry different types in the spec, against , so +# they are covered separately rather than assumed symmetric. +# +# Spec references, Template Schemas 2023-09: +# §3.3.1 L960 amounts[].max may be `` under FEATURE_BUNDLE_1, @fmtstring +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L960 +# §3.3.1 L958 the min counterpart, covered separately because the types differ +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L958 template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml index 494c1922..a847f930 100644 --- a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml @@ -1,11 +1,16 @@ -# hostRequirements.amounts[].min may be a format string under FEATURE_BUNDLE_1 (Template -# Schemas L958). The numeric constraint cannot be checked at decode, so it must be checked -# after resolution at job creation. Here the parameter resolves to a non-numeric string and -# the run must fail. +# hostRequirements.amounts[].min may be a format string under FEATURE_BUNDLE_1 (§3.3.1 L958). +# The numeric constraint cannot be checked at decode, so it must be checked after resolution at +# job creation. Here the parameter resolves to a non-numeric string and the run must fail. # -# This is the negative half of the amount-resolution gap. The positive half, asserting that -# min and max resolve to the CORRECT numbers, is not writable today: no openjd CLI surfaces -# resolved host requirements, in summary --output json or anywhere else. +# This is the negative half of the amount-resolution gap. The positive half, asserting that min +# and max resolve to the CORRECT numbers, is not writable today: no openjd CLI surfaces resolved +# host requirements, in summary --output json or anywhere else. +# +# Spec references, Template Schemas 2023-09: +# §3.3.1 L958 amounts[].min may be `` under FEATURE_BUNDLE_1, @fmtstring +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L958 +# §7.4 L2014 format strings not annotated @fmtstring[host] resolve at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml index a4e113d1..35fa2fb8 100644 --- a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml @@ -1,6 +1,6 @@ -# hostRequirements.amounts[].max is typed (Template Schemas L959-960), and -# section 3.3.1 confirms it separately from min, which is . So 0 is legal for -# min and illegal for max. +# hostRequirements.amounts[].max is typed (§3.3.1 L959-960), and §3.3.1 confirms +# it separately from min, which is . So 0 is legal for min and illegal for +# max. # # Under FEATURE_BUNDLE_1 max may be a format string, and the positivity constraint cannot be # checked at decode because the value is not yet known. It must be re-checked after resolution @@ -10,6 +10,14 @@ # for a resolved value. A literal max: 0 is already rejected at decode by # base/job_templates/3.3.1--amount-max-zero.invalid.yaml, so only the resolved path is # unguarded. +# +# Spec references, Template Schemas 2023-09: +# §3.3.1 L959 amounts[].max is ``, so 0 is illegal +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L959 +# §3.3.1 L957 amounts[].min is ``, so 0 IS legal there +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L957 +# §3.3.1 L960 max may be a format string under FEATURE_BUNDLE_1, so the bound defers past decode +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L960 template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md index da26c94c..faf99bab 100644 --- a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/proposed/README.md @@ -7,7 +7,7 @@ Promotion is `git mv` up one directory with no edit to the fixture. | Fixture | Construct | Observed | |---|---|---| -| `3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml` | `amounts[].max: "{{Param.CpuMax}}"` where the parameter default is `0`, against the `` type at L959 | openjd-model Python rejects at job creation. The Rust CLI accepts the resolved `0` and runs the job to completion | +| `3.3.1--amount-max-format-string-resolves-to-zero.invalid.test.yaml` | `amounts[].max: "{{Param.CpuMax}}"` where the parameter default is `0`, against the `` type at §3.3.1 L959 | openjd-model Python rejects at job creation. The Rust CLI accepts the resolved `0` and runs the job to completion | ## Classification @@ -35,3 +35,12 @@ Not covered by any fixture, here or in `../`: the positive case asserting a reso is the correct number. No `openjd` CLI surfaces resolved host requirements, and the runner asserts only on stdout and task status, so a host requirement has no observable effect on a single-host run. The negative cases are the reachable half. + +## Spec references, Template Schemas 2023-09 + +- [§3.3.1 L959](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L959) amounts[].max is ``, so 0 is illegal +- [§3.3.1 L957](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L957) amounts[].min is ``, so 0 IS legal there +- [§3.3.1 L960](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L960) max may be a format string under FEATURE_BUNDLE_1, so the bound defers past decode + +Line numbers are a locator for where each claim was verified. The section numbers are the +durable reference if the spec is re-flowed. diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml index 1927ace9..231d29a7 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml @@ -1,15 +1,23 @@ -# chunks.defaultTaskCount is annotated @fmtstring (Template Schemas L1263), so it may be -# a parameter reference and must be resolved at job creation. This case pins the resolved -# value by its observable effect: ChunkSize=3 over the range 1-8 can only produce the -# boundaries 1-3, 4-6, 7-8. An implementation that reads the pre-resolution template value -# fails here rather than at some later stage. +# chunks.defaultTaskCount is annotated @fmtstring (§3.4.1.5 L1263), so it may be a parameter +# reference and must be resolved at job creation. This case pins the resolved value by its +# observable effect: ChunkSize=3 over the range 1-8 can only produce the boundaries 1-3, 4-6, +# 7-8. An implementation that reads the pre-resolution template value fails here rather than at +# some later stage. # # The parameter default is written in the string form deliberately. A native -# `default: 3` would exercise only the format-string layer; the string form chains both, and -# the assertion is identical, so it is strictly stronger at no cost. +# `default: 3` would exercise only the format-string layer; the string form chains both, and the +# assertion is identical, so it is strictly stronger at no cost. # -# Status-only by design: a 3-chunk run cannot self-assert, because each task sees only its -# own output (see the conformance-tests README). +# Status-only by design: a 3-chunk run cannot self-assert, because each task sees only its own +# output (see the conformance-tests README). +# +# Spec references, Template Schemas 2023-09: +# §3.4.1.5 L1263 chunks.defaultTaskCount is ` | ` and @fmtstring +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1263 +# §2.3 L306 INT parameter default is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 +# §7.4 L2014 format strings not annotated @fmtstring[host] resolve at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 template: specificationVersion: jobtemplate-2023-09 extensions: @@ -19,7 +27,7 @@ template: - name: ChunkSize type: INT # form, with a leading zero. INT parameter defaults are - # ` | ` in base 2023-09 (L306), so this composes the two + # ` | ` in base 2023-09 (§2.3 L306), so this composes the two # coercions the fixture cares about: the string default must parse to 3, and the # format string below must then resolve to it. The boundaries assert both at once. default: '03' diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md index 5ba4483e..2705fd08 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md @@ -7,7 +7,7 @@ Promotion is `git mv` up one directory with no edit to the fixture. | Fixture | Construct | Observed | |---|---|---| -| `default-task-count-format-string-resolves-to-zero.invalid.test.yaml` | `chunks.defaultTaskCount: "{{Param.ChunkSize}}"` where the parameter default is `0`, against the documented minimum of 1 (Template Schemas L1276) | openjd-model Python rejects at job creation. The Rust CLI reports `Template ... passes validation checks` and then runs, logging `Frame(CHUNK[INT]) = 1-1`, so the resolved `0` is treated as `1` | +| `default-task-count-format-string-resolves-to-zero.invalid.test.yaml` | `chunks.defaultTaskCount: "{{Param.ChunkSize}}"` where the parameter default is `0`, against the documented minimum of 1 (§3.4.1.5 L1276) | openjd-model Python rejects at job creation. The Rust CLI reports `Template ... passes validation checks` and then runs, logging `Frame(CHUNK[INT]) = 1-1`, so the resolved `0` is treated as `1` | ## Classification @@ -28,3 +28,12 @@ A green twin ships in `../default-task-count-format-string.test.yaml`, which ass that a format-string `defaultTaskCount` resolves and produces the expected chunk boundaries. That case passes both implementations, so the divergence recorded here is specific to the bound check rather than to resolution itself. + +## Spec references, Template Schemas 2023-09 + +- [§3.4.1.5 L1263](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1263) chunks.defaultTaskCount is @fmtstring, so the bound defers past decode +- [§3.4.1.5 L1276](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1276) defaultTaskCount minimum value: 1, the bound not re-checked after resolution +- [§7.4 L2014](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014) format strings not annotated @fmtstring[host] resolve at job creation + +Line numbers are a locator for where each claim was verified. The section numbers are the +durable reference if the spec is re-flowed. diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml index 5f63609b..6f1eeb3a 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/default-task-count-format-string-resolves-to-zero.invalid.test.yaml @@ -1,10 +1,18 @@ -# chunks.defaultTaskCount has a minimum of 1. When the value is written as a format string -# the bound cannot be checked at decode, because the value is not yet known, so it must be -# checked after resolution at job creation. This case supplies a parameter that resolves to -# 0 and must therefore fail. +# chunks.defaultTaskCount has a minimum of 1. When the value is written as a format string the +# bound cannot be checked at decode, because the value is not yet known, so it must be checked +# after resolution at job creation. This case supplies a parameter that resolves to 0 and must +# therefore fail. # -# .invalid.test.yaml rather than .invalid.yaml: the template is statically valid and the -# error only fires once the expression is evaluated. +# .invalid.test.yaml rather than .invalid.yaml: the template is statically valid and the error +# only fires once the expression is evaluated. +# +# Spec references, Template Schemas 2023-09: +# §3.4.1.5 L1263 chunks.defaultTaskCount is @fmtstring, so the bound cannot be checked at decode +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1263 +# §3.4.1.5 L1276 defaultTaskCount minimum value: 1 +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1276 +# §2.3 L306 INT parameter default is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml index cbf08c1b..a9a86fa3 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/target-runtime-seconds-format-string.test.yaml @@ -1,14 +1,22 @@ -# chunks.targetRuntimeSeconds is annotated @fmtstring (Template Schemas L1264), so it may -# be a parameter reference and must be resolved at job creation. +# chunks.targetRuntimeSeconds is annotated @fmtstring (§3.4.1.5 L1264), so it may be a parameter +# reference and must be resolved at job creation. # -# The resolved value is 0, and section 3.4.1.5 says that when targetRuntimeSeconds is 0 a -# scheduler should ignore it and use defaultTaskCount for every chunk. That makes the -# assertion deterministic: the boundaries must be exactly those defaultTaskCount implies, -# with no adaptive resizing. A non-zero resolved value would let a conforming scheduler -# adjust the chunk size, so this case pins the one resolved value whose effect is fixed. +# The resolved value is 0, and §3.4.1.5 says that when targetRuntimeSeconds is 0 a scheduler +# should ignore it and use defaultTaskCount for every chunk. That makes the assertion +# deterministic: the boundaries must be exactly those defaultTaskCount implies, with no adaptive +# resizing. A non-zero resolved value would let a conforming scheduler adjust the chunk size, so +# this case pins the one resolved value whose effect is fixed. # # An implementation that reads the pre-resolution template value sees the literal # "{{Param.TargetRuntime}}" instead of 0 here. +# +# Spec references, Template Schemas 2023-09: +# §3.4.1.5 L1264 chunks.targetRuntimeSeconds is @fmtstring +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1264 +# §3.4.1.5 L1280 when the value is 0 a scheduler should ignore it and use defaultTaskCount +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1280 +# §2.3 L306 INT parameter default is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml b/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml index 90aefb30..1a559db0 100644 --- a/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml +++ b/conformance-tests/2023-09/WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-fmtstring-resolved.test.yaml @@ -1,18 +1,28 @@ -# cancelation.notifyPeriodInSeconds is annotated @fmtstring under FEATURE_BUNDLE_1 -# (Template Schemas L1776), so it may be a parameter reference. Neither `openjd run` nor -# `openjd summary` surfaces the field directly, so the resolved value would normally be -# unobservable and only assertable at level 2. +# cancelation.notifyPeriodInSeconds is annotated @fmtstring under FEATURE_BUNDLE_1 (§5.3.2 +# L1835), so it may be a parameter reference. Neither `openjd run` nor `openjd summary` surfaces +# the field directly, so the resolved value would normally be unobservable and only assertable +# at level 2. # # WrappedAction.Cancelation.NotifyPeriodInSeconds (RFC 0008) closes that: the reflection # variable carries the EFFECTIVE value, after schema defaults and after format-string # resolution, so a wrap script can read back what the job actually got. # # The parameter's own default is 120 and the submitted value is 47. Both are forbidden as -# outputs alongside the unresolved literal, so the fixture fails if the implementation -# resolves nothing, resolves to the default, or reflects a stale value. +# outputs alongside the unresolved literal, so the fixture fails if the implementation resolves +# nothing, resolves to the default, or reflects a stale value. # -# Companion to WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-injected.test.yaml, which -# covers the same reflection path for a literal notifyPeriodInSeconds. +# Companion to WRAP_ACTIONS/jobs/wrap-cancelation-notify-period-injected.test.yaml, which covers +# the same reflection path for a literal notifyPeriodInSeconds. +# +# Spec references, Template Schemas 2023-09: +# §5.3.2 L1835 notifyPeriodInSeconds may be `` under FEATURE_BUNDLE_1, @fmtstring +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1835 +# §4.3.1 L1630 WrappedAction.Cancelation.NotifyPeriodInSeconds reflection variable +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1630 +# §2.3 L306 INT parameter value is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 +# RFC 0008 wrap hook template variables +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/rfcs/0008-environment-wrap-actions.md template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml index c28ee2bc..16be9707 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-allowedvalues-violation.invalid.test.yaml @@ -1,14 +1,17 @@ # .allowedValues is `[ | , ... ]` in base -# 2023-09 (Template Schemas L307). Membership must be decided numerically when the list holds -# the string form and the submitted value arrives as a number. +# 2023-09 (§2.3 L307). Membership must be decided numerically when the list holds the string +# form and the submitted value arrives as a number. # # The list is ['1', '2', '4'] as strings, and the submitted value is the number 3, which is # absent. The job must be rejected at job creation. The paired positive case, submitting a -# member, is covered at template level by -# base/job_templates/2.3--allowed-values-intstring.yaml. +# member, is covered at template level by base/job_templates/2.3--allowed-values-intstring.yaml. # # '04' is used for the third element so an implementation comparing string forms rather than # parsed integers would also fail to match a submitted 4, which the mutation check exercises. +# +# Spec references, Template Schemas 2023-09: +# §2.3 L307 allowedValues is `[ | , ... ]` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L307 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml index b6f0145d..2750f064 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-satisfied.test.yaml @@ -1,14 +1,19 @@ # .minValue and .maxValue are ` | ` in base -# 2023-09 (Template Schemas L308-309). When the bound is written as a string and the submitted -# value arrives as a number, the comparison must be numeric. A naive implementation that -# compares the string bound against the value lexically accepts and rejects the wrong things. +# 2023-09 (§2.3 L308-309). When the bound is written as a string and the submitted value arrives +# as a number, the comparison must be numeric. A naive implementation that compares the string +# bound against the value lexically accepts and rejects the wrong things. # # Bounds here are the strings '10' and '100', and the submitted value is the number 50, which -# satisfies both. The paired negative is -# 1.1--int-intstring-bounds-violation.invalid.test.yaml. +# satisfies both. The paired negative is 1.1--int-intstring-bounds-violation.invalid.test.yaml. # # '010' is used for the lower bound so a lexical comparison against '50' would order them # differently from the numeric comparison, making the two paths distinguishable. +# +# Spec references, Template Schemas 2023-09: +# §2.3 L308 minValue is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L308 +# §2.3 L309 maxValue is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L309 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml index a4801073..309fdbfe 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--int-intstring-bounds-violation.invalid.test.yaml @@ -2,11 +2,17 @@ # # minValue and maxValue are written as the strings '010' and '100', and the submitted value is # the number 200. The bound must be interpreted numerically and the value rejected at job -# creation. An implementation that fails to parse a string bound, or that compares it -# lexically, admits this job. +# creation. An implementation that fails to parse a string bound, or that compares it lexically, +# admits this job. # # .invalid.test.yaml rather than .invalid.yaml: the template is statically valid and the error # only fires once the submitted value is bound. +# +# Spec references, Template Schemas 2023-09: +# §2.3 L308 minValue is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L308 +# §2.3 L309 maxValue is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L309 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml index f774bda0..c6bb7568 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml @@ -1,16 +1,20 @@ -# Template Schemas L2014 states that at job creation "PATH parameter defaults are joined -# with the job template directory". That join is a value transform, so the raw template and -# the resolved job differ, and a consumer reading the raw default gets a relative path. +# §7.4 L2014 states that at job creation "PATH parameter defaults are joined with the job +# template directory". That join is a value transform, so the raw template and the resolved job +# differ, and a consumer reading the raw default gets a relative path. # -# The existing fixture 1.1--validation-valid-path-default-within-template-dir.test.yaml -# cannot catch a missing join: its default is `./output` and it asserts the substring -# `/output`, which the un-joined `./output` also contains. An implementation that skipped the -# join entirely passes it. +# The existing fixture 1.1--validation-valid-path-default-within-template-dir.test.yaml cannot +# catch a missing join: its default is `./output` and it asserts the substring `/output`, which +# the un-joined `./output` also contains. An implementation that skipped the join entirely +# passes it. # # This fixture removes that escape. The default carries no leading `./`, so `RESULT:inputs/` # appears only if the value was not joined, and it is forbidden. Only the path suffix is # asserted, because the absolute prefix legitimately differs between implementations: one # resolves symlinks and reports /private/var/..., the other reports /var/... +# +# Spec references, Template Schemas 2023-09: +# §7.4 L2014 PATH parameter defaults are joined with the job template directory at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml index fddce4dc..abebd02b 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml @@ -1,14 +1,18 @@ # Strengthened: the expected substrings alone could not distinguish a joined path from an # un-joined one. The default is `./output`, and the POSIX assertion `/output` is a substring of -# the literal `./output`, so an implementation that skipped the template-directory join -# entirely still passed. Verified by running this fixture with the task hardcoded to print +# the literal `./output`, so an implementation that skipped the template-directory join entirely +# still passed. Verified by running this fixture with the task hardcoded to print # `OUTPUT:./output`, which passed. # -# The forbidden entries close that. `OUTPUT:./` and `OUTPUT:output` can only appear if the -# value reaching the task was not joined with the job template directory. +# The forbidden entries close that. `OUTPUT:./` and `OUTPUT:output` can only appear if the value +# reaching the task was not joined with the job template directory. # # See also 1.1--path-default-joined-with-template-dir.test.yaml, which asserts the same # behaviour with a default carrying no `./` prefix. +# +# Spec references, Template Schemas 2023-09: +# §7.4 L2014 PATH parameter defaults are joined with the job template directory at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml b/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml index 4eec320e..9245b3e3 100644 --- a/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml @@ -1,16 +1,22 @@ -# The job template `name` is annotated @fmtstring (Template Schemas L38) and resolves at -# job creation. Existing base fixtures assert a PARALLEL substitution of the same parameter -# into a task argument, which proves the parameter resolved somewhere but says nothing about -# the name field. EXPR/jobs/7.3.1--job-name-step-name.test.yaml asserts the resolved name -# properly, but needs the EXPR extension to read {{ Job.Name }}. +# The job template `name` is annotated @fmtstring (§1.1 L38) and resolves at job creation. +# Existing base fixtures assert a PARALLEL substitution of the same parameter into a task +# argument, which proves the parameter resolved somewhere but says nothing about the name field. +# EXPR/jobs/7.3.1--job-name-step-name.test.yaml asserts the resolved name properly, but needs +# the EXPR extension to read {{ Job.Name }}. # -# This asserts the name field itself at base level, through the implementation's own report -# of the job it ran. Both reference CLIs emit the resolved name twice, once as -# `Running job ''` and once as `Job: ` in the results block, so the assertion is -# written with anyOf and either satisfies it. +# This asserts the name field itself at base level, through the implementation's own report of +# the job it ran. Both reference CLIs emit the resolved name twice, once as `Running job +# ''` and once as `Job: ` in the results block, so the assertion is written with +# anyOf and either satisfies it. # # The parameters block overrides both defaults. The default-derived name is forbidden, so a # fixture that resolved against defaults instead of submitted values fails. +# +# Spec references, Template Schemas 2023-09: +# §1.1 L38 the job template name field is @fmtstring +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L38 +# §7.4 L2014 resolved at job creation, since it is not annotated @fmtstring[host] +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 template: specificationVersion: jobtemplate-2023-09 name: Job-{{Param.Project}}-{{Param.Version}} diff --git a/conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml b/conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml index 8ea685fb..7e5e2de1 100644 --- a/conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml +++ b/conformance-tests/2023-09/base/jobs/2.3--int-param-intstring-default-resolves.test.yaml @@ -1,13 +1,17 @@ -# .default is ` | ` in base 2023-09 (Template -# Schemas L306) with no extension gate. The string form is a distinct coercion surface: a -# consumer reading the raw template sees "007" where the resolved job carries 7. +# .default is ` | ` in base 2023-09 (§2.3 L306) +# with no extension gate. The string form is a distinct coercion surface: a consumer reading the +# raw template sees "007" where the resolved job carries 7. # # The suite has template-level coverage of the string form -# (base/job_templates/2.3--int-param-with-default-string.yaml) and no job-level coverage of -# any string-form definition field, so nothing asserts what the string resolves TO. +# (base/job_templates/2.3--int-param-with-default-string.yaml) and no job-level coverage of any +# string-form definition field, so nothing asserts what the string resolves TO. # # `007` is chosen over `7` so the assertion distinguishes a parsed integer from a passed-through # literal. `COUNT:007` is forbidden. +# +# Spec references, Template Schemas 2023-09: +# §2.3 L306 INT parameter default is ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml index 636479c0..a60e8dac 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml @@ -1,16 +1,24 @@ -# hostRequirements.attributes[].allOf is annotated @fmtstring in base 2023-09 (Template -# Schemas L1015), the same as anyOf at L1014, with no extension gate. Its element type -# is constrained to the identifier-like pattern in section 3.3.2.2, -# and that pattern cannot be checked at decode when the element is a format string, so it must -# be re-checked after resolution at job creation. +# hostRequirements.attributes[].allOf is annotated @fmtstring in base 2023-09 (§3.3.2 L1015), +# the same as anyOf at §3.3.2 L1014, with no extension gate. Its element type +# is constrained to the identifier-like pattern in §3.3.2.2, and that +# pattern cannot be checked at decode when the element is a format string, so it must be +# re-checked after resolution at job creation. # # Here the parameter resolves to "not valid!", violating the pattern on both the space and the # exclamation mark. # -# Companion to 3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml in -# this directory. anyOf and allOf are separate spec fields with separate validators, so they -# are covered separately rather than assumed symmetric, but the observed divergence is the same -# and reviewers may prefer to treat both as one issue. +# Companion to 3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml in this +# directory. anyOf and allOf are separate spec fields with separate validators, so they are +# covered separately rather than assumed symmetric, but the observed divergence is the same and +# reviewers may prefer to treat both as one issue. +# +# Spec references, Template Schemas 2023-09: +# §3.3.2 L1015 attributes[].allOf is @fmtstring in base 2023-09, no extension gate +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1015 +# §3.3.2 L1014 the anyOf counterpart, covered separately as a distinct field +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014 +# §3.3.2.2 L1054 `` pattern the resolved value must satisfy +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1054 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml index ef07e9e1..c18b072b 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml @@ -1,16 +1,21 @@ -# hostRequirements.attributes[].anyOf is annotated @fmtstring in base 2023-09 (Template -# Schemas L1014), with no extension gate. Its element type -# (section 3.3.2.2) constrains the value to the identifier-like pattern, and that constraint -# cannot be checked at decode when the element is a format string. It must therefore be -# checked after resolution at job creation. +# hostRequirements.attributes[].anyOf is annotated @fmtstring in base 2023-09 (§3.3.2 L1014), +# with no extension gate. Its element type (§3.3.2.2) constrains the +# value to the identifier-like pattern, and that constraint cannot be checked at decode when the +# element is a format string. It must therefore be checked after resolution at job creation. # -# Here the parameter resolves to "not valid!", which contains a space and an exclamation -# mark, so the run must fail. +# Here the parameter resolves to "not valid!", which contains a space and an exclamation mark, +# so the run must fail. # -# NOTE ON CURRENT STATE: this case is expected to FAIL against the Rust CLI, which accepts -# the resolved value and runs the job. The Python CLI rejects it with "Value not valid! is -# not a valid attribute capability value." That divergence is the defect this case exists to -# expose; it is not a broken test. +# NOTE ON CURRENT STATE: this case is expected to FAIL against the Rust CLI, which accepts the +# resolved value and runs the job. The Python CLI rejects it with "Value not valid! is not a +# valid attribute capability value." That divergence is the defect this case exists to expose; +# it is not a broken test. +# +# Spec references, Template Schemas 2023-09: +# §3.3.2 L1014 attributes[].anyOf is @fmtstring in base 2023-09, no extension gate +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014 +# §3.3.2.2 L1054 `` pattern the resolved value must satisfy +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1054 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml index c4db82fe..4f54d1bb 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml @@ -1,13 +1,19 @@ # elements are ` | ` in base 2023-09 (Template Schemas -# L1110), with no extension gate. The suite has 2,417 IntRangeList elements and not one uses -# the string form, so nothing asserts what a string element resolves to. +# §3.4.1.1 L1110), with no extension gate. The suite has 2,417 IntRangeList elements and not one +# uses the string form, so nothing asserts what a string element resolves to. # # is defined as "a string whose value is the string representation of an integer # value in base-10", so '02' and '003' represent the integers 2 and 3, and the task parameter # should carry those integers rather than the source text. # -# This is the value that reaches a task command line, so a wrong answer here means a renderer -# is invoked with --frame 02 instead of --frame 2. +# This is the value that reaches a task command line, so a wrong answer here means a renderer is +# invoked with --frame 02 instead of --frame 2. +# +# Spec references, Template Schemas 2023-09: +# §3.4.1.1 L1110 `` elements are ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1110 +# §2.3 L306 `` is defined only as a base-10 string representation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml index 113bcfd9..c87ec066 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml @@ -1,6 +1,6 @@ # elements are ` | ` in base 2023-09 (Template Schemas -# L1184), with no extension gate. The suite has 1,044 FloatRangeList elements and not one uses -# the string form. +# §3.4.1.2 L1184), with no extension gate. The suite has 1,044 FloatRangeList elements and not +# one uses the string form. # # Same question as the IntRangeList case in this directory: is a string # representation of a base-10 number, so '02.50' represents 2.5 and the task parameter should @@ -14,6 +14,12 @@ # default by asserting PARAM:3.500 from default "3.500". So a spec ruling is needed on whether # normalizes, and if it does, the landed fixture and this one cannot both be # right. This fixture states the normalizing reading; it should not merge until that is settled. +# +# Spec references, Template Schemas 2023-09: +# §3.4.1.2 L1184 `` elements are ` | ` +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1184 +# §2.4 L363 `` is defined only as a base-10 string representation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L363 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/README.md b/conformance-tests/2023-09/base/jobs/proposed/README.md index 38568ef2..284c3b3f 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/README.md +++ b/conformance-tests/2023-09/base/jobs/proposed/README.md @@ -8,7 +8,7 @@ Promotion is `git mv` up one directory with no edit to the fixture. | Fixture | Construct | Observed | |---|---|---| | `3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml` | `attributes[].anyOf` element written as `"{{Param.Software}}"`, resolving to `not valid!`, against the `` pattern in section 3.3.2.2 | Python rejects at job creation with `Value not valid! is not a valid attribute capability value.` The Rust CLI accepts the resolved value and runs the job | -| `3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml` | Same, for `allOf` at L1015 | Same. Swept across four invalid resolved values, a space, an exclamation mark, 120 characters and a leading digit: Python rejects all four, Rust runs all four | +| `3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml` | Same, for `allOf` at §3.3.2 L1015 | Same. Swept across four invalid resolved values, a space, an exclamation mark, 120 characters and a leading digit: Python rejects all four, Rust runs all four | | `3.4.1.1--int-range-intstring-elements-normalized.test.yaml` | `` elements in the `` string form, `['1', '02', '003']` | Rust substitutes `1`, `2`, `3`. Python substitutes `1`, `02`, `003`, so a task command line receives `--frame 02` | | `3.4.1.2--float-range-floatstring-elements-normalized.test.yaml` | `` elements in the `` string form, `['1.5', '02.50']` | Rust substitutes `1.5`, `2.5`. Python substitutes `1.5`, `02.50` | @@ -16,7 +16,7 @@ Promotion is `git mv` up one directory with no edit to the fixture. Implementation fix. -`anyOf` is annotated `@fmtstring` in base 2023-09 at Template Schemas L1014, with no +`anyOf` is annotated `@fmtstring` in base 2023-09 at (§3.3.2 L1014, with no extension gate, and its element type is constrained to the identifier-like pattern in section 3.3.2.2. That pattern cannot be checked at decode when the element is a format string, so it must be re-checked after resolution at job creation. The Python path does @@ -59,3 +59,15 @@ default. If the spec rules that `` normalizes, that fixture and These two fixtures state the normalizing reading. They should not be promoted out of `proposed/` until the spec says which reading is conformant. + +## Spec references, Template Schemas 2023-09 + +- [§3.3.2 L1014](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014) attributes[].anyOf is @fmtstring in base 2023-09, no extension gate +- [§3.3.2 L1015](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1015) attributes[].allOf, same +- [§3.3.2.2 L1054](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1054) `` pattern the resolved value must satisfy +- [§3.4.1.1 L1110](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1110) `` elements are ` | ` +- [§3.4.1.2 L1184](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1184) `` elements are ` | ` +- [§2.3 L306](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306) `` is defined only as a base-10 string representation + +Line numbers are a locator for where each claim was verified. The section numbers are the +durable reference if the spec is re-flowed. From 080ef2a30a977f8d7e3d488e5946f1b89558c11a Mon Sep 17 00:00:00 2001 From: David Leong <116610336+leongdl@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:03:21 -0700 Subject: [PATCH 5/6] docs: Repoint two citations that pointed at a heading and a table header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correcting the previous commit, whose message claimed "71 citations, 0 problems". That was not accurate. Two of the 22 distinct citations pointed at structure rather than at the claim, and my own verifier let them through. §7.4 L2014 is the stage table's header row, `| Stage | When | Known Values | ...`. Both claims cited against it, that format strings without @fmtstring[host] resolve at job creation and that PATH parameter defaults are joined with the job template directory, are on L2017, the Job creation row. Seven uses repointed. The worst instance put the PATH sentence in direct quotation marks attributed to a line that does not contain it. §3.3.2.2 L1054 is the heading `##### 3.3.2.2. `. Citing a heading tells a reader the section, which the §X.Y already says, and not where the requirement is. The load-bearing line for these fixtures is L1059, "After the format string has been resolved:", which is precisely the post-resolution point they assert. Three uses repointed. The cause was the verifier, not carelessness in transcription. It accepted a supporting keyword within ±3 lines of the target, and L2014 to L2017 is exactly 3, so a citation aimed at a table header scored as correct. A check that loose cannot catch the error it exists to catch. The verifier is now strict on three counts: the keyword must appear ON the cited line rather than near it; a heading is never a valid target; and a table header or separator row is never a valid target, which is exactly what L2014 was. Under those rules all 71 citations pass and every link still carries ?plain=1 and has a matching section reference in the same file. Tightening it also caught a fault in the checker rather than the fixtures: it expected the keyword `range:` at §3.4.1.1 L1110 and §3.4.1.2 L1184, but those lines are the `` and `` grammar productions, which is exactly what the fixtures cite them for. The expectation was wrong, not the citation, and I corrected the checker rather than the reference. All 12 live fixtures re-run on both reference CLIs. Comment-only change. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com> --- ...n-format-string-resolves-to-non-number.invalid.test.yaml | 4 ++-- .../jobs/default-task-count-format-string.test.yaml | 4 ++-- .../2023-09/TASK_CHUNKING/jobs/proposed/README.md | 2 +- .../1.1--path-default-joined-with-template-dir.test.yaml | 6 +++--- ...idation-valid-path-default-within-template-dir.test.yaml | 4 ++-- .../base/jobs/1.1.1--resolved-job-name-value.test.yaml | 4 ++-- ...ing-in-allof-resolves-to-invalid-value.invalid.test.yaml | 4 ++-- ...ing-in-anyof-resolves-to-invalid-value.invalid.test.yaml | 4 ++-- conformance-tests/2023-09/base/jobs/proposed/README.md | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml index a847f930..f75a7062 100644 --- a/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml +++ b/conformance-tests/2023-09/FEATURE_BUNDLE_1/jobs/3.3.1--amount-min-format-string-resolves-to-non-number.invalid.test.yaml @@ -9,8 +9,8 @@ # Spec references, Template Schemas 2023-09: # §3.3.1 L958 amounts[].min may be `` under FEATURE_BUNDLE_1, @fmtstring # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L958 -# §7.4 L2014 format strings not annotated @fmtstring[host] resolve at job creation -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 +# §7.4 L2017 format strings not annotated @fmtstring[host] resolve at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2017 template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml index 231d29a7..c6f85274 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/default-task-count-format-string.test.yaml @@ -16,8 +16,8 @@ # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1263 # §2.3 L306 INT parameter default is ` | ` # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 -# §7.4 L2014 format strings not annotated @fmtstring[host] resolve at job creation -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 +# §7.4 L2017 format strings not annotated @fmtstring[host] resolve at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2017 template: specificationVersion: jobtemplate-2023-09 extensions: diff --git a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md index 2705fd08..fb9709cc 100644 --- a/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md +++ b/conformance-tests/2023-09/TASK_CHUNKING/jobs/proposed/README.md @@ -33,7 +33,7 @@ specific to the bound check rather than to resolution itself. - [§3.4.1.5 L1263](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1263) chunks.defaultTaskCount is @fmtstring, so the bound defers past decode - [§3.4.1.5 L1276](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1276) defaultTaskCount minimum value: 1, the bound not re-checked after resolution -- [§7.4 L2014](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014) format strings not annotated @fmtstring[host] resolve at job creation +- [§7.4 L2017](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2017) format strings not annotated @fmtstring[host] resolve at job creation Line numbers are a locator for where each claim was verified. The section numbers are the durable reference if the spec is re-flowed. diff --git a/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml index c6bb7568..193989c9 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--path-default-joined-with-template-dir.test.yaml @@ -1,4 +1,4 @@ -# §7.4 L2014 states that at job creation "PATH parameter defaults are joined with the job +# §7.4 L2017 states that at job creation "PATH parameter defaults are joined with the job # template directory". That join is a value transform, so the raw template and the resolved job # differ, and a consumer reading the raw default gets a relative path. # @@ -13,8 +13,8 @@ # resolves symlinks and reports /private/var/..., the other reports /var/... # # Spec references, Template Schemas 2023-09: -# §7.4 L2014 PATH parameter defaults are joined with the job template directory at job creation -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 +# §7.4 L2017 PATH parameter defaults are joined with the job template directory at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2017 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml b/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml index abebd02b..47ca4338 100644 --- a/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1--validation-valid-path-default-within-template-dir.test.yaml @@ -11,8 +11,8 @@ # behaviour with a default carrying no `./` prefix. # # Spec references, Template Schemas 2023-09: -# §7.4 L2014 PATH parameter defaults are joined with the job template directory at job creation -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 +# §7.4 L2017 PATH parameter defaults are joined with the job template directory at job creation +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2017 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml b/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml index 9245b3e3..6c58a295 100644 --- a/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml +++ b/conformance-tests/2023-09/base/jobs/1.1.1--resolved-job-name-value.test.yaml @@ -15,8 +15,8 @@ # Spec references, Template Schemas 2023-09: # §1.1 L38 the job template name field is @fmtstring # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L38 -# §7.4 L2014 resolved at job creation, since it is not annotated @fmtstring[host] -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2014 +# §7.4 L2017 resolved at job creation, since it is not annotated @fmtstring[host] +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L2017 template: specificationVersion: jobtemplate-2023-09 name: Job-{{Param.Project}}-{{Param.Version}} diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml index a60e8dac..c6445e75 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml @@ -17,8 +17,8 @@ # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1015 # §3.3.2 L1014 the anyOf counterpart, covered separately as a distinct field # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014 -# §3.3.2.2 L1054 `` pattern the resolved value must satisfy -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1054 +# §3.3.2.2 L1059 `` pattern the resolved value must satisfy +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1059 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml index c18b072b..8d2d01cf 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml @@ -14,8 +14,8 @@ # Spec references, Template Schemas 2023-09: # §3.3.2 L1014 attributes[].anyOf is @fmtstring in base 2023-09, no extension gate # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014 -# §3.3.2.2 L1054 `` pattern the resolved value must satisfy -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1054 +# §3.3.2.2 L1059 `` pattern the resolved value must satisfy +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1059 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/README.md b/conformance-tests/2023-09/base/jobs/proposed/README.md index 284c3b3f..55ad391a 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/README.md +++ b/conformance-tests/2023-09/base/jobs/proposed/README.md @@ -64,7 +64,7 @@ These two fixtures state the normalizing reading. They should not be promoted ou - [§3.3.2 L1014](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014) attributes[].anyOf is @fmtstring in base 2023-09, no extension gate - [§3.3.2 L1015](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1015) attributes[].allOf, same -- [§3.3.2.2 L1054](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1054) `` pattern the resolved value must satisfy +- [§3.3.2.2 L1059](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1059) `` pattern the resolved value must satisfy - [§3.4.1.1 L1110](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1110) `` elements are ` | ` - [§3.4.1.2 L1184](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1184) `` elements are ` | ` - [§2.3 L306](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306) `` is defined only as a base-10 string representation From e1c9e79ecc24e2291ea4d1519ed68d208c48fd7d Mon Sep 17 00:00:00 2001 From: David Leong <116610336+leongdl@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:19:52 -0700 Subject: [PATCH 6/6] docs: Repoint two type-definition citations to the definition, not the field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two citations named the field declaration where the claim was about the type definition. §2.3 L306 is `default: | `, which supports "INT parameter default is ` | `" and does not support "`` is a base-10 string representation"; that sentence is L318. Same shape for §2.4 L363 against L375 for ``. Three sites repointed, with their links. The other uses of L306, which cite the field declaration, are correct and unchanged. Also reworded the §3.3.2.2 L1059 claim. The line is "After the format string has been resolved:", so citing it for "the pattern the resolved value must satisfy" overstated it; the pattern is L1060-L1062. It now reads "constraints apply AFTER the format string resolves", which is what the line says and is the fixture's actual point. The verifier is the recurring problem here, so it changed approach rather than gaining another tweak. Three versions each failed differently: a ±3-line window let a table-header citation pass; keying the expectation on the line number meant a line cited for two different claims was only checked against one, which is exactly how these two survived; and lifting the claim text to match tokens against the line produced twelve false positives, because it cannot match `amounts[].max` against `max:`, flags editorial words no spec line contains, and cannot tell a nested list item from its parent bullet. Three different failures from three attempts is a signal about the approach, not the tuning. Automated claim-to-line matching is not reliable on prose. The verifier now automates only what is mechanically decidable, and stops pretending about the rest: automated section containment, target is not a heading, not a table header or separator, not a blank line, every link has a matching reference in the same file, every schema link carries ?plain=1 human whether the line states the claim, printed as a two-column table of all 23 distinct references beside the spec text That division is not a concession, it is where the results came from: every real error in this series was found by a person reading the line, and none by any of the three checkers. Mechanical checks now pass with 0 problems across 21 files and 23 distinct references, and I read all 23 pairs. All 12 live fixtures re-run on both reference CLIs. Comment-only change, and all three touched fixtures are under proposed/, which CI does not discover. Signed-off-by: David Leong <116610336+leongdl@users.noreply.github.com> --- ...tring-in-allof-resolves-to-invalid-value.invalid.test.yaml | 2 +- ...tring-in-anyof-resolves-to-invalid-value.invalid.test.yaml | 2 +- ...3.4.1.1--int-range-intstring-elements-normalized.test.yaml | 4 ++-- ...1.2--float-range-floatstring-elements-normalized.test.yaml | 4 ++-- conformance-tests/2023-09/base/jobs/proposed/README.md | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml index c6445e75..b1d4480c 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-allof-resolves-to-invalid-value.invalid.test.yaml @@ -17,7 +17,7 @@ # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1015 # §3.3.2 L1014 the anyOf counterpart, covered separately as a distinct field # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014 -# §3.3.2.2 L1059 `` pattern the resolved value must satisfy +# §3.3.2.2 L1059 `` constraints apply AFTER the format string resolves # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1059 template: specificationVersion: jobtemplate-2023-09 diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml index 8d2d01cf..ed74fdf2 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.3.2--format-string-in-anyof-resolves-to-invalid-value.invalid.test.yaml @@ -14,7 +14,7 @@ # Spec references, Template Schemas 2023-09: # §3.3.2 L1014 attributes[].anyOf is @fmtstring in base 2023-09, no extension gate # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014 -# §3.3.2.2 L1059 `` pattern the resolved value must satisfy +# §3.3.2.2 L1059 `` constraints apply AFTER the format string resolves # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1059 template: specificationVersion: jobtemplate-2023-09 diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml index 4f54d1bb..203e4897 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.1--int-range-intstring-elements-normalized.test.yaml @@ -12,8 +12,8 @@ # Spec references, Template Schemas 2023-09: # §3.4.1.1 L1110 `` elements are ` | ` # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1110 -# §2.3 L306 `` is defined only as a base-10 string representation -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306 +# §2.3 L318 `` is a string representation of an integer in base-10 +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L318 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml index c87ec066..1c090bff 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml +++ b/conformance-tests/2023-09/base/jobs/proposed/3.4.1.2--float-range-floatstring-elements-normalized.test.yaml @@ -18,8 +18,8 @@ # Spec references, Template Schemas 2023-09: # §3.4.1.2 L1184 `` elements are ` | ` # https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1184 -# §2.4 L363 `` is defined only as a base-10 string representation -# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L363 +# §2.4 L375 `` is a string representation of a float or integer in base-10 +# https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L375 template: specificationVersion: jobtemplate-2023-09 name: TestJob diff --git a/conformance-tests/2023-09/base/jobs/proposed/README.md b/conformance-tests/2023-09/base/jobs/proposed/README.md index 55ad391a..4518c24d 100644 --- a/conformance-tests/2023-09/base/jobs/proposed/README.md +++ b/conformance-tests/2023-09/base/jobs/proposed/README.md @@ -64,10 +64,10 @@ These two fixtures state the normalizing reading. They should not be promoted ou - [§3.3.2 L1014](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1014) attributes[].anyOf is @fmtstring in base 2023-09, no extension gate - [§3.3.2 L1015](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1015) attributes[].allOf, same -- [§3.3.2.2 L1059](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1059) `` pattern the resolved value must satisfy +- [§3.3.2.2 L1059](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1059) `` constraints apply AFTER the format string resolves - [§3.4.1.1 L1110](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1110) `` elements are ` | ` - [§3.4.1.2 L1184](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L1184) `` elements are ` | ` -- [§2.3 L306](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L306) `` is defined only as a base-10 string representation +- [§2.3 L318](https://github.com/OpenJobDescription/openjd-specifications/blob/mainline/wiki/2023-09-Template-Schemas.md?plain=1#L318) `` is a string representation of an integer in base-10 Line numbers are a locator for where each claim was verified. The section numbers are the durable reference if the spec is re-flowed.