From bc50c9f82831efeab435b5f2f9a0e2c38720b7ca Mon Sep 17 00:00:00 2001 From: "R.V.Guha" Date: Sat, 22 Aug 2026 09:45:42 -0700 Subject: [PATCH] Publish the ARD base context at /context/v1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §4.1 says term IRIs come from a base context served at https://agenticresourcediscovery.org/context/v1, applied by a conformant consumer as the JSON-LD expandContext. That URL currently 404s, so the one normative reference an ARD entry makes to this site does not resolve. Follows the pattern spec_from_canonical.py already establishes: the context lives only in ards-project/ard-spec (spec/schemas/ard.context.jsonld) and is fetched at build time, so the site never holds a second copy that can drift. Publishes the same bytes at two paths. /context/v1 is the URL the spec names. /context/v1.jsonld is an alias, because static hosting assigns Content-Type from the file extension: GitHub Pages serves the extensionless path as application/octet-stream, and the JSON-LD 1.1 API requires a remote context to arrive as application/ld+json, application/json, or a +json type. The bare path resolves and reads correctly; a strict processor dereferencing it may still refuse it, and the alias gives those consumers something that works today. A fetch failure publishes nothing rather than a placeholder. A stand-in page is fine for prose a human reads; a partial or unparseable document at a context URL is worse than a 404, because a consumer would apply it and expand every term wrongly. The fetched document is parsed and checked for @context before it is published, so an HTML error body arriving with a 200 cannot be served as the context. Co-Authored-By: Claude Opus 5 --- hooks/context_from_canonical.py | 72 +++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 73 insertions(+) create mode 100644 hooks/context_from_canonical.py diff --git a/hooks/context_from_canonical.py b/hooks/context_from_canonical.py new file mode 100644 index 0000000..a9d9bf7 --- /dev/null +++ b/hooks/context_from_canonical.py @@ -0,0 +1,72 @@ +"""MkDocs build hook: publish the ARD base context at /context/v1. + +§4.1 of the specification says term IRIs come from a base context served at +`https://agenticresourcediscovery.org/context/v1`, and that a conformant consumer applies it as +the JSON-LD `expandContext`. That URL has to resolve, or the one normative reference an entry +makes to this site is a 404. + +The context lives only in ards-project/ard-spec (spec/schemas/ard.context.jsonld), the same way +the specification text does. This hook fetches it at build time and publishes it verbatim, so the +site never holds a second copy that can drift from the canonical one. + +Two paths are published for the same bytes: + + /context/v1 the URL the specification names + /context/v1.jsonld the same document with a `.jsonld` extension + +The alias exists because of how static hosting assigns Content-Type. GitHub Pages derives it from +the file extension and offers no way to set headers, so the extensionless path is served as +`application/octet-stream`. The JSON-LD 1.1 API requires a remote context to arrive as +`application/ld+json`, `application/json`, or a `+json` media type, and a strict processor rejects +anything else — so `/context/v1` resolves and reads correctly, but a strict consumer that +dereferences it may still refuse it. The `.jsonld` alias gives those consumers a URL that works +today; making the bare path correct needs either a host that can set headers, or a spec that names +the suffixed URL. Both are decisions for the spec, not for this build. + +Unlike the specification hook, a fetch failure here publishes NOTHING. A placeholder page is a +reasonable fallback for prose a human is reading; an unparseable or partial document served at a +context URL is worse than a 404, because a consumer would apply it and expand every term wrongly. +""" +import json +import urllib.error +import urllib.request + +from mkdocs.structure.files import File + +SRC = ("https://raw.githubusercontent.com/ards-project/ard-spec/main/" + "spec/schemas/ard.context.jsonld") +DEST = "context/v1" +DEST_SUFFIXED = "context/v1.jsonld" +TIMEOUT = 20 + + +def _fetch() -> str | None: + """The canonical context, or None if it cannot be had intact. + + The document is parsed before being published: a truncated or HTML error body that happened to + arrive with a 200 would otherwise be served as the context. + """ + try: + with urllib.request.urlopen(SRC, timeout=TIMEOUT) as r: + raw = r.read().decode("utf-8") + except (urllib.error.URLError, OSError, UnicodeDecodeError): + return None + try: + doc = json.loads(raw) + except json.JSONDecodeError: + return None + if not isinstance(doc, dict) or "@context" not in doc: + return None + return raw + + +def on_files(files, config): + raw = _fetch() + if raw is None: + # Leave the URL 404ing rather than publish something a consumer would trust. + print("WARNING - context_from_canonical: could not fetch the ARD base context from " + f"{SRC}; /context/v1 will not be published for this build.") + return files + for dest in (DEST, DEST_SUFFIXED): + files.append(File.generated(config, dest, content=raw)) + return files diff --git a/mkdocs.yml b/mkdocs.yml index 185cf66..eff26b5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -69,6 +69,7 @@ extra_css: # committed docs/spec.md if the repo isn't reachable. hooks: - hooks/spec_from_canonical.py + - hooks/context_from_canonical.py # Plugins # Empty list (not omitted) so MkDocs does NOT re-enable its default `search`