Note: This is a draft for review, not a submitted artifact. Kurt asked for this to be prepared for his own read-through before it goes to the W3C Holon Community Group. Nothing here has been pushed to GitHub.
The Holon Graph Architecture treats a dataset as a holon boundary and its
named graphs as the holons within it. Two relations connect those holons —
holon:parentHolon for structural containment, and holon:imports for
cross-cutting content — and a third, holon:importsShapes, was introduced
specifically to prevent validation constraints from following content
relationships where they were never intended to travel.
This document lays out all three relations, the root holon that anchors the tree, and the resolution semantics that follow from treating “structural” and “cross-cutting” as genuinely different kinds of edges rather than two names for the same thing.
| Relation | Shape | Transitive? | Governs | Cardinality |
|---|---|---|---|---|
holon:parentHolon |
Tree | Yes (holon:parentHolon*) |
Content fallback and shape fallback | 0 or 1 per holon (root has 0) |
holon:imports |
DAG, cross-cutting | No — single hop only | Content visibility only | 0 or more |
holon:importsShapes |
DAG, cross-cutting | No — single hop only | Validation composition only | 0 or more |
Important:
holon:importsandholon:importsShapesare declared independently. A holon may import another’s content without inheriting its shapes, import another’s shapes without inheriting its content, or do both by declaring both edges explicitly. Neither is implied by the other.
Every holon dataset has exactly one root holon — the sole node with no
holon:parentHolon — and it plays two distinct roles that are worth naming
separately even though one node fills both.
Invariant. Every holon in the dataset is reachable from root by
walking holon:parentHolon*. This is a structural connectivity property,
checkable by a single SPARQL property-path query (see
select-orphan-holons below), independent of any SHACL enforcement.
Template. Root is the natural place to declare the base holon shape
that every other holon in the dataset composes with (via sh:node /
sh:and), so type-specific shapes only have to state what’s different
about that type, not restate the base contract every time.
Root should be treated as a real but minimal holon — it carries its
own dct:title and dct:description like any other holon, rather than
being a null structural artefact. What it should not accumulate is
runtime or session state (SHACL toggle flags, active-holon caches,
federation registry entries) — those stay in server configuration, not
in root’s own graph. Root holds identity and a pointer to its shapes
graph; nothing else.
holon:parentHolonholon:parentHolon is the administrative spine: city → state → country,
chapter → book → series. It is transitive by design, and two things fall
out of that transitivity for free, without any additional declaration:
holon:parentHolon* to root.holon:shapesGraph of its own
inherits the nearest ancestor’s, walking the same chain.Both resolutions reuse one traversal. There is no separate “shape parent” concept — a holon’s shape ancestry and its content ancestry follow the same tree, because containment implies both by default.
holon:importsNot every relationship a holon needs is on its structural spine. A city’s
administrative parent is its state, but it may also need context from a
metro region, a watershed authority, or a trade bloc — none of which
“contain” it in the tree sense. holon:imports covers this: content from
outside the ancestry, added deliberately, one hop at a time.
It is kept non-transitive on purpose. If it were transitive, a city importing a metro region would silently pull in everything the metro region itself imports — cross-cutting relationships would compound in a way nobody chose and nobody can predict by looking at the city’s own declarations. Non-transitivity means every hop is visible in the data; nothing arrives as a side effect of something two hops away.
holon:imports never affects validation. Importing a holon for its
content is a query-time convenience with no write-time consequence.
holon:importsShapesholon:importsShapes is the same non-transitive, single-hop pattern as
holon:imports, applied to a different graph: instead of pulling in a
target holon’s content, it pulls in the target’s shapes graph as a
composable SHACL constraint (via sh:node on the importing holon’s own
shape).
This was split out from holon:imports deliberately, not merged into it,
because the two have different failure modes. An unwanted content import
means a query returns more than expected — low cost, easy to notice. An
unwanted shape import means a write can be rejected for a reason that
traces back to a holon nobody realised was in scope, or — worse — silently
accepted under looser tolerances a borrowed shape happens to allow.
Composition via sh:node is additive (SHACL doesn’t support “override” —
the more restrictive shape always wins), so importing shapes is a
one-way tightening operation: a holon can add constraints beyond what it
imports, but can never opt out of one it imported.
A concrete case for the distinction: cities within states within countries, where a city also needs content from a cross-border metro region.
holon:parentHolon spine. Transitive,
so a city automatically gets its state’s and country’s content and
shapes with zero explicit declarations.holon:imports <metro> to get the metro region’s economic/cultural
context for querying.holon:importsShapes <metro>. The metro
region’s shapes were authored for aggregate, cross-jurisdictional
data — not for validating an individual city record. Without the
split, importing the metro’s content for querying would also silently
bind the city’s writes to a validation contract meant for a different
kind of entity.Net effect: nothing outside a holon’s ancestry can ever affect what makes
its writes valid, unless holon:importsShapes was declared on that specific
holon, pointing at that specific source, explicitly.
Two resolvers, structurally identical, walking different edges for different purposes:
resolveReadScope(holon):
scope = { holon }
scope += ancestors(holon, via=parentHolon, transitive=true)
scope += targets(holon, via=imports, transitive=false)
return scope
resolveShapesScope(holon):
shapesGraph = holon.shapesGraph
or nearestAncestor(holon, via=parentHolon).shapesGraph
composed = { shapesGraph }
composed += targets(holon, via=importsShapes, transitive=false).shapesGraph
return composed
resolveReadScope governs what a query can see. resolveShapesScope
governs what a write must satisfy. They are never the same call, and a
holon’s presence in one does not imply its presence in the other.
@prefix holon: <https://w3id.org/holon/> .
@prefix dct: <http://purl.org/dc/terms/> .
# --- Root holon: real but minimal ----------------------------------------
<urn:holon:root> a holon:Holon ;
dct:title "Atlas — Root Holon" ;
dct:description "Anchors this dataset's holon tree; carries only
identity and a pointer to the base shapes graph." ;
holon:shapesGraph <urn:holon:root:shapes> .
# --- Administrative spine: holon:parentHolon, transitive --------------------
<urn:holon:country:example-country> a holon:Holon ;
dct:title "Example Country" ;
holon:parentHolon <urn:holon:root> .
<urn:holon:state:example-state> a holon:Holon ;
dct:title "Example State" ;
holon:parentHolon <urn:holon:country:example-country> .
<urn:holon:city:example-city> a holon:Holon ;
dct:title "Example City" ;
holon:parentHolon <urn:holon:state:example-state> ;
# Cross-cutting CONTENT import only — metro is not the administrative
# parent, and its shapes are deliberately NOT imported (see below).
holon:imports <urn:holon:region:example-metro> .
# --- Cross-cutting region: outside the administrative tree ---------------
<urn:holon:region:example-metro> a holon:Holon ;
dct:title "Example Metro Region" ;
dct:description "Cross-jurisdictional region; not an administrative
parent of any single city in it." ;
holon:parentHolon <urn:holon:root> ;
holon:shapesGraph <urn:holon:region:example-metro:shapes> .
Note: The city’s
holon:importsShapesis intentionally absent from this example. That absence is the point — see “Why the Split Matters” above.
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix holon: <https://w3id.org/holon/> .
@prefix dct: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
holon:HolonShape a sh:NodeShape ;
sh:targetClass holon:Holon ;
sh:property [
sh:path holon:parentHolon ;
sh:maxCount 1 ;
sh:class holon:Holon ;
] ;
sh:property [
sh:path dct:title ;
sh:minCount 1 ;
sh:datatype xsd:string ;
] .
# Type-specific shape composes the base via sh:node rather than
# restating dct:title / holon:parentHolon constraints.
holon:CityHolonShape a sh:NodeShape ;
sh:node holon:HolonShape ;
sh:property [
sh:path holon:population ;
sh:datatype xsd:integer ;
] .
PREFIX holon: <https://w3id.org/holon/>
SELECT ?orphan WHERE {
?orphan a holon:Holon .
FILTER NOT EXISTS { ?orphan holon:parentHolon* <urn:holon:root> }
}
Note: An empty result set confirms the connectivity invariant holds. Whether this should additionally be SHACL-enforced (
sh:minCount 1onholon:parentHolonfor every holon except root) or left as a checked convention is one of the open questions below.
holon: bound to https://w3id.org/holon/ per Kurt’s direction
(2026-07-03): a single terminating / rather than a # fragment
separator, so that context-head-based forms (JSON-LD @context,
YAML-LD) can treat the namespace uniformly without a second
terminator character — particularly since # also carries comment
semantics in Turtle/SPARQL, which has proven a source of friction.
This is contingent on the w3id.org/holon registration PR
(perma-id/w3id.org#6300) being accepted; if /holon/ is denied as
too generic, the fallback is /holon-graph/, applied uniformly here.sh:minCount 1 on holon:parentHolon for all non-root holons).holon:importsShapes require the source holon’s shapes to be
explicitly marked composable (e.g. holon:shapesComposable true), or
is any holon’s shapes graph fair game for import by default? The
metro-region example assumes the latter; a CG reviewer may reasonably
want the former to prevent a shapes graph authored for one purpose
being silently reused for an unintended one.