detached 43e3663

docs(action): recount the cases, document runner-mode keys, int32 and folding, note the Lint null key

commit
43e36633986e9bf19fcca255953bd7666031cb09
author
brice · · 2026-10-13 14:10 UTC
committer
brice · · 2026-10-18 14:59 UTC
parent
110f7f5
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +32 -7
M action/PORTING.md +16 -3
@@ -24,7 +24,7 @@ most files and differ on a few (below); the new one is not reproduced.
2424 ## Checks
2525
2626 - `testdata/runner.json`: the answer of the runner's loader on every file of
27- `testdata/cases` (299 files). `gen/runner/Driver.cs` is
27+ `testdata/cases` (338 files). `gen/runner/Driver.cs` is
2828 `ActionManifestManagerLegacy.Load/ConvertRuns/ConvertInputs` with what needs
2929 an `IExecutionContext` removed (tracing, the file table, expression values:
3030 none of it changes loading), compiled into a copy of `src/Sdk` with
@@ -40,7 +40,7 @@ most files and differ on a few (below); the new one is not reproduced.
4040 (`TestParseUsesAgainstRunner`).
4141 - `testdata/strict.json`: `parseAction` + `convertActionTemplate` of the npm
4242 package on every case; `Lint` is compared on the error list
43- (`TestLintAgainstReference`). One file is excused with its reason
43+ (`TestLintAgainstReference`). Eleven files are excused, each with its reason
4444 (`knownLintDifferences`).
4545 - `FuzzParse` (no panic, an action iff no error, at most ten errors, every step
4646 has an id and a condition) and `FuzzParseUses`.
@@ -49,7 +49,7 @@ Regenerate with `gen/regen.sh` (`RUNNER_CLONE=<clone>` for the runner part); the
4949 diff must be empty. The cases are: the 24 files of the runner's
5050 `src/Test/TestData` that are action manifests (`runner_*`), the 96 manifests
5151 inlined in the language service tests (`ls_*`: `action-parser.test.ts`,
52-`validate-action.test.ts`), and 179 written for this package. The
52+`validate-action.test.ts`), and 218 written for this package. The
5353 language service's own expectations (`toBeGreaterThan(0)`) are not ported: the
5454 cases are replayed through the reference instead, which gives the messages.
5555
@@ -109,6 +109,13 @@ All verified by `TestAgainstRunner`.
109109 parse event`, a collection used as a key `Expected mapping end`. Such an error
110110 stops the read where the runner meets it, so errors found earlier in the
111111 document stay (`template.Context.RunnerYAML`).
112+- **Keys, integers, case.** Mapping keys are resolved like values: `~` is null
113+ (`Unexpected value ''`), `0x10` is the key `16`, `1.0` is `1`, `TRUE` is `true`,
114+ so `0x10` and `16` are duplicates. `0x` and `0o` integers, plain or `!!int`,
115+ are int32 with wraparound (`0xFFFFFFFF` is `-1`, `0x80000000` is
116+ `-2147483648`; over 32 bits is an error, and `!!float` refuses them). Duplicate
117+ keys, input names and step ids compare with .NET's OrdinalIgnoreCase
118+ (`expr.OrdinalFold`): `é` and `É` are the same, `s` and `ſ` are not.
112119 - **Depth.** `TemplateMemory(maxDepth: 100)`: `Maximum object depth exceeded`
113120 from the 100th nested collection, the root mapping being the first. Skipped
114121 values (unknown keys) count (`template.Context.MaxDepth`).
@@ -209,6 +216,12 @@ All verified by `TestAgainstRunner`.
209216 - A mapping key that is a collection: the reference reads `undefined`
210217 (`Unexpected value 'undefined'`); `Lint` reports `Unexpected value ''`
211218 (`knownLintDifferences`). `Parse` is the runner's (`Expected mapping end`).
219+- `Lint` keeps keys as strings, so it differs from the reference on keys that
220+ are not strings: it misses a null key under a loose-key mapping (`inputs`,
221+ `env`), which the reference and the runner reject, and the reference fails on
222+ other non-string keys with `raw.indexOf is not a function`. Fixing the null
223+ key in `Lint` would change how `template` reads workflow files, so it is
224+ left alone.
212225
213226 ## To verify against recordings
214227
@@ -24,7 +24,7 @@ most files and differ on a few (below); the new one is not reproduced.
24 ## Checks24 ## Checks
25 25
26 - `testdata/runner.json`: the answer of the runner's loader on every file of26 - `testdata/runner.json`: the answer of the runner's loader on every file of
27- `testdata/cases` (299 files). `gen/runner/Driver.cs` is27+ `testdata/cases` (338 files). `gen/runner/Driver.cs` is
28 `ActionManifestManagerLegacy.Load/ConvertRuns/ConvertInputs` with what needs28 `ActionManifestManagerLegacy.Load/ConvertRuns/ConvertInputs` with what needs
29 an `IExecutionContext` removed (tracing, the file table, expression values:29 an `IExecutionContext` removed (tracing, the file table, expression values:
30 none of it changes loading), compiled into a copy of `src/Sdk` with30 none of it changes loading), compiled into a copy of `src/Sdk` with
@@ -40,7 +40,7 @@ most files and differ on a few (below); the new one is not reproduced.
40 (`TestParseUsesAgainstRunner`).40 (`TestParseUsesAgainstRunner`).
41 - `testdata/strict.json`: `parseAction` + `convertActionTemplate` of the npm41 - `testdata/strict.json`: `parseAction` + `convertActionTemplate` of the npm
42 package on every case; `Lint` is compared on the error list42 package on every case; `Lint` is compared on the error list
43- (`TestLintAgainstReference`). One file is excused with its reason43+ (`TestLintAgainstReference`). Eleven files are excused, each with its reason
44 (`knownLintDifferences`).44 (`knownLintDifferences`).
45 - `FuzzParse` (no panic, an action iff no error, at most ten errors, every step45 - `FuzzParse` (no panic, an action iff no error, at most ten errors, every step
46 has an id and a condition) and `FuzzParseUses`.46 has an id and a condition) and `FuzzParseUses`.
@@ -49,7 +49,7 @@ Regenerate with `gen/regen.sh` (`RUNNER_CLONE=<clone>` for the runner part); the
49 diff must be empty. The cases are: the 24 files of the runner's49 diff must be empty. The cases are: the 24 files of the runner's
50 `src/Test/TestData` that are action manifests (`runner_*`), the 96 manifests50 `src/Test/TestData` that are action manifests (`runner_*`), the 96 manifests
51 inlined in the language service tests (`ls_*`: `action-parser.test.ts`,51 inlined in the language service tests (`ls_*`: `action-parser.test.ts`,
52-`validate-action.test.ts`), and 179 written for this package. The52+`validate-action.test.ts`), and 218 written for this package. The
53 language service's own expectations (`toBeGreaterThan(0)`) are not ported: the53 language service's own expectations (`toBeGreaterThan(0)`) are not ported: the
54 cases are replayed through the reference instead, which gives the messages.54 cases are replayed through the reference instead, which gives the messages.
55 55
@@ -109,6 +109,13 @@ All verified by `TestAgainstRunner`.
109 parse event`, a collection used as a key `Expected mapping end`. Such an error109 parse event`, a collection used as a key `Expected mapping end`. Such an error
110 stops the read where the runner meets it, so errors found earlier in the110 stops the read where the runner meets it, so errors found earlier in the
111 document stay (`template.Context.RunnerYAML`).111 document stay (`template.Context.RunnerYAML`).
112+- **Keys, integers, case.** Mapping keys are resolved like values: `~` is null
113+ (`Unexpected value ''`), `0x10` is the key `16`, `1.0` is `1`, `TRUE` is `true`,
114+ so `0x10` and `16` are duplicates. `0x` and `0o` integers, plain or `!!int`,
115+ are int32 with wraparound (`0xFFFFFFFF` is `-1`, `0x80000000` is
116+ `-2147483648`; over 32 bits is an error, and `!!float` refuses them). Duplicate
117+ keys, input names and step ids compare with .NET's OrdinalIgnoreCase
118+ (`expr.OrdinalFold`): `é` and `É` are the same, `s` and `ſ` are not.
112 - **Depth.** `TemplateMemory(maxDepth: 100)`: `Maximum object depth exceeded`119 - **Depth.** `TemplateMemory(maxDepth: 100)`: `Maximum object depth exceeded`
113 from the 100th nested collection, the root mapping being the first. Skipped120 from the 100th nested collection, the root mapping being the first. Skipped
114 values (unknown keys) count (`template.Context.MaxDepth`).121 values (unknown keys) count (`template.Context.MaxDepth`).
@@ -209,6 +216,12 @@ All verified by `TestAgainstRunner`.
209 - A mapping key that is a collection: the reference reads `undefined`216 - A mapping key that is a collection: the reference reads `undefined`
210 (`Unexpected value 'undefined'`); `Lint` reports `Unexpected value ''`217 (`Unexpected value 'undefined'`); `Lint` reports `Unexpected value ''`
211 (`knownLintDifferences`). `Parse` is the runner's (`Expected mapping end`).218 (`knownLintDifferences`). `Parse` is the runner's (`Expected mapping end`).
219+- `Lint` keeps keys as strings, so it differs from the reference on keys that
220+ are not strings: it misses a null key under a loose-key mapping (`inputs`,
221+ `env`), which the reference and the runner reject, and the reference fails on
222+ other non-string keys with `raw.indexOf is not a function`. Fixing the null
223+ key in `Lint` would change how `template` reads workflow files, so it is
224+ left alone.
212 225
213 ## To verify against recordings226 ## To verify against recordings
214 227
M action/lint_test.go +7 -2
@@ -59,12 +59,17 @@ var knownLintDifferences = map[string]string{
5959 "key_float_dup.yml": jsKey,
6060 "key_hex_dup.yml": jsKey,
6161 "key_hex_input.yml": jsKey,
62- "key_null_env.yml": jsKey,
63- "key_null_inputs.yml": jsKey,
62+ "key_null_env.yml": nullKey,
63+ "key_null_inputs.yml": nullKey,
6464 "key_null_top.yml": jsKey,
6565 "key_number_runs.yml": jsKey,
6666 }
6767
68+// nullKey: Lint misses a null key under a loose-key mapping; the reference and
69+// the runner reject it. Fixing it in template would change how workflow files
70+// are read, so it is left as it is.
71+const nullKey = "Lint misses a null key under a loose-key mapping; the reference and the runner reject it"
72+
6873 // jsKey: the reference resolves a plain key to a number, boolean or null (and
6974 // then crashes on it, "raw.indexOf is not a function", or words it
7075 // differently); template keeps keys of the workflow-style readers as strings.
@@ -59,12 +59,17 @@ var knownLintDifferences = map[string]string{
59 "key_float_dup.yml": jsKey,59 "key_float_dup.yml": jsKey,
60 "key_hex_dup.yml": jsKey,60 "key_hex_dup.yml": jsKey,
61 "key_hex_input.yml": jsKey,61 "key_hex_input.yml": jsKey,
62- "key_null_env.yml": jsKey,62+ "key_null_env.yml": nullKey,
63- "key_null_inputs.yml": jsKey,63+ "key_null_inputs.yml": nullKey,
64 "key_null_top.yml": jsKey,64 "key_null_top.yml": jsKey,
65 "key_number_runs.yml": jsKey,65 "key_number_runs.yml": jsKey,
66 }66 }
67 67
68+// nullKey: Lint misses a null key under a loose-key mapping; the reference and
69+// the runner reject it. Fixing it in template would change how workflow files
70+// are read, so it is left as it is.
71+const nullKey = "Lint misses a null key under a loose-key mapping; the reference and the runner reject it"
72+
68 // jsKey: the reference resolves a plain key to a number, boolean or null (and73 // jsKey: the reference resolves a plain key to a number, boolean or null (and
69 // then crashes on it, "raw.indexOf is not a function", or words it74 // then crashes on it, "raw.indexOf is not a function", or words it
70 // differently); template keeps keys of the workflow-style readers as strings.75 // differently); template keeps keys of the workflow-style readers as strings.
M template/PORTING.md +5 -2
@@ -73,8 +73,11 @@ checked against the runner's own code): `MaxDepth` (`TemplateMemory`'s
7373 `maxDepth`, counting skipped values too), `ErrorLimit` (the runner keeps ten),
7474 `RunnerYAML` (anchors and aliases, tags other than `!!bool/!!float/!!int/!!null`,
7575 32-bit limits of `0x`/`0o` integers, a second document, an empty file and a
76-collection used as a key all end the read with the runner's message; a number
77-read as a string is shown as .NET's `G15`, `DotNetNumberString`). Without them
76+collection used as a key all end the read with the runner's message; mapping
77+keys are resolved like values, `~` giving an empty key and `0x10` the key `16`;
78+`0x`/`0o` integers wrap as int32, `0xFFFFFFFF` being -1; duplicate keys compare
79+with .NET's OrdinalIgnoreCase, `expr.OrdinalFold`; a number read as a string is
80+shown as .NET's `G15`, `DotNetNumberString`). Without them
7881 nothing changes. `Error.Exception` marks an error that is a .NET exception the
7982 runner reports without a position.
8083
@@ -73,8 +73,11 @@ checked against the runner's own code): `MaxDepth` (`TemplateMemory`'s
73 `maxDepth`, counting skipped values too), `ErrorLimit` (the runner keeps ten),73 `maxDepth`, counting skipped values too), `ErrorLimit` (the runner keeps ten),
74 `RunnerYAML` (anchors and aliases, tags other than `!!bool/!!float/!!int/!!null`,74 `RunnerYAML` (anchors and aliases, tags other than `!!bool/!!float/!!int/!!null`,
75 32-bit limits of `0x`/`0o` integers, a second document, an empty file and a75 32-bit limits of `0x`/`0o` integers, a second document, an empty file and a
76-collection used as a key all end the read with the runner's message; a number76+collection used as a key all end the read with the runner's message; mapping
77-read as a string is shown as .NET's `G15`, `DotNetNumberString`). Without them77+keys are resolved like values, `~` giving an empty key and `0x10` the key `16`;
78+`0x`/`0o` integers wrap as int32, `0xFFFFFFFF` being -1; duplicate keys compare
79+with .NET's OrdinalIgnoreCase, `expr.OrdinalFold`; a number read as a string is
80+shown as .NET's `G15`, `DotNetNumberString`). Without them
78 nothing changes. `Error.Exception` marks an error that is a .NET exception the81 nothing changes. `Error.Exception` marks an error that is a .NET exception the
79 runner reports without a position.82 runner reports without a position.
80 83
M template/context.go +4 -0
@@ -95,6 +95,10 @@ type Context struct {
9595 // ("Unexpected tag 'tag:yaml.org,2002:str'": the runner knows no !!str).
9696 // Such an error is reported alone, without the schema errors that the
9797 // runner would have found in the part of the document before it.
98+ // Mapping keys are resolved like values (~ is a null key, 0x10 is the key
99+ // 16, TRUE is true), 0x and 0o integers wrap as int32 (0xFFFFFFFF is -1),
100+ // and duplicate keys compare with .NET's OrdinalIgnoreCase
101+ // (expr.OrdinalFold) instead of JavaScript's toUpperCase.
98102 RunnerYAML bool
99103
100104 schema *Schema
@@ -95,6 +95,10 @@ type Context struct {
95 // ("Unexpected tag 'tag:yaml.org,2002:str'": the runner knows no !!str).95 // ("Unexpected tag 'tag:yaml.org,2002:str'": the runner knows no !!str).
96 // Such an error is reported alone, without the schema errors that the96 // Such an error is reported alone, without the schema errors that the
97 // runner would have found in the part of the document before it.97 // runner would have found in the part of the document before it.
98+ // Mapping keys are resolved like values (~ is a null key, 0x10 is the key
99+ // 16, TRUE is true), 0x and 0o integers wrap as int32 (0xFFFFFFFF is -1),
100+ // and duplicate keys compare with .NET's OrdinalIgnoreCase
101+ // (expr.OrdinalFold) instead of JavaScript's toUpperCase.
98 RunnerYAML bool102 RunnerYAML bool
99 103
100 schema *Schema104 schema *Schema

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 3

↑↓ moveenter openesc close