Use data-dict when the knowledge you have today is tabular. A data dictionary names the tables and columns, documents their meaning, and declares constraints and relationships. graft compiles the supported parts into the contract used to review candidate records.
The source contract does not become the knowledge store. Accepted records and their revisions live in graft’s DuckDB ledger.
Author once, then use the resolved dictionary
The package includes a small team directory in two forms:
-
team-directory.data-dict.yamlis the human-authored source; and -
team-directory.data-dict.jsonis the resolved output fromdata-dict export-spec.
Contract authors install the data-dict CLI, validate the readable YAML, and resolve it once:
data-dict validate-spec team-directory.data-dict.yaml
data-dict export-spec team-directory.data-dict.yaml --pretty > team-directory.data-dict.jsonThe rest of this guide starts from the shipped resolved JSON. That keeps the runnable package lesson R-only; the bundled JSON is the exact export of the bundled YAML under the CLI revision documented in the compiler reference.
library(graft)
resolved_path <- system.file(
"extdata",
"team-directory.data-dict.json",
package = "graft",
mustWork = TRUE
)
schema <- graft_schema(resolved_path)
schema@name
#> [1] "team_directory"
schema@version
#> [1] "0.1.0"
names(schema@classes)
#> [1] "organization" "person" "employment"When compiling this resolved source without output,
graft_schema() writes a temporary .graft.json
manifest. Supply an output path when the compiled contract should be
committed or deployed as an artifact:
schema <- graft_schema(
resolved_path,
output = "team-directory.graft.json"
)See how tables become a contract
The source YAML uses familiar table concepts. Its essential structure is:
$version: 0.1.0
$learn_more: https://data-dict.tidyverse.org/
name: team_directory
version:
number: 0.1.0
tables:
- name: organization
columns:
- name: id
type: string
constraints: [primary_key]
examples: ["org:daily-planet"]
- name: name
type: string
constraints: [required]
examples: ["Daily Planet"]
- name: person
columns:
- name: id
type: string
constraints: [primary_key]
examples: ["person:lois-lane"]
- name: full_name
type: string
constraints: [required]
examples: ["Lois Lane"]
- name: job_title
type: string
examples: ["Reporter"]
- name: employment
columns:
- name: id
type: string
constraints: [primary_key]
examples: ["employment:lois-lane:daily-planet"]
- name: person_id
type: string
constraints: [required, foreign_key]
examples: ["person:lois-lane"]
- name: organization_id
type: string
constraints: [required, foreign_key]
examples: ["org:daily-planet"]
relationships:
- join: employment.person_id = person.id
cardinality: many-to-one
- join: employment.organization_id = organization.id
cardinality: many-to-oneThe adapter maps each table to a Graft class and each column to a slot. Primary keys establish record identity. Scalar foreign keys become reference checks.
employment <- schema@classes$employment
names(employment@slots)
#> [1] "id" "person_id" "organization_id"
employment@slots$person_id@required
#> [1] TRUE
employment@slots$person_id@object_reference
#> [1] TRUE
employment@slots$person_id@range
#> [1] "person"Candidate inputs use the same table names:
records <- list(
organization = data.frame(
id = "org:daily-planet",
name = "Daily Planet"
),
person = data.frame(
id = "person:lois-lane",
full_name = "Lois Lane",
job_title = "Reporter"
),
employment = data.frame(
id = "employment:lois-lane:daily-planet",
person_id = "person:lois-lane",
organization_id = "org:daily-planet"
)
)The getting-started guide opens an empty store, shows a missing foreign key being rejected, commits this corrected candidate set, and retrieves two revisions of the person record.
Know what the table profile enforces
The current graft-table-v1 profile is deliberately
narrower than data-dict itself:
- every table has exactly one primary key named
id, represented as a scalar string; - IDs are non-empty and globally unique across all tables in one Graft store;
- every YAML column declares a type;
- non-key columns are scalars or one-level lists of scalar values;
- foreign keys are scalar strings that target another table’s primary
id; and - resolved
foreign_keyconstraints andreferencesmetadata must agree.
Struct columns, nested lists, list-valued foreign keys, and numeric ID types are rejected rather than converted. Use quoted strings for identifiers and codes.
Some data-dict metadata remains descriptive. Graft does not currently execute non-primary uniqueness, representative ranges, assertion text, relationship cardinality, join aliases, conflicts, range joins, or multi-column joins. Scalar foreign keys are validated references, not graph traversal edges.
The compiled mapping report records these boundaries:
schema@manifest$dictionary$profile
#> [1] "graft-table-v1"
unlist(schema@manifest$dictionary$mapped[c(
"tables_to_classes",
"columns_to_slots",
"foreign_keys_to_object_references"
)])
#> tables_to_classes columns_to_slots
#> 3 8
#> foreign_keys_to_object_references
#> 2The compiler reference is the authoritative list of descriptive or unsupported data-dict semantics.
Compile YAML when authoring the contract
The YAML route lets data-dict resolve its own source format. It
requires the optional data-dict executable; graft never
downloads or installs it during package use.
yaml_path <- system.file(
"extdata",
"team-directory.data-dict.yaml",
package = "graft",
mustWork = TRUE
)
options(graft.data_dict_cli = "/path/to/data-dict")
schema <- graft_schema(
yaml_path,
output = "team-directory.graft.json"
)graft runs data-dict export-spec; it does not run
data-dict’s metadata or data validation commands. Teams commonly resolve
YAML in a schema-authoring or CI environment and commit the YAML,
resolved JSON, and compiled .graft.json together.
Resolved JSON is trusted build input. graft checks its supported
export version and structure, but cannot prove that another program
actually produced it with export-spec.
See Contract compiler details for CLI pinning, compiler provenance, digest behavior, public-manifest redaction, exact numeric and datetime rules, and the LinkML compiler’s strict profile.
Where to go next
Continue with change control to review and accept plans, then retrieval and history to read the accepted knowledge and its revisions.
Use LinkML when the contract needs inheritance, class or predicate URIs, polymorphic references, Graft record roles, narrative or semantic statements, custom identity policies, qualifier fields, or graph traversal relationships. The LinkML guide continues from the same plan and commit model; only the contract provider changes.