Repository: tobie/specref (js)

Issue:
Consider supporting entries for specification series
Via discussion in https://github.com/tobie/specref/pull/810#discussion_r1722985394

Various specs in W3C come in levels that belong to the same series. Both levels and series have shortnames in W3C, but the series shortname is more to be seen as a way to redirect to the "current" level (which evolves over time). For example: `css-fonts-3`, `css-fonts-4`, `css-fonts-5` all belong to the `css-fonts` series. As the time of writing, the current spec is `css-fonts-4`.

Levels may be created after a first iteration on a spec. For example, `media-source-1` was known as `media-source` until level 2 got created. `media-source` is now the shortname of the underlying series, the current spec being `media-source-2`.

Specref does not have the concept of series. That means no entry for `css-fonts`, and no easy way to "follow" the current specification in the latter case, requiring clunky workarounds, as done in #810. It would be great to support series shortnames more directly in Specref. 

The end goal would be to make it easy to reference a spec by:
1. its series shortname, such as `css-fonts`, in which case the reference goes to the latest version of the current level in the series.
2. its shortname, such as `css-fonts-5`, in which case the reference goes to the latest version of that level.
3. a dated version, such as `css-fonts-4-20240201` in which case the reference goes to the specified version. The dated version typically includes the level, we should not need to create or use a `css-fonts-20240201` entry!

Adding support for series requires switching the W3C update script to the W3C API, because [`tr.rdf`](https://www.w3.org/2002/01/tr-automation/tr.rdf) does not know anything about series either.

Note the need to preserve past dated versions in the case where the level gets introduced afterwards (e.g. `media-source-20140717` needs to exist even though the `media-source` spec is now known as `media-source-1`). Also note that some specs start without a level, then get a level, and then get back to a no-level mode...

(I don't think the notion of specification series exists in other SDOs, although that could perhaps be useful to track versions of ETSI and ISO specs longer term)

Pull request description:
Support spec series and retrieve them for W3C specs
This adds support for specification series, as discussed in #811. Actual changes to the core logic are minimal and should be fully transparent for consumers: a new `isSeriesAlias` boolean flag can now be added to an alias to signal that it is a "series alias". This alias gets processed by bibref exactly as any other alias, except that bibref does not create version entries when the flag is set.

In other words, given:

```
{
  "css-fonts": {
    "aliasOf": "css-fonts-4",
    "isSeriesAlias": true
  }
}
```

`css-fonts` can then be used to reference `css-fonts-4` but, as opposed to a regular alias, `css-fonts-20240201` cannot be used to reference `css-fonts-4-20240201`. This is on purpose as it allows to update `css-fonts` later on to target `css-fonts-5` when it becomes the "current" specification.

The W3C script leverages the W3C API to retrieve the list of specification series and create/update series aliases when needed and possible. First run will add 243 series aliases.

Consumers won't even know that they are dealing with a series alias, but it's not clear that they need to know in any case, and we can make that more explicit later on if needed. Similarly, this does not link specifications that belong to the same series together. For example, nothing will say that `css-color-3`, `css-color-4`, `css-color-5` are part of the series `css-color`, only that `css-color` currently targets `css-color-4`. Same thing, I don't think that consumers of Specref currently need that information and we can consider adding a link to the series from specification entries afterwards when the need arises.

I certainly do not mind using another name for `isSeriesAlias`. We don't even need to encode the fact that it is a *series* alias in practice, merely that the alias does not create version entries.

Fixes #811.

Patch:
diff --git a/lib/bibref.js b/lib/bibref.js
index 866cbf681..58ed4ae8c 100644
--- a/lib/bibref.js
+++ b/lib/bibref.js
@@ -193,6 +193,9 @@ function expandRefs(refs) {
             });
             if (ref.aliases) {
                 ref.aliases.forEach(function(aliasKey) {
+                    if (refs[aliasKey].isSeriesAlias) {
+                        return;
+                    }
                     Object.keys(versions).forEach(function(subKey) {
                         // Watch out not to overwrite existing aliases.
                         if (!refs[aliasKey + '-' + subKey]) {
diff --git a/refs/ietf.json b/refs/ietf.json
index 5993363e0..ccbd5eeef 100644
--- a/refs/ietf.json
+++ b/refs/ietf.json
@@ -134533,6 +134533,9 @@
         ],
         "rawDate": "2020-02",
         "status": "Best Current Practice",
+        "updatedBy": [
+            "RFC9712"
+        ],
         "publisher": "IETF",
         "source": "https://www.rfc-editor.org/in-notes/rfc-index.xml"
     },
@@ -134545,6 +134548,9 @@
         ],
         "rawDate": "2020-02",
         "status": "Best Current Practice",
+        "updatedBy": [
+            "RFC9712"
+        ],
         "publisher": "IETF",
         "source": "https://www.rfc-editor.org/in-notes/rfc-index.xml"
     },
@@ -149881,6 +149887,23 @@
     "rfc971": {
         "aliasOf": "rfc0971"
     },
+    "rfc9712": {
+        "rfcNumber": "RFC9712",
+        "href": "https://www.rfc-editor.org/rfc/rfc9712",
+        "title": "IETF Meeting Venue Requirements Review",
+        "authors": [
+            "J. Daley",
+            "S. Turner"
+        ],
+        "rawDate": "2025-01",
+        "status": "Best Current Practice",
+        "updates": [
+            "RFC8718",
+            "RFC8719"
+        ],
+        "publisher": "IETF",
+        "source": "https://www.rfc-editor.org/in-notes/rfc-index.xml"
+    },
     "rfc9713": {
         "rfcNumber": "RFC9713",
         "href": "https://www.rfc-editor.org/rfc/rfc9713",
@@ -149896,6 +149919,31 @@
         "publisher": "IETF",
         "source": "https://www.rfc-editor.org/in-notes/rfc-index.xml"
     },
+    "rfc9715": {
+        "rfcNumber": "RFC9715",
+        "href": "https://www.rfc-editor.org/rfc/rfc9715",
+        "title": "IP Fragmentation Avoidance in DNS over UDP",
+        "authors": [
+            "K. Fujiwara",
+            "P. Vixie"
+        ],
+        "rawDate": "2025-01",
+        "status": "Informational",
+        "publisher": "IETF",
+        "source": "https://www.rfc-editor.org/in-notes/rfc-index.xml"
+    },
+    "rfc9717": {
+        "rfcNumber": "RFC9717",
+        "href": "https://www.rfc-editor.org/rfc/rfc9717",
+        "title": "A Routing Architecture for Satellite Networks",
+        "authors": [
+            "T. Li"
+        ],
+        "rawDate": "2025-01",
+        "status": "Informational",
+        "publisher": "IETF",
+        "source": "https://www.rfc-editor.org/in-notes/rfc-index.xml"
+    },
     "rfc9718": {
         "rfcNumber": "RFC9718",
         "href": "https://www.rfc-editor.org/rfc/rfc9718",
diff --git a/refs/w3c.json b/refs/w3c.json
index 37175fb27..1ae4ab166 100644
--- a/refs/w3c.json
+++ b/refs/w3c.json
@@ -111847,7 +111847,7 @@
         ],
         "href": "https://www.w3.org/TR/miniapp-manifest/",
         "title": "MiniApp Manifest",
-        "rawDate": "2024-11-14",
+        "rawDate": "2025-01-28",
         "status": "WD",
         "publisher": "W3C",
         "edDraft": "https://w3c.github.io/miniapp-manifest/",
@@ -112191,6 +112191,21 @@
                     "https://www.w3.org/2021/miniapps/"
                 ],
                 "source": "https://api.w3.org/specifications/miniapp-manifest/versions/20241114"
+            },
+            "20250128": {
+                "authors": [
+                    "Martin Alvarez-Espinar",
+                    "Yongjing ZHANG"
+                ],
+                "href": "https://www.w3.org/TR/2025/WD-miniapp-manifest-20250128/",
+                "title": "MiniApp Manifest",
+                "rawDate": "2025-01-28",
+                "status": "WD",
+                "publisher": "W3C",
+                "deliveredBy": [
+                    "https://www.w3.org/2021/miniapps/"
+                ],
+                "source": "https://api.w3.org/specifications/miniapp-manifest/versions/20250128"
             }
         },
         "repository": "https://github.com/w3c/miniapp-manifest"
@@ -112205,7 +112220,7 @@
         ],
         "href": "https://www.w3.org/TR/miniapp-packaging/",
         "title": "MiniApp Packaging",
-        "rawDate": "2023-10-23",
+        "rawDate": "2025-01-28",
         "status": "WD",
         "publisher": "W3C",
         "edDraft": "https://w3c.github.io/miniapp-packaging/",
@@ -112429,6 +112444,24 @@
                     "https://www.w3.org/2021/miniapps/"
                 ],
                 "source": "https://api.w3.org/specifications/miniapp-packaging/versions/20231023"
+            },
+            "20250128": {
+                "authors": [
+                    "Martin Alvarez-Espinar",
+                    "Qing An",
+                    "Tengyuan Zhang",
+                    "Yongjing ZHANG",
+                    "Dan Zhou"
+                ],
+                "href": "https://www.w3.org/TR/2025/WD-miniapp-packaging-20250128/",
+                "title": "MiniApp Packaging",
+                "rawDate": "2025-01-28",
+                "status": "WD",
+                "publisher": "W3C",
+                "deliveredBy": [
+                    "https://www.w3.org/2021/miniapps/"
+                ],
+                "source": "https://api.w3.org/specifications/miniapp-packaging/versions/20250128"
             }
         },
         "repository": "https://github.com/w3c/miniapp-packaging"
@@ -170887,7 +170920,7 @@
         ],
         "href": "https://www.w3.org/TR/vc-data-model-2.0/",
         "title": "Verifiable Credentials Data Model v2.0",
-        "rawDate": "2025-01-26",
+        "rawDate": "2025-01-27",
         "status": "CRD",
         "publisher": "W3C",
         "edDraft": "https://w3c.github.io/vc-data-model/",
@@ -173887,6 +173920,24 @@
                     "https://www.w3.org/2017/vc/"
                 ],
                 "source": "https://api.w3.org/specifications/vc-data-model-2.0/versions/20250126"
+            },
+            "20250127": {
+                "authors": [
+                    "Manu Sporny",
+                    "Ted Thibodeau Jr",
+                    "Ivan Herman",
+                    "Gabe Cohen",
+                    "Michael Jones"
+                ],
+                "href": "https://www.w3.org/TR/2025/CRD-vc-data-model-2.0-20250127/",
+                "title": "Verifiable Credentials Data Model v2.0",
+                "rawDate": "2025-01-27",
+                "status": "CRD",
+                "publisher": "W3C",
+                "deliveredBy": [
+                    "https://www.w3.org/2017/vc/"
+                ],
+                "source": "https://api.w3.org/specifications/vc-data-model-2.0/versions/20250127"
             }
         },
         "repository": "https://github.com/w3c/vc-data-model"
@@ -187972,7 +188023,7 @@
         ],
         "href": "https://www.w3.org/TR/webcodecs/",
         "title": "WebCodecs",
-        "rawDate": "2024-12-12",
+        "rawDate": "2025-01-28",
         "status": "WD",
         "publisher": "W3C",
         "edDraft": "https://w3c.github.io/webcodecs/",
@@ -190199,6 +190250,22 @@
                     "https://www.w3.org/media-wg/"
                 ],
                 "source": "https://api.w3.org/specifications/webcodecs/versions/20241212"
+            },
+            "20250128": {
+                "authors": [
+                    "Paul Adenot",
+                    "Bernard Aboba",
+                    "Eugene Zemtsov"
+                ],
+                "href": "https://www.w3.org/TR/2025/WD-webcodecs-20250128/",
+                "title": "WebCodecs",
+                "rawDate": "2025-01-28",
+                "status": "WD",
+                "publisher": "W3C",
+                "deliveredBy": [
+                    "https://www.w3.org/media-wg/"
+                ],
+                "source": "https://api.w3.org/specifications/webcodecs/versions/20250128"
             }
         },
         "repository": "https://github.com/w3c/webcodecs"
diff --git a/schemas/raw-reference.json b/schemas/raw-reference.json
index 4bf0eb8b1..0a11aa003 100644
--- a/schemas/raw-reference.json
+++ b/schemas/raw-reference.json
@@ -124,6 +124,10 @@
                 "aliasOf": {
                     "description": "ID of the aliased reference. Note aliasing can be recursive.",
                     "$ref": "#/definitions/id"
+                },
+                "isSeriesAlias": {
+                    "description": "Whether the alias is for the name of a specification series.",
+                    "type": "boolean"
                 }
             },
             "required": ["aliasOf"],
diff --git a/scripts/w3c.js b/scripts/w3c.js
index 48ee46ff4..0d482ae89 100755
--- a/scripts/w3c.js
+++ b/scripts/w3c.js
@@ -606,6 +606,55 @@ async function updateW3CRefs(sinceDate, verbose, filter) {
         }
     }
 
+    // Time to look at specification series and create custom aliases for them
+    // Note: the W3C API gives us the API url of the current specification in a
+    // series, which we need to map to an actual spec entry in the list. That
+    // mapping gets recorded through the "source" property. In particular, we
+    // cannot build that mapping while we process the specs above because the
+    // script typically runs in incremental mode and does not load the entire
+    // list of specs from the W3C API.
+    console.log("Updating specification series aliases...");
+    const source2Entry = {};
+    for (const [shortname, entry] of Object.entries(current)) {
+        source2Entry[entry.source] = shortname;
+    }
+    const specSeries = await fetchW3CPages('specification-series', 'specification-series', true);
+    for (const series of specSeries) {
+        // Look for the current specification in the list.
+        const currUrl = series._links['current-specification'].href;
+        const currShortname = source2Entry[currUrl];
+        if (!currShortname) {
+            continue;
+        }
+
+        // Make sure we can create an alias for the series
+        let entry = current[series.shortname];
+        if (entry) {
+            if (entry.aliasOf && entry.isSeriesAlias) {
+                // Make sure alias targets the right spec
+                entry.aliasOf = currShortname;
+                continue;
+            }
+            else {
+                // Many specs don't use levels. In such a case, the name of the
+                // series matches that of the specification, which should
+                // already be in the list.
+                continue;
+            }
+        }
+        else if (series.shortname in bibref.get(series.shortname)) {
+            console.warn(`- ${series.shortname}: series shortname already in another source in Specref`);
+            continue;
+        }
+
+        // Create an alias
+        entry = {
+            aliasOf: currShortname,
+            isSeriesAlias: true
+        };
+        current[series.shortname] = entry;
+    }
+
     console.log("Sorting references...");
     const sorted = helper.sortRefs(current);
 


Test patch:
diff --git a/test/bibref.js b/test/bibref.js
index 13ef3e4ba..0de07aa22 100644
--- a/test/bibref.js
+++ b/test/bibref.js
@@ -12,7 +12,8 @@ suite('Test bibref api', function() {
         },
         foo: { aliasOf: "FOO" },
         bar: { aliasOf: "fOO" },
-        hello: { title: "HELLO" }
+        hello: { title: "HELLO" },
+        series: { aliasOf: "FOO", isSeriesAlias: true }
     };
 
     test('bibref constructor handles a single reference obj', function() {
@@ -56,6 +57,11 @@ suite('Test bibref api', function() {
         assert.equal("FOO title", expanded["FOO-BAZ"].title);
     });
 
+    test('bibref.expandRefs does not create versions for series aliases', function() {
+        var b = bibref.create(obj);
+        assert.ok(!('FOO' in b.get("series-BAR")), "Cannot access a spec version from a series alias.");
+    });
+
     test('bibref.cleanupRefs modifies the refs correctly', function() {
         var cleaned = bibref.cleanupRefs({
             foo: {
diff --git a/test/ref-list.json b/test/ref-list.json
index 66f17b1cf..c4b1b7ffe 100644
--- a/test/ref-list.json
+++ b/test/ref-list.json
@@ -53340,6 +53340,8 @@
     "MINIAPP-MANIFEST-20230530",
     "miniapp-manifest-20241114",
     "MINIAPP-MANIFEST-20241114",
+    "miniapp-manifest-20250128",
+    "MINIAPP-MANIFEST-20250128",
     "miniapp-packaging",
     "MINIAPP-PACKAGING",
     "miniapp-packaging-20211130",
@@ -53366,6 +53368,8 @@
     "MINIAPP-PACKAGING-20230530",
     "miniapp-packaging-20231023",
     "MINIAPP-PACKAGING-20231023",
+    "miniapp-packaging-20250128",
+    "MINIAPP-PACKAGING-20250128",
     "miniapp-widget-req",
     "MINIAPP-WIDGET-REQ",
     "miniapp-widget-req-20220308",
@@ -90110,8 +90114,14 @@
     "RFC9709",
     "rfc971",
     "RFC971",
+    "rfc9712",
+    "RFC9712",
     "rfc9713",
     "RFC9713",
+    "rfc9715",
+    "RFC9715",
+    "rfc9717",
+    "RFC9717",
     "rfc9718",
     "RFC9718",
     "rfc972",
@@ -95810,6 +95820,8 @@
     "VC-DATA-MODEL-2.0-20250123",
     "vc-data-model-2.0-20250126",
     "VC-DATA-MODEL-2.0-20250126",
+    "vc-data-model-2.0-20250127",
+    "VC-DATA-MODEL-2.0-20250127",
     "vc-data-model-20170803",
     "VC-DATA-MODEL-20170803",
     "vc-data-model-20190208",
@@ -98874,6 +98886,8 @@
     "WEBCODECS-20241008",
     "webcodecs-20241212",
     "WEBCODECS-20241212",
+    "webcodecs-20250128",
+    "WEBCODECS-20250128",
     "webcodecs-aac-codec-registration",
     "WEBCODECS-AAC-CODEC-REGISTRATION",
     "webcodecs-aac-codec-registration-20211216",