Skip to content

Schema Developer FAQs

Some frequently asked questions about developing the NMDC Schema.

What are some effective strategies for collaborative schema development?

LinkML Collaborative Development Google Slides Presentation

How do I migrate from one version of the NMDC schema to another?

Version 10 to 11 migration

How do I view the NMDC schema programmatically?

NMDC SchemaView Documentation SchemaView Documentation

Where is the OWL generation documentation?

OWL Generation -- covers current build process, recommended config, CLI/YAML flag mapping, portal submission guidance, and upcoming LinkML changes.

Where should I put hand-written documentation?

The docs/ directory is a build output directorymake clean deletes docs/*.md and docs/*.html. Tracked files in docs/ that have no counterpart in src/docs/ will trigger a warning during make clean, but untracked files will be silently removed. Place all hand-written documentation in src/docs/ instead; the build copies it into docs/ automatically.

How do I make sure a directory exists?

Which mechanism to use depends on whether the directory has to be there before the build runs.

.gitkeep for directories that must be present in a fresh clone. Git does not track empty directories, so a tracked .gitkeep is what makes them survive a clone. The repo has 8, marking the example-data and asset directories that targets read from or write into:

  • src/data/valid/, src/data/invalid/
  • src/data/problem/ and its valid/ and invalid/ subdirectories
  • assets/jsons-for-mongodb/, assets/misc/
  • local/

mkdir -p in the recipe for anything the build creates. Do not add a .gitkeep for a build output directory. The Makefiles do this in 11 places: Makefile before writing $(DEST)/prefixmap, $(DEST)/pydantic, local, and the $(DOCDIR) subdirectories; project.Makefile with the $@ and $(@D) forms, so a target creates its own output directory; and makefiles/mixs.Makefile for local/mixs_regen.

local/ is worth understanding because it looks contradictory. .gitignore lists local/, yet local/.gitkeep is tracked, because an ignore rule has no effect on a file that is already tracked. The result is what you want: a fresh clone gets an empty local/ directory, and nothing written into it afterward is ever staged. Confirm with git archive origin/main | tar -t | grep '^local/'.

How do I configure environment variables for development scripts?

Some scripts need API keys or database credentials. Scripts that need them load from local/.env via python-dotenv. To get started:

# IMPORTANT: Do NOT overwrite an existing local/.env — it may contain
# real credentials that are not recoverable from this repo.
if [ ! -f local/.env ]; then
    cp .env.example local/.env
else
    echo "local/.env already exists — not overwriting."
    echo "Compare with .env.example to see if you need to add new variables:"
    echo "  diff <(sed 's/=.*//' .env.example) <(sed 's/=.*//' local/.env)"
fi

local/ is gitignored. .env.example in the repo root documents every variable, which script uses it, and where to get credentials. You only need to fill in the variables for the scripts you actually run.

The Docker-based migrator workflow has its own separate env file at nmdc_schema/migrators/.docker/.env.example — see the migrators README for details.

What are LinkML readonly metaslots and why shouldn't I assert them?

The LinkML metamodel defines 12 readonly slots that are automatically populated by the schema loader or generators. They belong to two different metamodel element types, which matters when writing tooling that filters on them.

Seven are on SlotDefinition, so they can appear on a slot or in a slot_usage block: definition_uri, domain_of, from_schema, imported_from, is_usage_slot, owner, usage_slot_name.

Five are on SchemaDefinition and describe the file a schema was read from. They never appear on a slot, even after induction: generation_date, metamodel_version, source_file, source_file_date, source_file_size.

Do not add these to hand-edited schema YAML files under src/schema/. The loader fills them in at runtime, so asserting them is redundant and can cause confusion when values drift from what the loader would compute.

For generated files like src/schema/mixs.yaml, the MIxS build pipeline (makefiles/mixs.Makefile) strips all 12 readonly slots via yq eval as part of the dematerialization step (PR #2696).

What architectural changes were made between Oct 2025 and Feb 2026?

Date PR Change
2026-02-26 #2848 Makefile reorganization: MIxS pipeline moved to makefiles/mixs.Makefile, migrator targets to makefiles/migrators.Makefile. RDF conversion tooling removed.
2026-02-26 #2849 LinkML 1.10.0 upgrade: new OWL flags (--enum-inherits-as-subclass-of), Python 3.9 dropped.
2026-02-26 #2846 Dead code removal: about.yaml and experimental scripts removed.
2026-02-21 #2302 Downloads page: schema-derived JSON and YAML files downloadable via docs website.
2026-02-20 #2839 Unified LinkML CLI: build uses linkml generate owl, linkml generate json-schema, etc. instead of legacy gen-owl, gen-json-schema.
2025-10-28 #2696 Dematerialize mixs.yaml: all 12 readonly metaslots stripped from generated mixs.yaml (35% line reduction).