extensionless ‘…/spec#’ would not be served as Turtle and the identifier
would resolve to nothing. spec-publish stages the graph to that path.
Intent
Provide a Python library that implements Cagel’s four-graph holon model on top of RDF named graphs, enabling distributed knowledge systems to expose genuine inside/outside distinction, governed traversal, and event-level provenance without inventing new query languages or wire protocols. The library is a client-side coordination layer in front of any quad-aware graph store (rdflib, Apache Jena Fuseki, Oxigraph, GraphDB) — not a graph database itself.
Purpose
Make it practical to build holarchies for digital engineering, enterprise knowledge graphs, and agentic memory systems. A holon is a named IRI whose four layers (interior, boundary, projection, context) live as named graphs in the quad store; portals are RDF entities carrying SPARQL CONSTRUCT queries; membranes are SHACL shapes. All state lives in the graph. Python is a thin convenience layer over SPARQL and PROV-O.
Requirements
R1 Four-Graph Model
R1.1 Each holon MUST have an IRI that threads through four named-graph layers: interior, boundary, projection, and context.
priority: MUST
implementation: verified
constrains: HolonicDataset
governs: cga:Holon
acceptance: Given a fresh HolonicDataset, when add_holon(iri, label) is called, then the holon’s IRI is registered as cga:Holon and its four layer graphs resolve via cga:hasInterior, cga:hasBoundary, cga:hasProjection, and cga:hasContext.
verifiedBy: src/holonic/test/test_deprecation_and_dispatch.py::test_add_holon_without_holon_type
R1.2 A holon MAY have multiple named graphs in any one layer role (e.g.
urn:holon:x/interior/radarandurn:holon:x/interior/fusion); operations that read a layer MUST treat the set as a union.priority: MUST
implementation: verified
constrains: HolonicDataset
governs: cga:LayerRole
acceptance: Given a holon with two interior graphs added with different graph_iri values, when a SPARQL union query ranges over GRAPH ?g patterns, then triples from both graphs match.
verifiedBy: src/holonic/test/test_client_projections.py::TestProjectHolon::test_multiple_interiors_are_merged
R1.3 Layer membership MUST be declared via
cga:hasInterior,cga:hasBoundary,cga:hasProjection,cga:hasContext. Layer graph IRIs MUST be discoverable by SPARQL from the holon IRI; discovery by IRI suffix convention is forbidden as a correctness mechanism.priority: MUST
implementation: verified
constrains: HolonicDataset, cga.ttl
acceptance: Given a holon with all four layers populated, when the registry is queried with SPARQL following cga:hasInterior et al., then every layer graph IRI is returned without relying on string patterns.
verifiedBy: src/holonic/test/test_holon.py::TestAllLayers::test_all_four_layers_on_one_holon
R1.4 The library MUST NOT flatten named graphs to a single default graph at any layer in the stack. Every triple belongs to a named graph.
priority: MUST
implementation: verified
verifiedBy: src/holonic/test/test_backend.py::TestNoDefaultGraphFlattening::test_full_lifecycle_writes_nothing_to_the_default_graph
constrains: HolonicDataset, HolonicStore
acceptance: Given any library-mediated write, when inspecting the backend’s default graph, then no triples produced by the library appear there.
R2 Store Protocol
R2.1 A
HolonicStoreprotocol MUST provide quad-aware named-graph CRUD (graph_exists,get_graph,put_graph,post_graph,delete_graph,parse_into,list_named_graphs) and SPARQL dispatch (query,construct,ask,update).priority: MUST
implementation: verified
constrains: HolonicStore, RdflibBackend, FusekiBackend
acceptance: Given an RdflibBackend instance, when isinstance checked against HolonicStore, then the check passes; every listed method exists and accepts the declared signature.
verifiedBy: src/holonic/test/test_backend.py::TestProtocolConformance::test_rdflib_backend_implements_protocol
R2.2 An in-memory backend (
RdflibBackend) MUST be shipped so the library is usable with no running server.priority: MUST
implementation: verified
constrains: RdflibBackend
acceptance: Given only the base
pip install holonic, when HolonicDataset() is called without arguments, then a working instance is returned with RdflibBackend bound as the default.verifiedBy: src/holonic/test/test_backend.py::TestRdflibBackend
R2.3 An HTTP backend (
FusekiBackend) MUST be shipped for Apache Jena Fuseki via SPARQL 1.1 and the W3C Graph Store Protocol.priority: MUST
implementation: verified
constrains: FusekiBackend
acceptance: Given a running Fuseki server, when FusekiBackend(url, dataset=ds) is instantiated and used with HolonicDataset, then CRUD and SPARQL operations round-trip through HTTP against the server.
verifiedBy: src/holonic/test/test_fuseki_integration.py::TestFusekiRoundTrip
R2.4
FusekiBackendMUST acceptextra_headersat construction so external orchestrators can pass bearer tokens, mTLS hints, or tenant identifiers through to Fuseki.priority: MUST
implementation: verified
constrains: FusekiBackend, _fuseki_client
acceptance: Given FusekiBackend(url, dataset=ds, extra_headers={“Authorization”: “Bearer x”}), when any HTTP request is issued, then the header is present on the outbound request.
verifiedBy: src/holonic/test/test_console_methods.py::TestFusekiBackendExtraHeaders::test_fuseki_client_stores_extra_headers
R2.5 The protocol MUST remain synchronous. Async consumers wrap calls in their own thread-pool bridge; an async variant, if added, MUST be a separate protocol and not replace the sync one.
priority: MUST
implementation: verified
constrains: HolonicStore
acceptance: Given the HolonicStore protocol definition, when method signatures are inspected, then none are declared with async def and no awaitables are returned.
verifiedBy: src/holonic/backends/store.py (structural, enforced by type system)
R2.6 Backend method return types MUST use
rdflib.Graphfor CONSTRUCT and graph-fetch operations to preserve rdflib’s serialization, iteration, and SPARQL evaluation surface.priority: MUST
implementation: verified
constrains: HolonicStore, RdflibBackend, FusekiBackend
acceptance: Given store.get_graph() or store.construct() is called, when the return value is inspected, then it is an rdflib.Graph instance supporting serialize(), iteration, and further SPARQL queries.
verifiedBy: src/holonic/test/test_client_projections.py::TestRawSparqlPassthrough::test_construct_returns_graph
R3 CGA Ontology and Membrane Validation
R3.1 The package MUST ship the Context-Graph Architecture (CGA) ontology (
cga.ttl) and SHACL shapes (cga-shapes.ttl) as package data.priority: MUST
implementation: verified
constrains: cga.ttl, cga-shapes.ttl, package-data declaration
acceptance: Given
pip install holonic, when the package is introspected, then both TTL files are present as resources under holonic.ontology and parse cleanly with rdflib.verifiedBy: src/holonic/test/test_ontology.py::TestOntologyFilesExist
R3.2 The ontology MUST define
cga:Holonand its functional subclasses (cga:DataHolon,cga:AlignmentHolon,cga:AgentHolon,cga:GovernanceHolon,cga:AggregateHolon,cga:IndexHolon),cga:Portaland its subclasses (cga:TransformPortal,cga:IconPortal,cga:SealedPortal),cga:LayerGraph, andcga:LayerRole. Portal subtype semantics MUST be enforced by SHACL shapes incga-shapes.ttl:cga:TransformPortalrequires exactly onecga:constructQuery;cga:IconPortalandcga:SealedPortalmust not carrycga:constructQuery(carrying one is semantically incoherent because an IconPortal is purely referential and a SealedPortal blocks traversal).priority: MUST
implementation: verified
constrains: cga.ttl
acceptance: Given cga.ttl, when SPARQL queries every declared class, then all listed class IRIs are returned with their subclass relationships intact.
verifiedBy: src/holonic/test/test_ontology.py::TestOntologyAutoLoaded
R3.3 The ontology MUST use RDFS plus minimal OWL (class hierarchy, domain/range). The library MUST NOT depend on an OWL reasoner for correctness of any method.
priority: MUST
implementation: verified
constrains: cga.ttl, HolonicDataset
acceptance: Given an rdflib-backed HolonicDataset, when the entailment extra is not installed, then every library method produces correct results without a reasoner.
verifiedBy: src/holonic/test/ (entire suite runs without owlrl installed)
R3.4
validate_membrane(holon_iri)MUST run pyshacl against the union of the holon’s interior graphs using the union of its boundary graphs and MUST return aMembraneResultwith one ofIntact/Weakened/Compromised.priority: MUST
implementation: verified
constrains: HolonicDataset.validate_membrane, MembraneResult
acceptance: Given a holon whose interior violates a boundary shape, when validate_membrane is called, then result.health is MembraneHealth.Compromised and result.violations lists the specific SHACL failures.
verifiedBy: src/holonic/test/test_membrane.py::TestMembraneValidation::test_compromised_membrane
R3.5 Membrane health MUST be recordable in the context graph as
cga:membraneHealthon aprov:Activity.priority: MUST
implementation: verified
constrains: HolonicDataset.record_validation, cga.ttl
acceptance: Given a governed traversal with validate=True, when the activity is read from the context graph, then cga:membraneHealth is present and matches the MembraneResult.health value.
verifiedBy: src/holonic/test/test_audit.py::TestCollectAuditTrail::test_validation_recorded_in_trail
R3.6 Membrane validation MUST fail closed on SHACL result severity. A result whose
sh:resultSeverityissh:Violation, or is absent, or is any term other thansh:Warningandsh:Info, MUST be counted as a violation;sh:Warningresults MUST lower health toWeakened;sh:Inforesults MUST be reported inMembraneResult.infosand MUST NOT lower health.MembraneResult.conformsMUST be false exactly when the SHACL report holds a result abovesh:Info.validate_membrane()anddry_run()MUST apply the same rule.priority: MUST
implementation: verified
constrains: holonic.client._parse_shacl_report, holonic.client._health_from_report, MembraneResult
acceptance: Given a boundary shape that declares a custom severity and an interior that violates it, when
traverse()is called withfail_on_breach=True, thenMembraneBreachErroris raised.verifiedBy: src/holonic/test/test_membrane.py::TestSeverityFailsClosed
verifiedBy: src/holonic/test/test_membrane.py::TestConformsAgreesAcrossCallers
R3.7 Membrane validation MUST report each typed interior node that no boundary shape targets as an
UntargetedNodeinMembraneResult.untargeted. Targeting follows SHACL Core (sh:targetClasswithrdfs:subClassOf*in the data graph, implicit class targets,sh:targetNode,sh:targetSubjectsOf,sh:targetObjectsOf); a boundary with a SPARQL-based target skips the check. A class named bycga:permitsTypein the holon’s boundary, and its subclasses, MUST be exempt. The severity MUST be the holon’scga:untargetedTypeSeveritywhen declared, resolved as R3.6 resolves SHACL severities; otherwise Info forvalidate_membrane()anddry_run(), and Violation for the nodestraverse(fail_on_breach=True)injects.priority: MUST
implementation: verified
constrains: holonic._membrane.find_untargeted, holonic._membrane.untargeted_policy, UntargetedNode, MembraneResult
acceptance: Given a portal that injects a node whose type no boundary shape targets, when
traverse()is called withfail_on_breach=True, thenMembraneBreachErroris raised and the target interior is unchanged.verifiedBy: src/holonic/test/test_membrane.py::TestUntargetedTypes
R4 Portal and Traversal Semantics
R4.1 Portals MUST be first-class RDF entities stored in boundary graphs with
cga:sourceHolon,cga:targetHolon, and (forcga:TransformPortal)cga:constructQuery.priority: MUST
implementation: verified
constrains: HolonicDataset.add_portal
governs: cga:Portal, cga:TransformPortal
acceptance: Given add_portal(iri, source, target, construct_query, label), when the registry is queried, then the portal is found with cga:sourceHolon, cga:targetHolon, and cga:constructQuery as RDF triples.
verifiedBy: src/holonic/test/test_console_methods.py::TestGetPortal::test_returns_construct_query
R4.2 Portal discovery MUST be SPARQL-driven (
find_portals_from,find_portals_to,find_portal,find_path). Python-side iteration over cached portal objects is forbidden.priority: MUST
implementation: verified
constrains: HolonicDataset discovery methods, sparql.py templates
acceptance: Given portals in the registry, when find_portals_from(iri) is called, then results come from a SPARQL SELECT executed against the store, not from a Python cache.
verifiedBy: src/holonic/test/test_portal.py::TestPortalDiscovery::test_find_portals_from
R4.3
traverse_portal(portal_iri)MUST execute the portal’s CONSTRUCT query against the quad store and MAY inject the result into a target named graph.priority: MUST
implementation: verified
constrains: HolonicDataset.traverse_portal
acceptance: Given a registered portal and inject_into=target_graph, when traverse_portal is called, then the CONSTRUCT’s triples appear in the target graph and the CONSTRUCT result Graph is returned.
verifiedBy: src/holonic/test/test_portal.py::TestPortalTraversal::test_traverse_portal_injects_into_target
R4.4
traverse(source, target, validate=True)MUST compose discovery, traversal, membrane validation, and provenance recording as one governed operation. Validation failure MUST NOT inject data into the target interior.priority: MUST
implementation: verified
constrains: HolonicDataset.traverse
acceptance: Given a boundary shape that rejects the source data, when traverse(source, target, validate=True) is called, then the target interior contains no new triples and the returned membrane result is Compromised.
verifiedBy: src/holonic/test/test_verified_gaps.py::TestFailClosedTraversal::test_fail_on_breach_leaves_interior_byte_identical
R4.5 Multi-hop path finding (
find_path) MUST discover a portal route from source to target. The current implementation fetches the portal adjacency list viaQ.ALL_PORTALSand runs Python BFS. Deferred to 0.8.0: migrate to a SPARQL property-path query or cache the adjacency list across calls.priority: MUST
implementation: in-progress
constrains: HolonicDataset.find_path, sparql.py
acceptance: Given a chain of three holons linked by portals, when find_path(source, target) is called, then a list of PortalInfo objects representing the path is returned.
verifiedBy: src/holonic/test/test_portal.py::TestPathFinding::test_multi_hop_path
R5 Provenance
R5.1 Every governed traversal and membrane validation MUST emit a
prov:Activityinto the target holon’s context graph, includingprov:wasAssociatedWith,prov:used,prov:generated, andprov:startedAtTime.priority: MUST
implementation: verified
constrains: HolonicDataset.traverse, HolonicDataset.record_traversal, HolonicDataset.record_validation
acceptance: Given traverse(source, target, agent_iri=”urn:agent:x”), when the context graph is queried, then a prov:Activity exists with all four listed predicates populated.
verifiedBy: src/holonic/test/test_audit.py::TestCollectAuditTrail::test_traverse_then_collect
R5.2 Graph-to-graph derivation produced by a traversal (target interior derived from source interior) MUST use
prov:wasDerivedFromand MUST be distinct fromcga:derivedFrom.priority: MUST
implementation: verified
verifiedBy: src/holonic/test/test_portal.py::TestTraversalDerivationProvenance::test_traversal_does_not_assert_structural_dependency
constrains: HolonicDataset.traverse, cga.ttl
acceptance: Given a completed traversal, when the context graph is queried, then the target interior carries prov:wasDerivedFrom pointing to the source interior AND no cga:derivedFrom triple was created as a side effect.
R5.3
cga:derivedFromMUST be reserved for persistent holon-to-holon structural dependency independent of any activity. The two properties coexist; neither replaces the other.priority: MUST
implementation: verified
verifiedBy: src/holonic/test/test_ontology.py::TestDerivationVocabulary::test_cga_derived_from_is_declared_holon_to_holon
verifiedBy: src/holonic/test/test_ontology.py::TestDerivationVocabulary::test_cga_derived_from_is_not_conflated_with_prov
constrains: cga.ttl
acceptance: Given cga.ttl, when SPARQL queries cga:derivedFrom, then it is declared an owl:ObjectProperty scoped Holon→Holon AND carries no rdfs:subPropertyOf or owl:equivalentProperty axiom relating it to prov:wasDerivedFrom.
R5.4
HolonSplitandHolonMergeMUST be modeled asprov:Activitysubclasses withprov:used(source holon) andprov:generated(resulting holons).priority: MUST
implementation: verified
verifiedBy: src/holonic/test/test_ontology.py::TestSplitMergeActivities::test_instance_carries_used_and_generated
constrains: cga.ttl
acceptance: Given a HolonSplit instance, when SPARQL queries its rdf:type chain, then prov:Activity appears in the class hierarchy and both prov:used and prov:generated are declared.
R6 Console Model
R6.1 The
holonic.console_modelmodule MUST expose dataclasses tuned for JSON serialization over HTTP:HolonSummary,HolonDetail,ClassInstanceCount,NeighborhoodNode,NeighborhoodEdge,NeighborhoodGraph,PortalSummary,PortalDetail.priority: MUST
implementation: verified
constrains: console_model.py
acceptance: Given each named dataclass, when round-tripped through dataclasses.asdict + json.dumps + json.loads, then no information is lost and no rdflib types leak.
verifiedBy: src/holonic/test/test_deprecation_and_dispatch.py::test_holon_summary_to_dict
R6.2
NeighborhoodGraph.to_graphology()MUST return a dict matching graphology’s JSON shape so sigma.js can consume it without further transformation.priority: MUST
implementation: verified
constrains: NeighborhoodGraph.to_graphology
acceptance: Given a NeighborhoodGraph with nodes and edges, when to_graphology() is called, then the returned dict has top-level ‘nodes’ and ‘edges’ arrays with the key/attributes shape graphology expects.
verifiedBy: src/holonic/test/test_console_model.py::TestNeighborhoodGraphologyShape::test_empty_graph
R6.3
HolonicDataset.list_holons_summary()MUST return lightweight summaries via a single SPARQL query with no per-holon fan-out.priority: MUST
implementation: verified
constrains: HolonicDataset.list_holons_summary, sparql.py
acceptance: Given N holons in the registry, when list_holons_summary() is called, then exactly one SELECT query is issued against the store and N HolonSummary objects are returned.
verifiedBy: src/holonic/test/test_console_methods.py::TestListHolonsSummary::test_returns_summaries
R6.4
HolonicDataset.get_holon_detail(iri)MUST return full layer IRIs plus interior triple count.priority: MUST
implementation: verified
constrains: HolonicDataset.get_holon_detail, HolonDetail
acceptance: Given a holon with two interior graphs, when get_holon_detail(iri) is called, then the returned HolonDetail lists both interior graph IRIs and interior_triple_count matches the actual triple sum.
verifiedBy: src/holonic/test/test_console_methods.py::TestGetHolonDetail::test_returns_layer_iris
R6.5
HolonicDataset.holon_interior_classes(iri)MUST return(class_iri, count)pairs computed by SPARQL, not by Python iteration over triples.priority: MUST
implementation: verified
constrains: HolonicDataset.holon_interior_classes, sparql.py
acceptance: Given an interior with three distinct rdf:type values, when holon_interior_classes is called, then three ClassInstanceCount pairs come back from a single GROUP BY SPARQL query.
verifiedBy: src/holonic/test/test_console_methods.py::TestHolonInteriorClasses::test_counts_distinct_subjects_per_class
R6.6
HolonicDataset.holon_neighborhood(iri, depth=1)MUST return a BFS subgraph bounded by portal topology, withdepthclamped to a reasonable maximum to bound runaway traversals.priority: MUST
implementation: verified
constrains: HolonicDataset.holon_neighborhood
acceptance: Given depth=1000, when holon_neighborhood(iri, depth=1000) is called, then the library clamps to its internal cap (not 1000) and returns within expected time bounds.
verifiedBy: src/holonic/test/test_console_methods.py::TestHolonNeighborhood::test_depth_one_includes_portal_neighbors
R6.7
HolonicDataset.portal_traversal_history(iri, limit=50)MUST return recentprov:Activityrecords scoped to the portal, withlimitclamped to 10,000.priority: MUST
implementation: verified
constrains: HolonicDataset.portal_traversal_history
acceptance: Given limit=50000, when portal_traversal_history is called, then at most 10000 records come back regardless of how many activities exist.
verifiedBy: src/holonic/test/test_console_methods.py::TestPortalTraversalHistory::test_records_appear
R7 Projections
R7.1 Projection operations MUST support two modes: CONSTRUCT (result stays as RDF, storable in the holarchy) and Pythonic (result exits RDF into LPG-shaped dicts for visualization consumers).
priority: MUST
implementation: verified
constrains: projections.py
acceptance: Given a source graph, when build_construct() returns a CONSTRUCT string AND project_to_lpg() returns a ProjectedGraph dict, then both modes are available and produce the documented shapes.
verifiedBy: src/holonic/test/test_projections.py::TestProjectToLPG::test_basic_projection
R7.2
project_to_lpg(graph, ...)MUST support four independent boolean flags controlling the core simplifications:collapse_types,collapse_literals,resolve_blanks, andresolve_lists. Each flag governs a distinct transformation (types→node annotations, literals→node attributes, blank nodes inlined as nested attributes, RDF lists resolved to Python lists) and each MUST be toggleable independently of the others.priority: MUST
implementation: verified
constrains: project_to_lpg
acceptance: Given a graph with all four RDF features, when project_to_lpg is called with each flag independently toggled, then each feature is transformed only when its corresponding flag is true.
verifiedBy: src/holonic/test/test_projections.py::TestProjectToLPG::test_type_collapse
R7.3
ProjectionPipelineMUST compose named steps as a sequence of CONSTRUCT queries and Python transforms, with.apply_to_lpg()and.apply_to_graph()terminal methods.priority: MUST
implementation: verified
constrains: ProjectionPipeline
acceptance: Given a pipeline of two CONSTRUCT steps and one transform, when apply_to_graph() is called, then steps run in the declared order and the result reflects all three transformations.
verifiedBy: src/holonic/test/test_projections.py::TestProjectionPipeline::test_chained_steps
R7.4
project_holon(iri, store_as=...)MUST merge all interior graphs, apply the pipeline, and optionally write the result back to a named graph.priority: MUST
implementation: verified
constrains: HolonicDataset.project_holon
acceptance: Given a holon with two interiors, when project_holon(iri, store_as=”urn:out”) is called, then the output graph contains triples derived from both interiors and is registered as a cga:hasProjection layer.
verifiedBy: src/holonic/test/test_client_projections.py::TestProjectHolon::test_store_as_writes_named_graph_and_registers_layer
R7.5
project_holarchy()MUST project the topology (holons as nodes, portals andcga:memberOfas edges) into an LPG.priority: MUST
implementation: verified
constrains: HolonicDataset.project_holarchy
acceptance: Given a holarchy with three holons and two portals and one memberOf relation, when project_holarchy() is called, then the resulting ProjectedGraph has three nodes and three edges.
verifiedBy: src/holonic/test/test_client_projections.py::TestProjectHolarchy::test_holarchy_member_of_edges_present
R8 Testing and Distribution
R8.1 The default pytest suite MUST pass against
RdflibBackendwith no external services.priority: MUST
implementation: verified
constrains: src/holonic/test/
acceptance: Given a clean environment with base install only, when
pytest src/holonic/test/is run, then every test passes or is skipped with a declared marker like @pytest.mark.fuseki.verifiedBy: src/holonic/test/ (full suite, 385 passing)
R8.2 Fuseki integration tests, if any, MUST be marked with
@pytest.mark.fusekiand skipped when Fuseki is unavailable.priority: MUST
implementation: in-progress
constrains: test_console_methods.py, pyproject.toml pytest config
acceptance: Given no Fuseki server, when pytest runs, then Fuseki-tagged tests are skipped rather than errored.
verifiedBy: pyproject.toml markers + src/holonic/test/test_console_methods.py usage of importorskip
R8.3 Test fixtures MUST use Turtle loaded via
backend.parse_into()rather than programmatic rdflib graph construction.priority: MUST
implementation: in-progress
constrains: src/holonic/test/, STYLE_GUIDE.md
acceptance: Given the test suite, when grepped for Graph().add((URIRef, URIRef, …)) patterns, then no production test uses that programmatic style for fixture data; all fixtures are Turtle.
verifiedBy: src/holonic/test/ (convention enforced by STYLE_GUIDE.md)
R8.4 The package MUST publish to PyPI under
holonicand be installable withpip install holonic.priority: MUST
implementation: verified
constrains: pyproject.toml, release workflow
acceptance: Given a clean virtualenv, when
pip install holonicis run, then the package installs from PyPI andpython -c "import holonic"succeeds.verifiedBy: .github/workflows/release.yml (manual verification at tag time)
R8.5 Optional extras (
dev,docs,entailment,fuseki,lint,notebooks,test,viz) MUST keep the base install under five seconds on a typical network.priority: SHOULD
implementation: verified
constrains: pyproject.toml optional-dependencies
acceptance: Given a clean virtualenv, when
pip install holonicis timed, then installation completes in under five seconds on a 50 Mbps connection.verifiedBy: manual timing at release rehearsal
R9 Next Evolution
Shipped in 0.3.3
R9.1 The registry SHOULD carry graph-level operational metadata per layer graph: triple count, last-modified timestamp, and materialized class inventory.
HolonicDataset.refresh_metadata(),refresh_all_metadata(), andget_graph_metadata()provide the read/write surface. Shipped withmetadata_updates="eager"|"off"policy.priority: SHOULD
implementation: verified
constrains: HolonicDataset, MetadataRefresher, cga.ttl
acceptance: Given a holon with one interior graph, when add_interior runs in eager mode, then get_graph_metadata returns a GraphMetadata with triple_count equal to the parsed count and last_modified within the last second.
verifiedBy: src/holonic/test/test_metadata.py::test_eager_mode_refreshes_on_add_interior
R9.2 The CGA ontology MUST declare graph-level metadata vocabulary (
cga:ClassInstanceCountclass;cga:tripleCount,cga:lastModified,cga:refreshedAt,cga:inGraph,cga:class,cga:count,cga:holonLastModifiedproperties).priority: MUST
implementation: verified
verifiedBy: src/holonic/test/test_ontology.py::TestGraphMetadataVocabulary::test_metadata_property_declared_with_range
constrains: cga.ttl section 7
acceptance: Given cga.ttl, when SPARQL queries the listed IRIs, then all are declared with appropriate rdfs:domain and rdfs:range.
Shipped in 0.3.4
R9.3 The CGA ontology MUST declare graph-category vocabulary so named graphs carry RDF types discoverable via SPARQL. Implemented as the flat+role pattern:
cga:HolonicGraphclass pluscga:graphRoleobject property ranged overcga:LayerRoleindividuals. New individualcga:RegistryRolecovers the cross-cutting registry graph. Seedocs/DECISIONS.md§ D-0.3.4-1.priority: MUST
implementation: verified
constrains: cga.ttl section 8
acceptance: Given add_interior(“urn:h”), when the registry is queried, then the interior graph is declared as cga:HolonicGraph with cga:graphRole cga:InteriorRole.
verifiedBy: src/holonic/test/test_typed_graphs.py::test_add_interior_types_graph
R9.4 The library MUST add a
resolve(predicate, from_holon, max_depth, order, limit)method implementing decreasing-priority scope resolution across the holarchy.HolonicDataset.resolve()inholonic.scope. Two predicate classes shipped:HasClassInInterior(uses 0.3.3 class inventory) andCustomSPARQL(escape hatch). Three ordering modes:"network"(default, outbound+inbound portals),"reverse-network","containment". Strict BFS topology.priority: MUST
implementation: verified
constrains: holonic.scope, HolonicDataset.resolve
acceptance: Given a linear chain of four holons with a target class in the last holon, when resolve(HasClassInInterior(cls), from_holon=first, max_depth=3) is called, then the last holon is returned with distance=3.
verifiedBy: src/holonic/test/test_scope.py::test_resolve_walks_outbound_portals
R9.5 The library MUST ship a migration CLI to backfill graph-type declarations for pre-0.3.4 deployments.
holonic-migrate-registryentry point, idempotent, dry-run by default.priority: MUST
implementation: verified
constrains: holonic.cli.migrate_registry
acceptance: Given a pre-0.3.4 registry without cga:HolonicGraph type declarations, when
holonic-migrate-registry --applyis run, then every layer graph gains its type; a second invocation is a no-op.verifiedBy: src/holonic/test/test_typed_graphs.py::test_migration_is_idempotent
Shipped in 0.3.5
R9.6 The library MUST ship a projection plugin system. Projection pipelines are RDF-modeled specs (
cga:ProjectionPipelineSpec) in the registry graph, carrying anrdf:Listofcga:ProjectionPipelineStepentries. Python transforms are discovered via theholonic.projectionsentry-point group (both first-party and third-party). Pipelines reference transforms by registered name; registration validates name resolution.priority: MUST
implementation: verified
constrains: holonic.plugins, HolonicDataset.register_pipeline, cga.ttl section 9
acceptance: Given a ProjectionPipelineSpec with one step referencing transform “strip_blank_nodes”, when register_pipeline(spec) is called, then the spec is written to the registry as cga:ProjectionPipelineSpec and the transform is resolved via the entry-point registry.
verifiedBy: src/holonic/test/test_plugins.py::test_register_pipeline_validates_transform_names
R9.7 The library MUST ship
HolonicDataset.register_pipeline(),attach_pipeline(),list_pipelines(),get_pipeline(), andrun_projection(). Running a pipeline emits aprov:Activityin the target holon’s context graph with loose version tracking (cga:transformVersion) and host metadata (cga:runHost,cga:runPlatform,cga:runPythonVersion,cga:runHolonicVersion). Running withstore_asrefreshes metadata on the output graph per D-0.3.3-5.priority: MUST
implementation: verified
constrains: HolonicDataset, holonic.plugins.host_metadata
acceptance: Given a registered pipeline attached to a holon, when run_projection(holon, spec, store_as=out) is called, then a prov:Activity appears in the holon’s context graph with all four cga:run* host metadata predicates populated.
verifiedBy: src/holonic/test/test_plugins.py::test_run_projection_records_provenance
Shipped in 0.4.0
R9.8 The backend protocol MUST split into a mandatory core and an optional mixin. Implemented as
HolonicStore(Protocol, mandatory) +AbstractHolonicStore(ABC, recommended base with hook points for optional methods). Backends declaring only the Protocol surface continue to work via the library’s Python-fallback helpers. Optional native methods are dispatched viahasattr. The initial optional surface is a single method:refresh_graph_metadata(graph_iri, registry_iri).priority: MUST
implementation: verified
constrains: holonic.backends.store, MetadataRefresher
acceptance: Given a store with a refresh_graph_metadata method, when MetadataRefresher.refresh_graph runs, then the native method is invoked; given a store without it, the Python fallback runs instead.
verifiedBy: src/holonic/test/test_deprecation_and_dispatch.py::test_metadata_refresher_dispatches_to_native
R9.9 The library MUST preserve
GraphBackendas a deprecated alias through the 0.4.x series and provide a migration guide (docs/MIGRATION.md). Deprecation warnings are suppressible viaHOLONIC_SILENCE_DEPRECATION=1. Superseded by R9.18 in 0.5.0: the alias was removed as planned.priority: MUST
implementation: verified
constrains: holonic/init.py, holonic/backends/init.py, holonic/backends/protocol.py
acceptance: Given holonic 0.5.0, when code imports GraphBackend, then an AttributeError is raised (alias removed per R9.18).
verifiedBy: src/holonic/test/test_deprecation_and_dispatch.py::test_graphbackend_removed_from_holonic
R9.10 The
HolonicDatasetconstructor parameter previously namedregistry_graphMUST be renamed toregistry_irifor consistency with internal usage. The old name was a deprecated alias through 0.4.x and was removed in 0.5.0 per R9.18.priority: MUST
implementation: verified
constrains: HolonicDataset.init
acceptance: Given holonic 0.5.0, when code passes registry_graph= to HolonicDataset, then a TypeError is raised (kwarg removed per R9.18). registry_iri= is the canonical parameter.
verifiedBy: src/holonic/test/test_deprecation_and_dispatch.py::test_registry_graph_kwarg_removed
Remaining (gated on evidence)
R9.11 The library SHOULD ship a
holonic.generatorsmodule formalizing the existingexamples/holarchy generators (company, research lab, random).priority: SHOULD
implementation: verified
constrains: holonic.generators (new module)
acceptance: Given
from holonic.generators import company_holarchy, when the generator is invoked with a seed, then a deterministic holarchy with documented structure is produced against any HolonicStore.verifiedBy: (pending implementation)
R9.12 The library SHOULD offer a
metadata_updates="lazy"mode (dirty tracking withflush_metadata()) if evidence emerges that eager and off modes leave a practical gap.priority: SHOULD
implementation: in-progress
constrains: HolonicDataset, MetadataRefresher
acceptance: Given HolonicDataset(metadata_updates=”lazy”), when multiple writes occur followed by flush_metadata(), then metadata reflects the final state without per-write overhead.
verifiedBy: (pending implementation, gated on evidence from downstream usage)
R9.13 The registry SHOULD optionally aggregate per-holon membrane health for fast dashboards and scope-resolution health predicates. Semantics and invalidation rules to be decided before implementation.
priority: SHOULD
implementation: in-progress
constrains: cga.ttl, HolonicDataset, registry schema
acceptance: Given a holarchy with recent membrane validations, when the registry is queried for aggregated health, then per-holon rollup values are returned without recomputation from scratch.
verifiedBy: (pending implementation; design in open question OQ4)
R9.14 The scope resolver SHOULD gain additional predicate classes (
HasPortalProducing,HasShapeFor,LabelMatches) if evidence emerges thatCustomSPARQLis being used as a workaround for missing first-class predicates.priority: SHOULD
implementation: in-progress
constrains: holonic.scope
acceptance: Given usage data showing CustomSPARQL patterns for portal/shape/label matching, when the named predicate classes ship, then the CustomSPARQL workarounds become first-class.
verifiedBy: (pending implementation, gated on usage evidence)
R9.15 The library SHOULD ship a migration pass that synthesizes minimal
cga:ProjectionPipelineSpecresources for projection graphs registered before 0.3.5, so historical projections get consistent provenance.priority: SHOULD
implementation: in-progress
constrains: holonic.cli.migrate_projections (new CLI)
acceptance: Given a pre-0.3.5 registry with projection layer graphs but no pipeline specs, when the migration CLI is run, then a minimal ProjectionPipelineSpec is synthesized for each projection and attached via cga:hasPipeline.
verifiedBy: (pending implementation)
R9.16 Pipeline steps SHOULD support per-step arguments (e.g.
filter_by_class(class_iri)) via acga:stepArgumentsJSON literal or structured argument record.priority: SHOULD
implementation: in-progress
constrains: ProjectionPipelineStep, cga.ttl
acceptance: Given a pipeline step with transform_name=”filter_by_class” and arguments={“class_iri”: “urn:ex:Widget”}, when run_projection executes, then the transform receives the arguments as keyword args.
verifiedBy: (pending implementation)
R9.17 The optional protocol surface SHOULD grow additively during 0.4.x with native hooks for scope walking, bulk load, and pipeline execution as backend-specific optimization opportunities arise.
priority: SHOULD
implementation: in-progress
constrains: holonic.backends.store, AbstractHolonicStore
acceptance: Given a backend implementing an optional method like
walk_neighbors_native, when the corresponding library helper dispatches, thenhasattr-based detection invokes the native method without regressing duck-typed backends.verifiedBy: (pending per-method implementation, following the refresh_graph_metadata precedent from R9.8)
R9.18 In 0.5.0, the
GraphBackendalias MUST be removed along with theregistry_graphkwarg andds.registry_graphproperty.priority: MUST
implementation: verified
constrains: holonic/init.py, holonic/backends/init.py, holonic/backends/protocol.py, HolonicDataset
acceptance: Given holonic 0.5.0 installed, when code imports GraphBackend or passes registry_graph=, then an ImportError or TypeError is raised respectively.
verifiedBy: src/holonic/test/test_deprecation_and_dispatch.py::test_graphbackend_removed_from_holonic, test_graphbackend_removed_from_backends_protocol, test_registry_graph_kwarg_removed, test_registry_graph_property_removed
Shipped in 0.4.1
R9.19 The library MUST ship a JupyterLite static build of the example notebooks for in-browser exploration without local Python installation. Build integrates with the existing
notebooks/directory viascripts/sync_notebooks_to_jlite.py; output lands indocs/source/_extra/jupyterlite/so Sphinx’shtml_extra_pathserves it at/jupyterlite/alongside the documentation. Notebook 13 (yFiles visualization) is excluded from in-browser execution because yFiles requires a Jupyter server extension that Pyodide cannot provide; the landing notebook00_start_here.ipynbdocuments this constraint.priority: MUST
implementation: verified
constrains: jupyterlite/, scripts/sync_notebooks_to_jlite.py, .readthedocs.yaml, pixi.toml (tasks-jlite), docs/source/_extra/jupyterlite/
acceptance: Given the hosted documentation, when a reader opens the docs and follows the “Try in browser” link, then the JupyterLite lab loads and notebooks 01-12 execute against a Pyodide kernel without local Python installation.
verifiedBy: docs/source/index.md “Try in browser” section + .readthedocs.yaml pre_build hooks + manual verification during release rehearsal
Shipped in 0.4.2
R9.20 The library MUST provide
HolonicDataset.remove_holon(iri)completing the CRUD lifecycle started byadd_holon(). Cleans up the holon’s registry entry, all four layer graphs (interior/boundary/projection/context), graph-typing triples, graph-level metadata records, the per-holon rollup, and cascades toremove_portal()for every portal where the holon is source or target. Child holons that reference the removed holon viacga:memberOfare orphaned (theirmemberOftriple is removed) but not themselves deleted. Idempotent — returnsFalsefor a non-existent IRI rather than raising. Provenance activities referencing the removed holon are preserved because provenance is immutable history.priority: MUST
implementation: verified
constrains: HolonicDataset.remove_holon, holonic/client.py
acceptance: Given a holon with interior, boundary, and context layer graphs, plus a portal from it and a portal to it, plus a child holon referencing it as
cga:memberOf, whenremove_holon(iri)is called, then the holon is gone fromlist_holons(), all four layer graphs are deleted viabackend.graph_exists()=False, both portals are gone fromfind_portals_from/find_portals_to, and the child remains inlist_holons()but without itsmemberOftriple.verifiedBy: src/holonic/test/test_lifecycle.py::TestRemoveHolon
R9.21 The library MUST provide
HolonicDataset.remove_portal(portal_iri)completing the CRUD lifecycle started byadd_portal(). Removes all triples withportal_irias subject from every graph containing them (typically the source holon’s boundary graph and the registry mirror). The boundary graph itself is preserved — sibling portals and SHACL shapes in the same graph are unaffected. Idempotent — returnsFalsefor a non-existent portal IRI rather than raising. Metadata refresh fires per affected graph whenmetadata_updates="eager".priority: MUST
implementation: verified
constrains: HolonicDataset.remove_portal, holonic/client.py
acceptance: Given a source holon with two portals declared in the same boundary graph plus a SHACL shape, when
remove_portal(portal_a)is called, then portal_a is absent fromfind_portals_from(source), portal_b is still discoverable, and the SHACL shape is still present in the boundary graph.verifiedBy: src/holonic/test/test_lifecycle.py::TestRemovePortal
R9.22 The
add_portal()method MUST support creation of all portal subtypes declared in the CGA ontology (cga:TransformPortal,cga:IconPortal,cga:SealedPortal) as well as downstream subclasses via three additive parameters:construct_queryis optional (defaultNone);portal_typeis a customizable RDF type (default"cga:TransformPortal");extra_ttlaccepts additional Turtle triples for predicates carried by downstream portal subclasses. All existing positional calls continue to work unchanged. Portal discovery (find_portals_from/to/direct) MUST match any portal subtype — the pre-0.4.2 hardcoded filter oncga:TransformPortalis relaxed because thecga:sourceHolon+cga:targetHolonpredicate pair uniquely identifies a portal regardless of its specific subtype. Discovery queries useSELECT DISTINCTto deduplicate results across the boundary graph and registry mirror. The CGA ontology shipscga:IconPortal(previously undeclared) and SHACL shapes (cga:IconPortalShape,cga:SealedPortalShape) that warn when these subtypes carrycga:constructQuery;cga:TransformPortalShapecontinues to require exactly onecga:constructQuery.priority: MUST
implementation: verified
constrains: HolonicDataset.add_portal, holonic/client.py, holonic/sparql.py (FIND_PORTALS_FROM), holonic/sparql.py (FIND_PORTALS_TO), holonic/sparql.py (FIND_PORTAL_DIRECT), holonic/ontology/cga.ttl (cga:IconPortal), holonic/ontology/cga-shapes.ttl (cga:IconPortalShape), holonic/ontology/cga-shapes.ttl (cga:SealedPortalShape)
acceptance: Given a portal created with
portal_type="cga:SealedPortal"and noconstruct_query, whenfind_portals_from(source)is called, then exactly onePortalInfois returned withconstruct_query=None; and given a portal created withextra_ttlcarrying a downstream predicate, when the boundary graph is queried by SPARQL for that predicate, then the extra triples are present; and given a TransformPortal without a constructQuery or a SealedPortal/IconPortal carrying one, when the registry is validated againstcga-shapes.ttl, then the corresponding shape reports a violation (for TransformPortal) or warning (for SealedPortal and IconPortal).verifiedBy: src/holonic/test/test_lifecycle.py::TestAddPortalExtensibility and src/holonic/test/test_ontology.py::TestPortalSubtypeShapeSemantics
Shipped in 0.6.0
R9.23 Portal discovery queries MUST return the portal’s RDF type (excluding base
cga:Portal) as aportal_typefield onPortalInfo,PortalSummary, andPortalDetail. All five discovery SPARQL queries includeOPTIONAL { ?portal a ?portalType . FILTER(?portalType != cga:Portal) }.priority: MUST
implementation: verified
constrains: holonic/model.py (PortalInfo), holonic/console_model.py (PortalSummary), holonic/console_model.py (PortalDetail), holonic/sparql.py, holonic/client.py
acceptance: Given a TransformPortal, when
find_portals_from()is called, thenportal.portal_typecontains"urn:holonic:ontology:TransformPortal". Given a SealedPortal, whenget_portal()is called, thendetail.portal_typecontains"SealedPortal".verifiedBy: src/holonic/test/test_verified_gaps.py::TestPortalTypeInDataModel
R9.24
traverse_portal()MUST check the portal’s RDF type before executing the CONSTRUCT query. If the portal iscga:SealedPortal, it MUST raiseSealedPortalError(aValueErrorsubclass) regardless of whether the portal carries aconstructQuery.priority: MUST
implementation: verified
constrains: holonic/client.py (traverse_portal), holonic/model.py (SealedPortalError)
acceptance: Given a SealedPortal with or without a CONSTRUCT query, when
traverse_portal()ortraverse()is called, thenSealedPortalErroris raised with a message identifying the portal.verifiedBy: src/holonic/test/test_verified_gaps.py::TestSealedPortalEnforcement
R9.25
traverse()MUST register the target interior graph ascga:hasInteriorafter injection. It MUST look up existing registered interiors first and inject into the first one found; if none exist, it falls back to the{target_iri}/interiorconvention name. This ensuresvalidate_membrane()can see the injected data.priority: MUST
implementation: verified
constrains: holonic/client.py (traverse)
acceptance: Given a target holon with no pre-existing interior, when
traverse()injects triples, thenGET_HOLON_INTERIORSreturns the injected graph andvalidate_membrane()validates against the injected data, not an empty graph.verifiedBy: src/holonic/test/test_verified_gaps.py::TestTraverseInteriorRegistration
R9.26
get_holon()MUST use a direct filtered SPARQL query (O(1) in holarchy size) rather than scanning all holons viaiter_holons().priority: MUST
implementation: verified
constrains: holonic/client.py (get_holon)
acceptance: Given 20 holons, when
get_holon()is called, then the method source does not referenceiter_holons.verifiedBy: src/holonic/test/test_verified_gaps.py::TestGetHolonEfficiency
R9.27
validate_all()MUST validate membranes for all holons and returndict[str, MembraneResult].priority: MUST
implementation: verified
constrains: holonic/client.py (validate_all)
acceptance: Given 2 holons, when
validate_all()is called, then a dict with 2 entries is returned, each value aMembraneResult.verifiedBy: src/holonic/test/test_verified_gaps.py::TestValidateAll
R9.28
traverse()MUST acceptfail_on_breach: bool = False. When True and validation returns COMPROMISED, it MUST roll back injected triples and raiseMembraneBreachError. Rollback restores a pre-injection snapshot of the target interior.priority: MUST
implementation: verified
constrains: holonic/client.py (traverse)
acceptance: Given a traversal that produces COMPROMISED validation, when
fail_on_breach=True, thenMembraneBreachErroris raised and the target interior is unchanged. Injected nodes of a type no shape targets compromise the membrane (R3.7).verifiedBy: src/holonic/test/test_verified_gaps.py::TestFailClosedTraversal
R9.29
update_portal()MUST update portal properties in-place without requiring remove+add. Uses per-graph SPARQL DELETE for old values and Turtle parse for new values.priority: MUST
implementation: verified
constrains: holonic/client.py (update_portal)
acceptance: Given a portal with a CONSTRUCT query, when
update_portal(construct_query=new_query)is called, thenget_portal()returns the new query.verifiedBy: src/holonic/test/test_verified_gaps.py::TestPortalUpdate
R9.30
traverse_path()MUST execute multi-hop traversals by callingfind_path()then loopingtraverse(). RaisesValueErrorif no path exists. Records provenance per hop whenagent_iriis provided.priority: MUST
implementation: verified
constrains: holonic/client.py (traverse_path)
acceptance: Given a 3-holon chain A->B->C, when
traverse_path("A", "C")is called, then 2 hops are executed and provenance records exist for each.verifiedBy: src/holonic/test/test_verified_gaps.py::TestTraversePath
R9.31
dry_run()MUST simulate a traversal without mutating state. Runs the CONSTRUCT, merges with existing interiors in memory, validates against boundary shapes, and returns(projected_graph, membrane_result).priority: MUST
implementation: verified
constrains: holonic/client.py (dry_run)
acceptance: Given a source and target with data, when
dry_run()is called, then projected triples are returned, a membrane result is returned, and the target interior is empty.verifiedBy: src/holonic/test/test_verified_gaps.py::TestPortalDryRun
R9.32
last_traversal(holon_iri)MUST return the most recentTraversalRecordtargeting a holon.derivation_chain(holon_iri)MUST walkprov:wasDerivedFromto return upstream holon IRIs.priority: MUST
implementation: verified
constrains: holonic/client.py (last_traversal), holonic/client.py (derivation_chain)
acceptance: Given traversals A->B->C, when
derivation_chain("C")is called, then["B", "A"]is returned. Whenlast_traversal("C")is called, then aTraversalRecordwithtarget_iri="C"is returned.verifiedBy: src/holonic/test/test_verified_gaps.py::TestPerHolonProvenanceHelpers
R9.33
compose(holon_iris)MUST union interior graphs across multiple holons into a singlerdflib.Graph. Accepts optionallayersparameter for specifying which layer types to include.priority: MUST
implementation: verified
constrains: holonic/client.py (compose)
acceptance: Given holons A and B with different vocabularies, when
compose(["A", "B"])is called, then the returned graph contains triples from both holons’ interiors.verifiedBy: src/holonic/test/test_verified_gaps.py::TestCompositionViews
R9.34
rollback_traversal(activity_iri)MUST undo a traversal by re-running the portal’s CONSTRUCT and removing the projected triples from the target interior. Returns the number of triples removed.priority: MUST
implementation: verified
constrains: holonic/client.py (rollback_traversal)
acceptance: Given a recorded traversal, when
rollback_traversal(activity_iri)is called, then the target interior is empty.verifiedBy: src/holonic/test/test_verified_gaps.py::TestRollback
R9.35
traverse_portal()MUST support source layer scoping. When a portal declarescga:sourceLayer cga:ProjectionRole, the CONSTRUCT runs against projection graphs only. When nosourceLayeris declared but the source holon has projection graphs, the CONSTRUCT defaults to projection scope. Portals can force full-dataset access viacga:sourceLayer cga:InteriorRole.priority: MUST
implementation: verified
constrains: holonic/client.py (traverse_portal)
acceptance: Given a source holon with PII in the interior and a sanitized projection, when a portal without explicit sourceLayer is traversed, then the CONSTRUCT sees only projection data and PII does not leak.
verifiedBy: src/holonic/test/test_verified_gaps.py::TestPortalConstructScope
R9.36
traverse()MUST compute a SHA-256 hash of the projected graph and compare against a stored hash (cga:lastProjectionHash) in the target’s context graph. When hashes match, injection is skipped and the traversal is recorded as a no-op. Hash tracking is active only whenagent_iriis provided.priority: MUST
implementation: verified
constrains: holonic/client.py (traverse)
acceptance: Given two identical traversals, when the second is executed with
agent_iri, then the audit trail contains a record with “no-op” in its label.verifiedBy: src/holonic/test/test_verified_gaps.py::TestIncrementalTraversal
R9.37
freshness(holon_iri)MUST return atimedeltasince the most recent traversal.is_stale(holon_iri, max_age)MUST return True when freshness exceeds the threshold.stale_holons(max_age)MUST return all holons exceeding the threshold.priority: MUST
implementation: verified
constrains: holonic/client.py (freshness), holonic/client.py (is_stale), holonic/client.py (stale_holons)
acceptance: Given a traversal with agent_iri, when
freshness()is called, then atimedeltais returned.verifiedBy: src/holonic/test/test_verified_gaps.py::TestStalenessTracking
Shipped in 0.7.1
R9.38
collect_audit_trail()MUST accept optionallimit,offset,since, andkindparameters for SPARQL-level pagination. Filtering is pushed to the SPARQL engine viaORDER BY DESC(?timestamp) LIMIT/OFFSETandFILTER(?timestamp > ...). Calling with no arguments returns the full trail (backward compatible).priority: MUST
implementation: verified
constrains: holonic/client.py (collect_audit_trail)
acceptance: Given 100 traversals, when
collect_audit_trail(limit=5, offset=10)is called, then exactly 5 records are returned starting from the 11th most recent.verifiedBy: src/holonic/test/test_audit.py::TestCollectAuditTrail
R9.39
get_activity(activity_iri)MUST return aTraversalRecordorValidationRecordfor a single provenance activity, or None if not found. Single SPARQL query per attempt.priority: MUST
implementation: verified
constrains: holonic/client.py (get_activity)
acceptance: Given a recorded traversal activity, when
get_activity(iri)is called, then aTraversalRecordwith matching fields is returned.verifiedBy: (verified via collect_audit_trail round-trip)
R9.40
classify_sparql(query)MUST return the SPARQL form ('select','ask','construct','describe','update') by stripping comments and string literals before keyword matching. RaisesValueErrorfor unclassifiable queries.priority: MUST
implementation: verified
constrains: holonic/client.py (classify_sparql)
acceptance: Given
"PREFIX ex: <urn:ex:> SELECT ?s WHERE { ?s ?p ?o }", whenclassify_sparql()is called, then'select'is returned.verifiedBy: (utility function; exercised by downstream consumers)
R9.41
validate_iri(iri)MUST be a public entry point to the library’s IRI validation. RaisesValueErrorfor empty strings or characters unsafe in Turtle/SPARQL contexts.priority: MUST
implementation: verified
constrains: holonic/client.py (validate_iri), holonic/client.py (_validate_iri)
acceptance: Given
validate_iri("<unsafe>"), when called, thenValueErroris raised.verifiedBy: src/holonic/test/test_audit_remediation.py::TestIRIValidation
R9.42
holarchy_summary(max_age=, recent_limit=)MUST return aHolarchySummarydataclass aggregating holon count, portal count, root count, health distribution, staleness count, and recent activities.priority: MUST
implementation: verified
constrains: holonic/client.py (holarchy_summary), holonic/console_model.py (HolarchySummary)
acceptance: Given a holarchy with 3 holons and 2 portals, when
holarchy_summary()is called, thensummary.holon_count == 3andsummary.portal_count == 2.verifiedBy: (integration method; exercised by downstream console)
R9.43
on_traversal(callback)andon_validation(callback)MUST register callbacks that fire synchronously after eachtraverse()orvalidate_membrane(). Callbacks receive the relevant arguments (source, target, projected, membrane_result for traversal; holon_iri, result for validation).priority: MUST
implementation: verified
constrains: holonic/client.py (on_traversal), holonic/client.py (on_validation), holonic/client.py (traverse), holonic/client.py (validate_membrane)
acceptance: Given a registered callback, when
traverse()completes, then the callback has been called exactly once with the traversal arguments.verifiedBy: (callback mechanism; exercised by downstream console EventBroker)
R9.44
MembraneResult.shape_violationsMUST carry alist[ShapeViolation]populated from the pyshacl report graph.ShapeViolationhas fields:shape_iri,focus_node,path,value,message,severity.priority: MUST
implementation: verified
verifiedBy: src/holonic/test/test_membrane.py::TestShapeViolationDetail::test_focus_node_identifies_the_offending_node
constrains: holonic/model.py (ShapeViolation), holonic/model.py (MembraneResult), holonic/client.py (_parse_shacl_report)
acceptance: Given a membrane violation, when
validate_membrane()returns, thenresult.shape_violations[0].focus_nodeidentifies the offending node.
Deferred to 0.8.0+
User Stories
US1 As a knowledge engineer, I create a holon, add interior data from multiple sources into named sub-graphs, declare boundary shapes, and validate the membrane — all from a single
HolonicDatasetinstance against in-memory rdflib.role: P1
benefit: I can enforce structural contracts on holon interior data without standing up external infrastructure
acceptance: Given HolonicDataset() with an in-memory backend, when I create a holon with two interiors and a boundary shape and call validate_membrane, then I receive a MembraneResult with health and violation details.
US2 As a systems integrator, I register a portal between two holons with a CONSTRUCT query that translates one vocabulary to another, traverse it, and see the target interior populated only when the membrane validates.
role: P2
benefit: vocabulary translation between holons is governed rather than ad-hoc
acceptance: Given a source holon with CCO-shaped data and a target holon with Schema.org boundary shapes, when I register a CONSTRUCT portal and call traverse(source, target, validate=True), then Schema.org triples appear in the target only if validation passes.
US3 As an operator-tool builder, I use
list_holons_summary(),holon_neighborhood(iri, depth=2), andto_graphology()to render a sigma.js graph of the holarchy in a web console without any custom SPARQL.role: P3
benefit: I can ship a working web console without writing SPARQL in my frontend code
acceptance: Given a populated holarchy, when I call list_holons_summary() and holon_neighborhood(iri, depth=2).to_graphology(), then the returned dicts deserialize to JSON suitable for direct sigma.js consumption.
US4 As a platform engineer, I deploy against Fuseki with bearer-token auth by passing
extra_headers={"Authorization": "Bearer ..."}toFusekiBackendand the library transparently authenticates every SPARQL request.role: P4
benefit: secrets-based authentication flows through to the SPARQL endpoint without library modifications
acceptance: Given FusekiBackend(url, dataset=ds, extra_headers={“Authorization”: “Bearer x”}), when any SPARQL request is issued, then the Authorization header reaches the server unchanged.
US5 As an agent author, I use portal traversal as an agentic memory write path: each agent turn runs through a portal with SHACL shape validation, and every turn produces a PROV-O record linking agent, input, output, and activity time.
role: P5
benefit: agent memory writes are validated and auditable end-to-end
acceptance: Given an agent iterating through a governed portal with validate=True, when a turn completes, then the context graph contains a prov:Activity with prov:wasAssociatedWith the agent, prov:used the input, prov:generated the output, and prov:startedAtTime the turn timestamp.
US6 As a data steward, I query the context graph across all holons in a holarchy and receive an audit trail of traversals with membrane health, agent attribution, and timestamps.
role: P6
benefit: I can produce a complete activity record for compliance review
acceptance: Given a holarchy with traversal history, when I run a SPARQL query over the union of context graphs, then I receive activity, agent, input, output, timestamp, and membrane health for every governed operation.
US7 As a spec author, I translate this SPEC to Turtle via
specl-translate, validate it against the shipped SHACL shapes, and publish a maturity badge in the README.role: P7
benefit: spec quality is machine-measurable and visible to external consumers
acceptance: Given docs/SPEC.md, when specl-translate and specl-validate score are run, then a maturity percentage and SVG badge artifact are produced and the spec’s structural integrity is asserted by SHACL.
Personas
Who the user stories are about. Declared once and referenced by identifier so
that two stories about the same person share a node rather than a spelling —
role: takes P3, never “operator-tool builder”, because minting from the
surface string would make “operator-tool builder” and “Operator-Tool Builder”
two personas with nothing reporting it.
P1 Knowledge engineer
prefLabel: knowledge engineer
altLabel: ontologist
P2 Systems integrator
prefLabel: systems integrator
P3 Operator-tool builder
prefLabel: operator-tool builder
altLabel: console developer
P4 Platform engineer
prefLabel: platform engineer
P5 Agent author
prefLabel: agent author
altLabel: agent developer
P6 Data steward
prefLabel: data steward
P7 Spec author
prefLabel: spec author
Agents
Who is accountable for an item. owner: takes an identifier for the same
reason role: does: held as a literal it was a spelling repeated across items
and joinable only by string equality.
AG1 Zach Welz
prefLabel: zwelz3
Design Considerations
DN1 The dataset is the holarchy. Python methods are convenience over SPARQL, not a separate object model. Dataclasses returned from queries are ephemeral views, not persistent state.
DN2 Named graphs are hypergraphs. Recognizing this subsumes the RDF-vs-LPG distinction and makes the four-graph model a natural consequence of standard RDF 1.1 semantics.
DN3 SPARQL is the primary control surface. Python handles only what SPARQL cannot express (Dijkstra path ranking, template rendering, pipeline orchestration).
DN4 Minimal dependencies. The hard deps are rdflib and pyshacl. Optional extras add jupyter, aiohttp (Fuseki), owlrl (entailment), and visualization widgets. pydantic was removed in 0.6.0; the library uses plain dataclasses with
_DictMixin.to_dict()for serialization.DN5 PROV-O for activity-level provenance. Graph-to-graph derivation via
prov:wasDerivedFrom. Holon-to-holon structural dependency viacga:derivedFrom. These are distinct concepts and both exist.DN6 SHACL shapes, not OWL axioms, for constraint checking. Reasoner-free by design; entailment is optional via the
owlrlextra.DN7 Nested Turtle notation everywhere the library emits RDF. Predicates grouped with
;, object lists with,. No verbose explicit-triple form in examples, fixtures, or docs.
Open Questions and Gaps (flag for follow-up)
OQ1 Registry-graph typing vs holon-graph-as-holon. If typed graphs (R9.1) are adopted, should the registry graph itself be typed as
cga:RegistryGraph, and should it be modeled as the interior of aRegistryHolon? Bootstrapping implications unresolved.owner: AG1
recommendation: Type the registry graph explicitly as cga:RegistryRole (shipped in 0.3.4); defer the registry-as-holon question until after R9.1 ships.
status: resolved
OQ2 Subclass vs flat+roles for graph categories.
cga:InteriorGraph rdfs:subClassOf cga:HolonicGraphversus?g a cga:HolonicGraph ; cga:graphRole cga:InteriorRole. Subclass is terser in queries; flat+roles supports multi-role graphs cleanly (e.g. a graph that is both an interior and a provenance aggregate).owner: AG1
recommendation: Adopt flat+roles because the dual-role case is real (see cga:AggregateHolon); cost is one extra triple pattern per query. Shipped in 0.3.4 via D-0.3.4-1.
status: resolved
OQ3 Scope resolution ordering configurability. The proposed order (self → portal neighbors → siblings → parent → wider) serves network-proximity queries. Containment-first ordering serves governance queries. Reverse portal distance serves debugging queries.
owner: AG1
recommendation: Default to network-proximity; accept an order= parameter on resolve() taking a strategy enum. Shipped in 0.3.4 with three modes: network, reverse-network, containment.
status: resolved
OQ4 Class-inventory staleness strategy. Materialized inventory in the registry needs a refresh policy: synchronous (slow writes), asynchronous (fast writes, stale reads), on-demand (explicit refresh), or TTL-based (configurable staleness).
owner: AG1
recommendation: Per-HolonicDataset strategy at construction time. Shipped in 0.3.3 with eager and off modes; lazy mode deferred to R9.12 gated on usage evidence. Resolved: that evidence never arrived, and the absence of usage was itself the determining criterion, so the gate answered the question rather than deferring it. Eager and off remain the shipped modes; reopen only if a consumer produces the usage R9.12 was waiting on.
status: resolved
OQ5 Graph-level metadata write amplification. If every portal traversal updates
cga:interiorTripleCountand last-modified in the registry, write amplification dominates in high-throughput pipelines.owner: AG1
recommendation: Make metadata updates opt-in via a constructor kwarg. Shipped in 0.3.3 as metadata_updates=”eager”|”off” on HolonicDataset.
status: resolved
OQ6 Protocol rename (
GraphBackendtoHolonicStore). Resolved in 0.4.0 via R9.8, R9.9. Deprecated alias removed in 0.5.0 via R9.18.owner: AG1
recommendation: Renamed in 0.4.0 via R9.8 and R9.9; the deprecated GraphBackend alias was removed in 0.5.0 via R9.18. No further action.
status: resolved
OQ7 Federation semantics across multiple registries are out of scope for 0.3.x. Open question preserved because the typed-graph design (R9.1) should not close the door on registry-to-registry declarations like
cga:federatesWith.owner: AG1
recommendation: Defer to 0.5.0 design session; ensure R9.3’s typed-graph vocabulary leaves room for a future federation property without schema migration.
status: deferred
OQ8 Graph-level tick semantics and transition rules. Cagle’s “The Graph as State Machine” (The Inference Engineer, April 2026) frames a holonic graph as a graph-level state machine in the Game-of-Life sense: Scene Graph (interior state), Boundary Graph (SHACL rules including
sh:SPARQLRuletransition functions), Event Graph (PROV-O transition history), and Projection Graph (external observation). The library implements all four layers structurally but does not implement a tick — portal traversals are event-triggered rather than clock-triggered, and SHACL is used for validation rather than for$this-based transformation rules. A futureHolonicDataset.tick()primitive could fire SHACL transition rules across the whole holarchy, either accumulating PROV-O activities into the Event Graph or mutating the Scene Graph directly via SPARQL UPDATE. Whether to add this is a distinct architectural question from anything currently in the roadmap; adopting it would extend the library from a read-heavy, portal-triggered coordination layer into a living-system model with continuous dynamics.owner: AG1
recommendation: Wait for Part 2 of the Cagle series (Active Inference / Free Energy Principle) to land before committing to a tick API. Evaluate whether a tick is the right primitive or whether something finer-grained (an active-inference step, an energy-minimization iteration) is more apt. If pursued at all, target a
holonic.contribexperimental module first to test the pattern without committing the core ontology. No release slot assigned; this may or may not be implemented.status: deferred
OQ9 DOM-style event propagation as a coordination model. Cagle proposes (LinkedIn thread, April 2026) that plural-orchestrator coordination can be resolved by treating the holarchy as a structure over which events propagate in the W3C Document Object Model’s capture/target/bubble pattern. Under this framing: the containing holon makes no expectations on child interiors (opacity is first-class); external events arrive at a holon and are either consumed, delegated to children via portals, or ignored after propagation completes; “eventually ignored” is a legitimate outcome rather than a failure mode. This is distinct from OQ8’s tick proposal — event propagation is structural (who receives an event depends on the containment topology) rather than clock-triggered. The library today implements something DOM-adjacent via portal traversal and multi-hop path finding, but does not expose explicit event-dispatch semantics. Open questions surface immediately when mapping DOM to holons: DOM events are synchronous and the DOM is a strict tree, whereas a federated holarchy is asynchronous and a containment graph may have multiple portal paths between two holons. Whether to adopt the DOM framing as library architecture, as a mental model for explaining existing capabilities, or neither, remains open.
owner: AG1
recommendation: Before any implementation, verify whether the existing portal-traversal API strains under real use cases that DOM-style event dispatch would address more naturally. If strain emerges, the design question becomes: extend existing portal semantics with explicit event-phase hooks (capture/target/bubble), add a dedicated
dispatch_eventAPI parallel totraverse, or adopt the DOM model as framing while keeping the current API unchanged. The documentation atdocs/source/dom-comparison.mdestablishes the mental-model mapping without committing to implementation. No release slot assigned; this may or may not be implemented.status: deferred
OQ10 Upper-ontology alignment (BFO/CCO, gist). The CGA ontology’s structural core (Holon, Portal, LayerGraph) is genuinely novel and does not reduce cleanly to existing upper-ontology categories. A holon is not a BFO Continuant, not an Occurrent, not a gist Content — it is a structural container with an inside/outside distinction, layer-scoped named graphs, and a membrane. Forcing it into an upper-ontology branch would misrepresent what it is. The governance vocabulary (DataDomain, BusinessProcess, ExternalSystem, Capability) maps more naturally to established categories (CCO Agent, CCO InformationBearingEntity, gist Organization). The recommended approach is alignment-not-import: ship optional alignment modules (
cga-bfo-alignment.ttl,cga-gist-alignment.ttl) that downstream consumers load when interoperability with a specific upper ontology is required. The core ontology stays standalone, reasoner-free, and philosophically uncommitted. BFO/CCO alignment supports consumers in regulated industries (ISO 21838-2); gist alignment supports commercial enterprise consumers. Both can be developed in parallel without affecting the core.owner: AG1
status: open
recommendation: Develop alignment modules as separate Turtle files under
src/holonic/ontology/. Useskos:broadMatch/skos:closeMatchin the core ontology for lightweight cross-referencing without owl:imports. Document the alignment rationale indocs/source/ontology.md. Gated on a concrete downstream consumer requesting BFO or gist compatibility.
OQ11 SHACL target-class validation gap for fail-on-breach. Standard SHACL
sh:targetClassvalidates only instances of the target class; when no instances exist, validation reports conformant. This meansfail_on_breach=Truecannot detect the case where a portal injects data of the WRONG type (e.g.bad:Wronginstead ofgood:Required). The membrane reports INTACT because there are nogood:Requiredinstances to validate against. Thefail_on_breachmechanism (R9.28) is correctly implemented for cases where SHACL does report violations. Closing this gap requires one of: (a) non-standard SHACL interpretation where boundary shapes assert minimum cardinality on target-class instances, not just properties; (b) a pre-validation check that verifies at least one instance of eachsh:targetClassexists in the interior after injection; (c) custom SPARQL-based validation in addition to SHACL. Two tests intest_verified_gaps.pyare markedxfailpending resolution. This is a critical design decision because it affects whetherfail_on_breachprovides meaningful protection against schema-mismatch errors, not just constraint violations within the correct schema.owner: AG1
status: resolved
recommendation: Resolved in 0.9.0 by R3.7 (D-0.9.0-2) with a variant of approach (c) rather than (b). Option (b) fails a valid interior that has no instances of some class yet. Instead, membrane validation reports each typed node that no shape targets; a fail-closed traversal treats the nodes it injected as violations unless the holon permits their type, and the two tests in
test_verified_gaps.pypass withoutxfail.
Appendix A: Suggested Namespaces
@prefix cga: <urn:holonic:ontology:> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix schema: <https://schema.org/> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
Appendix B: Module Map (0.9.0)
src/holonic/
├── __init__.py re-exports HolonicDataset, models, ontology loaders
├── client.py HolonicDataset — the primary API surface
├── model.py HolonInfo, PortalInfo, MembraneResult, ShapeViolation, AuditTrail, SurfaceReport
├── console_model.py HolonSummary, HolonDetail, NeighborhoodGraph, PortalSummary, ...
├── sparql.py SPARQL templates as named string constants
├── projections.py CONSTRUCT_STRIP_TYPES, project_to_lpg, ProjectionPipeline
├── backends/
│ ├── store.py HolonicStore protocol + AbstractHolonicStore ABC
│ ├── rdflib_backend.py in-memory rdflib.Dataset backend
│ ├── fuseki_backend.py Apache Jena Fuseki HTTP backend
│ └── _fuseki_client.py low-level HTTP helpers
├── ontology/
│ ├── cga.ttl CGA ontology (holons, portals, layers, governance)
│ └── cga-shapes.ttl SHACL shapes (membrane validation scaffolding)
├── viz/
│ ├── graph_builder.py build visualization-ready graph structures
│ ├── formatters.py text/table formatters
│ ├── provenance.py provenance visualization helpers
│ ├── styles.py color/shape palettes
│ └── widgets.py ipywidgets composite displays
└── test/ pytest suite (all run against RdflibBackend)
Comments
C1 The four layers (interior, boundary, projection, context) are explicit in the ontology as
cga:LayerRoleindividuals (cga:InteriorRole,cga:BoundaryRole,cga:ProjectionRole,cga:ContextRole) and as bridging properties (cga:hasInterioretc. as sub-properties ofcga:hasLayer). The model accommodates multiple graphs per role.C2 The registry graph (
urn:holarchy:registry) in R9 proposals refers to a cross-cutting catalog graph for holarchy-level declarations. It is distinct from per-holon layer graphs and is declared at the holarchy level, not the holon level.C3
holonic-consoleis the primary downstream consumer driving the 0.3.1 API additions. Its stage 2 work (sigma.js neighborhood view, portal browser, provenance feed) is the functional proof for R6.C4
speclprovides the SHACL validation of this SPEC itself. The spec is dogfood: the library under development is specified using a tool whose ontology was co-designed with it.C5 Cagle’s “The Graph as State Machine” (The Inference Engineer, April 2026) frames a holonic graph as a graph-level state machine where the four layers map to Scene / Boundary / Event / Projection and
sh:SPARQLRuleis the transition function driven by a clock-triggered tick. The library today implements the four layers structurally but operates event-triggered (portal traversals invoked by callers) rather than clock-triggered. Whether to add a tick primitive is captured as OQ8.