Generated from smart-base/skills/content/authoring-who-smart-guidelines/ig-artifact-ingestion.md — do not edit here.

✎ Edit this page’s source

ig-artifact-ingestion

Skill id: ig-artifact-ingestion · Package: authoring-who-smart-guidelines · Named by the fhir-artifact-index graph typology in cat-harness/schemas/cat-harness.ts, which is the declaration that sends a consumer here.

Point at a published FHIR Implementation Guide, read what it actually published, and reconstruct its artefact index as a knowledge graph keyed by the IG’s own canonical URLs — linking each artefact to its JSON, JSON Schema and JSON-LD representations.

Sourcing. The artefact set, the canonical URL scheme and artifacts.html are the HL7 IG Publisher’s; the DAK API layer is WHO SMART Guidelines’. This skill states how an index is reconstructed in this harness and defers to those for what an IG must contain. It is deliberately silent on whether an IG’s content is correct — that is fhir-validation’s lane, and an index that started grading its subject would be doing two jobs badly.

Which IGs this applies to

Not all of them. The test is whether the IG publishes a DAK API, and it is answered by one fact, not by the IG’s name or its publisher:

Are there enumeration schemas at the published root — files matching <Type>.schema.json, such as ValueSets.schema.json or LogicalModels.schema.json?

If yes, dakApi is present and the overlay is ingested. If the root was read and held none, it is absent. If the root was never read, it is unknown — and unknown is not a kind of absent. “This IG publishes no DAK API” and “nobody looked” are different facts, and only the first is a reason to stop.

An IG with no DAK API still indexes: the spine is the IG Publisher’s own output, which every IG has. What it loses is the JSON Schema and JSON-LD links, which is the half worth having — so a no-DAK IG is a thin index, not an error.

The index is RECONSTRUCTED, never downloaded

The finding that governs everything here, measured 2026-09-21 against the WHO SMART Trust IG v1.8.0:

No IG publishes an artefact-index instance document.

ValueSets.schema.json and LogicalModels.schema.json sit at the published root — not under schemas/, contrary to how dak-api.html links them — and both are JSON Schemas describing the shape of an enumeration response. Each carries an example block that happens to hold the real list. There is no ValueSets.json to fetch.

So every field in the resulting graph was assembled, and the index records in provenance which published file each part came out of. That is required, not optional: a reader who cannot tell which file a row came from cannot tell a transcription from an inference.

THREE DOCUMENT FAMILIES IN ONE DIRECTORY — because there are three SOURCES

fhir-artifact-index/ holds three kinds of document, and the reason is not convenience. Each comes from a different source, so each carries its own provenance block. An IG does not publish one thing about itself; it publishes one thing, keeps a second in its source config, and inherits a third from packages it merely depends on.

documentschemasourcewhat its provenance names
index.jsonfolio-fhir-artifact-index/v2the IG’s published output (a gh-pages tree){kind: "gh-pages", of, readAt}
menu.jsonfolio-ig-menu/v1the IG’s own sushi-config.yamlrepository + commit + path
ig-identity.jsonfolio-ig-identity/v1the IG’s own sushi-config.yaml: id, canonical, version, statusreadFrom + readAt
chrome.jsonfolio-ig-chrome/v2the fhir.template chain its ig.ini names — keyed by the chain’s top template, so it lives once, in smart-base’s themes/ beside the template’s theme (plan Q4), and every IG wears itone block per layer, each with its own commit

Folding any two together would give one file two answers to “where did this come from”. That is the whole argument, and it is 0818’s (the menu) and ajx9’s (the chrome) in one sentence.

The menu is the IG’s own ordering of itself

sushi-config.yaml’s menu: is a nested map, and order is the file’s order — alphabetising it would silently re-navigate somebody else’s IG. It was NOT transcribed from a screenshot, and schemas/ig-menu.ts records why: a picture carries no hrefs, and a closed dropdown is indistinguishable from an empty one. Measured 2026-09-23: the twelve labels visible in that screenshot matched 0 of 674 artefact titles — the harvest kept the IG’s artefacts and dropped both its navigation and the narrative pages it points at.

The chrome comes from a CHAIN, and only part of it is the IG’s own

An IG declares its appearance in no file it owns. Measured 2026-09-23 for the WHO SMART Trust IG:

local-template  →  who.template.root 0.5.0  →  fhir.base.template 1.0.0

So chrome.json carries layers base-first, and every token records which package won it and what it overrode. That is not bookkeeping: it is the only way to answer whose value is this? Of the three things a reader recognises as “the WHO IG”, only two are WHO’s — who.css styles no #publish-box at all, so the yellow box is HL7’s, inherited unchanged. The token comes out from: "fhir.base.template" with overrides: [], which is what proves it rather than asserting it.

An overlay is not a merge. --toc-box-border is 1px solid navy in HL7 and navy in WHO — each right in its own layer, and together they make HL7’s rule resolve to border: navy, which is invalid. Every individual layer parses, so a naive merge ships a broken rule silently.

Upstream defects are RECORDED, never corrected

A mirror that silently fixes its subject is not a mirror. Two kinds, both found 2026-09-23 and both carried verbatim into our copy:

  • shape — one token name carrying two different KINDS of value across layers, as above.
  • malformed — a value that is not valid CSS on its own terms. --breadcrumb-text-color: ##555555 at project.css:82, a doubled # in HL7’s source.

The second is in the skill because of how it presents downstream: it appears in our generated page as ##555555, looking exactly like a bug in the ingest that copied it. It cost the author of that ingest about thirty seconds of believing they had written it, with the source open in the next terminal. A reader without the source open cannot tell at all. So the mirror carries the value and the record says whose defect it is.

The detector is deliberately narrow — one pattern. A false positive accuses somebody else’s published stylesheet of a fault it does not have.

CI cannot obtain any of these inputs, and the checks say so

All three ingests read something outside this checkout, and the chrome needs three upstream checkouts rather than one. worldhealthorganization.github.io and litlfred.github.io both answer 403 CONNECT from this environment.

So ingest:ig-menu:check and ingest:ig-chrome:check exit 2 with no source — could not determine, never the 0 a silent skip would give — and both are registered in gates.ts as report rather than as gates. What IS gated on every run, without the network: smart-trust:pages:check regenerates all 681 pages from the committed data and compares byte for byte, and check:kind-validators parses each file against its schema.

Four partial views, and none of them is sufficient

Measured on the WHO SMART Trust IG v1.8.0. The coverage gaps are the reason the pipeline merges rather than picking a winner:

fileentriesholdsmisses
canonicals.json70canonical URL, version, namethe 604 Endpoints and Organizations
package.tgz → package/.index.json674every artefact’s type and idtitles, categories, canonicals
artifacts.html676 linksthe editorial category and human titlemachine-readable anything
package.manifest.json1package id, version, FHIR version, build date—

artifacts.html is the only source of an artefact’s category. Its per-row title attribute carries ResourceType/id verbatim, which is the index’s own key format — so the category join is on an identifier the publisher wrote, not one the pipeline composed from a filename.

Read the source, not the page about the source. dak-api.html is prose about what is published, and on the WHO SMART Trust IG it links the enumeration schemas to a schemas/ directory they are not in. A pipeline written from the documentation would have found nothing at the paths it named.

Two traps

openapi/openapi.json is not the DAK API. On the WHO SMART Trust IG that path holds the DDCC Gateway API — a domain API about certificate exchange that merely lives there. A pipeline that globs for openapi files a piece of subject matter as an artefact descriptor. The overlay is therefore keyed off each artefact’s own stem (schemas/<ResourceType>-<id>.openapi.json), never off a directory scan.

.index.json is lossy. Its Organization entries on the WHO SMART Trust IG carry a truncated "type": "[". Do not repair such a value and do not propagate it: canonicals.json is the spine for anything canonical, and for a resource that appears only in .index.json the field is simply absent. An absent field is a fact; a repaired one is a guess wearing a fact’s clothes.

What is never invented

canonical is optional on an artefact, and that is load-bearing. Examples and instance resources have no canonical URL and are the majority of a large IG — 604 of the WHO SMART Trust IG’s 674. Minting one to fill the slot produces an identifier that resolves to nothing while looking authoritative, which is the failure the who-iris catalogue refused for collection/hq-publications and refuses here for the same reason.

The same holds for category: absent means the IG published no artefact page, or listed no row for this artefact. It never means “Other” — the IG’s own artefact page has a literal Other section, and coining a second one would make the word mean two things in one index.

Materialisation

Default is catalogue by reference, following who-iris: the index records where each artefact is published and holds no bytes. --materialize-dak adds the DAK surface only — on the WHO SMART Trust IG, 332K across 71 files, against 7.1M for the full resource corpus.

Materialised nodes take purpose: "working", never archival: they are regenerable by re-running the ingest against the same source revision, so they carry none of archival’s obligations. --check is what proves that claim, which is what makes the claim admissible.

Each materialised node carries all five gates of folio-assistant-core/schemas/materialization.ts. Two of them — sourceLoss and copyright — are unknown for a WHO IG until somebody establishes otherwise, and leaving them unknown is correct rather than lazy: no statement has been made about how long a given version’s Pages build stays reachable, and the IG’s licensing has not been read. A permitted there would be the “no restrictions known in context” failure that GateVerdict is three-valued to prevent.

gh-pages and output/ are not the same evidence

Both are accepted (--kind), and the index records which was read. They are different claims: output/ is a local build whose contents depend on who ran the publisher and when, while gh-pages is what the world can see. An index built from the first and labelled the second asserts public availability for artefacts that may never have been published.

Prefer gh-pages for anything that will be cited. Use output/ when checking an IG you are mid-way through authoring — and expect the index to change when it is actually published.

Running it

bun run cat ingest:ig -- \
  --source /path/to/gh-pages --kind gh-pages \
  --id <ig> \
  --base https://worldhealthorganization.github.io/<ig> \
  --out <ig> --materialize-dak

--base is the URL the artefacts are published at, and it is not the canonical base. The WHO SMART Trust IG publishes at worldhealthorganization.github.io and is canonical at smart.who.int — the index carries both, and composing either from the other is how a link that resolves for nobody gets written down. canonicalBase is read from the IG’s own ImplementationGuide canonical, not derived from --base.

bun run cat ingest:ig:check re-runs the ingest and fails if the committed index differs. That is what makes this graph regenerable rather than a snapshot nobody can re-derive; readAt is excluded from the comparison because it moves every run by design.

Adding another IG

  1. Clone the IG’s gh-pages (shallow, single branch — a published IG is large; the WHO SMART Trust IG’s is 342,656 files).
  2. Run the ingest with a new --id and --out.
  3. Give the new directory a declaration declaring one directory of kind fhir-artifact-index, as an ingested IG’s own <name>.config.json does. The declaration is <name>.config.json since #695 — resolve it with declarationPathIn, never by joining a filename.
  4. Add its ingest:ig:check invocation to the gate set, so the index cannot go stale silently.

Step 4 is the one that gets skipped. An index nothing re-derives is a snapshot, and a stale snapshot of someone else’s corpus is worse than no index: it answers confidently and wrongly about artefacts that have since moved.

Processes that run this skill

processstep(s) that name it
SMART Guideline end to end5 · Choose L3 dependencies (WHO / jurisdiction IGs)

folio-assistant — code under the Apache License 2.0, documentation under CC BY 3.0. Generated schema and API references are rebuilt from source on every push to main.

deployed dev