eyeprolog 1.5.82 → 1.5.84
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 +2 -3
- package/conformance-report.md +11 -19
- package/package.json +1 -2
- package/test/README.md +3 -3
- package/test/conformance/ISO-COMPLIANCE.md +9 -9
- package/test/conformance/NEUMERKEL-LATEST.md +7 -3
- package/test/conformance/NEUMERKEL-LIVE.md +6 -3
- package/test/conformance/README.md +13 -16
- package/test/conformance/cases/iso/trealla_bagof_multi_free_variables.pl +23 -0
- package/test/conformance/cases/iso/trealla_sub_atom_modes.pl +32 -0
- package/test/conformance/expected/iso/trealla_bagof_multi_free_variables.pl +5 -0
- package/test/conformance/expected/iso/trealla_sub_atom_modes.pl +22 -0
- package/test/neumerkel.mjs +45 -19
- package/test/regression/cases-documentation-sync.mjs +7 -44
- package/test/regression/support.mjs +6 -8
- package/test/run-all.mjs +0 -2
- package/test/run-conformance-all.mjs +0 -2
- package/test/run-conformance-report.mjs +14 -75
- package/test/run-wg17.mjs +20 -39
- package/test/test-style.mjs +2 -2
- package/the-art-of-eyeprolog.md +13 -10
- package/tools/{upgrade-wg17.mjs → wg17-syntax.mjs} +4 -228
- package/test/conformance/WG17-SYNTAX-STATUS.md +0 -51
- package/test/conformance/wg17-syntax-coverage.json +0 -21
- package/tools/report-wg17-syntax-coverage.mjs +0 -167
package/README.md
CHANGED
|
@@ -95,11 +95,10 @@ npm test
|
|
|
95
95
|
```
|
|
96
96
|
The npm command list is deliberately small:
|
|
97
97
|
|
|
98
|
-
- `npm test` (or `npm run test`): run the release gate, including live upstream conformity checks.
|
|
99
|
-
- `npm run conformance:update:wg17`: update the vendored WG17 syntax cases and their inventory documentation.
|
|
98
|
+
- `npm test` (or `npm run test`): run the release gate, including live upstream conformity checks (WG17 syntax among them).
|
|
100
99
|
- `npm run generate`: rebuild generated library and book files.
|
|
101
100
|
- `npm run benchmark`: run the wall-clock benchmarks.
|
|
102
101
|
|
|
103
|
-
Use `npm test -- --offline` for a network-free local pass. Focused checks remain available directly, for example `node test/run-regression.mjs docs
|
|
102
|
+
Use `npm test -- --offline` for a network-free local pass (this also skips the live-discovered WG17 syntax check, since it has no offline snapshot). Focused checks remain available directly, for example `node test/run-regression.mjs docs`; see [test runners](test/README.md). The automatic version hooks still run the release gate, refresh and stage conformance reports, and push the release. Detailed upstream report maintenance is documented in the [conformance guide](test/conformance/README.md).
|
|
104
103
|
|
|
105
104
|
EyeProlog is released under the [MIT License](LICENSE.md).
|
package/conformance-report.md
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
# EyeProlog conformance report
|
|
2
2
|
|
|
3
|
-
This report combines
|
|
4
|
-
conformance corpus under `test/conformance/`. The
|
|
5
|
-
when this report is generated; it is not inferred from fixture counts.
|
|
3
|
+
This report combines a live external conformance gate with the file-based
|
|
4
|
+
conformance corpus under `test/conformance/`. The file-based corpus is
|
|
5
|
+
measured when this report is generated; it is not inferred from fixture counts.
|
|
6
6
|
|
|
7
7
|
## Latest Neumerkel evidence
|
|
8
8
|
|
|
9
|
-
See the tracked [latest Neumerkel conformity report](test/conformance/NEUMERKEL-LATEST.md)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
| Gate | Passed | Total | Status |
|
|
17
|
-
|---|---:|---:|---|
|
|
18
|
-
| WG17 syntax | 379 | 379 | pass |
|
|
19
|
-
|
|
20
|
-
The WG17 syntax row executes the vendored 379-case conformity-testing matrix
|
|
21
|
-
against EyeProlog's strict ISO reader/writer. A behavior fix such as operator-token
|
|
22
|
-
spelling therefore changes this report even when no corpus file is added or removed.
|
|
9
|
+
See the tracked [latest Neumerkel conformity report](test/conformance/NEUMERKEL-LATEST.md)
|
|
10
|
+
for the executable external gate, including WG17 syntax conformance: `npm test`
|
|
11
|
+
fetches all eight TU Wien sources (syntax discovered and executed live, not a
|
|
12
|
+
vendored fixture) and executes the discovered inventory. The release workflow
|
|
13
|
+
then synchronizes this tracked report from those exact successful cached source
|
|
14
|
+
bytes, avoiding a second live fetch and its race window.
|
|
23
15
|
|
|
24
16
|
## File-based corpus inventory
|
|
25
17
|
|
|
@@ -32,7 +24,7 @@ spelling therefore changes this report even when no corpus file is added or remo
|
|
|
32
24
|
| context | 11 | 0 | 0 | 0 | 11 |
|
|
33
25
|
| control | 15 | 0 | 0 | 0 | 15 |
|
|
34
26
|
| explicit-tabling | 6 | 0 | 0 | 0 | 6 |
|
|
35
|
-
| iso |
|
|
27
|
+
| iso | 176 | 219 | 0 | 0 | 395 |
|
|
36
28
|
| lists | 52 | 3 | 0 | 0 | 55 |
|
|
37
29
|
| modules | 2 | 0 | 0 | 0 | 2 |
|
|
38
30
|
| negation | 8 | 0 | 19 | 0 | 27 |
|
|
@@ -45,7 +37,7 @@ spelling therefore changes this report even when no corpus file is added or remo
|
|
|
45
37
|
| terms | 26 | 3 | 0 | 0 | 29 |
|
|
46
38
|
| unification | 18 | 0 | 0 | 0 | 18 |
|
|
47
39
|
| variables | 16 | 7 | 0 | 0 | 23 |
|
|
48
|
-
| **Total** | **
|
|
40
|
+
| **Total** | **498** | **274** | **19** | **21** | **812** |
|
|
49
41
|
|
|
50
42
|
## DCG conformance clarification
|
|
51
43
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.5.
|
|
6
|
+
"version": "1.5.84",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"test": "node test/run-all.mjs",
|
|
49
|
-
"conformance:update:wg17": "node tools/upgrade-wg17.mjs",
|
|
50
49
|
"benchmark": "node test/benchmark.mjs",
|
|
51
50
|
"generate": "node tools/generate-library-autoload-index.mjs && node tools/generate-predicate-reference.mjs && node tools/extract-book-examples.mjs",
|
|
52
51
|
"preversion": "npm test && node test/run-neumerkel.mjs --cached --update-report && node test/run-conformance-report.mjs conformance-report.md && git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md",
|
package/test/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Test runners
|
|
2
2
|
|
|
3
|
-
Normally, run `npm test`. Use `npm test -- --offline` to skip live upstream fetching
|
|
4
|
-
|
|
3
|
+
Normally, run `npm test`. Use `npm test -- --offline` to skip live upstream fetching
|
|
4
|
+
(this also skips the WG17 syntax check, which is discovered live and has no
|
|
5
|
+
offline snapshot).
|
|
5
6
|
|
|
6
7
|
For development, run a focused check directly from the repository root:
|
|
7
8
|
|
|
@@ -9,7 +10,6 @@ For development, run a focused check directly from the repository root:
|
|
|
9
10
|
node test/run-regression.mjs # all regression sections
|
|
10
11
|
node test/run-regression.mjs docs # documentation checks only
|
|
11
12
|
node test/run-conformance-all.mjs # conformance layers; accepts --offline
|
|
12
|
-
node test/run-wg17.mjs # vendored WG17 syntax
|
|
13
13
|
node test/run-iso-strict.mjs # strict ISO core
|
|
14
14
|
node test/run-iso-part2-amendment.mjs
|
|
15
15
|
node test/run-neumerkel.mjs # live upstream; --cached reproduces last fetch
|
|
@@ -23,7 +23,7 @@ relevant implementation-dependent overlap choice to an explicit review outcome.
|
|
|
23
23
|
|
|
24
24
|
| Requirement | Status | EyeProlog evidence / remaining work |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| 5.1(a) prepare conforming Prolog text | covered | Clause 6 token/term production and rejection families are mapped by the strict production gate and the complete
|
|
26
|
+
| 5.1(a) prepare conforming Prolog text | covered | Clause 6 token/term production and rejection families are mapped by the strict production gate and the complete WG17 syntax matrix, discovered dynamically and executed live as part of the Neumerkel conformity gate; 7.4 preparation/directive behavior is separately closed. |
|
|
27
27
|
| 5.1(b) execute conforming Prolog goals | covered | Clause 7 term/control/execution semantics, the higher-level 7.10 stream model, 7.11 flags, the 7.12 error envelope, all 8.2-8.17 built-in rows, and Clause 9 arithmetic have explicit executable dispositions. |
|
|
28
28
|
| 5.1(c) reject nonconforming text/read-terms | covered | WG17 negative syntax cases, focused malformed-production/escape/comment/operator cases, read-term syntax errors, and strict rejection of implementation-specific syntax provide explicit rejection evidence across the Clause 6 families. |
|
|
29
29
|
| 5.1(d) document permitted variations | covered | The clause-by-clause [ISO 5.4 decision index](ISO-IMPLEMENTATION-DEFINED.md) records every explicit implementation-defined decision found in the Part 1 + Corrigenda baseline and separately inventories implementation-specific extension families. The index retains an `gap` category for future discoveries; no current release-facing row depends on one. |
|
|
@@ -35,7 +35,7 @@ relevant implementation-dependent overlap choice to an explicit review outcome.
|
|
|
35
35
|
|
|
36
36
|
| Standard area | Status | Current evidence |
|
|
37
37
|
| --- | --- | --- |
|
|
38
|
-
| Clause 6 — tokens, terms, lists, operators, quoted text | covered | The strict Clause 6 gate maps atomic/variable/compound/operator/list/curly/double-quoted term forms, layout/comments, quoted escapes, integer bases/character codes, floating tokens, and solo/meta token boundaries, with malformed counterparts rejected. The complete
|
|
38
|
+
| Clause 6 — tokens, terms, lists, operators, quoted text | covered | The strict Clause 6 gate maps atomic/variable/compound/operator/list/curly/double-quoted term forms, layout/comments, quoted escapes, integer bases/character codes, floating tokens, and solo/meta token boundaries, with malformed counterparts rejected. The complete WG17 syntax matrix, discovered dynamically at run time, provides externally sourced production-level expectations, and a cross-profile gate verifies that normal syntax extensions preserve every strict-accepted standard observation. The implementation-defined 6.5/6.6 PCS/collation choices are documented separately. |
|
|
39
39
|
| 7.1-7.3 — term types, term order, unification | covered | [ISO-TERM-SEMANTICS-MATRIX.md](ISO-TERM-SEMANTICS-MATRIX.md) records the five mutually exclusive strict term types, derived variable/compound/list notions, the complete standard-order classes and implementation-dependent variable-order choice, NSTO MGU/failure behavior, and EyeProlog's permitted consistent occurs-check failure for Part 1's undefined STO ordinary-unification cases. The normal JavaScript API string term is documented as a 5.5.4 extension and rejected at strict program/goal entry. |
|
|
40
40
|
| 7.4 — Prolog text and directives | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes the preparation/directive rows: declaration semantics, source clauses, cross-text operators/character conversion/flags, initialization order/lifetime, `include/1`, and one-time `ensure_loaded/1`, with implementation-defined cross-text choices indexed under 5.4. |
|
|
41
41
|
| 7.5-7.6 — database and term/clause conversion | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes static/dynamic and private/public procedure semantics, clause order, logical-update visibility, empty/unknown lifetime, and term/body/clause conversion. Source and runtime assertion conversion recurse through `,/2`, `;/2`, and `->/2`, preserve variable identity, and protect standardized static/control procedures. |
|
|
@@ -278,10 +278,11 @@ A release intended to advance ISO conformance uses one canonical command:
|
|
|
278
278
|
npm test
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
-
That command first fetches and runs the **latest
|
|
282
|
-
sources
|
|
283
|
-
|
|
284
|
-
architecture gates. The upstream
|
|
281
|
+
That command first fetches and runs the **latest eight Neumerkel conformity
|
|
282
|
+
sources** (WG17 syntax among them, discovered live, not vendored), then
|
|
283
|
+
executes the deterministic local conformance, strict ISO,
|
|
284
|
+
regression/API, examples, documentation and architecture gates. The upstream
|
|
285
|
+
case counts are discovered dynamically.
|
|
285
286
|
|
|
286
287
|
Useful focused commands are:
|
|
287
288
|
|
|
@@ -289,7 +290,6 @@ Useful focused commands are:
|
|
|
289
290
|
node test/run-conformance-all.mjs
|
|
290
291
|
node test/run-neumerkel.mjs
|
|
291
292
|
node test/run-iso-strict.mjs
|
|
292
|
-
node test/run-wg17.mjs
|
|
293
293
|
npm test -- --offline
|
|
294
294
|
```
|
|
295
295
|
|
|
@@ -307,7 +307,7 @@ post-N289 STC drafts remain review input until standardized.
|
|
|
307
307
|
| Criterion | Status | Evidence |
|
|
308
308
|
| --- | --- | --- |
|
|
309
309
|
| Clause 5 processor obligations have explicit dispositions | covered | `ISO-PROCESSOR-REQUIREMENTS.md` gives explicit outcomes for 5.1-5.5, including the strict/normal extension boundary. |
|
|
310
|
-
| Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the
|
|
310
|
+
| Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the live-discovered WG17 matrix cover the standard families; cross-profile preservation verifies that normal extensions do not reinterpret strict-accepted text. |
|
|
311
311
|
| Clause 7 semantic requirements have explicit dispositions | covered | 7.1-7.12 now have explicit semantic, implementation-defined, or error-envelope dispositions backed by strict tests and the specialized matrices. |
|
|
312
312
|
| Clause 8 built-in modes/errors have explicit dispositions | covered | `ISO-BUILTIN-MODE-ERROR-MATRIX.md` accounts for 8.2-8.17 condition by condition, using grouped rows only where every grouped condition and its evidence are named |
|
|
313
313
|
| Clause 9 evaluable-functor requirements have explicit dispositions | covered | `ISO-EVALUABLE-FUNCTOR-MATRIX.md` plus the strict arithmetic regression closes the published Part 1 + Corrigenda arithmetic rows |
|
|
@@ -315,7 +315,7 @@ post-N289 STC drafts remain review input until standardized.
|
|
|
315
315
|
| 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. |
|
|
316
316
|
| Published Corrigenda 1-3 are incorporated | covered | `ISO-CORRIGENDA-MATRIX.md` inventories every published amendment cluster with executable, editorial, or superseded disposition |
|
|
317
317
|
| 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 |
|
|
318
|
-
| Latest Neumerkel conformity is a live release gate | covered | `npm test` fetches and executes the eight current TU Wien conformity sources with dynamic inventories
|
|
318
|
+
| Latest Neumerkel conformity is a live release gate | covered | `npm test` fetches and executes the eight current TU Wien conformity sources with dynamic inventories, including WG17 syntax; a small offline corpus of reviewed cases (`test/conformance/wg17-syntax-cases.json`) supplies an additional exact-outcome regression lock, cross-referenced by id against the live-discovered cases |
|
|
319
319
|
| 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` |
|
|
320
320
|
| No unexplained deviation remains in the release-facing ledger | covered | the release-facing ledger contains no remaining `review` rows; documented variation points are implementation-defined/specific or draft-only rather than unexplained deviations. |
|
|
321
321
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# EyeProlog — latest Neumerkel conformity
|
|
2
2
|
|
|
3
|
-
Status: **PASS** — **
|
|
3
|
+
Status: **PASS — 1 divergence to be addressed** — **718/719** discovered upstream cases passed.
|
|
4
4
|
|
|
5
5
|
This tracked report records the latest upstream inventory successfully checked by EyeProlog.
|
|
6
6
|
`npm test` fetches the eight TU Wien sources again and executes the discovered cases.
|
|
@@ -15,9 +15,13 @@ Counts are output from upstream, not hard-coded test constants.
|
|
|
15
15
|
| dif/2 | 26 | 26 |
|
|
16
16
|
| length/2 | 37 | 37 |
|
|
17
17
|
| phrase/2,3 | 58 | 58 |
|
|
18
|
-
| Prologue draft |
|
|
18
|
+
| Prologue draft | 32 | 33 |
|
|
19
19
|
| setup_call_cleanup/3 | 25 | 25 |
|
|
20
|
-
| **Total** | **
|
|
20
|
+
| **Total** | **718** | **719** |
|
|
21
|
+
|
|
22
|
+
## Known divergences
|
|
23
|
+
|
|
24
|
+
- **Prologue draft** (line 118): bounded=false: EyeProlog reports no max_integer value at all (ISO 7.11.1.1), so this quad's evaluation_error(int_overflow) | Max = unbounded pair never applies
|
|
21
25
|
|
|
22
26
|
## Upstream sources
|
|
23
27
|
|
|
@@ -19,9 +19,12 @@ The Prologue corpus carries one permanent, documented divergence (its
|
|
|
19
19
|
`max_integer` quad — see `KNOWN_QUAD_DIVERGENCES` in `test/neumerkel.mjs`):
|
|
20
20
|
EyeProlog's `bounded=false` reports no `max_integer` value at all, which
|
|
21
21
|
neither of the quad's two anticipated answers describes. That quad is still
|
|
22
|
-
executed and
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
executed every run and counted honestly (it does not report as passing), but
|
|
23
|
+
it does not abort the run the way an unexplained failure would; the tracked
|
|
24
|
+
report and console summary both say "N divergence(s) to be addressed" rather
|
|
25
|
+
than a bare pass/fail count. See `NEUMERKEL-LATEST.md`'s own "Known
|
|
26
|
+
divergences" section for the explanation. If the divergence ever stopped
|
|
27
|
+
reproducing, the counts would simply climb back to a plain, unqualified PASS.
|
|
25
28
|
|
|
26
29
|
The runner discovers the inventory at run time. A new upstream row is therefore
|
|
27
30
|
executed automatically and a removed row disappears automatically. The syntax
|
|
@@ -22,8 +22,10 @@ implements of Part 2 (modules) and Part 3 (definite clause grammar rules),
|
|
|
22
22
|
including the 2013 module-amendment evidence and the known gaps and deviations.
|
|
23
23
|
Neither part carries a conformance claim. Built-in rows may group closely related conditions only when the
|
|
24
24
|
row names every grouped condition and its executable evidence.
|
|
25
|
-
The exit checklist is embedded in [ISO-COMPLIANCE.md](ISO-COMPLIANCE.md).
|
|
26
|
-
|
|
25
|
+
The exit checklist is embedded in [ISO-COMPLIANCE.md](ISO-COMPLIANCE.md). WG17
|
|
26
|
+
syntax cases are discovered live and executed as part of the Neumerkel
|
|
27
|
+
conformity gate (see [NEUMERKEL-LIVE.md](NEUMERKEL-LIVE.md)); there is no
|
|
28
|
+
separate offline coverage ledger to keep in sync.
|
|
27
29
|
[STC-DRAFT-STATUS.md](STC-DRAFT-STATUS.md) separately tracks executable
|
|
28
30
|
implementation questions from the post-N289 working draft (reviewed through the 2026-08-23 items #73-#76); those cases are
|
|
29
31
|
review evidence, not normative ISO claims.
|
|
@@ -96,24 +98,19 @@ node test/run-neumerkel.mjs --cached # exact last fetched bytes; reproductio
|
|
|
96
98
|
node test/run-neumerkel.mjs --cached --verify-report # verify tracked report against last successful live snapshot
|
|
97
99
|
node test/run-iso-strict.mjs # Part 1 + Corrigenda strict-core processor gate
|
|
98
100
|
node test/run-iso-part2-amendment.mjs # 2013 Part 2 amendment module requirements
|
|
99
|
-
node test/run-wg17.mjs # vendored reviewed WG17 syntax regression
|
|
100
101
|
```
|
|
101
102
|
|
|
102
103
|
`node test/run-neumerkel.mjs` always fetches the current TU Wien sources. It does not skip a
|
|
103
104
|
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. `node test/run-neumerkel.mjs --update-report` performs a fresh live run and refreshes the report; after `npm test`, `node test/run-neumerkel.mjs --cached --update-report` refreshes it from the exact successful cached snapshot; and `node test/run-neumerkel.mjs --cached --verify-report` verifies that snapshot without a second network fetch. Exact bytes, SHA-256 hashes, timestamps, and HTTP validators stay under Git-ignored `.cache/neumerkel/` for inspection/reproduction only. See [NEUMERKEL-LIVE.md](NEUMERKEL-LIVE.md).
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Every vendored syntax case is still checked against its upstream Codex
|
|
115
|
-
expectation; reviewed exact outcomes are an additional regression lock rather
|
|
116
|
-
than an alternative acceptance rule.
|
|
106
|
+
WG17 syntax cases are discovered live, the same way the other seven TU Wien
|
|
107
|
+
sources are: there is no separate vendored snapshot or update step. A small
|
|
108
|
+
offline corpus, `test/conformance/wg17-syntax-cases.json`, additionally pins
|
|
109
|
+
the exact reviewed EyeProlog outcome for most cases, cross-referenced by id
|
|
110
|
+
against the live-discovered cases in `test/neumerkel.mjs`. Every case, live or
|
|
111
|
+
reviewed, is still checked against its upstream Codex expectation
|
|
112
|
+
(`matchesUpstreamExpectation` in `test/run-wg17.mjs`); a reviewed exact outcome
|
|
113
|
+
is an additional regression lock, never an alternative acceptance rule.
|
|
117
114
|
|
|
118
115
|
Regenerate the top-level local-corpus report with `node test/run-conformance-report.mjs conformance-report.md`. 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.
|
|
119
116
|
|
|
@@ -141,7 +138,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
|
|
|
141
138
|
Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
|
|
142
139
|
are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
|
|
143
140
|
|
|
144
|
-
The corpus has
|
|
141
|
+
The corpus has 395 cases in `iso/` and 812 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 strict-reader WG17 syntax matrix is discovered dynamically and executed live by the Neumerkel gate, the same as its other seven TU Wien sources; 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.
|
|
145
142
|
|
|
146
143
|
## Updating expected output
|
|
147
144
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
% Adapted from Trealla Prolog tests/tests/test0027.pl, test0030.pl, and test0031.pl.
|
|
2
|
+
% See test/conformance/THIRD_PARTY.md.
|
|
3
|
+
foo(a, b, cc).
|
|
4
|
+
foo(a, b, dd).
|
|
5
|
+
foo(b, c, ee).
|
|
6
|
+
foo(b, c, ff).
|
|
7
|
+
foo(c, c, gg).
|
|
8
|
+
foo(d, e, gg).
|
|
9
|
+
|
|
10
|
+
%% goal: bagof_grouped_by_two_free_variables(X0)
|
|
11
|
+
|
|
12
|
+
bagof_grouped_by_two_free_variables(Cs) :-
|
|
13
|
+
bagof(C, foo(_, _, C), Cs).
|
|
14
|
+
|
|
15
|
+
%% goal: bagof_two_free_variables_quantified(X0)
|
|
16
|
+
|
|
17
|
+
bagof_two_free_variables_quantified(Cs) :-
|
|
18
|
+
bagof(C, A^B^foo(A, B, C), Cs).
|
|
19
|
+
|
|
20
|
+
%% goal: setof_two_free_variables_quantified(X0)
|
|
21
|
+
|
|
22
|
+
setof_two_free_variables_quantified(Cs) :-
|
|
23
|
+
setof(C, A^B^foo(A, B, C), Cs).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
% Adapted from Trealla Prolog tests/tests/test0058.pl.
|
|
2
|
+
% See test/conformance/THIRD_PARTY.md.
|
|
3
|
+
|
|
4
|
+
%% goal: sub_atom_all(X0, X1, X2, X3)
|
|
5
|
+
|
|
6
|
+
sub_atom_all(B, L, A, S) :-
|
|
7
|
+
sub_atom(abc, B, L, A, S).
|
|
8
|
+
|
|
9
|
+
%% goal: sub_atom_length_2(X0, X1, X2)
|
|
10
|
+
|
|
11
|
+
sub_atom_length_2(B, A, S) :-
|
|
12
|
+
sub_atom(abc, B, 2, A, S).
|
|
13
|
+
|
|
14
|
+
%% goal: sub_atom_before_0_length_2(X0, X1)
|
|
15
|
+
|
|
16
|
+
sub_atom_before_0_length_2(A, S) :-
|
|
17
|
+
sub_atom(abc, 0, 2, A, S).
|
|
18
|
+
|
|
19
|
+
%% goal: sub_atom_before_0(X0, X1, X2)
|
|
20
|
+
|
|
21
|
+
sub_atom_before_0(L, A, S) :-
|
|
22
|
+
sub_atom(abc, 0, L, A, S).
|
|
23
|
+
|
|
24
|
+
%% goal: sub_atom_after_0(X0, X1, X2)
|
|
25
|
+
|
|
26
|
+
sub_atom_after_0(B, L, S) :-
|
|
27
|
+
sub_atom(abc, B, L, 0, S).
|
|
28
|
+
|
|
29
|
+
%% goal: sub_atom_length_2_after_0(X0, X1)
|
|
30
|
+
|
|
31
|
+
sub_atom_length_2_after_0(B, S) :-
|
|
32
|
+
sub_atom(abc, B, 2, 0, S).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
sub_atom_all(0, 0, 3, '').
|
|
2
|
+
sub_atom_all(0, 1, 2, a).
|
|
3
|
+
sub_atom_all(0, 2, 1, ab).
|
|
4
|
+
sub_atom_all(0, 3, 0, abc).
|
|
5
|
+
sub_atom_all(1, 0, 2, '').
|
|
6
|
+
sub_atom_all(1, 1, 1, b).
|
|
7
|
+
sub_atom_all(1, 2, 0, bc).
|
|
8
|
+
sub_atom_all(2, 0, 1, '').
|
|
9
|
+
sub_atom_all(2, 1, 0, c).
|
|
10
|
+
sub_atom_all(3, 0, 0, '').
|
|
11
|
+
sub_atom_length_2(0, 1, ab).
|
|
12
|
+
sub_atom_length_2(1, 0, bc).
|
|
13
|
+
sub_atom_before_0_length_2(1, ab).
|
|
14
|
+
sub_atom_before_0(0, 3, '').
|
|
15
|
+
sub_atom_before_0(1, 2, a).
|
|
16
|
+
sub_atom_before_0(2, 1, ab).
|
|
17
|
+
sub_atom_before_0(3, 0, abc).
|
|
18
|
+
sub_atom_after_0(0, 3, abc).
|
|
19
|
+
sub_atom_after_0(1, 2, bc).
|
|
20
|
+
sub_atom_after_0(2, 1, c).
|
|
21
|
+
sub_atom_after_0(3, 0, '').
|
|
22
|
+
sub_atom_length_2_after_0(1, bc).
|
package/test/neumerkel.mjs
CHANGED
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
htmlCellText,
|
|
12
12
|
parseWg17SyntaxTable,
|
|
13
13
|
setupInput,
|
|
14
|
-
} from '../tools/
|
|
14
|
+
} from '../tools/wg17-syntax.mjs';
|
|
15
15
|
import {
|
|
16
16
|
executeWg17Item,
|
|
17
17
|
matchesUpstreamExpectation,
|
|
18
|
+
readWg17SyntaxOutcomes,
|
|
18
19
|
wg17TestDescription,
|
|
19
20
|
} from './run-wg17.mjs';
|
|
20
21
|
|
|
@@ -308,10 +309,11 @@ function expectedMatches(expected, actual) {
|
|
|
308
309
|
|
|
309
310
|
// A permanent, deliberate divergence from an upstream quad: EyeProlog's own
|
|
310
311
|
// implementation choice, not a bug to chase. Keyed by corpus key, then by the
|
|
311
|
-
// answer description's source line.
|
|
312
|
-
//
|
|
313
|
-
// happened with the length corpus's
|
|
314
|
-
//
|
|
312
|
+
// answer description's source line. It is still reported and counted as an
|
|
313
|
+
// ordinary failing test (so the totals stay honest and it self-corrects the
|
|
314
|
+
// moment it stops reproducing, exactly as happened with the length corpus's
|
|
315
|
+
// occurs-check quad); it just does not abort the run the way an unexplained
|
|
316
|
+
// failure would.
|
|
315
317
|
const KNOWN_QUAD_DIVERGENCES = {
|
|
316
318
|
prologue: new Map([
|
|
317
319
|
[118, 'bounded=false: EyeProlog reports no max_integer value at all (ISO 7.11.1.1), ' +
|
|
@@ -358,17 +360,9 @@ function runQuadCorpus(reporter, label, item, knownDivergences = new Map()) {
|
|
|
358
360
|
const divergence = knownDivergences.get(description.line);
|
|
359
361
|
const name = divergence == null
|
|
360
362
|
? `${label} ${locator} (line ${description.line ?? '?'}) ?- ${formatQuadTerm(program, description.query)}`
|
|
361
|
-
: `${label} ${locator} (line ${description.line ?? '?'}, documented divergence) ?- ${formatQuadTerm(program, description.query)}`;
|
|
363
|
+
: `${label} ${locator} (line ${description.line ?? '?'}, documented divergence: ${divergence}) ?- ${formatQuadTerm(program, description.query)}`;
|
|
362
364
|
try {
|
|
363
365
|
reporter.test(name, () => {
|
|
364
|
-
if (divergence != null) {
|
|
365
|
-
// Verify the divergence itself, not just tolerate it: if it stops
|
|
366
|
-
// reproducing, that is exactly the kind of change worth noticing.
|
|
367
|
-
if (description.ok) {
|
|
368
|
-
throw new Error(`documented divergence (${divergence}) no longer reproduces; remove it from KNOWN_QUAD_DIVERGENCES`);
|
|
369
|
-
}
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
366
|
if (!description.ok) {
|
|
373
367
|
const reason = description.reason ? `: ${description.reason}` : '';
|
|
374
368
|
throw new Error(`${description.kind ?? 'failed'}${reason}`);
|
|
@@ -376,10 +370,12 @@ function runQuadCorpus(reporter, label, item, knownDivergences = new Map()) {
|
|
|
376
370
|
});
|
|
377
371
|
passed++;
|
|
378
372
|
} catch (error) {
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
|
|
373
|
+
// A documented divergence is expected to keep failing; note it without
|
|
374
|
+
// aborting the run over it. Upstream changes otherwise often arrive in
|
|
375
|
+
// small clusters -- keep running the corpus so one live test run
|
|
376
|
+
// exposes every new mismatch instead of forcing a fix/rerun cycle for
|
|
377
|
+
// each row.
|
|
378
|
+
if (divergence == null) failures.push(error);
|
|
383
379
|
}
|
|
384
380
|
});
|
|
385
381
|
return { total: result.results.length, passed, failures };
|
|
@@ -392,6 +388,7 @@ export async function executeNeumerkel({ reporter, mode = 'live', cacheDir = def
|
|
|
392
388
|
reporter.section(`Neumerkel conformity (${mode === 'live' && sourceDir == null ? 'live upstream' : sourceDir != null ? 'source fixtures' : 'cached'})`);
|
|
393
389
|
|
|
394
390
|
const syntaxCases = materializeSyntaxCases(sources.get('syntax').text);
|
|
391
|
+
const syntaxOutcomes = readWg17SyntaxOutcomes();
|
|
395
392
|
const syntaxFailures = [];
|
|
396
393
|
let syntaxPassed = 0;
|
|
397
394
|
for (const item of syntaxCases) {
|
|
@@ -401,6 +398,16 @@ export async function executeNeumerkel({ reporter, mode = 'live', cacheDir = def
|
|
|
401
398
|
if (!matchesUpstreamExpectation(item.expected, actual, item)) {
|
|
402
399
|
throw new Error(`syntax #${item.id} expected ${item.expected}; actual ${JSON.stringify(actual)}`);
|
|
403
400
|
}
|
|
401
|
+
// A case can also carry a reviewed exact-behavior lock (see
|
|
402
|
+
// wg17-syntax-outcomes.json): a stronger regression check than the
|
|
403
|
+
// upstream assertion alone, catching a change that still happens to
|
|
404
|
+
// satisfy the loose Codex expectation.
|
|
405
|
+
const reviewed = syntaxOutcomes.get(item.id);
|
|
406
|
+
if (reviewed != null && JSON.stringify(actual) !== JSON.stringify(reviewed)) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`syntax #${item.id} reviewed outcome changed; expected ${JSON.stringify(reviewed)}; actual ${JSON.stringify(actual)}`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
404
411
|
});
|
|
405
412
|
syntaxPassed++;
|
|
406
413
|
} catch (error) {
|
|
@@ -514,10 +521,21 @@ export function formatNeumerkelMarkdown({ summary }) {
|
|
|
514
521
|
const rows = labels.map(([key, label]) => ({ key, label, ...summary[key] }));
|
|
515
522
|
const passed = rows.reduce((sum, row) => sum + row.passed, 0);
|
|
516
523
|
const total = rows.reduce((sum, row) => sum + row.total, 0);
|
|
524
|
+
const divergenceCount = Object.values(KNOWN_QUAD_DIVERGENCES)
|
|
525
|
+
.reduce((sum, byLine) => sum + byLine.size, 0);
|
|
526
|
+
const shortfall = total - passed;
|
|
527
|
+
// A shortfall fully accounted for by known, documented divergences (see
|
|
528
|
+
// KNOWN_QUAD_DIVERGENCES and the list below) is not a build failure; any
|
|
529
|
+
// other shortfall is.
|
|
530
|
+
const status = shortfall === 0
|
|
531
|
+
? 'PASS'
|
|
532
|
+
: shortfall === divergenceCount
|
|
533
|
+
? `PASS — ${shortfall} divergence${shortfall === 1 ? '' : 's'} to be addressed`
|
|
534
|
+
: 'FAIL';
|
|
517
535
|
const lines = [
|
|
518
536
|
'# EyeProlog — latest Neumerkel conformity',
|
|
519
537
|
'',
|
|
520
|
-
`Status: **${
|
|
538
|
+
`Status: **${status}** — **${passed}/${total}** discovered upstream cases passed.`,
|
|
521
539
|
'',
|
|
522
540
|
'This tracked report records the latest upstream inventory successfully checked by EyeProlog.',
|
|
523
541
|
'`npm test` fetches the eight TU Wien sources again and executes the discovered cases.',
|
|
@@ -530,6 +548,14 @@ export function formatNeumerkelMarkdown({ summary }) {
|
|
|
530
548
|
for (const row of rows) lines.push(`| ${row.label} | ${row.passed} | ${row.total} |`);
|
|
531
549
|
lines.push(`| **Total** | **${passed}** | **${total}** |`);
|
|
532
550
|
|
|
551
|
+
if (divergenceCount > 0) {
|
|
552
|
+
lines.push('', '## Known divergences', '');
|
|
553
|
+
for (const [key, byLine] of Object.entries(KNOWN_QUAD_DIVERGENCES)) {
|
|
554
|
+
const label = labels.find(([labelKey]) => labelKey === key)?.[1] ?? key;
|
|
555
|
+
for (const [line, reason] of byLine) lines.push(`- **${label}** (line ${line}): ${reason}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
533
559
|
lines.push(
|
|
534
560
|
'',
|
|
535
561
|
'## Upstream sources',
|
|
@@ -7,8 +7,7 @@ import * as publicApi from '../../src/index.js';
|
|
|
7
7
|
import { createDefaultRegistry, eyePrologLibraryIndicators, eyePrologNativeLibraryIndicators, eyePrologPortableLibraryIndicators, standardLibrarySources } from '../../src/index.js';
|
|
8
8
|
import { assertEqual, assertIncludes, assertNotIncludes } from '../test-style.mjs';
|
|
9
9
|
import { buildConformanceReport, formatConformanceReport } from '../run-conformance-report.mjs';
|
|
10
|
-
import {
|
|
11
|
-
import { parseWg17SyntaxTable, updateWg17InventoryReferences } from '../../tools/upgrade-wg17.mjs';
|
|
10
|
+
import { parseWg17SyntaxTable } from '../../tools/wg17-syntax.mjs';
|
|
12
11
|
import { executeWg17Item, matchesUpstreamExpectation, readWg17SyntaxFixture } from '../run-wg17.mjs';
|
|
13
12
|
import {
|
|
14
13
|
assertArrayEqual,
|
|
@@ -163,29 +162,10 @@ ${profile}`;
|
|
|
163
162
|
},
|
|
164
163
|
},
|
|
165
164
|
{
|
|
166
|
-
name: 'WG17 syntax
|
|
167
|
-
run: () => {
|
|
168
|
-
const filename = path.join(testRoot, 'conformance', 'WG17-SYNTAX-STATUS.md');
|
|
169
|
-
assertEqual(fs.readFileSync(filename, 'utf8'), renderWg17SyntaxStatus(), 'WG17 syntax status');
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: 'WG17 inventory documentation accepts omitted counts and repairs partial updates',
|
|
174
|
-
run: () => {
|
|
175
|
-
const countFree = 'The vendored WG17 syntax snapshot is intentionally secondary.';
|
|
176
|
-
assertEqual(updateWg17InventoryReferences(countFree, 379), countFree, 'no count required');
|
|
177
|
-
const stale = 'The 366-case vendored WG17 matrix and 365-case WG17 syntax matrix; WG17 matrix has 366 executable cases. An unrelated 366-case suite stays unchanged.';
|
|
178
|
-
const expected = 'The 379-case vendored WG17 matrix and 379-case WG17 syntax matrix; WG17 matrix has 379 executable cases. An unrelated 366-case suite stays unchanged.';
|
|
179
|
-
const updated = updateWg17InventoryReferences(stale, 379);
|
|
180
|
-
assertEqual(updated, expected, 'all stale WG17 counts repaired independently of fixture state');
|
|
181
|
-
assertEqual(updateWg17InventoryReferences(updated, 379), updated, 'retry is idempotent');
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
name: 'WG17 upgrader accepts omitted HTML table end tags',
|
|
165
|
+
name: 'WG17 syntax parser accepts omitted HTML table end tags',
|
|
186
166
|
run: () => {
|
|
187
167
|
// HTML permits </td> and </tr> to be omitted. TU Wien uses this
|
|
188
|
-
// compact form, so the
|
|
168
|
+
// compact form, so the parser must not depend on explicit closes.
|
|
189
169
|
const rows = Array.from({ length: 120 }, (_, index) =>
|
|
190
170
|
`<tr><td>${index + 1}<td><code>write(${index + 1}).</code><td>ok`).join('\n');
|
|
191
171
|
const html = `<table><tr><th>#<th>Query<th>Codex${rows}</table>`;
|
|
@@ -197,7 +177,7 @@ ${profile}`;
|
|
|
197
177
|
},
|
|
198
178
|
},
|
|
199
179
|
{
|
|
200
|
-
name: 'WG17
|
|
180
|
+
name: 'WG17 syntax parser normalizes presentation non-breaking spaces',
|
|
201
181
|
run: () => {
|
|
202
182
|
const rows = Array.from({ length: 120 }, (_, index) =>
|
|
203
183
|
`<tr><td>${index + 1}<td>set_prolog_flag( double_quotes,chars).<td>succeeds`).join('\n');
|
|
@@ -207,7 +187,7 @@ ${profile}`;
|
|
|
207
187
|
},
|
|
208
188
|
},
|
|
209
189
|
{
|
|
210
|
-
name: 'WG17
|
|
190
|
+
name: 'WG17 syntax parser removes presentation footnote markers from Codex text',
|
|
211
191
|
run: () => {
|
|
212
192
|
const rows = Array.from({ length: 120 }, (_, index) =>
|
|
213
193
|
`<tr><td>${index + 1}<td>writeq(-(1^2)).<td>- (1^2)³`).join('\n');
|
|
@@ -649,7 +629,7 @@ ${profile}`;
|
|
|
649
629
|
const publishIndex = publishWorkflow.indexOf('run: npm publish');
|
|
650
630
|
assertEqual(testIndex >= 0 && testIndex < publishIndex, true, 'publish workflow test gate');
|
|
651
631
|
assertEqual(packIndex >= 0 && packIndex < publishIndex, true, 'publish workflow package gate');
|
|
652
|
-
assertArrayEqual(Object.keys(pkg.scripts).sort(), ['benchmark', '
|
|
632
|
+
assertArrayEqual(Object.keys(pkg.scripts).sort(), ['benchmark', 'generate', 'postversion', 'preversion', 'test'], 'small npm command surface');
|
|
653
633
|
assertEqual(pkg.scripts.test, 'node test/run-all.mjs', 'full release gate');
|
|
654
634
|
const runner = fs.readFileSync(path.join(packageRoot, 'test', 'run-all.mjs'), 'utf8');
|
|
655
635
|
assertIncludes(runner, 'runOpenRuleBenchChecks(reporter)', 'OpenRuleBench remains in release gate');
|
|
@@ -667,28 +647,11 @@ ${profile}`;
|
|
|
667
647
|
assertEqual(report.total.total >= 475, true, 'conformance case count');
|
|
668
648
|
assertEqual(report.total.positive + report.total.errors + report.total.warnings + report.total.proofs, report.total.total, 'conformance total');
|
|
669
649
|
assertEqual(report.rows.some((row) => row.category === 'legacy-numbered'), false, 'legacy-numbered category');
|
|
670
|
-
const wg17 = report.executable.find((gate) => gate.name === 'WG17 syntax');
|
|
671
|
-
const wg17FixtureTotal = readWg17SyntaxFixture().cases.length;
|
|
672
|
-
assertEqual(wg17?.total, wg17FixtureTotal, 'WG17 executable total');
|
|
673
|
-
assertEqual(wg17?.passed, wg17FixtureTotal, 'WG17 executable passed');
|
|
674
|
-
assertArrayEqual(wg17?.failures ?? [], [], 'WG17 executable failures');
|
|
675
650
|
const text = formatConformanceReport(report);
|
|
676
|
-
assertIncludes(text,
|
|
651
|
+
assertIncludes(text, 'latest Neumerkel conformity report', 'report links live evidence');
|
|
677
652
|
assertIncludes(text, '| variables |', 'report');
|
|
678
653
|
assertIncludes(text, '| Proofs |', 'report');
|
|
679
654
|
assertIncludes(text, '| **Total** |', 'report');
|
|
680
|
-
|
|
681
|
-
const failing = buildConformanceReport({
|
|
682
|
-
wg17Suite: (reporter) => {
|
|
683
|
-
reporter.section('WG17 syntax');
|
|
684
|
-
reporter.test('synthetic syntax failure', () => { throw new Error('synthetic mismatch'); });
|
|
685
|
-
reporter.sectionTotal('WG17 syntax');
|
|
686
|
-
},
|
|
687
|
-
});
|
|
688
|
-
assertEqual(failing.executable[0].passed, 0, 'failing WG17 executable passed');
|
|
689
|
-
assertEqual(failing.executable[0].total, 1, 'failing WG17 executable total');
|
|
690
|
-
assertEqual(failing.executionIssues.length, 1, 'failing WG17 report issue count');
|
|
691
|
-
assertIncludes(formatConformanceReport(failing), '| WG17 syntax | 0 | 1 | fail |', 'failing report');
|
|
692
655
|
},
|
|
693
656
|
},
|
|
694
657
|
|
|
@@ -512,7 +512,6 @@ export function documentedConformanceMetricIssues() {
|
|
|
512
512
|
const report = buildConformanceReport();
|
|
513
513
|
const iso = report.rows.find((row) => row.category === 'iso')?.total;
|
|
514
514
|
const total = report.total.total;
|
|
515
|
-
const wg17 = report.executable.find((gate) => gate.name === 'WG17 syntax')?.total;
|
|
516
515
|
const checks = [
|
|
517
516
|
{
|
|
518
517
|
file: path.join(packageRoot, 'the-art-of-eyeprolog.md'),
|
|
@@ -542,6 +541,9 @@ export function documentedConformanceMetricIssues() {
|
|
|
542
541
|
}
|
|
543
542
|
}
|
|
544
543
|
}
|
|
544
|
+
// WG17 syntax cases are discovered live (test/neumerkel.mjs), never
|
|
545
|
+
// vendored, so no document should hard-code a specific count for them: it
|
|
546
|
+
// would just go stale the next time upstream adds or removes a case.
|
|
545
547
|
for (const file of [
|
|
546
548
|
path.join(packageRoot, 'test', 'conformance', 'README.md'),
|
|
547
549
|
path.join(packageRoot, 'test', 'conformance', 'ISO-COMPLIANCE.md'),
|
|
@@ -552,13 +554,9 @@ export function documentedConformanceMetricIssues() {
|
|
|
552
554
|
...[...text.matchAll(/\b(\d+)-case[^\n|]*WG17/g)].map((match) => Number(match[1])),
|
|
553
555
|
...[...text.matchAll(/WG17[^\n|]*?\b(\d+) executable/g)].map((match) => Number(match[1])),
|
|
554
556
|
];
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
for (const claim of claims) {
|
|
560
|
-
if (claim !== wg17) issues.push(`${relative}: WG17 count ${claim} != ${wg17}`);
|
|
561
|
-
}
|
|
557
|
+
const dynamicPolicy = /live Neumerkel|current upstream inventory|discovered dynamically/i.test(text);
|
|
558
|
+
if (!dynamicPolicy) issues.push(`${relative}: WG17 dynamic-upstream policy not found`);
|
|
559
|
+
for (const claim of claims) issues.push(`${relative}: WG17 count ${claim} is hard-coded, not dynamic`);
|
|
562
560
|
}
|
|
563
561
|
return issues.sort();
|
|
564
562
|
}
|