diff --git a/.github/deployment/sparql/CCO_classes_have_BFO_superclass.sparql b/.github/deployment/sparql/CCO_classes_have_BFO_superclass.sparql index 54836ac6..8f5fa842 100644 --- a/.github/deployment/sparql/CCO_classes_have_BFO_superclass.sparql +++ b/.github/deployment/sparql/CCO_classes_have_BFO_superclass.sparql @@ -13,7 +13,7 @@ PREFIX rdfs: SELECT DISTINCT ?class ?label WHERE { ?class a owl:Class . -FILTER (regex(str(?class),"http://www.ontologyrepository.com/CommonCoreOntologies/")) +FILTER (regex(str(?class),"https://www.commoncoreontologies.org/")) FILTER NOT EXISTS {?class rdfs:subClassOf+ } OPTIONAL {?class rdfs:label ?label} } diff --git a/.github/deployment/sparql/annotation_language_tag.sparql b/.github/deployment/sparql/annotation_language_tag.sparql new file mode 100644 index 00000000..6d812cc0 --- /dev/null +++ b/.github/deployment/sparql/annotation_language_tag.sparql @@ -0,0 +1,60 @@ +# Title: +# Annotation Values Must Have English Language Tag +# Constraint Description: +# All natural-language annotation values on CCO ontology elements must carry +# an English (@en) language tag. This applies to: rdfs:label, skos:definition, +# rdfs:comment, skos:altLabel, skos:prefLabel, skos:editorNote, +# cco:ont00001737 [doctrinal definition], and cco:ont00001748 [doctrinal label]. +# Values typed as xsd:anyURI are exempt (they are IRI references, not NL text). +# English dialect tags such as @en-US and @en-GB are accepted. +# Severity: +# Error +# Reference: +# # Author: +# github.com/shanmukhkalasamudram + +PREFIX owl: +PREFIX rdfs: +PREFIX skos: +PREFIX xsd: +PREFIX cco: + +SELECT DISTINCT ?resource ?prop ?val ?error +WHERE { + VALUES ?type { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + # Scope to natural-language annotation properties only. + # xsd:anyURI-typed values (IRI references) are handled by the datatype filter below. + VALUES ?prop { + rdfs:label + skos:definition + rdfs:comment + skos:altLabel + skos:prefLabel + skos:editorNote + cco:ont00001737 + cco:ont00001748 + } + ?resource a ?type . + FILTER (STRSTARTS(STR(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + ?resource ?prop ?val . + FILTER (isLiteral(?val)) + # langMatches handles dialect tags: @en-US, @en-GB all pass. + # lang("") of a plain literal returns "" — langMatches("", "en") = FALSE — flagged. + FILTER (!langMatches(lang(?val), "en")) + # Allow xsd:anyURI values (IRI references do not need a language tag). + FILTER (datatype(?val) != xsd:anyURI) + BIND (CONCAT( + "ERROR: Term ", STR(?resource), + " has annotation <", STR(?prop), + "> with value \"", STR(?val), + "\" that is missing an @en language tag (lang='", lang(?val), "')." + ) AS ?error) +} +ORDER BY ?resource ?prop diff --git a/.github/deployment/sparql/curated_in_matches_ontology.sparql b/.github/deployment/sparql/curated_in_matches_ontology.sparql new file mode 100644 index 00000000..2f92fb63 --- /dev/null +++ b/.github/deployment/sparql/curated_in_matches_ontology.sparql @@ -0,0 +1,58 @@ +# Title: +# cco:ont00001760 Value Must Point to a Declared CCO Ontology +# Constraint Description: +# The value of cco:ont00001760 [is curated in ontology] on every CCO ontology +# element must be the IRI of a CCO ontology that is actually declared as +# owl:Ontology within the CCO namespace +# (https://www.commoncoreontologies.org/). +# This catches values that: +# - Point to a non-existent or deleted ontology module. +# - Use the old v1 namespace (http://www.ontologyrepository.com/...). +# - Contain a typo in the ontology name. +# Severity: +# Error +# Reference: +# CCO Release Process v2, Step 6c / Step 6f +# Author: +# github.com/shanmukhkalasamudram + +PREFIX owl: +PREFIX cco: + +SELECT DISTINCT ?resource ?curatedIn ?error +WHERE { + VALUES ?type { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + ?resource a ?type . + FILTER (STRSTARTS(STR(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + ?resource cco:ont00001760 ?curatedIn . + # The merged file only carries one owl:Ontology IRI (CommonCoreOntologiesMerged), + # so dynamic lookup against owl:Ontology triples is not feasible for merged runs. + # Instead validate against the fixed allowlist of the 11 CCO module ontology IRIs. + # Update this list whenever a module is added or removed. + FILTER (STR(?curatedIn) NOT IN ( + "https://www.commoncoreontologies.org/AgentOntology", + "https://www.commoncoreontologies.org/ArtifactOntology", + "https://www.commoncoreontologies.org/CurrencyUnitOntology", + "https://www.commoncoreontologies.org/EventOntology", + "https://www.commoncoreontologies.org/ExtendedRelationOntology", + "https://www.commoncoreontologies.org/FacilityOntology", + "https://www.commoncoreontologies.org/GeospatialOntology", + "https://www.commoncoreontologies.org/InformationEntityOntology", + "https://www.commoncoreontologies.org/QualityOntology", + "https://www.commoncoreontologies.org/TimeOntology", + "https://www.commoncoreontologies.org/UnitsOfMeasureOntology" + )) + BIND (CONCAT( + "ERROR: Term <", STR(?resource), + "> has cco:ont00001760 value <", STR(?curatedIn), + "> which does not match any known CCO module ontology IRI." + ) AS ?error) +} +ORDER BY ?resource diff --git a/.github/deployment/sparql/duplicate_iri_number.sparql b/.github/deployment/sparql/duplicate_iri_number.sparql new file mode 100644 index 00000000..e8ee09b9 --- /dev/null +++ b/.github/deployment/sparql/duplicate_iri_number.sparql @@ -0,0 +1,42 @@ +# duplicate_iri_number.sparql +# Step 6 QC — Flags any two OWL entities whose IRI ends in the same +# "ont" + 8-digit numeric fragment, regardless of namespace. +# +# Why this matters: +# The canonical IRI pattern is https://www.commoncoreontologies.org/ont<8digits> +# If the same 8-digit number appears under a different base (e.g., old v1 +# namespace http://www.ontologyrepository.com/CommonCoreOntologies/ or a +# typo namespace), two distinct IRIs share the same logical ID — creating +# ambiguity, potential reasoner issues, and broken cross-references. +# +# Reports: pairs of IRIs sharing the same numeric fragment, plus that fragment. +# Expected result on a clean release: 0 violations. + +PREFIX owl: +PREFIX rdf: + +SELECT DISTINCT ?iri1 ?iri2 ?sharedID WHERE { + VALUES ?owlType { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + + # Both IRIs must be declared OWL entities with the ont+8digit pattern + ?iri1 rdf:type ?owlType . + ?iri2 rdf:type ?owlType . + + FILTER(?iri1 != ?iri2) + FILTER(REGEX(STR(?iri1), "ont[0-9]{8}$")) + FILTER(REGEX(STR(?iri2), "ont[0-9]{8}$")) + + # Extract the shared numeric fragment (e.g. "ont00001234") + BIND(REPLACE(STR(?iri1), "^.*(ont[0-9]{8})$", "$1") AS ?sharedID) + FILTER(STRENDS(STR(?iri2), ?sharedID)) + + # Report each pair once only + FILTER(STR(?iri1) < STR(?iri2)) +} +ORDER BY ?sharedID diff --git a/.github/deployment/sparql/exactly_1_curated_in.sparql b/.github/deployment/sparql/exactly_1_curated_in.sparql new file mode 100644 index 00000000..6d9a0597 --- /dev/null +++ b/.github/deployment/sparql/exactly_1_curated_in.sparql @@ -0,0 +1,56 @@ +# Title: +# Exactly One cco:ont00001760 [is curated in ontology] Per Ontology Element +# Constraint Description: +# All CCO ontology elements (classes, properties, individuals) must have +# exactly one cco:ont00001760 annotation. This query flags: +# (a) Elements with NO cco:ont00001760 annotation at all. +# (b) Elements with MORE THAN ONE cco:ont00001760 annotation. +# Severity: +# Error +# Reference: +# CCO Release Process v2, Step 6c +# Author: +# github.com/shanmukhkalasamudram + +PREFIX owl: +PREFIX cco: + +SELECT DISTINCT ?resource ?error +WHERE { + { + # Branch (a): missing cco:ont00001760 entirely + # All patterns are self-contained inside each UNION branch so that + # ?resource is properly bound before FILTER NOT EXISTS is evaluated. + VALUES ?type { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + ?resource a ?type . + FILTER (regex(str(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + FILTER NOT EXISTS { ?resource cco:ont00001760 ?any } + BIND (CONCAT("ERROR: Term ", str(?resource), " is missing cco:ont00001760 [is curated in ontology].") AS ?error) + } + UNION + { + # Branch (b): more than one cco:ont00001760 annotation + VALUES ?type { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + ?resource a ?type . + FILTER (regex(str(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + ?resource cco:ont00001760 ?val1 . + ?resource cco:ont00001760 ?val2 . + FILTER (STR(?val1) != STR(?val2)) + BIND (CONCAT("ERROR: Term ", str(?resource), " has duplicate cco:ont00001760 values: <", str(?val1), "> and <", str(?val2), ">.") AS ?error) + } +} +ORDER BY ?resource diff --git a/.github/deployment/sparql/exactly_1_label.sparql b/.github/deployment/sparql/exactly_1_label.sparql new file mode 100644 index 00000000..5a12bb64 --- /dev/null +++ b/.github/deployment/sparql/exactly_1_label.sparql @@ -0,0 +1,59 @@ +# Title: +# Exactly One rdfs:label Per Language Per Ontology Element +# Constraint Description: +# All CCO ontology elements (classes, properties, individuals) must have +# exactly one rdfs:label. This query flags: +# (a) Elements with NO rdfs:label at all. +# (b) Elements with MORE THAN ONE rdfs:label in the same language. +# Severity: +# Error +# Reference: +# CCO Release Process v2, Step 6c +# Pattern based on: exactly_1_prefLabel_per_lang.sparql +# Author: +# github.com/shanmukhkalasamudram + +PREFIX owl: +PREFIX rdfs: + +SELECT DISTINCT ?resource ?error +WHERE { + { + # Branch (a): missing rdfs:label entirely + # All patterns must be self-contained inside each UNION branch so that + # ?resource is properly bound before FILTER NOT EXISTS is evaluated. + VALUES ?type { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + ?resource a ?type . + FILTER (regex(str(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + FILTER NOT EXISTS { ?resource rdfs:label ?anyLbl } + BIND (CONCAT("ERROR: Term ", str(?resource), " has no rdfs:label.") AS ?error) + } + UNION + { + # Branch (b): more than one rdfs:label in the same language + VALUES ?type { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + ?resource a ?type . + FILTER (regex(str(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + ?resource rdfs:label ?lbl1 . + ?resource rdfs:label ?lbl2 . + FILTER (?lbl1 != ?lbl2) + FILTER (lang(?lbl1) = lang(?lbl2)) + FILTER (lang(?lbl1) != "") + BIND (CONCAT("ERROR: Term ", str(?resource), " has duplicate rdfs:label values in language '", lang(?lbl1), "': \"", str(?lbl1), "\" and \"", str(?lbl2), "\".") AS ?error) + } +} +ORDER BY ?resource diff --git a/.github/deployment/sparql/iri_format_check.sparql b/.github/deployment/sparql/iri_format_check.sparql new file mode 100644 index 00000000..dcb05f3b --- /dev/null +++ b/.github/deployment/sparql/iri_format_check.sparql @@ -0,0 +1,38 @@ +# Title: +# CCO Term IRI Format Check +# Constraint Description: +# All CCO ontology elements (classes, properties, individuals) must have an IRI +# matching the canonical CCO format: +# https://www.commoncoreontologies.org/ont<8 digits> +# Example: https://www.commoncoreontologies.org/ont00001760 +# Ontology-level IRIs (owl:Ontology) are not term IRIs and are intentionally +# excluded from this check. +# Severity: +# Error +# Reference: +# CCO Release Process v2, Step 6e +# Author: +# github.com/shanmukhkalasamudram + +PREFIX owl: + +SELECT DISTINCT ?resource ?error +WHERE { + VALUES ?type { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + ?resource a ?type . + FILTER (STRSTARTS(STR(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + # Flag any term IRI that does NOT match ont + exactly 8 digits. + FILTER (!REGEX(STR(?resource), "^https://www\\.commoncoreontologies\\.org/ont[0-9]{8}$")) + BIND (CONCAT( + "ERROR: Term IRI <", STR(?resource), + "> does not match the required CCO format https://www.commoncoreontologies.org/ont<8 digits>." + ) AS ?error) +} +ORDER BY ?resource diff --git a/.github/deployment/sparql/missing_definition_source.sparql b/.github/deployment/sparql/missing_definition_source.sparql new file mode 100644 index 00000000..3adb75be --- /dev/null +++ b/.github/deployment/sparql/missing_definition_source.sparql @@ -0,0 +1,41 @@ +# Title: +# Definition Source Required +# Constraint Description: +# All CCO classes and object properties that have a skos:definition should also +# have a cco:ont00001754 [definition source] annotation. Terms missing a +# definition source are flagged as warnings. Some terms may legitimately lack a +# source (e.g., primitive relations); those should be reviewed case by case. +# Severity: +# Warning +# Reference: +# CCO Release Process v2, Step 6h +# Pattern based on: min_1_eng_def.sparql +# Author: +# github.com/shanmukhkalasamudram + +PREFIX owl: +PREFIX rdfs: +PREFIX skos: +PREFIX cco: + +SELECT DISTINCT ?resource ?label ?error +WHERE { + VALUES ?type { owl:Class owl:ObjectProperty } + ?resource a ?type . + + # Must have a definition (already caught by min_1_eng_def if missing) + ?resource skos:definition ?definition . + + # Scope to current CCO terms only + FILTER (regex(str(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + + # Flag those missing a definition source + OPTIONAL { ?resource cco:ont00001754 ?source } + FILTER (!bound(?source)) + + OPTIONAL { ?resource rdfs:label ?label } + + BIND (CONCAT("WARNING: Term ", str(?resource), " has a definition but is missing cco:ont00001754 [definition source].") AS ?error) +} +ORDER BY ?resource diff --git a/.github/deployment/sparql/no_cco_elucidation.sparql b/.github/deployment/sparql/no_cco_elucidation.sparql new file mode 100644 index 00000000..37d4e1bd --- /dev/null +++ b/.github/deployment/sparql/no_cco_elucidation.sparql @@ -0,0 +1,28 @@ +# Title: +# No Use of Deprecated cco:elucidation Property +# Constraint Description: +# The annotation property cco:elucidation (from the old CCO v1 namespace) is +# deprecated. It was replaced by skos:editorNote in CCO v2. Any CCO term that +# still uses cco:elucidation as a predicate must be updated. +# Severity: +# Error +# Author: +# github.com/shanmukhkalasamudram +# Inspired by: Giacomo Decolle + +PREFIX owl: +PREFIX rdfs: + +SELECT DISTINCT ?subject ?label ?error +WHERE { + ?subject ?value . + + # Scope to current CCO terms only + FILTER (regex(str(?subject), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?subject)) + + OPTIONAL { ?subject rdfs:label ?label } + + BIND (CONCAT("ERROR: Term ", str(?subject), " uses deprecated cco:elucidation. Replace with skos:editorNote.") AS ?error) +} +ORDER BY ?subject diff --git a/.github/deployment/sparql/no_duplicate_declarations.sparql b/.github/deployment/sparql/no_duplicate_declarations.sparql new file mode 100644 index 00000000..4dc5cd33 --- /dev/null +++ b/.github/deployment/sparql/no_duplicate_declarations.sparql @@ -0,0 +1,46 @@ +# Title: +# No Duplicate OWL Entity Type Declarations +# Constraint Description: +# A CCO term IRI must not be declared as more than one OWL entity type +# from the set: owl:Class, owl:ObjectProperty, owl:AnnotationProperty, +# owl:DatatypeProperty, owl:NamedIndividual. +# Example violations: a term typed as both owl:Class and owl:NamedIndividual, +# or as both owl:ObjectProperty and owl:AnnotationProperty. +# Severity: +# Error +# Reference: +# CCO Release Process v2, Step 6d +# Author: +# github.com/shanmukhkalasamudram + +PREFIX owl: + +SELECT DISTINCT ?resource ?type1 ?type2 ?error +WHERE { + VALUES ?type1 { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + VALUES ?type2 { + owl:Class + owl:ObjectProperty + owl:AnnotationProperty + owl:DatatypeProperty + owl:NamedIndividual + } + ?resource a ?type1 . + ?resource a ?type2 . + FILTER (STRSTARTS(STR(?resource), "https://www.commoncoreontologies.org/")) + FILTER (!isBlank(?resource)) + # Only report each unordered pair once by enforcing lexicographic order + FILTER (STR(?type1) < STR(?type2)) + BIND (CONCAT( + "ERROR: Term <", STR(?resource), + "> is declared as both <", STR(?type1), + "> and <", STR(?type2), ">." + ) AS ?error) +} +ORDER BY ?resource ?type1 ?type2 diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..1d231c34 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,35 @@ +# GitHub Automatic Release Notes Configuration +# Categorizes pull requests by label for auto-generated release changelogs. +# See: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes + +changelog: + categories: + - title: New Features + labels: + - enhancement + - feature + - title: Bug Fixes + labels: + - bug + - fix + - title: Ontology Changes + labels: + - ontology + - new-term + - term-update + - deprecation + - title: Infrastructure & CI + labels: + - infrastructure + - ci + - build + - title: Documentation + labels: + - documentation + - docs + - title: Uncategorized + labels: + - "" + - title: Other Changes + labels: + - "*" diff --git a/.github/workflows/manage_release.yml b/.github/workflows/manage_release.yml index 3a466949..dca32ccb 100644 --- a/.github/workflows/manage_release.yml +++ b/.github/workflows/manage_release.yml @@ -44,10 +44,6 @@ jobs: echo "RELEASE_FILE_PATH=$(make output-release-filepath)" >> $GITHUB_OUTPUT echo "RELEASE_NAME=$(make output-release-name)" >> $GITHUB_OUTPUT - - name: Get basename of the release file - id: get_basename - run: echo "BASENAME=$(basename ${{ steps.build_release.outputs.RELEASE_FILE_PATH }})" >> $GITHUB_ENV - - name: Save artifacts uses: actions/upload-artifact@v4 if: always() @@ -63,6 +59,10 @@ jobs: path: ${{ env.cache-path }} key: ${{ runner.os }}-${{ env.cache-key }} + - name: Get basename of the release file + id: get_basename + run: echo "BASENAME=$(basename ${{ steps.build_release.outputs.RELEASE_FILE_PATH }})" >> $GITHUB_ENV + - name: Draft GitHub Release uses: actions/create-release@v1 id: draft_release @@ -83,3 +83,16 @@ jobs: asset_content_type: ${{ env.release-build-content-type }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Release Notes + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/releases/generate-notes \ + -f tag_name="${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" \ + --jq '.body' > /tmp/release-notes.md + gh release edit "${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" \ + --notes-file /tmp/release-notes.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index e43b0f98..8ed58300 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .DS_Store +build/artifacts/ +build/lib/ \ No newline at end of file diff --git a/Makefile b/Makefile index de2b8e28..30a7bb24 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ REQUIRED_DIRS = $(config.LIBRARY_DIR) $(config.SOURCE_DIR) $(config.QUERIES_DIR) # ---------------------------------------- #### Targets / main "goals" of this Makefile .PHONY: all -all: setup reason-individual test-individual build-combined reason-combined test-combined +all: setup reason-individual test-individual build-combined reason-combined validate-profile-combined test-combined # Setup target for creating necessary directories .PHONY: setup @@ -86,6 +86,19 @@ reason-individual: $(ROBOT_FILE) java -jar $(ROBOT_FILE) reason --input $$file --catalog src/cco-modules/catalog-v001.xml --reasoner HermiT; \ done +# Validate OWL DL profile on individual files (Step 6b) +.PHONY: validate-profile-individual +validate-profile-individual: $(ROBOT_FILE) + for file in $(DEV_FILES); do \ + echo "Validating OWL DL profile for $$file..."; \ + java -jar $(ROBOT_FILE) validate-profile --profile DL --input $$file; \ + done + +# Validate OWL DL profile on combined file (Step 6b) +.PHONY: validate-profile-combined +validate-profile-combined: $(combined-file) | $(ROBOT_FILE) + java -jar $(ROBOT_FILE) validate-profile --profile DL --input $(combined-file) + # Test individual files .PHONY: test-individual test-individual: $(ROBOT_FILE)