eyeprolog 1.5.32 → 1.5.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,9 +74,9 @@ All 33 bundled modules that overlap Scryer's current `src/lib/` surface cover Sc
74
74
  - [Symbiotic Knowledge Graphs](https://eyereasoner.github.io/eyeprolog/examples/deck/symbiotic-knowledge-graphs) — RDF ↔ Prolog heatwave-response demo for human/AI/KG co-evolution
75
75
  - [rdf-prolog-roundtrip](https://github.com/eyereasoner/rdf-prolog-roundtrip) — standalone RDF 1.2 ↔ ISO Prolog bridge used by the RDF examples
76
76
  - [ISO conformance audit](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
77
- - [Conformance report](conformance-report.md) — generated executable conformance status and corpus summary
77
+ - [Latest Neumerkel conformity](test/conformance/NEUMERKEL-LATEST.md) — tracked result from the current live upstream inventory
78
+ - [Conformance report](conformance-report.md) — generated executable conformance status and local corpus summary
78
79
  - [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
79
-
80
80
  ## RDF, Prolog, and symbiotic knowledge graphs
81
81
 
82
82
  EyeProlog can sit behind an RDF knowledge graph without inventing a private graph representation. [`rdf-prolog-roundtrip`](https://github.com/eyereasoner/rdf-prolog-roundtrip) converts RDF 1.2 datasets to ordinary `rdf(Subject, Predicate, Object, Graph)` facts, EyeProlog applies portable rules, and ground `rdf/4` results can be converted back to RDF.
@@ -95,5 +95,5 @@ cd eyeprolog
95
95
  npm install
96
96
  npm test
97
97
  ```
98
-
98
+ `npm test` is the release gate and fetches the latest seven Neumerkel conformity sources before the local gates; use `npm run test:offline` for a network-free pass, `npm run test:conformance` for all conformance layers, `npm run test:neumerkel` for live upstream only, and `npm run test:neumerkel:cached` only to reproduce the last fetch. Upstream counts are discovered dynamically. A stale tracked [latest Neumerkel report](test/conformance/NEUMERKEL-LATEST.md) produces a warning during normal tests. `npm run conformance:update:neumerkel` refreshes it from live upstream; `npm run conformance:sync:neumerkel` refreshes it from the exact successful snapshot already fetched by `npm test`; and `npm run conformance:check:neumerkel` verifies that snapshot without a second live fetch. npm's version lifecycle uses the sync path and stages the generated reports into the release commit. Exact bytes/hashes stay under Git-ignored `.cache/neumerkel/`. Benchmarks remain `npm run benchmark` and `npm run benchmark:lips`.
99
99
  EyeProlog is released under the [MIT License](LICENSE.md).
@@ -4,6 +4,13 @@ This report combines an executable external conformance gate with the file-based
4
4
  conformance corpus under `test/conformance/`. The executable result is measured
5
5
  when this report is generated; it is not inferred from fixture counts.
6
6
 
7
+ ## Latest Neumerkel evidence
8
+
9
+ See the tracked [latest Neumerkel conformity report](test/conformance/NEUMERKEL-LATEST.md).
10
+ `npm test` fetches all seven TU Wien sources once and executes the discovered inventory.
11
+ The release workflow then synchronizes this tracked report from those exact successful
12
+ cached source bytes, avoiding a second live fetch and its race window.
13
+
7
14
  ## Executable conformance status
8
15
 
9
16
  | Gate | Passed | Total | Status |
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.32",
6
+ "version": "1.5.34",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -46,31 +46,34 @@
46
46
  },
47
47
  "scripts": {
48
48
  "test": "node test/run-all.mjs",
49
- "generate": "node tools/generate-library-autoload-index.mjs && node tools/generate-predicate-reference.mjs && node tools/extract-book-examples.mjs",
50
- "generate:autoload": "node tools/generate-library-autoload-index.mjs",
51
- "test:eyeprolog": "node test/run-all.mjs",
52
- "test:conformance": "node test/run-conformance.mjs",
53
- "test:iso-strict": "node test/run-iso-strict.mjs",
54
- "test:interop": "node test/run-interop.mjs",
55
- "test:openrulebench": "node test/run-openrulebench.mjs",
49
+ "test:offline": "node test/run-all.mjs --offline",
50
+ "test:conformance": "node test/run-conformance-all.mjs",
51
+ "test:conformance:offline": "node test/run-conformance-all.mjs --offline",
52
+ "test:neumerkel": "node test/run-neumerkel.mjs",
53
+ "test:neumerkel:cached": "node test/run-neumerkel.mjs --cached",
54
+ "test:neumerkel:harness": "node test/run-neumerkel-tests.mjs",
55
+ "test:iso": "node test/run-iso-strict.mjs",
56
56
  "test:wg17": "node test/run-wg17.mjs",
57
- "test:examples": "node test/run-examples.mjs",
58
57
  "test:regression": "node test/run-regression.mjs",
59
- "test:regression:api": "node test/run-regression.mjs api",
60
- "test:regression:core": "node test/run-regression.mjs regression",
61
- "test:regression:docs": "node test/run-regression.mjs docs",
62
- "test:regression:white-box": "node test/run-regression.mjs white-box",
58
+ "test:examples": "node test/run-examples.mjs",
59
+ "test:interop": "node test/run-interop.mjs",
63
60
  "test:playground": "node test/run-playground.mjs",
64
- "wg17:upgrade": "node tools/upgrade-wg17.mjs",
65
- "report:wg17": "node tools/report-wg17-syntax-coverage.mjs",
66
- "report:wg17-syntax": "node tools/report-wg17-syntax-coverage.mjs",
67
- "preversion": "npm test && node test/run-conformance-report.mjs conformance-report.md",
68
- "postversion": "git push origin HEAD --follow-tags",
69
61
  "test:architecture": "node test/run-architecture.mjs",
62
+ "test:openrulebench": "node test/run-openrulebench.mjs",
63
+ "test:benchmark": "node test/run-benchmark-tests.mjs",
70
64
  "benchmark": "node test/benchmark.mjs",
65
+ "benchmark:lips": "node test/lips-benchmark.mjs",
71
66
  "benchmark:baseline": "node test/benchmark.mjs --save .benchmarks/baseline.json",
72
- "test:benchmark": "node test/run-benchmark-tests.mjs",
67
+ "conformance:update": "npm run conformance:update:wg17 && npm run conformance:update:neumerkel",
68
+ "conformance:report": "node test/run-conformance-report.mjs conformance-report.md",
69
+ "generate": "node tools/generate-library-autoload-index.mjs && node tools/generate-predicate-reference.mjs && node tools/extract-book-examples.mjs",
70
+ "generate:autoload": "node tools/generate-library-autoload-index.mjs",
73
71
  "generate:reference": "node tools/generate-predicate-reference.mjs",
74
- "benchmark:lips": "node test/lips-benchmark.mjs"
72
+ "preversion": "npm test && npm run conformance:sync:neumerkel && npm run conformance:report && git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md",
73
+ "postversion": "git push origin HEAD --follow-tags",
74
+ "conformance:update:wg17": "node tools/upgrade-wg17.mjs",
75
+ "conformance:update:neumerkel": "node test/run-neumerkel.mjs --update-report",
76
+ "conformance:check:neumerkel": "node test/run-neumerkel.mjs --cached --verify-report",
77
+ "conformance:sync:neumerkel": "node test/run-neumerkel.mjs --cached --update-report"
75
78
  }
76
79
  }
package/src/parser.js CHANGED
@@ -811,6 +811,14 @@ class Parser {
811
811
  let left = this.parsePrefixTerm(minPrecedence, allowBar, allowOperatorAtom);
812
812
  const leftIsBareOperatorAtom = initialWasCurrentOperator &&
813
813
  left.type === ATOM && left.name === initialOperatorName;
814
+ // Neumerkel syntax #379 (2026-09-03) pins a strict-ISO distinction for
815
+ // the processor-defined grammar-rule operator: `(-->)/2` is not an ISO
816
+ // predicate-indicator spelling. Keep the ordinary profile permissive for
817
+ // existing DCG/module code; the moving ISO conformity gate runs strict.
818
+ if (this.strictIso && left.type === ATOM && left.name === '-->' &&
819
+ this.operatorTokenName() === '/') {
820
+ throw new Error(`parse line ${this.token.line}: operator atom --> is not permitted in a strict ISO predicate indicator`);
821
+ }
814
822
  let strictPostfixPrecedence = null;
815
823
  while (true) {
816
824
  const op = this.token.type === TOK.COMMA && allowComma
@@ -270,17 +270,30 @@ requirement of ISO/IEC 13211-2:2000 or ISO/IEC TS 13211-3.
270
270
 
271
271
  ## Release gate
272
272
 
273
- A release intended to advance ISO conformance must pass all of:
273
+ A release intended to advance ISO conformance uses one canonical command:
274
274
 
275
275
  ```sh
276
276
  npm test
277
- npm run test:iso-strict
277
+ ```
278
+
279
+ That command first fetches and runs the **latest seven Neumerkel conformity
280
+ sources**, then executes the deterministic local conformance, strict ISO,
281
+ vendored WG17 regression, regression/API, examples, documentation and
282
+ architecture gates. The upstream case counts are discovered dynamically.
283
+
284
+ Useful focused commands are:
285
+
286
+ ```sh
278
287
  npm run test:conformance
288
+ npm run test:neumerkel
289
+ npm run test:iso
279
290
  npm run test:wg17
291
+ npm run test:offline
280
292
  ```
281
293
 
282
- The unified `npm test` gate includes the strict-core suite. Expected conformance
283
- outputs are never auto-accepted.
294
+ `test:offline` is a development/reproduction aid; it is not sufficient for a
295
+ release claim that says EyeProlog passes the latest Neumerkel suites. Expected
296
+ conformance outputs are never auto-accepted.
284
297
 
285
298
  ## Exit criteria for a full conformance claim
286
299
 
@@ -300,7 +313,7 @@ post-N289 STC drafts remain review input until standardized.
300
313
  | Implementation-specific strict/normal boundary is documented and tested | covered | all 5.5 hooks have explicit dispositions; the WG17 cross-profile gate verifies syntax-preservation for standard text accepted by the strict reader. |
301
314
  | Published Corrigenda 1-3 are incorporated | covered | `ISO-CORRIGENDA-MATRIX.md` inventories every published amendment cluster with executable, editorial, or superseded disposition |
302
315
  | Current post-N289 draft is tracked without silently changing the published baseline | covered | `STC-DRAFT-STATUS.md` tracks reviewed draft items separately from normative requirements |
303
- | Externally sourced syntax corpus is an offline release gate | covered | the vendored WG17 syntax matrix has 366 executable dispositions checked against its upstream expectations; this is external review evidence, not certification |
316
+ | Latest Neumerkel conformity is a live release gate | covered | `npm test` fetches and executes the seven current TU Wien conformity sources with dynamic inventories; the vendored WG17 matrix remains an offline reviewed-outcome regression layer |
304
317
  | Third-party standard-core regression provenance is retained | covered | adapted Logtalk, Scryer, Trealla, and SWI-Prolog cases retain source identifiers and licenses in `THIRD_PARTY.md` |
305
318
  | No unexplained deviation remains in the release-facing ledger | covered | the release-facing ledger contains no remaining `audit` rows; documented variation points are implementation-defined/specific or draft-only rather than unexplained deviations. |
306
319
 
@@ -0,0 +1,38 @@
1
+ # EyeProlog — latest Neumerkel conformity
2
+
3
+ Status: **PASS** — **686/686** discovered upstream cases passed.
4
+
5
+ This tracked report records the latest upstream inventory successfully checked by EyeProlog.
6
+ `npm test` fetches the seven TU Wien sources again and executes the discovered cases.
7
+ Release/report checks can additionally require these tracked counts to match the live suites.
8
+ Counts are output from upstream, not hard-coded test constants.
9
+
10
+ | Suite | Passed | Total |
11
+ |---|---:|---:|
12
+ | syntax | 379 | 379 |
13
+ | number_chars/2 | 86 | 86 |
14
+ | variable_names/1 | 75 | 75 |
15
+ | dif/2 | 26 | 26 |
16
+ | length/2 | 37 | 37 |
17
+ | phrase/2,3 | 58 | 58 |
18
+ | setup_call_cleanup/3 | 25 | 25 |
19
+ | **Total** | **686** | **686** |
20
+
21
+ ## Upstream sources
22
+
23
+ - [syntax](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing)
24
+ - [number_chars](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/number_chars_cont_quad.pl)
25
+ - [variable_names](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/variable_names_quad.pl)
26
+ - [dif](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/dif)
27
+ - [length](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/length_quad.pl)
28
+ - [phrase](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/phrase_quad.pl)
29
+ - [cleanup](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/cleanup)
30
+
31
+ Exact fetched bytes, SHA-256 hashes, fetch timestamps, and HTTP validators remain under
32
+ Git-ignored `.cache/neumerkel/` for local audit/reproduction and are intentionally not committed.
33
+ A normal test run warns when this tracked report is stale. Refresh directly from live
34
+ upstream with `npm run conformance:update:neumerkel`, or sync the exact successful
35
+ snapshot already fetched by `npm test` with `npm run conformance:sync:neumerkel`.
36
+ `npm run conformance:check:neumerkel` verifies the tracked report against that last
37
+ successful live snapshot without fetching upstream a second time.
38
+
@@ -0,0 +1,73 @@
1
+ # Live Neumerkel conformity gate
2
+
3
+ EyeProlog treats Ulrich Neumerkel's current ISO/WG17 conformity material as a
4
+ moving upstream release gate, not as a frozen snapshot with permanent case
5
+ counts.
6
+
7
+ `npm run test:neumerkel` fetches these seven TU Wien sources on every live run:
8
+
9
+ 1. `conformity_testing` — Part 1 syntax/reader/writer matrix;
10
+ 2. `number_chars_cont_quad.pl` — `number_chars/2` continuation corpus;
11
+ 3. `variable_names_quad.pl` — `variable_names/1` corpus;
12
+ 4. `dif` — `dif/2` comparison table;
13
+ 5. `length_quad.pl` — `length/2` corpus;
14
+ 6. `phrase_quad.pl` — `phrase/2,3` corpus;
15
+ 7. `cleanup` — `setup_call_cleanup/3` examples.
16
+
17
+ The runner discovers the inventory at run time. A new upstream row is therefore
18
+ executed automatically and a removed row disappears automatically. The syntax
19
+ extractor keys the expected result from TU Wien's labelled `Codex` column rather
20
+ than assuming a fixed cell position, and cross-checks the discovered inventory
21
+ against the total declared by the live page so hand-edited HTML cannot silently
22
+ reduce coverage.
23
+
24
+ ## Tracked GitHub evidence
25
+
26
+ The latest successful discovered inventory is committed as
27
+ [`NEUMERKEL-LATEST.md`](NEUMERKEL-LATEST.md). This is the stable report to link
28
+ from GitHub, releases, or other documentation.
29
+
30
+ A normal live run always executes the current upstream inventory. If the tracked
31
+ Markdown no longer matches, the test still reflects engine conformance and prints a
32
+ warning with the refresh command:
33
+
34
+ ```sh
35
+ npm run conformance:update:neumerkel
36
+ ```
37
+
38
+ Commit the resulting `test/conformance/NEUMERKEL-LATEST.md` after reviewing the
39
+ change. After a successful `npm test`, `npm run conformance:sync:neumerkel` writes
40
+ the tracked report from the exact cached source bytes that just passed, avoiding a
41
+ second network fetch. `npm run conformance:check:neumerkel` verifies the tracked
42
+ report against that same last successful snapshot. The npm version lifecycle
43
+ uses this race-free sync path and stages the generated reports into the release
44
+ commit. The tracked report intentionally omits fetch timestamps and HTTP validators,
45
+ so repeated runs against unchanged upstream suites do not dirty the checkout.
46
+
47
+ ## Local audit cache
48
+
49
+ Exact downloaded bytes, SHA-256 hashes, fetch timestamps, HTTP validators, and
50
+ machine-readable results are kept under `.cache/neumerkel/`. `.cache/` remains
51
+ Git-ignored: it is an audit/reproduction cache, not published project evidence.
52
+
53
+ Use:
54
+
55
+ ```sh
56
+ npm run test:neumerkel
57
+ ```
58
+
59
+ for the canonical live check. For offline reproduction of the exact last live
60
+ fetch, use:
61
+
62
+ ```sh
63
+ npm run test:neumerkel:cached
64
+ ```
65
+
66
+ The cached command never claims to check the latest upstream suites by itself.
67
+ The release flow first performs the canonical live `npm test`, then uses those exact
68
+ just-fetched bytes only to synchronize and verify the tracked evidence without
69
+ contacting upstream twice.
70
+
71
+ The vendored WG17 syntax matrix remains useful as a deterministic reviewed
72
+ regression snapshot, but it is secondary to this live gate: passing the snapshot
73
+ cannot mask a new or changed Neumerkel case.
@@ -69,68 +69,57 @@ Case names may be nested in category directories such as `arithmetic/`, `strings
69
69
 
70
70
  ## Running the suite
71
71
 
72
- Run all tests, including conformance, regression, documentation sync, API,
73
- examples, and book examples:
72
+ The release gate is network-aware by design: it checks the current upstream
73
+ Neumerkel conformity material before the deterministic local corpus.
74
74
 
75
75
  ```sh
76
76
  npm test
77
77
  ```
78
78
 
79
- Run only the conformance suite:
79
+ For an offline-only development pass:
80
80
 
81
81
  ```sh
82
- node test/run-conformance.mjs
82
+ npm run test:offline
83
83
  ```
84
84
 
85
- Run the Part 1 + Corrigenda strict-core processor gate:
85
+ The conformance commands are:
86
86
 
87
87
  ```sh
88
- npm run test:iso-strict
88
+ npm run test:conformance # live Neumerkel + local ISO/conformance layers
89
+ npm run test:conformance:offline # same local layers, no network
90
+ npm run test:neumerkel # the seven live upstream suites only
91
+ npm run test:neumerkel:cached # exact last fetched bytes; reproduction only
92
+ npm run conformance:check:neumerkel # verify tracked report against last successful live snapshot
93
+ npm run test:iso # Part 1 + Corrigenda strict-core processor gate
94
+ npm run test:wg17 # vendored reviewed WG17 syntax regression
89
95
  ```
90
96
 
91
- Run the vendored WG17 conformity matrix independently:
97
+ `test:neumerkel` always fetches the current TU Wien sources. It does not skip a
98
+ fetch because a cache exists. The runner discovers the number of active tests from those sources and fails on any newly introduced case EyeProlog does not pass. If the stable, tracked [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) is stale, normal tests warn rather than turning a passing engine run into a failure. `npm run conformance:update:neumerkel` performs a fresh live run and refreshes the report; after `npm test`, `npm run conformance:sync:neumerkel` refreshes it from the exact successful cached snapshot; and `npm run conformance:check:neumerkel` verifies that snapshot without a second network fetch. Exact bytes, SHA-256 hashes, timestamps, and HTTP validators stay under Git-ignored `.cache/neumerkel/` for audit/reproduction only. See [NEUMERKEL-LIVE.md](NEUMERKEL-LIVE.md).
92
99
 
93
- ```sh
94
- npm run test:wg17
95
- ```
96
-
97
- Refresh the WG17 snapshot from the TU Wien conformity tables before a release
98
- or whenever upstream changes:
100
+ The vendored WG17 syntax snapshot is intentionally secondary. Update all upstream conformance evidence with `npm run conformance:update`, or use the focused commands:
99
101
 
100
102
  ```sh
101
- npm run wg17:upgrade
103
+ npm run conformance:update:wg17
104
+ npm run conformance:update:neumerkel
105
+ npm run conformance:sync:neumerkel
102
106
  npm run test:wg17
103
107
  ```
104
108
 
105
- `wg17:upgrade` reconciles the upstream inventory by identifier: unchanged cases
106
- may keep their reviewed exact outcomes as an additional regression lock,
107
- removed cases disappear, and new or semantically changed cases initially have
108
- no local snapshot. **Every case is always checked independently against the
109
- upstream Codex expectation**, so a reviewed EyeProlog outcome can never make a
110
- non-conforming result pass (the failure mode that previously hid WG17 #227).
111
- The runner follows the upstream `read(G), G` input protocol, including the
112
- terminating newline, so stream-sensitive cases such as #270 and #271 exercise
113
- the characters left after `read/1`. Normal `npm test` remains offline and uses
114
- only the committed upstream snapshot.
109
+ Every vendored syntax case is still checked against its upstream Codex
110
+ expectation; reviewed exact outcomes are an additional regression lock rather
111
+ than an alternative acceptance rule.
115
112
 
116
- Regenerate the public conformance report, including the executable WG17 syntax status and the file-based category inventory:
113
+ Regenerate the top-level local-corpus report with `npm run conformance:report`. It links to [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) rather than duplicating live evidence. Counts in the tracked Neumerkel report are generated evidence, not permanent constants in project policy.
117
114
 
118
- ```sh
119
- node test/run-conformance-report.mjs
120
- node test/run-conformance-report.mjs conformance-report.md
121
- ```
122
-
123
- Run matching conformance cases by passing a filename or directory fragment:
115
+ Run a matching local file-based conformance subset directly with:
124
116
 
125
117
  ```sh
126
118
  node test/run-conformance.mjs reusable
127
- node test/run-conformance.mjs 092_scalar_string_conversions
128
119
  node test/run-conformance.mjs variables/
129
120
  node test/run-conformance.mjs error/variables
130
121
  ```
131
122
 
132
- The runner executes normal programs with queries in-process through the public JavaScript API so small conformance cases avoid measuring Node startup overhead. Warning and proof cases intentionally use the CLI because warning output and `why/2` proof output are host-interface contracts.
133
-
134
123
  ## Scope
135
124
 
136
125
  The corpus covers accepted syntax, typed scalar identity and explicit scalar conversions, query answers,
@@ -147,12 +136,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
147
136
  Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
148
137
  are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
149
138
 
150
- The corpus has 386 cases in `iso/` and 802 file-based conformance cases in
151
- total. Of those, 11 cases in `stc/` are explicitly labelled working-draft
152
- review evidence rather than normative ISO claims. The separate strict-reader WG17 matrix has 366 executable dispositions.
153
- The generated `conformance-report.md` is the authoritative source for the current
154
- executable WG17 syntax result and file-based category totals. Together with regression, documentation-sync, API, example,
155
- and book-example checks, `npm test` is the release gate.
139
+ The corpus has 386 cases in `iso/` and 802 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The separate vendored strict-reader WG17 matrix is a deterministic regression snapshot; the live Neumerkel gate discovers the current upstream inventory at run time; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
156
140
 
157
141
  ## Updating expected output
158
142
 
@@ -46,6 +46,6 @@ None.
46
46
 
47
47
  ## Maintenance
48
48
 
49
- 1. Run `npm run wg17:upgrade` to reconcile the dated fixture with upstream.
49
+ 1. Run `npm run conformance:update` to reconcile the dated fixture with upstream.
50
50
  2. Review every new or changed ISO expectation before adding its expected outcome.
51
51
  3. Run `npm run test:wg17` and keep this generated status page synchronized.