LATTICE / FIELD GUIDE Back to home
A practical book on governed semantics

The LATTICE Field Guide

Every layer, from Foundation to Instrument, and how Surface, MORK, and a family of deterministic backend compilers turn a declared graph into trusted, provenance-bearing execution — with real examples pulled straight out of this repository.

For ontology authors, projection authors, compiler builders, reviewers, and operators. Where something is declared but not yet built, this guide says so plainly rather than quietly implying otherwise — that's the house style, and it's the one worth keeping.

1. Orientation

LATTICE is a semantic substrate for governed systems. It exists for situations where a document, policy, contract, protocol, or operational rule must become a graph that can be inspected, validated, transformed, queried, and eventually executed — without losing its source meaning along the way.

meaningful declarations
        |
        v
validated graph
        |
        v
compiled mode of operation
        |
        v
runtime answers with provenance

The central discipline is to separate semantic authority from computational convenience. A source declaration says what is true. A generated surface, mapping, query, shape, rule, or runtime plan is a derived way of asking questions about it — and a derived thing may only ever restate, never outrank, the declaration it came from.

Working question: can a reviewer explain what the system believes, why it believes it, which compiler produced the operational form, and what must be regenerated when an input changes?

LATTICE is actually three related pieces of work, and the name strictly names only the middle one. MORK gets messy source material — schemas, API payloads, free-text clauses — aligned onto a target ontology, largely without human intervention once enough confirmed mappings exist. LATTICE is the stack of domain-neutral ontology layers that alignment lands in. SPC, a formal session-typed process calculus for the live exchange between agents, ships a substantial ontology of its own but is not yet wired into the layers below — it still uses a placeholder namespace, and connecting it is deliberately out of scope for now rather than an oversight.

TermMeaning
DeclarationAn authored graph statement that defines domain meaning.
SurfaceA generated, query-facing restatement of declared graph facts — promoted, indexed, or projected.
EligibilityA declarative model for conditions, questions, and three-valued decisions.
MORKA machine-facing, provenance-rich mapping graph, reviewable and compiled deterministically.
ArtefactA generated shape, query, rule, transform, or runtime plan — never the source of truth.
Applied domainA full, ported real-world ontology built on the substrate — insurance, capacity, and others, at varying maturity.

2. The semantic stack

LATTICE is layered because different kinds of meaning have different owners, and the layering rule is strict: a lower layer never names a term belonging to a higher one. Foundation knows nothing of Party; Party knows nothing of Instrument. Where a higher layer needs to compose with a lower one, the composition is declared from the higher layer's own side — typically in a projection/ directory reserved for exactly that — so nothing about a lower layer's specification ever has to change to make it possible. This is what keeps Foundation, Vocabulary, and Quantification genuinely domain-neutral: nothing above them is allowed to leak downward into them.

LayerQuestion it answersTypical output
FoundationWhat is this artefact, and who governs it?Identity, version, evidence, governance state.
VocabularyWhich external concepts are available, and to whom?Scheme contracts, governed SKOS scheme wrappers.
QuantificationWhat does a value mean — ordered, bounded, converted, repeated?Value spaces, quantities, bounds, ranges, recurrences.
SurfaceCan this already-true fact be restated somewhere cheaper to ask?Promoted properties, index classes, lowered projections.
PartyWho participates, in what capacity, for how long?Actors, roles, role occupancies, participation groups.
EligibilityDoes evidence satisfy a condition?Questions and three-valued decisions.
BehaviourHow does a system change state?Triggers, guards, transitions, effects, allowances.
InstrumentWhich applied object carries the rules?Elements, provisions, obligations, qualifiers.

Direct SPARQL, SHACL, reasoning, materialisation, and generated surfaces are peer realisation strategies throughout this stack. None becomes the source of truth merely because it happens to be faster — a specification that reads as though compiling something were mandatory for it to be usable at all has quietly smuggled an implementation detail into a definition.

Foundation — four capabilities, never one bundle

Foundation is the bedrock everything else builds on, and its defining choice is refusing to be one thing. An earlier draft offered a single fnd:Governed superclass carrying identity, evidence, temporal scoping, and governance status all at once — rejected, because adopting any one capability would have forced adopting all four. Instead there are four small, freely combinable mixins: fnd:Version (this individual is a specific, identified state of some persistent thing), fnd:Evidenced (can carry evidence for why it holds), fnd:TemporallyScoped (has a validity period distinct from when anyone recorded it), and fnd:Governable (moves through a review lifecycle before it's trusted). Each has a disjoint companion value class it points at — fnd:PersistentIdentity, fnd:Evidence, fnd:TemporalScope, fnd:GovernanceState — so the role and the thing it points at can never collapse into each other.

fnd:GovernanceState is deliberately a bare current value — Draft, Reviewed, Active, Superseded — with no built-in state machine governing how one moves to another. That omission is the whole point: Foundation gives you the noun, and a layer built for exactly that job (Behaviour, or MORK's own governance model — see §5) supplies the verb.

Vocabulary — binding an outside word list without ever naming it

Some property genuinely needs values from an external, editorially-governed list — peril codes, jurisdictions, currencies — without the layer that declares the property ever having to name, or even know about, that list. The mechanism is a deliberate two-class split. A voc:SchemeContract is purely structural: "this property must eventually be bound to some scheme meeting these criteria," saying nothing about what the scheme is about — that's left entirely to prose and to whichever domain layer owns the property. A voc:ConceptScheme is a governed, versioned wrapper around an ordinary skos:ConceptScheme. The two connect through voc:boundScheme, which starts unset — an unbound contract is the normal starting state, filled in later by a completely different party once a scheme actually exists.

Quantification — one mechanism for anything ordered, bounded, or repeated

Named after the mechanism, not after any one kind of value, so that grades and tiers sit alongside currency amounts without misdescribing either. Its central object is the qnt:ValueSpace: an ordered set with a declared density and, critically, a declared, explicit list of which operations are actually permitted over it — holding numeric-looking literals never by itself grants permission to average them.

A position (a point on an ordered space) and an extent (a magnitude along it) are two genuinely separate declared spaces, not two readings of one — a position minus a position yields an extent, an extent plus an extent yields an extent, but a position plus a position is meaningless, and keeping the spaces apart makes that combination structurally unwritable rather than merely discouraged. This is also why temporal modelling needs no peer layer of its own: a moment in time is a position on a totally-ordered, unit-bearing space, and a recurring schedule is a generator of canonically identified ranges over it. Granularity (known only to the month — genuinely known, just imprecisely) and unresolvedness (genuinely missing, disputed, or pending) can both yield an Undetermined comparison, but conflating them — an earlier draft did — loses information a consumer needs to remediate correctly.

Party — who is standing in which capacity

Party answers one narrow question — who participates, in what capacity, for how long, and how does responsibility divide or transfer — and never what the obligation actually is, which stays a layer up in Instrument. Its central move is reification: an actor is never connected directly to a role. Every occupancy is its own timestamped, evidenced individual, pty:RoleOccupancy, with inRole required but occupiedBy optional — which is exactly the formal mechanism behind a role existing in a design from the outset while genuinely nobody occupies it yet. A pty:ParticipationGroup under a declared pty:CompositionRule expresses both "everyone capped independently at their own share" and "any one member liable for the full amount, with recourse against the others" using the same machinery — only the named rule differs. A pty:Delegation records that one occupancy performs what a different occupancy remains accountable for, without ever transferring the accountability itself.

Behaviour — state, and the line between reading it and changing it

A state-machine layer stacked on Eligibility (to gate transitions) and Instrument/Party (to change something), organised into declaration, occurrence, execution, and state-record tiers. Its single most important structural decision is the strict separation between a guard and an effect. A guard's only substantive property, bhv:requiresEligibility, points into Eligibility and nothing in the guard's own vocabulary is even capable of describing a write — there is no property path from "evaluate this guard" to "change something." An effect is the write side, targeting an Instrument element, a Party role occupancy, or an allowance balance, and every transition requires at least one. A failed or undetermined guard therefore cannot partially mutate anything: the mechanism that reads eligibility has no vocabulary for mutation to begin with. bhv:Sequential allowance absorption is usable today; bhv:Proportional absorption and reset edge cases are declared in the vocabulary and deliberately left inert, pending formal conservation laws and cross-profile test coverage that don't exist yet.

Instrument — the generic shape of a governing document

The shallowest substrate layer, deliberately: it gives "a governing document" — contract, policy, statute — its most generic possible shape without committing to what any particular document says. Everything hangs off ins:Element, of which ins:Provision (a structural grouping), ins:Obligation (a duty requiring at least one obligor and one obligee, reaching into Party for both), and ins:Qualifier (a constraint attachable to any element, including another qualifier) are disjoint kinds. Instrument borrows Party for who, Eligibility for under what condition, and Quantification for how much or by when — its own vocabulary stays confined to the skeleton those pieces hang on.

Literate specifications

Layer READMEs are not prose written about the ontology — they are its authoring surface. Fenced turtle-spec, turtle-vocab, and turtle-shapes blocks inside a layer's README are mechanically extracted, in document order, into the compiled files a reasoner would actually load; a turtle-example block is deliberately never extracted, so a README can show a worked case without it leaking into the compiled ontology. Run the drift check after changing a README, vocabulary, ontology, or shape file:

python3 tools/literate_extract.py ontology/surface/README.md \
  --layer surface --root . \
  --shapes shapes/structural.ttl shapes/constraints.ttl --check
Known doc bug: tools/README.md still documents this script's path as tools/lattice/literate_extract.py. No such directory exists — the real path is tools/literate_extract.py, exactly as used above and exactly as the CI workflow invokes it. This guide uses the correct path throughout.

3. Surface: making graph questions cheap

A correct graph can still be awkward to query. Surface creates a generated local restatement that makes a declared class of question answerable by direct lookup, while the source stays the only semantic authority. It declares three mechanisms now — Promotion, Indexing, and, since ADR-A17, Projection — and every one of them is required to record, alongside whatever it generates, exactly what it read to produce itself and how far it may be trusted. Conservativity is what makes an index and a promotion safe to discard and regenerate at will: an index mints only its own terms, so adding one entails nothing new about anything else. A promotion is the one case that isn't automatically conservative — restating a value onto a property another layer already declares produces triples indistinguishable from authored facts — which is why every generated surface records its srf:signatureScope (LocalSignature or SourceSignature) and why a promotion reaching an authored property must preserve exact meaning and be written out directly rather than left as a bare definition.

Promotion

Promotion reads a value at the end of a declared path and puts it directly on the carrier — turning three hops into one property read.

Subscription
  --hasPlan--> Plan
  --hasPricing--> Pricing
  --inCurrency--> EUR

becomes:
Subscription --subscriptionCurrency--> EUR

The complete, real contract — from ontology/surface/examples/saas-subscription-currency.ttl — declares the path as three ordered, directed steps rather than a SHACL property-path expression (whose blank-node labels don't survive canonicalisation cleanly):

ex:subscription-currency a srf:PromotionContract ;
    srf:contractKey "subscription-currency" ;
    srf:carrier saas:Subscription ;
    srf:hasPathStep ex:step-0, ex:step-1, ex:step-2 ;
    srf:promotesTo saas:subscriptionCurrency ;
    srf:sourceFidelity srf:ExactSource ;
    srf:realisationMode srf:Materialised ;
    srf:targetNamespace <https://example.org/saas/generated/> ;
    srf:surfaceProfile ex:default-profile .

ex:step-0 srf:stepIndex 0 ; srf:stepProperty saas:hasPlan    ; srf:stepDirection srf:Forward .
ex:step-1 srf:stepIndex 1 ; srf:stepProperty saas:hasPricing ; srf:stepDirection srf:Forward .
ex:step-2 srf:stepIndex 2 ; srf:stepProperty saas:inCurrency ; srf:stepDirection srf:Forward .

The sibling example, clinical-trial-crosswalk.ttl, promotes across two SKOS schemes via skos:closeMatch rather than a same-ontology property — an inexact crosswalk. That combination forces srf:sourceFidelity srf:CrosswalkInexact, which by law srf:X5 caps the resulting surface at Advisory authority and, by law srf:X6, forbids it from landing on an authored property at all — it must mint its own property in its own target namespace, so its derived nature stays visible in the IRI rather than masquerading as something the target vocabulary actually asserts.

Indexing

FormUse it whenRuntime shape
NominalClassPopulation is bounded and enumerable.?carrier a generated:ValueClass
MembershipAssertionAnswers must work without a reasoner.Materialised type assertions.
ClosureRelationAncestor retrieval is a real query.?carrier generated:matches ?ancestor
DirectPropertyValue space is open-ended or large — this is also what a promotion always emits.?carrier generated:property ?value

ontology/surface/examples/employment-job-family.ttl declares all three per-value forms together over a scheme-bound population: NominalClass and MembershipAssertion answer "is this assignment in Engineering," while ClosureRelation, built over skos:broader, answers "is this assignment in Engineering or anything narrower than it" without walking the taxonomy at query time. Past a few hundred values a per-value form becomes suspect on its own terms — a contract or profile declares a srf:populationBudget (falling back to 5000), exceeding it is a hard failure, and a warning fires past 500 regardless — which is exactly the point at which the guidance is to restructure as DirectProperty instead of raising the budget.

Hierarchical closure

Closure is declared, scoped, reflexive, and checked for cycles at generation time — SPARQL cannot express a property path over a variable predicate, so no fixed SHACL shape can verify acyclicity for an arbitrary declared basis; the generator traverses the declared basis over the declared scope itself and records the result as a law discharge (srf:R5). A job-family leaf can generate matches to itself and each in-scope ancestor, so runtime queries never walk the taxonomy live.

Fidelity and signature scope

Exact and exact-crosswalk source-signature promotions may target authored properties, but only when materialised — never definition-only, since an OWL axiom asserted onto an authored term isn't conservative under any reading. Lossy results are confined to a generated property in the contract's own namespace. This is law srf:X6, and it is the one place the original conservativity claim (srf:X1) needed a stated exception rather than being quietly bent.

Projection — the third mechanism Declaration lowers today · backend compilation partial

Promotion restates one value; indexing restates a value as a retrieval symbol. Neither covers intent that needs new graph structure, a computed value, evidence combined across more than one carrier, or one relation expanding into several — the case srf:ProjectionContract exists for, added by ADR-A17. Annual recurring revenue is the canonical illustration: it isn't reachable by any read path at all, because it's computed from two of the subscription's own properties rather than read off the end of a chain.

A projection contract names a srf:projectionKind — one of four, each covering a distinct shape of intent that promotion and indexing structurally cannot express:

KindCovers
GraphConstructionProjectionIntent that mints new graph structure not present as any single reachable value.
DerivationProjectionA value computed from one or more of the carrier's own source values.
JoinProjectionEvidence combined across more than one carrier.
ExpansionProjectionOne relation restated as several.

Rather than a free-text description of intent, a projection contract declares one or more srf:ProjectionRoleBinding individuals — the same discipline srf:PathStep applies to a read path, extended to intents with more than one moving part. Each binding carries a srf:roleKind: at most one EvaluationSubjectRole (the carrier instance being evaluated) and at most one ResultTargetRole (where the computed result is written) are required by every contract; RequiredEvidenceRole and CandidateEvidenceRole may repeat, and at least one is required for a join or derivation; ClosureBasisRole and TransformDependencyRole round out the vocabulary for the other two kinds.

The complete worked example, ontology/surface/examples/saas-subscription-arr-projection.ttl, computes ARR from two required-evidence bindings:

ex:arr-subject a srf:ProjectionRoleBinding ;
    srf:roleKind srf:EvaluationSubjectRole .

ex:arr-price a srf:ProjectionRoleBinding ;
    srf:roleKind srf:RequiredEvidenceRole ;
    srf:bindsProperty ex:hasMonthlyPrice .

ex:arr-frequency a srf:ProjectionRoleBinding ;
    srf:roleKind srf:RequiredEvidenceRole ;
    srf:bindsProperty ex:hasBillingPeriodsPerYear .

ex:arr-target a srf:ProjectionRoleBinding ;
    srf:roleKind srf:ResultTargetRole ;
    srf:bindsProperty ex:annualRecurringRevenue .

ex:arr-backend-policy a srf:ProjectionBackendPolicy ;
    srf:allowedBackend srf:SparqlBackend, srf:NativeIrBackend ;
    srf:deterministicOnly true ;
    srf:llmCompletionPolicy srf:NoLLMCompletion .

ex:subscription-arr-projection a srf:ProjectionContract ;
    srf:contractKey "subscription-arr" ;
    srf:carrier ex:Subscription ;
    srf:projectionKind srf:DerivationProjection ;
    srf:hasRoleBinding ex:arr-subject, ex:arr-price, ex:arr-frequency, ex:arr-target ;
    srf:realisationMode srf:Materialised ;
    srf:backendPolicy ex:arr-backend-policy .

Why it lowers into MORK instead of Surface growing a second compiler

Graph construction, derivation, joins, and expansion are mapping-graph concerns MORK already owns — it already models mork:DataMapping, mork:ShapeMapping, mork:RuleMapping, mork:QueryTemplate, and mork:ProjectionMapping, with parameter bindings, targeting specs, and provenance. Building a second compiler inside Surface would re-solve a problem MORK already solves, and hand authors a second vocabulary to learn for a need that is, in kind, the same as promotion and indexing: state intent against a carrier, generate an artefact, record what was generated and from what. So ADR-A18 draws the boundary precisely: Surface never emits an executable artefact — SPARQL, SHACL, SWRL, RML — directly for a projection contract. It lowers deterministically into MORK mapping nodes, which downstream compilers then target, and lowered output must satisfy MORK's own completeness checks before any backend compiles it — MORK validation is the acceptance gate, not a downstream nicety.

python3 -m surface lower \
    --contracts ontology/surface/examples/saas-subscription-arr-projection.ttl \
    --out ontology/surface/execution/subscription-arr-mapping.ttl
Honest status, straight from the example file's own comment: "The three non-domain examples this sibling to only need to resolve promotion and index contracts, so no compiler currently reads this file's contract." Lowering to a MORK mapping graph works today. A general backend compiler that turns a lowered projection mapping into SPARQL, SHACL, or SWRL does not exist yet — see §6 for exactly what does.

LLM participation is opt-in, gated, and never silent

Lowering could, in principle, use an LLM to complete a gap a contract leaves declarative — a missing parameter, an ambiguous role. Left unconstrained that threatens the determinism the staged compiler pipeline is built to guarantee, so ADR-A25 makes LLM output proposal-grade by default. A srf:ProjectionBackendPolicy declares srf:deterministicOnly (the recommended default for production, and what the ARR example sets) alongside a srf:llmCompletionPolicy — NoLLMCompletion, or BoundedLLMCompletion, under which an LLM may complete only declared gaps using a pre-approved template, never freely improvise. Law srf:P5 ties the two together: a policy declaring deterministicOnly true may name no completion policy other than NoLLMCompletion. Whatever an LLM does propose is materialised as explicit, visibly distinguishable graph nodes and requires governance sign-off before it may compile in production — the production gate rejects any mapping carrying an ungoverned LLM-originated node, regardless of how cleanly it otherwise validates.

4. Eligibility: conditions to decisions

Eligibility describes conditions, evidence, matching strategies, profiles, and decisions. It does not ask OWL to perform interval arithmetic or decide what missing evidence means — it is a small, closed algebra producing exactly one of three answers.

The full worked declaration, ontology/eligibility/examples/interval-containment.ttl, ties a qnt:ValueSpace through a closed qnt:Range to an elg:IntervalCondition:

ex:required-range a qnt:Range ; qnt:onSpace ex:credit-score-space ;
    qnt:lowerBound ex:lower-bound ; qnt:upperBound ex:upper-bound .
    # lower-bound = 700, Closed; upper-bound = 850, Closed

ex:minimum-credit-condition
    a elg:IntervalCondition ;
    elg:matchStrategy elg:IntervalContainment ;
    elg:compatibilityOperation elg:AllRequired ;
    elg:wildcardSemantics elg:NoWildcard ;
    elg:requiredRangeSet ex:required-rangeset .

ex:question-1
    a elg:Question ;
    elg:forCondition ex:minimum-credit-condition ;
    elg:candidateRangeSet ex:required-rangeset .

ex:decision-1
    a elg:EligibilityDecision ;
    elg:forProfile ex:profile-a ;
    elg:hasQuestion ex:question-1 ;
    elg:decisionValue elg:Permitted .
OutcomeMeaning
PermittedEvidence demonstrates satisfaction.
DeniedEvidence demonstrates failure.
UndeterminedEvidence is missing, malformed, incompatible, or insufficient.
Required:  [700, 850]
Candidate: [720, 810]   => Permitted
Candidate: [650, 810]   => Denied
Candidate: missing      => Undetermined

A second worked example, ontology/eligibility/examples/hierarchical-match.ttl, matches a peril candidate against a SKOS taxonomy (industrial-fire narrower than fire, narrower than peril) under elg:HierarchicalMatch — valid exactly when a candidate stands in the reflexive-transitive closure of the bound scheme's ordering relation, restricted to that scheme's own members; a scheme whose ordering relation contains a cycle simply isn't evaluable under this strategy at all. That closure may be computed by live traversal at query time, or answered from a Surface-generated ClosureRelation index (§3) — Eligibility doesn't care which, as long as the two agree; this is realisation-strategy neutrality showing up concretely. A third file, ontology/eligibility/examples/condition-taxonomy.ttl, is a compact side-by-side reference of all four admitted match strategies — ExactMatch, SetMembership, IntervalContainment, Wildcard — each paired with its own compatibility operation and wildcard policy, since every condition declares all three independently rather than any being implied by a condition's subtype. IntervalOverlap is deliberately excluded as an admissibility strategy: two ranges can each overlap a third separately while having no three-way intersection at all, and Eligibility only admits strategies with settled, closed-form semantics.

5. MORK: the machine-facing mapping graph

MORK is designed for machines, mapping agents, and LLMs. It records mapping intent in a form that can be reviewed, versioned, governed, and compiled deterministically — every mapping is a first-class graph object rather than an opaque script, so it can be queried, composed, scored, and audited back to whatever evidence justified it.

MappingProduces
DataMappingGeneral mapping intent and dependency anchor.
ShapeMappingStructured SHACL definitions.
RuleMappingStructured SWRL implications.
TransformMappingRML or R2RML transformations.
ProjectionMappingGenerated lookup-surface class definitions and provenance.
QueryTemplateParameterized query artefacts.

Surface is the authoring layer; MORK is the expanded machine graph. Domain authors normally declare semantic roles and backend policy in Surface rather than hand-writing parameter bindings, precedence edges, and structured rule atoms — the same relief promotion and indexing already give for the simpler cases.

Compiling a real condition, backend by backend

A newer package, tools/mork_compilers/, takes an elg:IntervalCondition and its Quantification range through a shared, backend-neutral IntervalPlan, then fans it out to three separate backend adapters. Against ontology/eligibility/examples/interval-containment.ttl's credit-score condition, here is exactly what each backend actually emits:

python3 -m mork_compilers.cli compile-condition \
    --declarations ontology/eligibility/examples/interval-containment.ttl \
    --condition https://example.org/lattice/eligibility/minimum-credit-condition \
    --backend sparql --out artefact.ttl
Package entry point: invoke the installed compiler family with python3 -m mork_compilers.cli compile-condition, exactly as above.

SPARQL compiles to one parameterized SELECT, binding ?decision to the string "Permitted", "Denied", or "Undetermined" — never a silent denial for missing evidence:

SELECT ?question ?decision WHERE {
  ?question elg:forCondition <...minimum-credit-condition> .
  OPTIONAL {
    ?question elg:candidateRangeSet ?candidateRangeSet .
    ?candidateRangeSet qnt:hasRange ?candidateRange .
    ?candidateRange qnt:lowerBound/qnt:boundValue/qnt:numericValue ?candLower .
    ?candidateRange qnt:upperBound/qnt:boundValue/qnt:numericValue ?candUpper .
  }
  BIND(
    IF(!BOUND(?candLower) || !BOUND(?candUpper), "Undetermined",
       IF((?candLower >= 700.0 && ?candUpper <= 850.0), "Permitted", "Denied")
    ) AS ?decision
  )
}

SHACL compiles to two SPARQL-based node shapes scoped per-condition — a readiness shape (does the Question even have candidate evidence?) and a containment shape (is the candidate contained in the required interval?) — rather than one blanket shape targeting every elg:Question in the graph. SWRL compiles to one structured swrl:Imp per required interval, built from real RDF atom lists (ClassAtom, IndividualPropertyAtom, BuiltinAtom using swrlb:greaterThanOrEqual/lessThanOrEqual), positive-only by design: the consequent only ever asserts elg:Permitted, never Denied or Undetermined, because SWRL's monotonic entailment has no sound way to conclude a negative from absent evidence.

All four artefacts — the query template, the two shapes, the rule — trace back through one exe:IntervalContainmentPlan provenance node to every Eligibility and Quantification node the compiler actually consumed, so a reviewer can walk from a generated SPARQL query all the way back to the declared range that produced it.

BackendStatus
SPARQLImplemented for elg:IntervalCondition, via tools/mork_compilers.
SHACLImplemented for elg:IntervalCondition, via tools/mork_compilers.
SWRLImplemented for elg:IntervalCondition, positive-only, via tools/mork_compilers.
RMLSeparate path — handled by the pre-existing tools/mork2rml.py for TransformMapping, a different problem (ingesting external representations) from compiling an Eligibility condition.
Native execution planNot built — no document in this repository defines what a native artefact would be, so none exists and none is planned pending an actual definition.

Governance catches up with the compiler family

A companion change makes mork:GenerativeMapping — and by subsumption every ShapeMapping, RuleMapping, TransformMapping, and ProjectionMapping — Foundation-aligned, adding fnd:Governable and fnd:Version as further superclasses, and introduces mork:CompilationMode (DraftMode, ReviewMode, ProductionMode, unset defaulting to draft) on a MappingScheme. A production-mode gate shape now requires governance state beyond Draft, plus a recorded identity, before a mapping scheme opted into ProductionMode may compile — additive throughout, so nothing existing fails merely by continuing to exist; the gate only fires for a scheme that explicitly opts in, and nothing does yet. The same change quietly closes an old open item: the four fnd:GovernanceState named individuals (Draft/Reviewed/Active/Superseded) — long referenced but never actually declared — are now backfilled into ontology/foundation/vocab/foundation-vocab.ttl.

6. Compilation

validate -> normalise -> lower -> compile -> emit -> record provenance

Every backend compiler runs this same staged pipeline (ADR-A19). A backend is an adapter attached after the shared validate/normalise/lower stages, not a parallel reimplementation of them — determinism (identical input graph and profile identity producing identical output) is a property of the shared stages, checked once, rather than re-verified per backend. Adding a new backend means writing a compile/emit adapter against a stable intermediate model, not re-deriving parameter resolution or dependency ordering from scratch.

SPARQL

Use SPARQL for complete decisions, diagnostics, missing-data distinction, and profile aggregation.

SHACL

Use SHACL for readiness and validation reports. A validation report still needs an adapter before it becomes a three-valued Eligibility decision.

SWRL

Use SWRL for positive monotonic classification. It must not infer denial or indeterminacy from absence of evidence.

RML

Use RML for mapping external representations into RDF aligned with a target ontology. The mapping remains inspectable before transformation.

Same validated graph plus same compiler profile should produce the same semantic hashes, dependency order, identifiers, and artefact content — tools/mork_compilers's own dependency-ordering and range-sorting both sort by IRI specifically so the same declaration graph yields the same order regardless of triple-parse order. Generated outputs point back to their source contracts, mappings, and declaration nodes, never the other way round.

Scope, honestly stated: today's SPARQL/SHACL/SWRL compilers target elg:IntervalCondition specifically, not an arbitrary lowered Surface ProjectionContract. The staged pipeline and the shared intermediate-representation pattern are built to generalise — that's the whole point of keeping stage boundaries explicit — but the generalisation itself hasn't happened yet.

7. Applied domains

Two applied domains currently sit on top of the substrate, at genuinely different levels of maturity, and reading them side by side says more about how this framework is meant to grow than either one does alone.

Capacity — a generic finite-resource model

ontology/applied/capacity/ generalises a pattern that shows up under a dozen different names across industries: a finite resource with a quantity, a balance, consumption by demand, a possible reset, eligibility gating, and typed relations to other resources (one resource depleting, bounding, or gating another). Insurance limits and towers are one instance; so are lending facilities, SaaS quotas, credit lines, inventory allocation, grant budgets, emissions allowances, and access entitlements. Behaviour already has a generic single-resource allowance mechanism (bhv:AllowanceDefinition/bhv:AllowanceAccount), but nothing models a network of interacting resources — Capacity exists to develop that vocabulary as a staged applied domain before it's proven generic enough to promote into Behaviour proper, against an explicit "Behaviour promotion protocol" of genericity, multi-domain, formal-law, architectural, and governance tests.

It builds on Foundation, Vocabulary, Quantification, Party, Eligibility, Behaviour, and Instrument — deliberately not Surface — and its namespace is explicitly barred from referencing any existing insurance-specific vocabulary, so the generic model can't quietly absorb domain assumptions from the one industry that happens to be driving it. A companion performance document, ontology/applied/capacity/docs/perf.md, states the reason a second, compact capx: execution vocabulary exists alongside the richer source model at all: high-throughput transaction paths (admission, allocation, reset, ordering) can't tolerate the unbounded joins a fully general graph traversal would need at request time, so the execution projection is a bounded-hop, O(1)-lookup runtime shape distilled from the general model — the same "restate for cheap lookup, keep the source authoritative" discipline Surface applies elsewhere, worked out by hand for a domain whose hot path is too latency-sensitive to wait for a generic mechanism.

As of today, only that compact execution projection is actually authored (ontology/applied/capacity/spec/); the richer, generic source ontology the architecture document proposes hasn't been written yet, and there is no shapes/, vocab/, projection/, ontology/examples/, or test/ directory under ontology/applied/capacity/ yet either.

Insurance — staged, not yet integrated

ontology/applied/insurance/ is a substantial hand port of an existing insurance contract-structure ontology — ctr:Contract, ctr:ContractElement, ctr:TermApplication, and a family of ctr:Facet subtypes (limit, retention, defense cost, trigger, collateral, reinstatement, and more). It is genuinely large and detailed. It is also, as of today, not wired into the rest of this framework: its projection/ and execution/ directories exist but are empty, and its ontology header still imports a namespace outside the LATTICE tree (ontology/nsd/core, ontology/nsd/skos) rather than composing with Party, Eligibility, and Behaviour through a declared contract. The Capacity architecture document lays out a planned "Phase 4" refactor — insurance limits and towers becoming domain-specialised subclasses of the generic Capacity vocabulary (ins:InsuranceCapacityResource ⊑ cap:CapacityResource, and similarly for demand and draw) — but that refactor hasn't started; a placeholder ontology/applied/insurance/spec/capacity/ directory exists and is empty, reserved for exactly this.

The honest read: a domain being "applied" and a domain being "composed with the substrate" are two different milestones, and a domain can sit at the first without having reached the second. Capacity, ironically the newer of the two, is closer to substrate-clean composition than Insurance is, precisely because it was designed from the outset against the same layering discipline the rest of this guide describes.

8. Worked examples

Every example below exists as a real, complete file in this repository — nothing here is illustrative pseudocode.

Job-family index

A scheme-bound population, nominal classes, membership assertions, and closure make ancestor retrieval direct.

python3 -m surface compile \
  --contracts ontology/surface/examples/employment-job-family.ttl \
  --out ontology/surface/execution \
  --verify-determinism --parity

Multi-hop currency promotion

An exact path from Subscription through Plan and Pricing becomes a direct currency property without enumerating the currency space.

python3 -m surface parity \
  --contracts ontology/surface/examples/saas-subscription-currency.ttl

Inexact crosswalk

The clinical example crosses an inexact relation. The result remains queryable but is advisory and cannot masquerade as authored target-vocabulary truth.

Annual recurring revenue (projection)

The ARR example binds two required-evidence properties and a result target, then lowers a derivation intent into MORK — see §3 for the complete declaration and current compilation status.

python3 -m surface lower \
  --contracts ontology/surface/examples/saas-subscription-arr-projection.ttl \
  --out ontology/surface/execution/subscription-arr-mapping.ttl

Eligibility compilation

The credit-score interval condition, compiled to a real SPARQL query, two SHACL shapes, and a SWRL rule — see §5.

python3 -m mork_compilers.cli compile-condition \
  --declarations ontology/eligibility/examples/interval-containment.ttl \
  --condition https://example.org/lattice/eligibility/minimum-credit-condition \
  --backend sparql --out /tmp/eligibility-sparql.ttl

Hierarchical match and the condition taxonomy

ontology/eligibility/examples/hierarchical-match.ttl matches a peril candidate against a SKOS taxonomy; ontology/eligibility/examples/condition-taxonomy.ttl lays out all four admitted match strategies side by side as a single reference fixture.

Two things worth knowing before you go looking: the four cross-layer composition placeholders under root ontology/examples/ (employment.ttl, lending-covenant.ttl, saas-subscription.ttl, clinical-trial.ttl) are still empty — zero bytes each. And a separate, substantial ontology/examples/insure-o/ package (spec, shapes, vocab, projection, cases, test — about twenty files) exists independently of ontology/applied/insurance/ described in §7; the two are not the same thing, and which one is current for insurance-domain work is worth confirming with whoever's driving that port before building on either.

9. Operating the system

Install

python3 -m venv .venv
. .venv/bin/activate
python -m pip install -c requirements-lock.txt ".[reasoning]"

Requires Python 3.14+ (pyproject.toml's stated floor, raised on 2026-09-23 so every tool shares Unicode 16.0 data); CI validates against 3.14. Core runtime dependencies are rdflib and pyshacl, with owlrl (the OWL/RDFS reasoner) pulled in by the [reasoning] extra used above. A separate [community] extra (numpy, networkx, pydantic, langchain-core, langgraph) is for MORK's community-detection tooling specifically — install it only when working on that, not for ordinary validation.

Validate

python3 -m unittest surface.test_surface mork_compilers.test_mork_compilers -v
python3 -m tools.phase8_conformance

The conformance gate does three things in sequence: SHACL-validates a MORK governance-and-versioning example against ontology/mork/shapes/constraints.ttl; compiles SHACL shapes from the credit-score Eligibility condition and validates its own data against them; and runs the shared Surface parity suite against test/conformance/manifest.ttl, failing loudly if that manifest resolves to zero cases. Three further Python test modules live under tools/mork/src/ and are picked up by pytest's configured test paths, though CI itself currently runs only the two unittest suites shown above plus the conformance gate.

CI is manual-dispatch only, for now. The single workflow, .github/workflows/phase8-conformance.yml, triggers on workflow_dispatch — someone has to press the button — while the repository's package-management workflow stabilises. It is not yet wired to run automatically on every push or pull request.

Freshness and regeneration

Generated surfaces record read-set hashes. Source, mapping, artefact, profile, and canonicalisation changes are planned through dependency-scoped invalidation. Profile and canonicalisation changes intentionally widen the regeneration scope.

ChangeScope
Instance assertionAffected materialised assertions.
Scheme membershipContract inventory and dependent closure.
Projection contractLowered mapping and dependent mappings.
ProfileAll artefacts using the profile.
CanonicalisationFull estate rehash and regeneration.
python3 -m surface check \
  --manifest ontology/surface/execution/job-family/manifest.ttl \
  --contracts ontology/surface/examples/employment-job-family.ttl

10. Boundaries and design choices

Surface is not the whole semantic compiler. Promotion and indexing do not by themselves create arbitrary domain nodes, perform unrestricted multi-source derivation, resolve datatype literals through concept joins, or expand one source node into coordinated behavioural nodes — that's now Projection's job, and Projection itself does not emit an executable artefact; it lowers into MORK and stops there until a backend compiler picks the result up.

A full CSO-to-FBO-style compiler must create tanks, actions, qualifiers, claims, and spans, and must enforce projection completeness. Surface surrounds that compiler with exact restatement, indexing, closure, governed acceleration, freshness, and invalidation — it accelerates a graph that's already meaningful; it does not make an under-specified one operationally evaluable.

Intentional deferred capabilities include RangePartitionPopulation, stacking beyond depth one, ExternalIndex, richer entailment regimes, profile-level Eligibility results, a native execution-plan backend, and full CSO-to-FBO semantic compilation. To that list, Projection adds its own: a general backend compiler for lowered projection mappings (today's SPARQL/SHACL/SWRL compilers target Eligibility's IntervalCondition specifically), and cross-batch dependency resolution during lowering (a role binding's crosswalk match relation, or a bound scheme contract, is not yet followed into a mork:dependsOnMapping edge — only same-batch carrier and property references are).

11. Roadmap

  1. Resolve the Foundation boundary for generic derived artefacts — srf:DerivedArtefact and its siblings still live in Surface pending a decision on whether they migrate to a Foundation-level contract that MORK's own artefacts could then share.
  2. Generalise the SPARQL/SHACL/SWRL compiler family beyond elg:IntervalCondition to arbitrary lowered ProjectionContract mappings, closing the gap §6 and §10 both flag.
  3. Define — or formally retire — the native execution-plan backend; the vocabulary (srf:NativeIrBackend) still exists with no compiler and no definition behind it.
  4. Move CI from manual dispatch to running automatically on push/PR, once the package-management workflow it's waiting on stabilises.
  5. Keep tools/README.md aligned with the installed surface package and its lower subcommand.
  6. Carry the Capacity architecture's planned "Phase 4" refactor through: rebuild ontology/applied/insurance/'s limits and towers as specialisations of ontology/applied/capacity/, dropping its non-LATTICE namespace imports in the process.
  7. Author the generic ontology/applied/capacity/ source ontology the architecture document proposes — today only the compact execution projection exists.
  8. Resolve cross-batch dependency edges during Surface-to-MORK lowering (crosswalk match relations, bound scheme contracts) that today are silently left unresolved.
  9. Add broader Projection and Behaviour conformance cases, and extend test/conformance/manifest.ttl as new mechanisms land.
Definition of done: an author declares intent — promoted, indexed, or projected — without touching raw MORK internals; every generated artefact is governed and traceable back to its source; and a stale output regenerates without rebuilding any unrelated part of the estate.

12. Further reading

This guide is deliberately example-driven and narrow to what's actually built. For the fuller conceptual backdrop — how a semantic graph works from first principles, what every layer's terms mean, and how the pieces relate to each other — see the LATTICE glossary. For the record of every architectural decision named above, including the ones this guide only summarises, see the ADR index. For the mechanics of literate specification and the drift-checking discipline that keeps a README and its compiled ontology honest, see the architecture notes.