About
Scope
A knowledge explorer of the research-information ecosystem: identifiers, registration agencies, indices, institutional systems, standards, communities and coalitions. It currently holds 169 entries and 357 relationships.
The site is read-only. It does not assess or score services.
Inclusion criteria
An entry is included when it has a stable public URL, when another entry in the graph refers to it or it is a recognised registry, standards body or coalition, and when it is not a single project's internal tooling.
Editorial policy
- One fact, one place: derived facts (inverse relations, layers, totals) are computed, never written by hand.
- Every relationship is written once, in its canonical direction.
- Descriptive, not evaluative: no quality adjectives.
- Provenance required: every entry cites at least one source with an access date.
- Freshness tracked: every entry records when it was last reviewed.
How to read the map
Each dot is an entry: a concept, an identifier, a service, a rule or an organisation. Each line is a directed relationship that reads as a sentence: "Crossref issues DOI".
Relationships fall into 5 families, which the map and the entry pages use to order what they show:
- Who is behind it
- Who issues, owns, governs, operates, develops or funds the thing.
- Builds on
- Standards, protocols and services the thing is built on or assessed against.
- Connects to
- Interoperability, indexing, membership and containment between infrastructures.
- Shapes
- Policies, mandates, endorsements and replacements that steer what is done.
- What it is
- Classification — which concept the thing falls under, or is related to.
Data model
These tables are generated from the repository vocabularies (vocab/entity-types.yaml and vocab/predicates.yaml), so they cannot drift from the rules applied when the data is validated.
Entry types
| Type | Definition | Entries |
|---|---|---|
| Aggregator | Infrastructure that harvests, links or federates metadata across institutions and countries for content it does not itself hold. | 7 |
| Coalition | A political grouping pushing change rather than writing specifications. | 7 |
| Community | A body that writes the rules or certifies against them. | 10 |
| Concept | An idea or term — persistent identifier, open access, metadata — of which concrete things are instances. | 31 |
| CRIS | An institution's administrative register of its own research activity. | 5 |
| Identifier | A permanent code pointing at a thing, and the commitment not to delete it. | 10 |
| Index | A database recording facts about research outputs and the infrastructure holding them. | 15 |
| Organization | The actor behind the thing — company, non-profit, university, agency, intergovernmental body. | 42 |
| Policy | A normative instrument constraining behaviour. | 3 |
| Publishing platform | A programme that hosts, marks up and publishes journals, preprints or books under its own admission criteria. | 3 |
| Registration agency | A body that mints identifiers and holds their metadata. | 4 |
| Repository | Where objects are deposited and preserved — both the software and the running service. | 11 |
| Standard | A specification, set of principles, protocol or certification criteria others conform to. | 21 |
Relationships
| Relationship | Inverse reading | Family | Uses |
|---|---|---|---|
| certified by | certifies | Builds on | 1 |
| complies with | complied with by | Builds on | 4 |
| depends on | depended on by | Builds on | 22 |
| implements | implemented by | Builds on | 21 |
| is a profile of | has profile | Builds on | 1 |
| indexes | indexed by | Connects to | 4 |
| interoperates with | interoperates with | Connects to | 10 |
| is a member of | has member | Connects to | 42 |
| is part of | has part | Connects to | 4 |
| applies to | subject to | Shapes | 1 |
| endorsed by | endorses | Shapes | 8 |
| mandates | mandated by | Shapes | 1 |
| supersedes | superseded by | Shapes | 1 |
| is a kind of | includes | What it is | 43 |
| relates to | relates to | What it is | 98 |
| developed by | develops | Who is behind it | 18 |
| funded by | funds | Who is behind it | 16 |
| governed by | governs | Who is behind it | 15 |
| issues | issued by | Who is behind it | 5 |
| operated by | operates | Who is behind it | 30 |
| owned by | owns | Who is behind it | 12 |
Validation rules (35)
| Rule | Level | Definition |
|---|---|---|
| I1 | ERROR | `id` matches /^[a-z0-9]+(?:-[a-z0-9]+)*$/, 2–64 chars, equals the filename stem. |
| I2 | ERROR | `id` is unique across all entity types. No type-scoped namespacing. |
| I3 | ERROR | No dangling references: every relations[].target resolves. |
| I4 | ERROR | No self-loops. |
| I5 | ERROR | No duplicate triples (id, predicate, target). |
| I6 | ERROR | predicate ∈ vocab/predicates.yaml. Closed vocabulary. |
| I7 | ERROR | Canonical direction only. An inverse-form predicate (owns, issued-by, implemented-by, …) is rejected with a message naming the canonical form and the file it belongs in. |
| I8 | ERROR | Domain/range: subject.type ∈ predicate.domain, target.type ∈ predicate.range; where sameTypeOnly, also subject.type === target.type. |
| I9 | ERROR | Symmetric predicates authored once per unordered pair, on the lexicographically smaller id. |
| I10 | ERROR | Acyclic predicates form a DAG (part-of, supersedes, profile-of, depends-on, owned-by, member-of). Error prints the cycle path. |
| I11 | ERROR | No orphans: in-degree + out-degree ≥ 1 unless isolated: true with an explanatory note. |
| I12 | ERROR | Every declared EntityType has ≥1 instance and every declared predicate ≥1 edge. Unused vocabulary is removed or populated; an entry flagged `pending: true` is exempt until its first instance (WARN if the flag is then left behind). |
| I13 | ERROR | Every tags[], disciplines[], status, governance value exists in the corresponding vocab file. |
| I14 | ERROR | summary (in each locale file) is 40–200 chars, single-line, no Markdown syntax, not identical to name. |
| I15 | ERROR | sources.length ≥ 1; every Source.url is https:; accessed is a valid ISO date not in the future. Relations with confidence !== 'asserted' carry ≥1 source. |
| I16 | ERROR | codebase: open-source requires a licence-bearing sourceUrl or an explanatory note; SPDX identifiers validated against spdx-license-ids. |
| I17 | ERROR | url and all Source.url are syntactically valid absolute https: URLs. (Reachability checked weekly, non-blocking — §11.) |
| I18 | ERROR | Populated identifiers.* match their format regexes (§5.1). |
| I19 | ERROR | owned-by targets an organization with orgKind: company. Ownership by a non-company is governed-by or operated-by, not owned-by. |
| I20 | ERROR | since ≤ until where both present; valid partial ISO dates; neither >1 year in the future. |
| I21 | ERROR | EntityId-valued subtype fields resolve to an entity of the required type: persistentIdScheme[] → identifier, harvestProtocols[] → standard, markupStandard[] → standard. |
| I22 | ERROR | governance: commercial requires ≥1 outgoing owned-by edge, or a note on the entity explaining why none is recorded. Enforces the memo's central question at authoring time. |
| I23 | ERROR | layer is never present in an entity file — it is derived (§5.1, C1). |
| I24 | ERROR | Every View.query (§5.6) resolves against the current graph; kind: 'explicit' entity IDs must exist. |
| I25 | ERROR | A View.query resolving to zero entities is an error, not an empty page. |
| I26 | WARN | lastReviewed older than 18 months (the date itself is shown on the entity page). |
| I27 | WARN | An identifier entity with no inbound issues edge. |
| I28 | WARN | An entity authors more relations of one predicate than its maxOutDegree (the map shows the most relevant neighbours and "show more" for the rest; the page lists all); or the graph has >1 weakly connected component (prints each component's size and members). |
| I29 | ERROR | Every entity and every view has a prose file in the default locale, with a valid summary. A facts file without content/i18n/<default>/<id>.yaml fails the build (§5.7). |
| I30 | WARN / ERROR | WARN per missing non-default-locale prose file, aggregated as one line per locale (e.g. 'en: 12 of 78 entities untranslated') — or, with REQUIRE_FULL_I18N on (src/lib/invariants/config.ts / env), one ERROR per missing file. ERROR if a locale file declares a field outside the translatable set, if a relationNotes key does not resolve to a relation authored in the facts file, or if shortName is longer than the name or set in some locales but not others. |
| I31 | ERROR | An entity facts file carries prose (a body, summary or description field). Prose exists only in content/i18n/** (C10). |
| I32 | WARN | Every edge of a governance predicate (owned-by, governed-by, operated-by, developed-by, funded-by) carries relations[].verified: { date, url } recording the primary source it was checked against. |
| I33 | WARN | Completeness floor: every repository/index/aggregator/publishing-platform has ≥1 of {operated-by, developed-by, governed-by}; every standard has ≥1 of {developed-by, governed-by}. |
| I34 | WARN | Heuristic: an entity's note/relationNote/source-note text mentions another entity by name with no relation connecting the two — a likely fact-recorded-as-prose gap. |
| I35 | ERROR | Every predicate declares a `family` that exists in vocab/predicate-families.yaml (display grouping, ≤5 families). |
Downloads
The complete data is published under CC-BY-4.0 in open formats:
- Entries (CSV)
- Relationships (CSV)
- Full graph with texts in every language (JSON)
- Each entry on its own as JSON, for example: /entity/doi.json
- RSS feed of recently reviewed entries
- Sitemap (XML)
Contribute
You do not need to edit any file. To add, change or remove something, open an issue: pick a form, say what is wrong or missing and where it can be checked. A maintainer checks the source and makes the change. Issues are public. Open an issue on GitHub(opens in a new tab)
The content of this site (entities, relations, translations, stories) is plain YAML in src/content/, checked by a Zod schema and a set of graph invariants every time you run npm run validate and every time you build. A change is good when the validator passes and a person can tell from the diff that it is true, sourced and in scope.
Suggest a change without editing files
Open an issue at https://github.com/Data-Observatory/open-ecosystem-explorer/issues/new/choose and pick the form that fits. The forms are in English and Spanish.
- New entry on the map: an institution, standard, identifier, infrastructure or concept that is missing.
- Connection between two entries: add, change or remove one line on the map (how entry A relates to entry B, from the list of 21 kinds of connection).
- Description or facts of an entry: the short description, the longer text, the name, a fact in the table, the note for Chile and Latin America, a translation, a source or link, or where it appears on the map.
- Remove an entry: it does not belong, is a duplicate, is not true or no longer exists.
- Bug or accessibility problem and Idea or feature: about the site itself, not the content of the map.
Every entry page has two links ("Suggest a change to the description or facts" and "Suggest a change to its connections") that open the matching form with that entry filled in, and the footer links to the chooser.
- A maintainer reads the issue, checks the source, and makes the change as a pull request that closes the issue, or answers why not. If in doubt, the change is left out.
- Give a source for what you propose: the organisation's own page or an authoritative register. A proposal without one may wait until one is found.
- Issues are public. Do not include personal data.
- Issues are labelled
new-entry,connection,description,remove-entry,bugandidea.
Everything below is for people who prefer to make the change themselves, in a pull request.
Before you start
You need Node 22 or newer and npm. Run npm install once. Run npx playwright install chromium only if you also want to run the browser tests.
Read the specs.md sections for the part you touch: §3 (curation policy), §5 (data model and invariants) and §8 (authoring format). docs/known-issues.md lists what is already known to be unverified, so you do not repeat it.
Add or change an entity
- For autocomplete and validation while you type, map
schema/entity.schema.jsontosrc/content/entities/*.yamlandschema/prose.schema.jsontosrc/content/i18n/*/*.yamlin your editor's YAML settings (for example theyaml.schemassetting of theredhat.vscode-yamlextension). Personal editor settings are not committed (.vscode/is ignored).
- An entity is two or three small files, never one big one. Facts and outgoing relations go in
src/content/entities/<id>.yaml. The filename is the id. - Prose goes in
src/content/i18n/es/<id>.yamlandsrc/content/i18n/en/<id>.yaml: a one-sentencesummaryand a shortbody. Both languages are required, and a missing one is a validation error. - Copy two existing files of the same
typeas models. The closed vocabularies insrc/content/vocab/(types, predicates, tags, disciplines, statuses) say which values are allowed. Do not invent terms; adding a vocabulary term is a separate change. - Author each relation once, in the canonical direction, in the file of the subject. The inverse is derived. If the target entity does not exist, add it in the same change or leave the relation out.
- Every entity needs at least one entry in
sourceswith a real URL and the date you read it. Cite the organisation's own page or an authoritative register, not a summary of it. - Descriptions are neutral and descriptive, not evaluative. Chile and Latin America specifics go in an optional
localNote(30 words at most), never in the definition. - If in doubt, leave it out. A missing relation costs less than a wrong one.
- Ideas such as open access, metadata or FAIR are entities of type
concept. Link concrete things to them withis-aorrelated-to(with a shortnote), not with the other predicates. prominence(core,extendedordetail) controls what a newcomer sees first. Leave the default unless you have discussed it.
prompts/add-entity.md is a ready-made prompt for an LLM agent doing the same job. An agent edits, the validator checks, a person reviews and merges.
The check loop
Run these after every edit, from the repository root:
npm run validatemust end with 0 errors. It takes a few seconds and prints the rule, the file, the field and the fix. Warnings are information; report them, do not hide them.npm run layout:generateafter you add, remove or change a relation or an entity, then commitsrc/generated/graph-layout.json. The build fails (layout:check) if it is stale.npm run check,npm run lintandnpm testbefore you open a pull request.npm run buildthennpm run test:e2ewhen you touch pages or the map. The e2e tests run against the build. SetPW_PORTto a free port, andPLAYWRIGHT_CHROME_PATHto use a system Chrome.
Change the site or the UI text
UI strings live in messages/es.json and messages/en.json. Every key must exist in both. Code under src/lib/model, graph, i18n, invariants, views, stories and export must stay free of Svelte and SvelteKit imports. docs/architecture.md explains the routes and the map.
Commits and reviews
- Use conventional commits (
feat:,fix:,docs:,chore:,refactor:,test:,style:) and small, incremental commits: one idea per commit. - Do not add co-author or tool attribution lines to commit messages.
- The reviewer reads for what the validator cannot see: is the relation true, is the source the right one, is the summary neutral, is the entity in scope.
mainis protected: work on a branch and open a pull request. It can be merged only when the CI jobstestandinfraare green and the branch is up to date withmain; direct pushes are refused, for administrators too. Merging is squash or rebase, so keep the pull request title a conventional commit.- CI (
.github/workflows/ci.yml) runs the check loop above on every pull request and on every push tomain. - Every change merged to
mainis deployed to https://oee.dataobservatory.net by.github/workflows/deploy.ymlafter CI passes onmain, with no further approval. Merge only what you are ready to publish. - Analytics, hosting and deployment settings (
static/consent-loader.js,infra/,.github/workflows/deploy.yml) are decided by the project owner. Open an issue instead of changing them.
Template for adding an entry
Add an entity
Add <name> to this repository as an entity.
Read first: specs.md §5.1–5.2 (fields per type), specs.md A.2 (the predicates, with domain and range), src/content/vocab/*.yaml (closed vocabularies — do not invent terms), and two existing files of the same type as models.
Then: create src/content/entities/<id>.yaml with the facts and outgoing relations only, and src/content/i18n/es/<id>.yaml with summary and body prose (en too if you can source it). Author relations in the canonical direction only; if a target entity does not exist, either add it in the same change or omit the relation. Every entity needs ≥1 sources entry with a real URL and today's accessed date — do not cite the memo for an external fact, cite the thing itself.
Optional prominence: core | extended | detail (default extended) controls progressive disclosure; set core only for the handful of nodes a newcomer must see first. A new idea or term (persistent identifier, open access, metadata, ...) is a concept (13th type; governance: not-applicable, url = its reference page, no type-specific fields): link concrete things to it with is-a (any type -> concept; concept -> concept for subtypes; acyclic; only when the thing genuinely *is a kind of* the concept) or related-to (symmetric, any type -> concept, at most 5 per node (a warning beyond that), always with a short note saying how; use it when the thing operates, practises or addresses the idea without being a kind of it), and write the concept's own relations in the same direction. Principles and frameworks (FAIR, CARE, TRUST) are concepts, not standards: tie them to things with related-to, never complies-with or developed-by. For a symmetric edge between two concepts, author it on the file whose id sorts first. Prose: an optional localNote (<=30 words) carries a Chile/LatAm-specific fact about a global concept; definitions stay neutral. Provide both es and en files: a missing locale is a validation ERROR (REQUIRE_FULL_I18N in src/lib/invariants/config.ts).
Finally: run npm run validate and fix every ERROR before stopping; if you added or changed any relation or entity, also run npm run layout:generate and keep the changed src/generated/graph-layout.json. Report the WARN lines rather than suppressing them. Do not add a vocabulary term (that is a separate PR, C6), and do not edit any other entity's file to add the inverse relation.
Changelog
Changes to the data and to the site are recorded in the changelog.
- Proposals through GitHub issues (2026-11-14)
- CI and deployment infrastructure (2026-11-14)
- Spec brought in line with the code (2026-11-14)
- Contributor documentation (2026-11-14)
- Clean-up after the SvelteKit migration (2026-11-14)
Privacy and cookies
To understand how the site is used, visit activity is measured through Google Tag Manager, a Google service that loads the analytics tools configured for this site (for example, Google Analytics). It receives technical usage data, such as the pages visited, the browser and device type and the approximate location, together with a cookie that identifies the browser with a random code. The site asks for no name, email or other personal data, and analytics are not used for advertising.
Analytics are on by default. The notice shown on the first visit lets you keep them on or turn them off, with equal weight, and it is shown again after one year.
You can turn them off or on again at any time with the analytics settings button on this page or in the footer. Turning them off stops Google Tag Manager from loading on later visits and deletes the Google Analytics cookies of this site.
Your choice is stored only in this browser (localStorage, key oee-consent), with its date. It is not sent to any server. Apart from that, the site only remembers in the browser your map mode preference.