The two-tier vocabulary
How cybedtools represents heterogeneous frameworks in a single graph
The design problem
14 frameworks. Each one was authored against a different theory of what counts as a structural element. NICE has work roles, tasks, knowledge statements, and skill statements, plus competency areas as a second grouping axis. DigComp has competence areas and proficiency levels. SFIA has skills and 7 levels of responsibility. Cyber.org K-12 has grade-band-by-sub-concept cells plus prose clarification statements. CSTA has level-by-concept cells plus practices. CSEC2017 has 8 knowledge areas. ECSF has 12 profiles. DCWF aligns to NICE work roles with overlap. CyQUAL has qualification units and proficiency levels. CCSSF has work roles and skills. OTCCF has job roles, skills, and role-to-role relations.
A single shared vocabulary that flattens these into one type hierarchy would erase the differences each framework was designed to express. A pure per-framework vocabulary with no shared terms would make cross-framework queries impossible. The schema has to preserve native vocabulary and enable shared queries at the same time.
The two-tier solution
A graph in 30 seconds. A graph is nodes connected by labeled edges. A query asks “find every node where these edges hold.” That is enough to read the diagram below. The technical names beneath each box show what the same graph looks like as RDF, but you do not need to know RDF or SPARQL to follow the structure.
cybedtools uses a two-tier RDF vocabulary.
Tier 1: cybed: cross-framework abstract terms
Every framework asserts these. They define the joinable surface for cross-framework queries.
Tier 2: per-framework subtypes
Each framework defines its own vocabulary that subclasses the cybed: abstracts. These preserve the framework’s native terminology.
The two-tier shape lets a researcher write one SPARQL query that runs across the entire corpus at the Tier 1 surface, without losing the ability to ask framework-specific questions through Tier 2 subtypes.
Tier 1 vocabulary
| Term | Type | Description |
|---|---|---|
cybed:Framework |
Class | The framework as a whole |
cybed:OrganizingUnit |
Class (subClassOf skos:Concept) |
Any top-level enumerated unit. All frameworks in the corpus assert this on their parents. |
cybed:Role |
Class (subClassOf cybed:OrganizingUnit) |
Workforce-restricted. NICE, DCWF, CyQUAL, and CCSSF work roles, ECSF profiles, and OTCCF job roles. |
cybed:RoleElement |
Class | Anything attached to an organizing unit via cybed:hasElement
|
cybed:Subpoint |
Class (subClassOf cybed:RoleElement) |
Bullet points and enumerations embedded inside an element’s text |
cybed:Example |
Class (subClassOf cybed:RoleElement) |
Pedagogical scaffolding (such as clarification statements). Reachable only via cybed:hasExample
|
cybed:hasOrganizingUnit |
Property | Framework → organizing unit |
cybed:hasRole |
Property | Framework → role (workforce subset) |
cybed:hasElement |
Property | Organizing unit → RoleElement (excluding Examples by default) |
cybed:hasExample |
Property | Organizing unit → Example (separate predicate) |
cybed:elaborates |
Property | Subpoint → parent (back-edge) |
cybed:relatedUnit |
Property | Organizing unit → cybed:UnitRelation, the reified edge between two units |
cybed:UnitRelation |
Class | A reified relation between two organizing units, so the edge can carry its own label and level |
cybed:fromUnit |
Property | Unit relation → the source organizing unit |
cybed:toUnit |
Property | Unit relation → the target organizing unit |
cybed:relationLabel |
Property | The steward’s own wording for what the relation means |
cybed:proficiencyLevel |
Property | The level as the steward printed it, always a literal string |
cybed:niceCrossReference |
Property | A framework’s own declared pointer at a NICE identifier |
schema:creditText |
Property | The attribution wording a steward asked to be reproduced |
schema:inLanguage |
Property | The BCP 47 language tag of an element’s statement text. Supported by the ingestion helpers, not asserted by any framework in the current corpus. |
Proficiency levels are stored as printed and are never coerced to numbers. A CyQUAL level and an SFIA level are both called a level and are not the same scale, so arithmetic across them would invent a comparison the stewards never made.
Tier 2 vocabulary (per-framework subtypes)
Each framework declares its own per-framework namespace and subclasses the Tier 1 abstracts:
| Framework | Namespace | Per-framework subtypes |
|---|---|---|
| NICE | nice: |
nice:Framework, nice:WorkRole, nice:Task, nice:KnowledgeStatement, nice:SkillStatement
|
| DCWF | dcwf: |
dcwf:Framework, dcwf:WorkRole, dcwf:Task, dcwf:KSA
|
| ECSF | ecsf: |
ecsf:Framework, ecsf:Profile, ecsf:KSA
|
| SFIA | sfia: |
sfia:Framework, sfia:Skill, sfia:LevelDescriptor
|
| Cyber.org K-12 | cyberorg: |
cyberorg:Framework, cyberorg:StandardGroup, cyberorg:Standard
|
| CSTA | csta: |
csta:Framework, csta:StandardGroup, csta:Standard
|
| CSEC2017 | csec: |
csec:Framework, csec:KnowledgeArea, csec:Topic
|
| DigComp | digcomp: |
digcomp:Framework, digcomp:CompetenceArea, digcomp:Competence, digcomp:ProficiencyDescriptor
|
| CyQUAL | cyqual: |
cyqual:Framework, cyqual:WorkRole, cyqual:Competency, cyqual:Requirement, cyqual:Task
|
| CCSSF | ccssf: |
ccssf:Framework, ccssf:WorkRole, ccssf:Competency, ccssf:Task, ccssf:ToolOrTechnology, ccssf:AdjacentRole
|
| OTCCF | otccf: |
otccf:Framework, otccf:JobRole, otccf:KeyTask, otccf:TechnicalSkillCompetency, otccf:CriticalCoreSkill, otccf:Knowledge, otccf:Ability
|
The first eight prefixes resolve under a steward’s own domain. The last three do not. CyQUAL, CCSSF, and OTCCF are minted by the package under https://w3id.org/cybed/framework/<slug>#, because those stewards publish no term namespace of their own. Putting a package-coined type under the package’s namespace is the point: a reader who resolves the URI lands on cybedtools documentation and can see that cybedtools coined the type, so a package-coined type is never mistaken for an identifier the steward issued.
A nice:WorkRole is also a cybed:Role, which is also a cybed:OrganizingUnit, which is also a skos:Concept. The chain runs all the way up the abstraction hierarchy. A query at any tier returns results from every framework that asserts the relevant Tier 1 type.
Why a custom URI
The package uses https://w3id.org/cybed/ontology# as the namespace for cybed: terms. The W3ID redirect resolves to the schema documentation hosted with the package. That gives the vocabulary a stable, persistent URI that does not depend on any one repository’s URL.
What’s reachable from where
A cybed:hasElement traversal on any organizing unit returns cybed:Subpoint nodes, because they subclass cybed:RoleElement. It does not return cybed:Example nodes, because the Example predicate is separate. This is intentional. Examples are pedagogical scaffolding. Including them in default element traversals would double-count the same conceptual content under two different shapes.
A query that wants Examples explicitly uses cybed:hasExample instead. The package’s example_framework_bindings() helper does exactly this.
What this enables
The cybed:OrganizingUnit abstraction lets a single SPARQL query return organizing units across the entire corpus. The cybed:Role abstraction restricts that query to the 6 workforce frameworks whose top-level unit is genuinely a role (NICE, DCWF, ECSF, CyQUAL, CCSSF, OTCCF). The cybed:hasElement predicate returns the strict numbered-element traversal across every framework that uses it. The cybed:hasExample predicate exposes the pedagogical scaffolding layer separately, where it exists at all (currently Cyber.org K-12 and CSTA only).
That split is worth stating as an argument, because the count behind it has moved. cybed:Role now covers 6 of the 14 frameworks, so it is the majority type, not a minority carve-out. The reason to keep it separate is unchanged and now easier to see. A NICE work role and a DigComp competence area are both organizing units and only one of them is a job. Collapsing the two would make every role-scoped query silently include competence areas, grade-band cells, and knowledge areas. cybed:OrganizingUnit is what makes the 8 non-role frameworks reachable in the same query as the rest. cybed:Role is what keeps them out when the question is about jobs.
The two-tier shape is what makes the density spread query work across every framework with a single expression rather than reapplying per framework.