Status: Accepted Date: 2026-09-22 Supersedes: none Related: rdf-sparql-patterns-guide.md, ADR-A01, ADR-A79, ADR-A80, proposed A74/A75, persistence-profile-substrate sketch
rdf-sparql-patterns-guide.md defines portable patterns for uniqueness, ordering, concurrency, and their combination, and states which choices are baseline defaults and which are optional strong-profile upgrades. It does not say how an adopter selects among these choices for their own applied ontology, at what granularity, or how conflicting selections at overlapping scopes are resolved.
LATTICE is a framework, not a single application. An adopter builds their own applied ontology on the substrate layers and decides, per class or per deployment, whether a population of individuals is an aggregate, what its containment boundary is, whether it needs compare-and-set, what ordering grain it needs, and what receipt model it needs. Some of that population may be classes the adopter’s own ontology declares. Some may be classes declared in a LATTICE substrate ontology (Behaviour, Party) that two different applied ontologies deploy differently. Nothing in the substrate ontologies or the guide currently expresses this choice, so every adopter would either hand-write inconsistent SPARQL against the same patterns, or LATTICE would have to bake in one aggregate-boundary strategy and one concurrency default for everyone, contradicting the framework’s own multi-tenant-hosting, adopter-chooses-their-slice design.
Three structural strategies were considered for declaring where an RDF aggregate’s boundary lies (named-graph partitioning, hand-declared property-path traversal, SHACL shape traversal), and the guide’s Part V assumes named-graph partitioning throughout. An adopter must be able to choose a different strategy per part of their own model, and LATTICE must be able to detect when a chosen strategy is incompatible with a chosen concurrency or ordering profile before generating anything.
Introduce ontology/persistence, a cross-cutting ontology (prefix dal:, for the Data Access Layer vocabulary it defines) that lets an adopter declare, per scope, which of the guide’s patterns apply to a population of RDF resources. It targets classes and graphs by IRI reference only and is not part of the Foundation-to-Behaviour dependency chain, so it imports nothing from the domain layers and they import nothing from it.
Six independently scopable profile dimensions, each its own class of individual: dal:AggregateBoundaryProfile, dal:ConcurrencyProfile, dal:OrderingProfile, dal:ReceiptProfile, dal:MetaTopologyProfile, dal:UniquenessConstraint. A composite dal:DataAccessProfile is sugar that declares several dimensions at one scope in one individual, and is decomposed at compile time into the same six-dimension model. Every dimension resolves independently, so a namespace-level profile can set ordering and concurrency while a class-level profile overrides only the receipt model, and the rest is inherited.
A dal:ProfileScope vocabulary with five scope kinds, ranked by whether they need reasoning: dal:GraphPatternScope (a graph-IRI prefix match, no reasoning), dal:NamespaceScope (an IRI-prefix match on the target’s own IRI), dal:ClassScope (asserted rdf:type, optionally walked over asserted rdfs:subClassOf via a depth-bounded property path, still no reasoning), dal:ShapeScope (SHACL node-shape membership, evaluated once at compile time against the adopter’s data model, not at write time), and dal:EquivalentClassScope (OWL entailment-dependent, the only kind that needs a reasoner or a materialised closure).
A fixed, documented precedence algorithm, resolved per dimension, never per whole profile. Filter to scopes that match the target. If, and only if, the adopter has supplied an optional dal:CapabilitySpec (point 6 below), drop any scope whose reasoning requirement that spec does not claim to satisfy, warning on each. Among the remainder, the highest explicit dal:priority wins, ties are broken by a fixed non-reasoning-over-reasoning rule, and any remaining tie is a compile-time ProfileAmbiguityError, never a silent pick. A profile with no matching scope for a dimension inherits the platform’s baseline default for that dimension, as stated in the guide’s §29.4 and §21.1.
Two runtime boundary mechanisms behind two authoring surfaces. dal:NamedGraphBoundary is the physical mechanism the guide’s Part V assumes. dal:CompositePropertyBoundary is declared exclusively through a dal:boundaryShape, an sh:NodeShape whose sh:property/sh:node tree is walked once at compile time into a closure model. A hand-declared composition-property vocabulary (making a domain property a sub-property of a dal: term) was considered and rejected: it carries real OWL entailment consequences for the domain ontology and creates exactly the import dependency this ADR otherwise avoids, so ontology/persistence declares no such vocabulary. No target backend is required to execute SHACL at write time for the shape-based mechanism to work. dal:NoBoundary declares triple-level, value-based CAS only (§14.2).
A convention, not a mechanism, resolves shared-class reuse. A LATTICE substrate ontology never binds a DataAccessProfile to its own classes. Authority over a shared class’s persistence profile belongs to the applied ontology deploying it, expressed by scoping the profile to that deployment’s graph pattern or namespace, which structurally outranks a bare class-level scope in the precedence order. Two applied ontologies deploying the same substrate class into disjoint graph families never collide.
Cross-axis consistency is validated at compile time, not deferred to runtime, and needs no live backend to do it: dal:NoBoundary with an aggregate-grained concurrency profile is rejected with a named remediation. The compiler always emits an unconditional dal:CapabilityRequirement per target, computed purely from the resolved profile. It never consults a live SPI or a TCK-verified StoreCapabilities report (Chapter 25), because this work package builds no SPI to consult. An adopter may optionally supply a dal:CapabilitySpec, a self-authored, unverified declaration sharing that record’s field shape, and the compiler then emits a dal:CapabilityCheck comparing the two, failing the compile only on the adopter’s own declared terms. Absent a spec, nothing is rejected on capability grounds, and the requirement record is the only capability artefact produced.
ontology/persistence has no place in the seven-layer dependency table in ontology-architecture.md. It is documented as a cross-cutting substrate alongside Surface and MORK, and that document gains a new section describing it, its scope-resolution algorithm, and its non-dependency on the domain layers.ontology/persistence as the authored form. A compiled dal:DataAccessProfile graph can still be projected into that YAML shape for a human summary, but it is no longer the source of truth.instantiate stage in ADR-A79, the housekeeping component in ADR-A80, and any future Request Query Mapping or Query Execution layer) reads the same dal:CompiledProfile and dal:CapabilityRequirement, never a bespoke re-derivation.DataAccessProfile to their own class triggers a lint warning (point 5 above), enforced in the same slice that authors the shapes.