|
| 1 | +# Architecture: knowledgecomplex |
| 2 | + |
| 3 | +## The 2×2 Responsibility Map |
| 4 | + |
| 5 | +This is the central architectural constraint. Every rule in the system belongs to exactly one cell. The Python package exists to hide this table from the user. |
| 6 | + |
| 7 | +| | **OWL** | **SHACL** | |
| 8 | +|---|---|---| |
| 9 | +| **Topological** | `kc:Element` base class; `kc:Vertex`, `kc:Edge`, `kc:Face` as subclasses. `kc:Edge` has exactly 2 `boundedBy` (Vertex); `kc:Face` has exactly 3 `boundedBy` (Edge). `kc:Complex` as collection of elements via `kc:hasElement`. | Boundary vertices are distinct; boundary edges of a face form a closed triangle; boundary-closure of a complex (all instance-level; require `sh:sparql`) | |
| 10 | +| **Ontological** | Concrete subclasses and their allowed attributes; property domain/range declarations | Controlled vocabulary enforcement (e.g. `status ∈ {passing, failing, pending}`); attribute presence rules; co-occurrence constraints | |
| 11 | + |
| 12 | +### Why Both OWL and SHACL at Each Layer |
| 13 | + |
| 14 | +**Topological layer:** OWL cardinality axioms enforce structural counts at the class level (reasoning over schema). SHACL is required for the closed-triangle constraint because OWL cannot express a constraint that references the *co-values* of three different property assertions on the same individual — this is a known expressivity boundary of OWL-DL. The `sh:sparql` constraint in `kc_core_shapes.ttl` is the explicit test of this boundary. |
| 15 | + |
| 16 | +**Ontological layer:** OWL defines what attributes a concrete type *has* (property declarations, domain, range, subclass hierarchy). SHACL defines what values those attributes *must have* at the instance level (vocabulary constraints, cardinality on the concrete shape, required/optional). OWL cannot enforce controlled vocabulary on data properties at the instance level without enumerating individuals, which is inappropriate for string-valued attributes. |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## Component Layers |
| 21 | + |
| 22 | +``` |
| 23 | +┌─────────────────────────────────────────────────────┐ |
| 24 | +│ Application / Demo (user code) │ |
| 25 | +│ build_my_instance() | domain-specific queries │ |
| 26 | +│ Concrete elements: vertices, edges, faces │ |
| 27 | +├─────────────────────────────────────────────────────┤ |
| 28 | +│ Domain Model (user code) │ |
| 29 | +│ build_my_schema() | domain SPARQL templates │ |
| 30 | +│ MyVertex, MyEdge, MyFace type definitions │ |
| 31 | +├─────────────────────────────────────────────────────┤ |
| 32 | +│ knowledgecomplex Python Package │ |
| 33 | +│ SchemaBuilder DSL | KnowledgeComplex I/O │ |
| 34 | +│ (OWL + SHACL emit) | (rdflib graph + SPARQL) │ |
| 35 | +├──────────────────────┬──────────────────────────────┤ |
| 36 | +│ kc_core.ttl │ kc_core_shapes.ttl │ |
| 37 | +│ (abstract OWL) │ (abstract SHACL) │ |
| 38 | +├──────────────────────┴──────────────────────────────┤ |
| 39 | +│ rdflib | pyshacl | owlrl │ |
| 40 | +└─────────────────────────────────────────────────────┘ |
| 41 | +``` |
| 42 | + |
| 43 | +The **domain model** layer sits between the core framework and the application. It defines domain-specific types and queries using the core's `SchemaBuilder` DSL. The application layer then instantiates that model with concrete data. |
| 44 | + |
| 45 | +The static resources (`kc_core.ttl`, `kc_core_shapes.ttl`) are loaded once at `SchemaBuilder.__init__`. Model schema and shapes are merged into the same rdflib `Graph` objects at runtime. |
| 46 | + |
| 47 | +--- |
| 48 | + |
| 49 | +## Abstraction Boundary: Core vs. Domain Models |
| 50 | + |
| 51 | +The layers above are separated by a key abstraction boundary: the **core framework** (`knowledgecomplex/`) vs. **domain models** (user code). Everything inside the package boundary (core, static resources, libraries) is framework-owned and invariant. Everything outside (model definitions, instances) is user-authored. |
| 52 | + |
| 53 | +### Core Framework (`knowledgecomplex/`, prefixes `kc:` and `kcs:`) |
| 54 | + |
| 55 | +- **Topological rule enforcement.** The Element/Vertex/Edge/Face hierarchy, cardinality axioms, distinctness, closed-triangle, and boundary-closure constraints. Static OWL and SHACL shipped with the package. Users cannot modify them. |
| 56 | +- **Superstructure attributes.** `kc:uri` (optional, at-most-one) allows any element to reference a source file. Enforced by `kcs:ElementShape`. |
| 57 | +- **Ontological rule authoring.** `SchemaBuilder` provides the DSL for declaring types, attributes, and vocabularies. It *generates* OWL classes and SHACL shapes on behalf of the domain model but does not itself define any domain types. |
| 58 | +- **Instance management.** `KnowledgeComplex` loads the merged schema, manages the RDF graph, validates on every write, and executes named SPARQL queries. |
| 59 | +- **Framework queries.** Generic SPARQL templates (`vertices`, `coboundary`) that work for any domain model. |
| 60 | + |
| 61 | +### Domain Models (user code, prefix `{namespace}:`) |
| 62 | + |
| 63 | +- **Ontological rule enforcement.** The concrete OWL types and SHACL shapes generated by calling `SchemaBuilder.add_*_type()`. |
| 64 | +- **Concrete complex authoring.** Instance data constructed via `KnowledgeComplex.add_*()` calls. |
| 65 | +- **Domain queries.** Model-specific SPARQL templates. |
| 66 | + |
| 67 | +### The Type Inheritance Chain Crosses the Boundary |
| 68 | + |
| 69 | +``` |
| 70 | +kc:Element → kc:Vertex → aaa:spec → (instance "spec-001") |
| 71 | + core core model application |
| 72 | +``` |
| 73 | + |
| 74 | +The core owns `Element → Vertex`; the model owns `Vertex → spec`; the application owns the instance `spec-001`. The boundary is at the subclass declaration — `add_vertex_type("spec")` is the model calling the core's authoring API to extend the core's type hierarchy. |
| 75 | + |
| 76 | +### Layer Ownership of the 2×2 Map |
| 77 | + |
| 78 | +| | **OWL** | **SHACL** | |
| 79 | +|---|---|---| |
| 80 | +| **Topological** | Core owns (static `kc_core.ttl`) | Core owns (static `kc_core_shapes.ttl`) | |
| 81 | +| **Ontological** | Domain model authors via `SchemaBuilder` → core generates | Domain model authors via `vocab()`/attributes → core generates | |
| 82 | + |
| 83 | +Both ontological cells are *authored* by the domain model but *generated and managed* by the core. The domain model never touches OWL or SHACL directly. |
| 84 | + |
| 85 | +--- |
| 86 | + |
| 87 | +## Key Design Decisions |
| 88 | + |
| 89 | +### DD1: Attributes over Subclasses (for Simple Domains) |
| 90 | + |
| 91 | +For simple domain models, a single concrete type with a controlled-vocabulary attribute (e.g. `verification` with `status ∈ {passing, failing, pending}`) is preferred over two subclasses (`PassingVerification`, `FailingVerification`). The framework supports both patterns. |
| 92 | + |
| 93 | +**Rationale:** The single-type-with-attribute pattern makes the data more inspectable before schema-level concerns are promoted. `promote_to_attribute()` supports the transition path from untyped patterns to typed attributes. |
| 94 | + |
| 95 | +### DD2: SPARQL Templates, Not Free Queries |
| 96 | + |
| 97 | +All SPARQL is encapsulated as named template files. `KnowledgeComplex.query()` accepts only registered template names. |
| 98 | + |
| 99 | +**Rationale:** Maintains API opacity, prevents arbitrary SPARQL from bypassing validation invariants, and makes the query surface explicit and testable. |
| 100 | + |
| 101 | +### DD3: Validation on Write |
| 102 | + |
| 103 | +`add_vertex()`, `add_edge()`, and `add_face()` each trigger SHACL validation immediately and raise `ValidationError` on failure. Rollback removes all added triples on failure. |
| 104 | + |
| 105 | +**Rationale:** Fail fast; keep the graph in a valid state at all times. Verification is not a batch post-processing step — it is enforced at assertion time. |
| 106 | + |
| 107 | +### DD4: Static Core Resources |
| 108 | + |
| 109 | +`kc_core.ttl` and `kc_core_shapes.ttl` are static files shipped with the package, not generated at runtime. |
| 110 | + |
| 111 | +**Rationale:** The topological rules are framework invariants, not user-configurable. Separating them from user schema makes the 2×2 boundary visible in the file system. |
| 112 | + |
| 113 | +### DD5: `dump_owl()` and `dump_shacl()` Merge Core and User Schema |
| 114 | + |
| 115 | +Both dump methods return the full merged graph (core + user-defined), serialized as Turtle. |
| 116 | + |
| 117 | +**Rationale:** The merged graph is what `pyshacl` and `owlrl` operate on. Showing the full graph makes the system inspectable and demonstrates that user types genuinely extend (not replace) the core ontology. |
| 118 | + |
| 119 | +### DD6: Shared-Domain Removal (`_set_owl_domain`) |
| 120 | + |
| 121 | +When the same property name appears on multiple types, the OWL `rdfs:domain` assertion is removed (leaving no domain) rather than adding multiple domain values. SHACL shapes still enforce per-type constraints correctly via each type's `NodeShape`. |
| 122 | + |
| 123 | +**Rationale:** Multiple `rdfs:domain` values trigger RDFS inference to classify any individual with that property as a member of *all* domain types — violating the type hierarchy. Removing domain resolves the conflict; SHACL handles the per-type enforcement. |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## Known OWL Expressivity Limits (Design Seams) |
| 128 | + |
| 129 | +| Constraint | OWL can express? | Resolution | |
| 130 | +|---|---|---| |
| 131 | +| Edge has exactly 2 boundary vertices | Yes (cardinality on `boundedBy`) | OWL cardinality axiom | |
| 132 | +| Face has exactly 3 boundary edges | Yes (cardinality on `boundedBy`) | OWL cardinality axiom | |
| 133 | +| Boundary vertices are distinct individuals | No (OWL open-world; same-as/different-from is individual-level) | SHACL `sh:sparql` (COUNT DISTINCT) | |
| 134 | +| Boundary edges of a face form a closed triangle | No (requires co-reference across 3 property values) | SHACL `sh:sparql` constraint | |
| 135 | +| Boundary-closure of a complex | No (requires co-reference across `hasElement` and `boundedBy` on different individuals) | SHACL `sh:sparql` constraint | |
| 136 | +| Controlled vocabulary on data property | No (without `owl:oneOf` on individuals, impractical for strings) | SHACL `sh:in` | |
| 137 | +| At-most-one `kc:uri` per element | Not enforced practically (open-world) | SHACL `sh:maxCount 1` in `ElementShape` | |
| 138 | + |
| 139 | +These seams are documented as comments in the relevant `.ttl` files. |
| 140 | + |
| 141 | +--- |
| 142 | + |
| 143 | +## Namespace Conventions |
| 144 | + |
| 145 | +```turtle |
| 146 | +@prefix kc: <https://example.org/kc#> . # core framework |
| 147 | +@prefix kcs: <https://example.org/kc/shape#> . # core shapes |
| 148 | +@prefix aaa: <https://example.org/aaa#> . # user namespace (example) |
| 149 | +@prefix aaas: <https://example.org/aaa/shape#> .# user shapes (example) |
| 150 | +``` |
| 151 | + |
| 152 | +User namespaces are set via `SchemaBuilder(namespace="aaa")`. The URI base `https://example.org/` is a placeholder for local development; a real deployment would use a dereferenceable IRI. |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +## File Inventory |
| 157 | + |
| 158 | +| File | Layer | Purpose | |
| 159 | +|---|---|---| |
| 160 | +| `knowledgecomplex/resources/kc_core.ttl` | Abstract OWL | Topological backbone: classes, properties, cardinality axioms, `kc:uri` | |
| 161 | +| `knowledgecomplex/resources/kc_core_shapes.ttl` | Abstract SHACL | Topological constraints: distinctness, closed-triangle, boundary-closure, `kc:uri` at-most-one | |
| 162 | +| `knowledgecomplex/schema.py` | Python API — schema authoring | `SchemaBuilder` DSL: `add_*_type`, `dump_owl`, `dump_shacl`, `export`, `load` | |
| 163 | +| `knowledgecomplex/graph.py` | Python API — instance I/O | `KnowledgeComplex`: `add_vertex`, `add_edge`, `add_face`, `query`, `dump_graph`, `export`, `load` | |
| 164 | +| `knowledgecomplex/exceptions.py` | Public exceptions | `ValidationError`, `SchemaError`, `UnknownQueryError` | |
| 165 | +| `knowledgecomplex/queries/vertices.sparql` | Framework SPARQL | Return all vertices and their types | |
| 166 | +| `knowledgecomplex/queries/coboundary.sparql` | Framework SPARQL | Inverse boundary operator | |
0 commit comments