Start with data-dict when the domain is naturally a set of tables. It gives Graft table and column descriptions, primary keys, supported value rules, and scalar foreign keys that can be checked before records are accepted.
LinkML is the next step when the contract must express meaning beyond
those tables. It can define ontology identifiers, inheritance,
polymorphic ranges, and Graft semantic-statement roles. Both providers
compile to the same .graft.json runtime contract and use
the same plan, commit, history, and retrieval functions.
There is an important distinction: a checked reference is not automatically a graph edge. Graft validates ordinary LinkML object-reference slots, just as it validates supported data-dict foreign keys. The semantic graph projection is driven by classes declared as graph-producing edges or semantic statements.
| Need | Start with |
|---|---|
| Tables, columns, keys, enums, and checked scalar foreign keys | data-dict |
| Class and slot ontology identifiers | LinkML |
| Reusable inheritance or polymorphic references | LinkML |
| Subject-predicate-object statements in the semantic graph | LinkML with Graft semantic roles |
Add ontology identifiers and inheritance
An ordinary LinkML schema can introduce shared fields through inheritance and attach stable ontology identifiers to classes and slots:
id: https://w3id.org/example/team-directory
name: team_directory
version: 0.1.0
prefixes:
linkml: https://w3id.org/linkml/
sdo: https://schema.org/
team: https://w3id.org/example/team-directory/
default_prefix: team
default_range: string
imports:
- linkml:types
classes:
NamedThing:
abstract: true
attributes:
id:
identifier: true
required: true
name:
required: true
Person:
is_a: NamedThing
class_uri: sdo:Person
attributes:
works_for:
range: Organization
inlined: false
Organization:
is_a: NamedThing
class_uri: sdo:OrganizationThis contract reuses identity and name fields and validates
works_for as an organization reference. That reference
alone does not create a traversal edge. Use a graph-producing statement
when the relationship must participate in the semantic graph.
Declare a semantic statement
Graft ships a small LinkML core for schemas that need record and statement roles. To run a complete graph example without introducing another team-directory fixture, the rest of this guide switches deliberately to the bundled materials domain. It defines nodes and a semantic statement:
classes:
Material:
is_a: GraftNode
slots:
- preferred_name
- description
- cas_number
Measurement:
is_a: GraftSemanticStatement
slots:
- measurement_method
- temperatureGraftSemanticStatement supplies subject,
predicate, object_entity, and
object_value. A concrete statement must have exactly one
object representation. Accepted entity-valued statements produce the
semantic edges used by bounded neighbor retrieval. Narrative statements
remain searchable claims but are excluded from that semantic edge
projection.
This richer behavior is why LinkML follows, rather than replaces, the simpler data-dict path. Use it when the domain actually needs semantic graph meaning.
Compile once, then run in R
graft_schema() compiles LinkML YAML and its import
closure into a canonical .graft.json manifest:
library(graft)
schema <- graft_schema(
system.file(
"extdata",
"materials.linkml.yaml",
package = "graft",
mustWork = TRUE
),
output = "materials.graft.json"
)Compilation requires Python and linkml-runtime. Graft
rejects source semantics that its runtime contract cannot preserve
instead of silently downgrading them. Contract compiler boundaries lists
the supported LinkML profile and the exact compilation dependencies.
Loading a compiled manifest and operating a store are R-only:
schema <- graft_schema(system.file(
"extdata",
"materials.graft.json",
package = "graft",
mustWork = TRUE
))Commit the LinkML source and compiled manifest together when users or deployed workflows should run without Python.
Inspect what the contract enables
The typed schema properties expose the parts most applications need:
schema@name
#> [1] "materials"
schema@version
#> [1] "0.1.0"
schema@structural_digest
#> [1] "sha256:5eb12af7a75caa1d99d405db168634b58a8655addf5cf3d0200aea635d36286a"
names(schema@classes)
#> [1] "Claim" "Evidence" "Material" "Measurement" "Source"
material <- schema@classes$Material
material@role
#> [1] "node"
material@label_slot
#> [1] "preferred_name"
material@search_slots
#> [1] "description" "preferred_name"
measurement <- schema@classes$Measurement
measurement@role
#> [1] "statement"
measurement@statement_shape
#> [1] "semantic"
measurement@slots$subject@object_reference
#> [1] TRUE
measurement@slots$object_entity@object_reference
#> [1] TRUEThe complete manifest is available as schema@manifest
for lower-level tooling. Domain records remain ordinary data frames; the
S7 schema object protects the compiled contract and its digests.
Accept and traverse semantic statements
The materials contract can accept two nodes and a semantic statement in one plan:
store <- graft_open(schema, ":memory:", okf = "disabled")
polyethylene_id <- "graft:40X0M02WR9Z55QWFDJYTNS3KX8"
water_id <- "graft:64W8ZFH68S76XQ2MWW0H46Q9Y6"
records <- list(
Material = data.frame(
id = c(polyethylene_id, water_id),
preferred_name = c("Polyethylene", "Water"),
cas_number = c("9002-88-4", "7732-18-5")
),
Measurement = data.frame(
id = "graft:0D6WMEBSAMAS7JRQWGT5W44ASR",
subject = polyethylene_id,
predicate = "materials:testedWith",
object_entity = water_id,
measurement_method = "immersion",
temperature = 23
)
)
plan <- graft_plan(
store,
records,
graft_provenance(
producer = "materials-example",
idempotency_key = "materials-v1"
)
)
plan@valid
#> [1] TRUE
plan@changes[, c("class", "record_id", "action"), drop = FALSE]
#> class record_id action
#> 1 Material graft:40X0M02WR9Z55QWFDJYTNS3KX8 insert
#> 2 Material graft:64W8ZFH68S76XQ2MWW0H46Q9Y6 insert
#> 3 Measurement graft:0D6WMEBSAMAS7JRQWGT5W44ASR insert
plan@issues
#> [1] class input_row record_id field
#> [5] rule message condition_class
#> <0 rows> (or 0-length row.names)
stopifnot(plan@valid)
result <- graft_commit(store, plan)
result$inserted
#> Material Measurement
#> 2 1Because Measurement is a semantic statement, the bounded
neighbor operation can return its subject, object, and predicate:
neighbors <- graft_query(
store,
operation = "neighbors",
request = list(
id = polyethylene_id,
projection = "semantic",
hops = 1,
max_nodes = 25,
max_edges = 50
)
)
neighbors$nodes[, c("id", "label"), drop = FALSE]
#> id label
#> 1 graft:40X0M02WR9Z55QWFDJYTNS3KX8 Polyethylene
#> 2 graft:64W8ZFH68S76XQ2MWW0H46Q9Y6 Water
neighbors$edges[, c("subject", "predicate", "object"), drop = FALSE]
#> subject predicate
#> 1 graft:40X0M02WR9Z55QWFDJYTNS3KX8 materials:testedWith
#> object
#> 1 graft:64W8ZFH68S76XQ2MWW0H46Q9Y6The graph is a derived read view of accepted revisions. It is not a separate place to insert or edit relationships.
Change the contract explicitly
Every plan and accepted revision records the active schema digest. Editing the LinkML source therefore creates a new contract version rather than silently changing the meaning of existing knowledge.
During v0.1 development, Graft can register a compatible semantic contract and rebuild its derived views. A change that transforms accepted payloads requires rebuilding the development store from source records. Historical revisions retain the contract digest that governed their acceptance.
graft_close(store)