kapps-semantic-middleware 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. kapps_semantic_middleware/AGENTS.md +170 -0
  2. kapps_semantic_middleware/CONTEXT-MAP.md +59 -0
  3. kapps_semantic_middleware/CONTEXT.md +349 -0
  4. kapps_semantic_middleware/__init__.py +38 -0
  5. kapps_semantic_middleware/activity.py +384 -0
  6. kapps_semantic_middleware/connectors/__init__.py +42 -0
  7. kapps_semantic_middleware/connectors/knowledge_graph_connector.py +140 -0
  8. kapps_semantic_middleware/connectors/mqtt_binding.py +371 -0
  9. kapps_semantic_middleware/connectors/rest_binding.py +437 -0
  10. kapps_semantic_middleware/connectors/semantic.py +346 -0
  11. kapps_semantic_middleware/connectors/wiring.py +506 -0
  12. kapps_semantic_middleware/credentials.py +90 -0
  13. kapps_semantic_middleware/demonstrations/__init__.py +9 -0
  14. kapps_semantic_middleware/demonstrations/transferunits/CONTEXT.md +77 -0
  15. kapps_semantic_middleware/demonstrations/transferunits/README.md +220 -0
  16. kapps_semantic_middleware/demonstrations/transferunits/__init__.py +24 -0
  17. kapps_semantic_middleware/demonstrations/transferunits/__main__.py +43 -0
  18. kapps_semantic_middleware/demonstrations/transferunits/algorithm.py +398 -0
  19. kapps_semantic_middleware/demonstrations/transferunits/control_station.py +206 -0
  20. kapps_semantic_middleware/demonstrations/transferunits/controller.py +1105 -0
  21. kapps_semantic_middleware/demonstrations/transferunits/factory.ttl +16 -0
  22. kapps_semantic_middleware/demonstrations/transferunits/index.py +170 -0
  23. kapps_semantic_middleware/demonstrations/transferunits/launcher.py +485 -0
  24. kapps_semantic_middleware/demonstrations/transferunits/middleware.py +187 -0
  25. kapps_semantic_middleware/demonstrations/transferunits/plc/__init__.py +10 -0
  26. kapps_semantic_middleware/demonstrations/transferunits/plc/__main__.py +87 -0
  27. kapps_semantic_middleware/demonstrations/transferunits/plc/panel.py +202 -0
  28. kapps_semantic_middleware/demonstrations/transferunits/plc/static/transferunit.svg +99 -0
  29. kapps_semantic_middleware/demonstrations/transferunits/plc/templates/panel.html +181 -0
  30. kapps_semantic_middleware/demonstrations/transferunits/plc/transfer_unit.py +449 -0
  31. kapps_semantic_middleware/demonstrations/transferunits/seed.py +259 -0
  32. kapps_semantic_middleware/demonstrations/transferunits/station_board.py +631 -0
  33. kapps_semantic_middleware/demonstrations/transferunits/templates/index.html +311 -0
  34. kapps_semantic_middleware/demonstrations/transferunits/templates/station_board.html +404 -0
  35. kapps_semantic_middleware/demonstrations/transferunits/transferunit.ttl +114 -0
  36. kapps_semantic_middleware/docs/mechanics/01-instantiation-and-lifecycle.md +75 -0
  37. kapps_semantic_middleware/docs/mechanics/02-workflow-registration.md +103 -0
  38. kapps_semantic_middleware/docs/mechanics/03-state-and-parameters.md +79 -0
  39. kapps_semantic_middleware/docs/mechanics/04-connector-binding.md +131 -0
  40. kapps_semantic_middleware/docs/mechanics/05-operation-coordination.md +108 -0
  41. kapps_semantic_middleware/docs/mechanics/06-views-and-projection.md +86 -0
  42. kapps_semantic_middleware/docs/mechanics/07-writing-to-the-graph-and-to-devices.md +48 -0
  43. kapps_semantic_middleware/docs/mechanics/08-provisioning-and-seeding.md +109 -0
  44. kapps_semantic_middleware/docs/mqtt-payloads.md +98 -0
  45. kapps_semantic_middleware/examples/CONTEXT.md +45 -0
  46. kapps_semantic_middleware/examples/__init__.py +8 -0
  47. kapps_semantic_middleware/examples/demo_handover.ttl +42 -0
  48. kapps_semantic_middleware/examples/demo_scenario1.ttl +66 -0
  49. kapps_semantic_middleware/examples/demo_scenario2.ttl +75 -0
  50. kapps_semantic_middleware/examples/docker/docker-compose.yml +36 -0
  51. kapps_semantic_middleware/examples/docker/graphdb-repo-config.ttl +74 -0
  52. kapps_semantic_middleware/examples/docs/transferunit-ontology.md +275 -0
  53. kapps_semantic_middleware/examples/handlers.py +64 -0
  54. kapps_semantic_middleware/examples/scenario1_hello_world.ipynb +432 -0
  55. kapps_semantic_middleware/examples/scenario1_hello_world.py +282 -0
  56. kapps_semantic_middleware/examples/scenario2_door.ipynb +438 -0
  57. kapps_semantic_middleware/examples/scenario2_door.py +285 -0
  58. kapps_semantic_middleware/examples/seed.py +269 -0
  59. kapps_semantic_middleware/examples/transferunit.ttl +114 -0
  60. kapps_semantic_middleware/examples_cli.py +199 -0
  61. kapps_semantic_middleware/middleware.py +1255 -0
  62. kapps_semantic_middleware/modes.py +47 -0
  63. kapps_semantic_middleware/ontology/core.ttl +755 -0
  64. kapps_semantic_middleware/ontology/mes.ttl +90 -0
  65. kapps_semantic_middleware/ontology/service.ttl +178 -0
  66. kapps_semantic_middleware/projection.py +314 -0
  67. kapps_semantic_middleware/py.typed +0 -0
  68. kapps_semantic_middleware/registration.py +877 -0
  69. kapps_semantic_middleware/rest_router.py +356 -0
  70. kapps_semantic_middleware/seeding.py +99 -0
  71. kapps_semantic_middleware/shacl_interop/CONTEXT.md +33 -0
  72. kapps_semantic_middleware/shacl_interop/__init__.py +5 -0
  73. kapps_semantic_middleware/shacl_interop/shape_from_typehints.py +159 -0
  74. kapps_semantic_middleware/vocabulary.py +213 -0
  75. kapps_semantic_middleware-0.1.0.dist-info/METADATA +142 -0
  76. kapps_semantic_middleware-0.1.0.dist-info/RECORD +79 -0
  77. kapps_semantic_middleware-0.1.0.dist-info/WHEEL +4 -0
  78. kapps_semantic_middleware-0.1.0.dist-info/entry_points.txt +3 -0
  79. kapps_semantic_middleware-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,170 @@
1
+ # AGENTS.md
2
+
3
+ Orientation for an agent building against `kapps_semantic_middleware`.
4
+
5
+ This library lets a piece of Python code running on or next to a shopfloor resource expose its
6
+ functionality through an RDF knowledge graph, and lets other middleware instances discover and
7
+ invoke that functionality through the graph rather than through hardcoded network references.
8
+
9
+ If you are prototyping against this middleware, read the consumption rules below first. They are
10
+ constraints that **do not surface in type signatures** — most of them fail silently, producing
11
+ structurally wrong behavior rather than an exception. Then read the mechanics pages for whichever
12
+ part you are touching.
13
+
14
+ ## Where to read what
15
+
16
+ **Vocabulary — what the words mean.** Read these before the mechanics pages; the mechanics pages
17
+ assume their terms and do not re-define them.
18
+
19
+ | File | Covers |
20
+ |---|---|
21
+ | [`CONTEXT-MAP.md`](CONTEXT-MAP.md) | The five contexts, how they depend on each other, and the three-module ontology layering |
22
+ | [`src/kapps_semantic_middleware/CONTEXT.md`](src/kapps_semantic_middleware/CONTEXT.md) | The core vocabulary: Service, Workflow, Parameter, ClassScope, Projection, Binding descriptor, Operation, Mode |
23
+ | [`src/kapps_semantic_middleware/shacl_interop/CONTEXT.md`](src/kapps_semantic_middleware/shacl_interop/CONTEXT.md) | Precondition and outcome shapes derived from a function's type hints |
24
+ | [`examples/CONTEXT.md`](examples/CONTEXT.md) | The self-contained scenario notebooks |
25
+ | [`demo/transferunits/CONTEXT.md`](demo/transferunits/CONTEXT.md) | The runnable multi-process factory demonstration |
26
+
27
+ **Mechanics — how to use it.** Written in construction order; a reader can go start to finish
28
+ without forward references.
29
+
30
+ | Page | Covers |
31
+ |---|---|
32
+ | [`docs/mechanics/01-instantiation-and-lifecycle.md`](docs/mechanics/01-instantiation-and-lifecycle.md) | Constructing an instance, choosing a mode, what appears in the graph at startup, heartbeat, deregistration |
33
+ | [`docs/mechanics/02-workflow-registration.md`](docs/mechanics/02-workflow-registration.md) | Declaring what a Service exposes, the ontology prerequisites, signature-derived shapes, address vs. endpoint |
34
+ | [`docs/mechanics/03-state-and-parameters.md`](docs/mechanics/03-state-and-parameters.md) | Modelling resource state as Parameters, committed value vs. locator, where a parameter's shape comes from |
35
+ | [`docs/mechanics/04-connector-binding.md`](docs/mechanics/04-connector-binding.md) | The recognition chain, adding a protocol the library does not ship, connector wiring, transports |
36
+ | [`docs/mechanics/05-operation-coordination.md`](docs/mechanics/05-operation-coordination.md) | Dispatch, the event trigger, pull-and-run, the status lifecycle, handover, recovery |
37
+ | [`docs/mechanics/06-views-and-projection.md`](docs/mechanics/06-views-and-projection.md) | Defining a ClassScope, what a view cannot select, what the northbound projection removes |
38
+ | [`docs/mechanics/07-writing-to-the-graph-and-to-devices.md`](docs/mechanics/07-writing-to-the-graph-and-to-devices.md) | Every path that causes a write, naming the field that moved, northbound vs. southbound, IRI handling |
39
+ | [`docs/mechanics/08-provisioning-and-seeding.md`](docs/mechanics/08-provisioning-and-seeding.md) | **Read first if `ogm=` is a mystery.** The bootstrap order, loading the shared ontologies, authoring the domain TBox, and seeding the instance data a Parameter needs |
40
+
41
+ ### A note on the citations you will find in the glossaries
42
+
43
+ The `CONTEXT.md` files cite architecture decision records as **ADR 00nn** and **root ADR
44
+ 000n**. Those records are development-repository material and are **not part of this
45
+ distribution** — no copy of them ships, and the release rewrites every path that pointed
46
+ at one, so a citation here is never a link you can follow.
47
+
48
+ **Do not go looking for them.** The citations are provenance markers, not links. Every mechanism a
49
+ glossary cites a record for is explained in full by the mechanics page covering it — ADR 0004's
50
+ subject is the "Address and endpoint" section of
51
+ [`02-workflow-registration.md`](docs/mechanics/02-workflow-registration.md), ADR 0006's is "The
52
+ graph-facing connector" in [`04-connector-binding.md`](docs/mechanics/04-connector-binding.md), and
53
+ so on. If a citation seems to point at something the mechanics pages do not cover, that is a
54
+ documentation bug worth reporting, not a missing file worth hunting.
55
+
56
+ ## Where the names come from
57
+
58
+ This library re-exports much of its surface from its three dependencies, so the import you need is
59
+ often *not* from `kapps_semantic_middleware`. Use this table rather than guessing.
60
+
61
+ | Name | Import from |
62
+ |---|---|
63
+ | `SemanticMiddleware`, `Mode` | `kapps_semantic_middleware` |
64
+ | `IRI`, `GraphDB` | `kapps_triplestore_interface` |
65
+ | `OGM` | `kapps_ogm` |
66
+ | `ClassScope` | `kapps_ogm.utils.class_scope` |
67
+ | `SyncDirection` | `transitional_sync_middleware.middleware.sync.synced_connector` |
68
+ | `ConnectionInfo` | `transitional_sync_middleware.middleware.registries` |
69
+ | `INF` (the interface vocabulary) | `kapps_semantic_middleware.vocabulary` |
70
+ | `graphdb_for`, `credentials_for` | `kapps_semantic_middleware.credentials` |
71
+ | `DataModel`, `Reference`, `Identifier` | `kapps_semantic_middleware` (re-exported from `transitional_sync_middleware`) |
72
+
73
+ Binding internals, if you are adding a protocol, live under
74
+ `kapps_semantic_middleware.connectors` — `semantic`, `mqtt_binding`, `rest_binding`,
75
+ `knowledge_graph_connector`, `wiring`.
76
+
77
+ > `kapps_semantic_middleware/ontology/` is **not** a Python module. It is the directory holding the
78
+ > three vocabulary files (`core.ttl`, `mes.ttl`, `service.ttl`). Importing from it fails.
79
+
80
+ ## Consumption rules
81
+
82
+ ### These fail silently
83
+
84
+ **`GraphDB.from_env()` connects to whatever `GRAPHDB_REPOSITORY` names, and seeding destroys what it
85
+ connects to.** `seeding.clear_repository` clears the default graph of the client's current
86
+ repository, so a value left over in a shell — from another project, another checkout, a `.bashrc`
87
+ written months ago — silently redirects a wipe. Nothing validates that the repository was the one you
88
+ meant; it only has to exist. Use `credentials.graphdb_for("name")`, which names the repository in
89
+ code and ignores the variable, for anything that seeds, clears, or re-seeds. Note this hazard belongs
90
+ to the *variable*, not the server: pinning the repository does not stop `GRAPHDB_URL` pointing at a
91
+ shared instance.
92
+
93
+ **A `ClassScope` terminates at a Parameter and cannot select within one.** Any chain element below
94
+ a complex property is silently discarded during fetch. A view that tries to reach inside a
95
+ parameter blanknode materializes only as far as the parameter itself, with no error raised. A scope
96
+ chooses *which* parameters, never *which parts* of one.
97
+
98
+ **Connector bindings must be registered at construction, not later.** The framework calls
99
+ `connect()` on everything in the connection registry before it runs `on_start_up` callbacks. A
100
+ connector registered after construction never has `connect()` called, so inbound traffic dies
101
+ silently — the listener task never starts and the queue is never fed — while outbound may limp
102
+ along, making the fault one-directional and quiet.
103
+
104
+ **A Parameter's shape comes from the TBox restriction on its property's range, not from the
105
+ instance data.** Anything the restriction does not declare is dropped at materialization with only
106
+ a warning logged. Metadata a connector needs must be declared in the restriction or it never
107
+ arrives, and the connector fails with nothing raised.
108
+
109
+ **Under the locator pattern a Parameter's value is not in the graph at all.** Fast-changing
110
+ parameters keep only metadata in the graph; the live value exists only in the datamodel and over
111
+ REST. An unobserved parameter materializes as an empty list, which means *not yet read* — not zero,
112
+ and not null. Handle the empty case.
113
+
114
+ **A persistence write must name the region of the model that changed.** The fan-out notifies only
115
+ the connectors that region covers. A write that does not name its field notifies every synced
116
+ connector, so devices are written that nobody touched — and for a settable parameter that
117
+ fabricates a command rather than merely wasting traffic.
118
+
119
+ **The northbound REST payload never carries protocol connection metadata.** Broker addresses,
120
+ topics and endpoints are pruned from the served datamodel *before* any data is fetched. This is
121
+ structural, not a permission check: the northbound model has no field able to carry a broker
122
+ address. It stops a peer *learning* an address from this surface; it does not stop someone who
123
+ already knows it.
124
+
125
+ **OWL existential restrictions do not make a field required.** Only SHACL shapes enforce
126
+ requiredness, and only at admission. A parameter with no observed value does not raise a validation
127
+ error on materialization merely because its restriction declares `owl:someValuesFrom`. Absence of a
128
+ triple means *unknown*, never *false*.
129
+
130
+ **Any prettified or shortened IRI is display only.** Production code carries fully back-resolvable
131
+ IRIs in their mangled form — REST path segments, datamodel field names, `svc:endpoint` triples. A
132
+ consumer that parses a displayed IRI, or round-trips one back into a query, gets a wrong answer
133
+ with no exception raised.
134
+
135
+ ### These fail loudly
136
+
137
+ **Every class you register against must already exist in the ontology.** The middleware creates
138
+ instances automatically but never mints classes. A domain-specific subclass of `svc:Service`,
139
+ `svc:Workflow` or `svc:StateProperty`, and the `cfc:Capability` subclass a Workflow realizes, must
140
+ all pre-exist. Startup fails immediately, naming the missing class.
141
+
142
+ **All knowledge-graph writes go through the OGM.** Raw SPARQL `UPDATE` or direct
143
+ `kapps_triplestore_interface` mutation calls bypass the validated write path, so the written node passes no
144
+ shape check and a property replacement loses its atomicity — the intermediate state fails
145
+ validation. Reads may use the access module directly; only writes are constrained.
146
+
147
+ **`@mw.workflow` and `@mw.state` are resource-mode only.** Each raises `RuntimeError` when called
148
+ on an instance in any other mode, and `ValueError` when a required class IRI is missing.
149
+
150
+ ### Structural facts
151
+
152
+ **Build against `src/kapps_semantic_middleware/`. `examples/` and `demo/` are illustrations, not
153
+ API.** They are teaching vehicles seeded against throwaway repositories, not production patterns to
154
+ copy.
155
+
156
+ **The demo imports as `kapps_semantic_middleware.demonstrations`, not `demo`.** The wheel remaps
157
+ the `demo/` directory into the library namespace. `import demo.transferunits` works only in a
158
+ development checkout.
159
+
160
+ **There is one Service per middleware *instance*, not per Resource.** Several instances may wrap
161
+ one Resource — a controller and a monitor, say — each owning its own Service node, address and
162
+ heartbeat, all linked by `svc:isServiceOf`. Discovery may therefore return several Services for one
163
+ Resource; select by address or by advertised capability rather than assuming exactly one.
164
+
165
+ **Only `resource` and `watchdog` modes are implemented.** `server` is reserved and raises
166
+ `NotImplementedError`. A graph-*consuming* participant — a planner, a controller, a mobile robot —
167
+ is a resource-mode instance with its own Resource, not a server-mode one.
168
+
169
+ **Docker is a prerequisite for running the examples, never for using the library.** The bundled
170
+ `docker compose` file provides the GraphDB the scenarios and the factory need.
@@ -0,0 +1,59 @@
1
+ # Context Map
2
+
3
+ ## Contexts
4
+
5
+ - [Core Middleware](./src/kapps_semantic_middleware/CONTEXT.md) — the middleware library itself.
6
+ It holds the Service/Workflow/Capability/Operation/Mode registration machinery and the
7
+ execution machinery.
8
+ - [SHACL Interop](./src/kapps_semantic_middleware/shacl_interop/CONTEXT.md) — the temporary SHACL
9
+ seam for workflow precondition shapes and outcome shapes. It generates and parses them. It is
10
+ explicit scaffolding, will move to `kapps_ogm` (see its ADR 0001).
11
+ - [Example Scenarios](./examples/CONTEXT.md) — self-contained demonstration notebooks, plus the
12
+ seed-data logic and the ontology-provisioning logic under them. That logic makes each notebook
13
+ reproducible against a dummy repository, and never against the production state.
14
+ - [TransferUnit Factory](./demo/transferunits/CONTEXT.md) — the runnable multi-process demo. A
15
+ launcher seeds N units and starts one process per participant (ADR 0029). Its decisions live
16
+ with the Core Middleware ADRs, next to ADR 0029.
17
+ - **Module Requirements** — requirements this project places on sibling KAPPS-family
18
+ modules (`kapps_ogm`, the not-yet-created visual-toolbox GUI). Not a code context, and its
19
+ documents are development-repository material that does not ship.
20
+
21
+ ## Relationships
22
+
23
+ - **Core Middleware → SHACL Interop**: Core Middleware calls into SHACL Interop to read or write
24
+ a shape. The shape belongs to a Workflow class or a StateProperty class. The call happens when
25
+ `@mw.workflow` or `@mw.state` registers or resolves one. SHACL Interop has no dependency back
26
+ on Core Middleware. It operates on class IRIs and shapes, and not on types of the Core Middleware.
27
+ - **Example Scenarios → Core Middleware**: Example Scenarios instantiate and exercise Core
28
+ Middleware end-to-end, against the ontology and the instance data they seed themselves. Core Middleware
29
+ has no dependency on Example Scenarios.
30
+ - **TransferUnit Factory → Core Middleware**: the factory runs several instances of the library
31
+ in separate processes. That is one instance per unit, plus a controller. A monitor is
32
+ milestone 2 (ADR 0032) and is not built. Core Middleware has no dependency on the factory.
33
+ - **Module Requirements** records obligations that Core Middleware and SHACL Interop place on
34
+ `kapps_ogm` and the visual-toolbox repo. It does not depend on the code contexts, and no code
35
+ context depends on it. It is a record for work that belongs elsewhere.
36
+
37
+ ## Ontology-module layering
38
+
39
+ The vocabulary of the project is layered across three modules (Core Middleware ADR 0012):
40
+
41
+ - **`cfc:` — Core** (`.../Core#`): published, external, superior — `Operation`/`Capability`/
42
+ `Resource`/`Task`. The system imports and specializes it. The system does not modify it.
43
+ - **`mes:` — MES** (`.../MES#`, `src/kapps_semantic_middleware/ontology/mes.ttl`): a
44
+ **domain** ontology that imports Core and details it out — possession + handover ability.
45
+ What domain experts touch.
46
+ - **`svc:` — Service** (`.../Service#`, `ontology/service.ttl`): middleware-to-middleware
47
+ **reachability and coordination only**, domain code does not touch it. It holds Service, Workflow and
48
+ StateProperty, the address, the endpoint and the heartbeat. It also holds the resolution chain,
49
+ and an Operation's status and provenance.
50
+
51
+ `mes:` and `svc:` are siblings that both import Core. `mes:` is domain-facing, `svc:` is
52
+ middleware-facing.
53
+
54
+ ## Where the decisions are written down
55
+
56
+ The architecture decision records are development-repository material and are not part of
57
+ this distribution. What they decided is described, without the deliberation, in
58
+ [`docs/mechanics/`](docs/mechanics/); [`AGENTS.md`](AGENTS.md) indexes those pages.
59
+
@@ -0,0 +1,349 @@
1
+ # Core Middleware
2
+
3
+ One of five contexts in this repo — see `/CONTEXT-MAP.md` at the repo root for the others
4
+ (SHACL Interop, Example Scenarios, Module Requirements).
5
+
6
+ > This file defines the **vocabulary**. For how to *use* the mechanisms it names — and for the
7
+ > constraints that do not surface in a type signature — read `AGENTS.md` and the `docs/mechanics/`
8
+ > set beside it. The `ADR 00nn` citations below are provenance markers for the development
9
+ > repository; those records are not distributed, and `docs/mechanics/` is their shipped
10
+ > replacement.
11
+
12
+ The reference implementation of the KAPPS architecture's Semantic Middleware Runtime: the
13
+ Interface-Layer component that lets a piece of Python code running on or next to a shopfloor
14
+ resource (or as a standalone service) expose functionality through the knowledge graph, and
15
+ lets other middleware instances discover and invoke that functionality via the graph rather
16
+ than via hardcoded network references. This context owns the Service/Workflow/Capability/
17
+ Operation/Mode registration and execution machinery. It delegates the actual generation and
18
+ parsing of workflow precondition/outcome SHACL shapes to the SHACL Interop context.
19
+
20
+ Built on `transitional_sync_middleware` (forked by inheritance, being incrementally reimplemented locally),
21
+ `kapps_ogm` (all knowledge graph reads/writes), and `kapps_triplestore_interface` (raw triple store
22
+ access, used directly where `kapps_ogm` has no equivalent yet, e.g. instance discovery).
23
+
24
+ ## Language
25
+
26
+ **Service**:
27
+ A distributed runtime entity wrapped by a single middleware instance (e.g. a door
28
+ controller, a screwing-resource controller, a planning service). Typed via a
29
+ domain-specific subclass of `svc:Service` that must pre-exist in the ontology.
30
+ There is **one Service per middleware instance, not per Resource**: several instances may be bound to
31
+ one Resource, each owning its own Service node, address and heartbeat, all linked by
32
+ `svc:isServiceOf` (ADR 0022). Discovery may therefore return several Services for one Resource.
33
+ _Avoid_: Middleware instance (that is the Python object; Service is its graph representation), Resource (Service *wraps* a Resource, it is not one); "the service of a resource" (there may be several).
34
+
35
+ **Connector wiring** (of a resource-mode instance):
36
+ A configuration of the one library, not a distinct class. Resource mode is a **library woven into a
37
+ domain expert's Python package**, never a monolithic server. A wiring is two facts: a **protocol**
38
+ and a **direction**. This is the only axis that describes an instance (ADR 0033).
39
+
40
+ *Direction.* **Driving**: connectors wired bidirectionally, writes as well as reads. **Observing**:
41
+ connectors wired `TO_PERSISTENCE`, reads live values, structurally unable to write. **Inspecting**:
42
+ `autoregister_connectors=False`, nothing connected, structure and graph content only.
43
+
44
+ *Protocol.* **MQTT** reaches a device, recognised on the parameter (`inf:hasMQTTTopic`). **REST**
45
+ reaches another middleware instance over its ADR 0017 routes, recognised through the resource's
46
+ Service (`svc:address`). A peer middleware is a device as far as the seam is concerned.
47
+
48
+ Recognition and the **Projection** run identically in every combination, so connection metadata
49
+ never reaches a **served datamodel** (ADR 0020, ADR 0032, ADR 0033).
50
+
51
+ **The log path is held to the same claim** (issue #76, closed 2026-08-09). `activity.py` serves
52
+ this package's INFO records over HTTP, which for a while made `/activity` a way around the
53
+ projection: `connectors/mqtt_binding.py` logged each value with its topic at INFO. The topic now
54
+ sits at DEBUG on both legs, and the feed's handler filters at its own level rather than the
55
+ logger's — so widening the package logger to DEBUG for a debugging session still cannot put a
56
+ topic on the page. The INFO line names the parameter and the value, which is what the feed is
57
+ for.
58
+ _Avoid_: Flavour, retired by ADR 0032. **Role**, retired by ADR 0033 — an instance has no property
59
+ beyond its wiring. Consumer and Resource middleware as *defined* terms; they survive only as informal
60
+ shorthand. Read-only mode, because a **Mode** is `resource`/`server`/`watchdog`, and a connector
61
+ wiring is a configuration *within* resource mode. Monitor mode.
62
+
63
+ **Transport**:
64
+ The thing a connector dials, as opposed to the connector that dials it — an MQTT broker, and
65
+ nothing else so far. A Parameter's connection metadata *names* a transport by address; it never
66
+ provides one. A middleware may be asked to **ensure** a transport exists at a declared address
67
+ before it registers the first connector aimed there, but it never holds a transport
68
+ implementation: it states the need and the deployment meets it (ADR 0034). So a transport is the
69
+ one part of the southbound path that is neither in the graph nor in this library.
70
+ _Avoid_: Connector (the client this middleware constructs, one per topic — it *uses* a transport);
71
+ Binding / Binding descriptor (the recognition rule that builds connectors, ADR 0023); Broker as a
72
+ general term, since it is one transport and the seam is not MQTT-shaped; Endpoint and Address,
73
+ which are northbound (`svc:address`, the ADR 0017 routes).
74
+
75
+ **Workflow**:
76
+ An invokable function exposed by a Service, registered with `@mw.workflow(...)`. Realizes
77
+ exactly one Capability. Typed via a domain-specific subclass of `svc:Workflow` that must
78
+ pre-exist in the ontology, carrying a SHACL shape describing its arguments (precondition)
79
+ and return value (outcome) — see the SHACL Interop context for how that shape is read/
80
+ written.
81
+ _Avoid_: Skill, Action (AAS-tradition terms for the same invocation-interface idea; KAPPS
82
+ reserves "Service" for the deployable middleware-wrapped entity and "Workflow" for what it
83
+ exposes — see the paper's explicit divergence from the AAS capability-skill-service model).
84
+
85
+ **Parameter** (interface-accessible parameter) — _supersedes StateProperty (ADR 0015)_:
86
+ A readable and/or settable state of a Resource, modelled as **one graph node** carrying its
87
+ value/unit **and** the metadata a protocol connector needs to reach the device (e.g. an MQTT
88
+ topic + broker). It carries **no named `rdf:type`** — its only types are anonymous restriction nodes,
89
+ which exist by inference and never survive an explicit-graph fetch — so it is recognised by the
90
+ **Interface property** its domain property specializes, never by its class (ADR 0020).
91
+ The middleware's former "readable state" and the shop-floor "parameter" are one thing seen from
92
+ two directions — southbound (how the middleware reaches the device) and northbound (how peers
93
+ reach the middleware). Whether it is externally settable is a **facet** (an access mode), not a
94
+ subclass. Northbound it is **atomic**: value, unit
95
+ and access mode are read and written together as one dict, because they share one blanknode — the
96
+ locked circular-factory pattern for metadata about a property, RDF having no properties-about-
97
+ properties (ADR 0017). Its **shape is the TBox restriction** on its property's `rdfs:range`, not the
98
+ instance data: anything the restriction does not declare is dropped at materialization with only a
99
+ warning, so metadata a connector needs must be declared there or it never arrives (ADR 0028).
100
+ A complex property that matches no registered connector is **not** a Parameter — it is ordinary
101
+ data the consumer asked for, displayed and readable, with nothing wired (ADR 0020).
102
+ Whether its value lives in the graph is the domain's choice — see **Committed value** / **Locator**. It is
103
+ also the deepest thing a binding can address: `ConnectionInfo` bottoms out at the Parameter, never at the
104
+ value inside it (ADR 0023).
105
+ _Avoid_: StateProperty (retired term, ADR 0015); Sensor value / Observation (those describe the
106
+ data, not the graph node); Capability (states have none — there is no "light-barrier capability").
107
+
108
+ **Interface class** — _retired term, do not use_:
109
+ A protocol-specific parameter *class* (`inf:MQTTParameter`, `inf:OPCUAParameter`) that a connector was
110
+ paired with one-to-one, resolved by the parameter's `rdf:type`. **The parameter node has no named
111
+ type**, so nothing could ever match on it (ADR 0020, measured). The concept it reached for — the
112
+ protocol-extensibility seam — survives intact as the **Interface property**, and the ontology terms it
113
+ named are gone from the scenario-3 TBox. Use **Interface property**.
114
+ _Avoid_: the term itself. Also Adapter, Driver.
115
+
116
+ **ClassScope**:
117
+ A **projection — a view** — over the graph, expressed (in the OGM) as a tree of property-chains
118
+ rooted at a class. A view **belongs to its consumer** and is rooted at the node that consumer cares
119
+ about. There is no single "the datamodel" for a resource. This is how one central ontology serves
120
+ both the IT-OT boundary and the control/factory layer without duplicating concepts (ADR 0018).
121
+ A view **terminates at a Parameter and cannot select within one**: below a complex property the
122
+ chain is silently discarded, and the blanknode's contents are fixed by the TBox restriction, the
123
+ same for every consumer (ADR 0028). A scope chooses *which* parameters, never *which parts* of one.
124
+ An **empty** projection is legitimate — the graph holds the information, and a resource whose view
125
+ is a bare individual serves a one-field datamodel and starts normally.
126
+ _Avoid_: Filter, Query (a ClassScope is a reusable named view of which metadata to materialise).
127
+
128
+ **Root** (of a view):
129
+ The node a ClassScope is rooted at. Being a root is what makes a Resource a *top-level* thing rather
130
+ than a component — TransferUnit1 is a unit and ConveyorBelt1_left is a part of it **because a view is
131
+ rooted at the unit and reaches the belt**, not because of any part-of relation in the graph. There is
132
+ no composition property in Core, and none is needed.
133
+ _Avoid_: Top-level resource, Aggregate (both suggest an intrinsic property of the resource; rootedness
134
+ is a property of the view).
135
+
136
+ **User view**:
137
+ The ClassScope a resource-mode middleware is constructed with, and the one it materializes into the
138
+ datamodel it REST-exposes: the **northbound** projection. Stated by the domain code that embeds the
139
+ library, since only that code knows what the instance is for. Omitting it falls back to an unscoped
140
+ fetch, which materializes the `id` alone — a legitimate, if minimal, projection.
141
+ Connection metadata is absent from it not because the view declines to fetch it (it cannot — see
142
+ **ClassScope**) but because the **Projection** removes it (ADR 0028), so a peer cannot learn the
143
+ broker address and bypass the middleware.
144
+ _Avoid_: The datamodel, Schema (it is one view among many; a connector's view of the same resource is
145
+ a different one).
146
+
147
+ **Projection** (northbound):
148
+ What keeps connection metadata out of the served datamodel: the middleware **removes the protocol
149
+ properties from the ClassSpec before fetching**, and materializes the pruned spec (ADR 0028). What
150
+ counts as protocol metadata is **read from the ontology**, per Parameter, at every startup: everything
151
+ contributed by an **Interface property** strictly between the Parameter's own property and
152
+ `inf:isInterfaceAccessibleParameter`. The Parameter's own range (value, unit) and the root's own range
153
+ (`inf:accessMode`) stay. It runs for **every connector wiring**, including one that wires nothing — gating it
154
+ would make the least-privileged instance the one that leaks.
155
+
156
+ Deriving the set from the **registry** instead was tried and **fails open**: it knows only the
157
+ protocols this middleware has code for, so a Parameter reachable over an unregistered protocol had its
158
+ endpoint served (measured). A **Binding descriptor**'s connection metadata is now a *cross-check*
159
+ against the ontology, not the source. A keep-list — naming what is safe — was rejected: it is a second
160
+ closed-world moment (ADR 0025 allows exactly one) and it hides new domain content by default.
161
+ _Avoid_: Deny-list *of field names* (the point is that the list is derived from the authoritative
162
+ ontology, not enumerated); Access control (the Projection stops a peer *learning* the broker address
163
+ from this REST surface, not someone who already knows it — that is future work).
164
+
165
+ Earlier this was recorded as *not* a middleware step at all: a Parameter materializes to exactly what its
166
+ property's restriction declares, so on the merge-depth reading a broker address physically could not
167
+ reach a peer. That premise died when the `inf:` interface properties gained their own ranges (#53) —
168
+ necessary so provisioning can write connection metadata through the OGM — because `PropertySpec` merges
169
+ the entire `rdfs:subPropertyOf*` chain with no depth parameter. Measured: the unpruned belt materializes
170
+ carrying topic, set topic and broker. Merge depth remains the right *description* of the two views; the
171
+ middleware has to realize the shallow one itself (ADR 0019 stays retired as written; ADR 0026's
172
+ projection claim is superseded).
173
+ _Avoid_: Filter, Stripping *of data* (the prune is on the shape, before any data is read — the northbound
174
+ model has no field to carry a broker address in); View (the view is the ClassScope, which selects *which*
175
+ Parameters, not which parts of one).
176
+
177
+ **Interface property**:
178
+ The property a **Semantic connector** binds to — `inf:isInterfaceAccessibleMQTTParameter` and its
179
+ siblings, under `inf:isInterfaceAccessibleParameter`. A resource's parameter declares its protocol by
180
+ being a **subproperty** of one, which is how the authoritative upstream ontology already models it.
181
+ Recognition is `rdfs:subPropertyOf*` against the registry. The parameter blanknode itself carries no
182
+ named class to match on (ADR 0020).
183
+ _Avoid_: Interface class (retained for the ontology concept, but the *match* is on the property).
184
+
185
+ **Known primitives**:
186
+ The bounded form of the system's flexibility: novel *combinations* are handled, novel *vocabulary* is
187
+ not. A task assembled from grip/move/place may never have been seen in that combination, and a product
188
+ may combine a screw and a gear never before combined — but grip, move, place, screw and gear are all
189
+ known. Views and discovery listings are configured over known classes for this reason. It is a
190
+ deliberate boundary, not a limitation to be engineered away.
191
+ _Avoid_: Zero-configuration, Fully generic (both overclaim — the system does not discover concepts it
192
+ has never been taught).
193
+
194
+ **Semantic connector**:
195
+ Any connector able to **register itself from the knowledge graph**. Every connector transitional_sync_middleware ships
196
+ (MQTT, OPC-UA, HTTP, websocket, webhook, AAS client, model) is a candidate. Only the bare `Connector`
197
+ protocol is not, being the interface specification itself. Realized as a **Binding descriptor**, not as a
198
+ connector subclass (ADR 0023).
199
+ _Avoid_: Connector (the bare transitional_sync_middleware protocol, without the metadata ontology); Adapter, Driver;
200
+ MQTT connector as the archetype (MQTT is the first instance, not the shape of the concept).
201
+
202
+ **Binding descriptor**:
203
+ The object that makes a connector semantic: it names the **connector class** it builds, the **Interface
204
+ property** it binds to, the connection-metadata properties its protocol needs — which are also exactly
205
+ what the **Projection** hides northbound — and how to turn one Parameter's metadata into one or more
206
+ framework registrations. It references its connector class rather than subclassing it, so a connector
207
+ nobody here owns can still be made semantic. One binding may yield **two** connectors (a read topic and a
208
+ write topic) against **one** binding target, differing only in direction. Built and registered **at
209
+ construction**, from the ClassSpec and the graph — registering later means the framework never connects
210
+ them and inbound traffic dies silently (ADR 0023).
211
+ _Avoid_: Connector factory, Plugin (the descriptor is declarative — it states what a protocol needs, and
212
+ building is one method on it).
213
+
214
+ **Static facet**:
215
+ A part of a Parameter that does not change with a reading — unit, access mode. Captured by the **Binding
216
+ descriptor** at wiring time and reassembled into the payload on every inbound message, because
217
+ `setattr` replaces the whole Parameter node and `Formatter.deserialize` sees only the payload, with no
218
+ access to the current value. Free to carry, since `OGM.commit` filters unchanged triples (ADR 0018,
219
+ ADR 0023).
220
+
221
+ ADR 0027 retired the *graph* reason for this — a skolemised Parameter node is addressable, so a commit
222
+ diffs per triple and an unchanged facet cannot be wiped. The **in-memory** reason stands and is why the
223
+ reassembly remains: without it, a bare inbound scalar blanks the unit in the very model that is served
224
+ over REST.
225
+ _Avoid_: Constant, Config (a facet belongs to the Parameter and is authored in the ontology, not to the
226
+ deployment).
227
+
228
+ **Connector registry**:
229
+ The universal map from **Interface property** to **Binding descriptor**. Built at middleware
230
+ initialization from the known, tested descriptors shipped in the middleware, and extensible after init by
231
+ injecting a domain-built one. Resolution is
232
+ `parameter property rdfs:subPropertyOf* → interface property → binding descriptor`. Supporting a new
233
+ protocol is registering a new entry, never a core change. Recognition runs over the **ClassSpec and the
234
+ graph**, not over materialized instance data, which is what allows registration to happen early enough
235
+ for the framework to connect them (ADR 0020, ADR 0023).
236
+ _Avoid_: Connector factory, Plugin loader (the registry keys specifically on the interface property);
237
+ keying on `rdf:type` (superseded — the parameter blanknode has no named type).
238
+
239
+ **Committed value** / **Locator**:
240
+ The two legitimate ways a domain may treat a Parameter's value, chosen per subproject — the middleware is
241
+ agnostic and enforces neither. **Committed value**: the data point changes slowly, so the domain code
242
+ commits it and the graph holds the value. `@state` is not involved. **Locator**: the data point changes
243
+ fast, so the graph holds only *where the value lives* — unit, access mode, topic, broker — and never the
244
+ value itself, which exists only in the datamodel and over REST. Scenario 3 is a locator, which is why its
245
+ instance data carries no `inf:hasValue` literals. The restriction still declares the field, so an
246
+ unobserved Parameter reads as `[]` (ADR 0024).
247
+ _Avoid_: Cached value, Stale value (a committed value is authoritative for its update rate, not a stale
248
+ copy); "the live value is never persisted" as a middleware rule (it is the locator pattern's property).
249
+
250
+ **Capability**:
251
+ Defined in Core (`cfc:Capability`, subclasses `EquippedCapability`/`FlexibilityCapability`/
252
+ `ChangeabilityCapability`). An ability a Resource currently has. In this project's usage,
253
+ every Capability instance is created automatically by the middleware from a pre-existing
254
+ Capability *type* the moment a matching Workflow or StateProperty is registered — it is
255
+ never instantiated by hand.
256
+ _Avoid_: Skill (AAS term for a related but not identical concept).
257
+
258
+ **Operation**:
259
+ Defined in Core (`cfc:Operation`, subclass of `Task`). The executable, resource-assigned
260
+ form of a task; links to a Capability via `cfc:implementsCapability`. A caller creates an
261
+ Operation in the graph and dispatches it to the resource that will carry it out. That
262
+ resource queues it, pulls it, runs it, and the outcome is recorded back onto it. The
263
+ graph-level unit of work exchanged between middleware instances.
264
+ _Avoid_: Workflow invocation, Job (Operation is the graph-level unit of work; a single
265
+ Operation resolves to exactly one Workflow via its Capability).
266
+
267
+ **Event trigger** (`execute()`):
268
+ The receiver-side built-in Workflow that every resource-mode middleware exposes on its REST
269
+ API. A caller "triggers" it to signal that an Operation addressed to that resource now exists in
270
+ the graph. The receiver enqueues the Operation, `ogm.fetch`es it, and hands it to an optional
271
+ domain callback (else leaves it `queued`). The trigger carries only the Operation IRI — the
272
+ payload lives in the graph — and does not block on the work or return a business result.
273
+ _Avoid_: RPC call, Invoke (the event trigger notifies; it does not run the work synchronously).
274
+
275
+ **Dispatch**:
276
+ The caller-side act of handing an Operation to another resource: create the Operation
277
+ individual in the graph (through the OGM-routed write path) and then fire the receiver's
278
+ event trigger. Accessed from domain Python as a transaction context manager — the body populates
279
+ the Operation, the atomic exit performs the create-and-notify.
280
+ _Avoid_: Send, Publish (there is no message bus; dispatch is a graph write plus an event trigger).
281
+
282
+ **Operation queue**:
283
+ A resource-mode middleware's pending-work list — the Operations addressed to its Resource
284
+ that await or are in progress. Held in memory as a cache and reconstructed at startup by
285
+ querying the graph (own `queued` operations, plus own orphaned `running` operations to
286
+ reclaim); filled live by event triggers. A dead resource's stranded operations are swept
287
+ centrally by a watchdog, not by per-resource polling.
288
+ _Avoid_: Message queue, Broker (there is no broker; the queue is a view over graph state).
289
+
290
+ **Operation status**:
291
+ The lifecycle state of an Operation: `queued` (created and addressed, awaiting pull) →
292
+ `running` (pulled, in progress) → `done` or `failed` (terminal). Drives coordination and
293
+ recovery. Execution provenance (which Workflow ran it, when, the result) is written as part
294
+ of the terminal transition, so the status is itself the provenance record — there is no
295
+ separate success flag.
296
+
297
+ **Pull-and-run**:
298
+ The receiver-side transaction context manager by which domain code takes the next `queued`
299
+ Operation, sets it `running` (re-fetching it under a domain-supplied `ClassScope`), runs the
300
+ work in the body, and on atomic exit records the terminal `done`/`failed` state and
301
+ provenance — dumping the Resource's datamodel to the graph on failure.
302
+ _Avoid_: Poll, Consume (pull-and-run is the guarded unit of work, not the delivery mechanism).
303
+
304
+ **Resource**:
305
+ Defined in Core (`cfc:Resource`). The physical or logical thing a Service wraps (a door, a
306
+ transformer cell, a screwing tool). Required at construction time in resource mode.
307
+
308
+ **Mode**:
309
+ A `SemanticMiddleware` construction-time choice governing what the instance is *for*:
310
+ - `"resource"` — wraps one Resource; the REST surface is the user-registered Workflows/
311
+ StateProperties, the built-in `execute()` event trigger, and a CRUD REST API generated from
312
+ the resource's own datamodel (`generate_rest_api_for_data_model`; ADR 0005 #13 amendment). The
313
+ transactional context-manager surface (dispatch/`request`, pull-and-run, handover) and the
314
+ graph-write helpers stay Python-only, not REST-exposed.
315
+ - `"server"` — wraps no Resource. CRUD/`execute` themselves are the REST surface (e.g. a
316
+ future data-serving "product server"). Not yet implemented, and deliberately still reserved: a
317
+ graph-*consuming* participant (a planner, a mobile robot, a controller) is a resource-mode
318
+ planner with its own Resource, not a server (ADR 0005, #32 amendment).
319
+ - `"watchdog"` — wraps no Resource, exposes little to no REST surface; runs a sweep loop
320
+ that removes stale `svc:address`/`svc:endpoint` triples left by resource-mode instances
321
+ that stopped heartbeating.
322
+ _Avoid_: Deployment type, Role (Mode is specifically the constructor discriminator).
323
+
324
+ **Heartbeat**:
325
+ A resource-mode Service's periodic re-assertion of its own liveness — refreshing
326
+ `svc:lastHeartbeat` on its Service individual via an internal interval-based Workflow. Read
327
+ by watchdog-mode instances to decide staleness.
328
+
329
+ **Address vs. Endpoint**:
330
+ `svc:address` is a Service's base URL, set on startup and removed on
331
+ deregistration/staleness. `svc:endpoint` is the full, directly callable URL for one specific
332
+ Workflow or StateProperty, also set on startup and removed on deregistration/staleness. Both
333
+ being present is a deliberate divergence from the paper's literal "address on Service only"
334
+ wording — see the "Address and endpoint" section of
335
+ `docs/mechanics/02-workflow-registration.md`.
336
+ _Avoid_: URL, endpoint URL used interchangeably for both — they are distinct properties on
337
+ distinct entity types.
338
+
339
+ **KnowledgeGraphConnector**:
340
+ An `transitional_sync_middleware`-protocol `Connector` (`connect`/`disconnect`/`provide`/`consume`) whose
341
+ `provide`/`consume` wrap `kapps_ogm.OGM.fetch`/`commit`. The mechanism by which any
342
+ `transitional_sync_middleware` construct (workflows, synced connectors) can read/write the knowledge graph
343
+ without going around the OGM's validated write path. See "The graph-facing connector"
344
+ in `docs/mechanics/04-connector-binding.md`.
345
+
346
+ **Deregistration**:
347
+ The reverse of registration: on shutdown (or, for watchdog-mode-detected staleness), removing
348
+ a Service's `svc:address` and its Workflows'/StateProperties' `svc:endpoint` triples while
349
+ preserving the individuals themselves, for provenance.
@@ -0,0 +1,38 @@
1
+ # Re-export full transitional_sync_middleware public API.
2
+ # As each layer is migrated locally, swap the import below to point at the
3
+ # local implementation instead of transitional_sync_middleware.
4
+ from transitional_sync_middleware import (
5
+ Middleware,
6
+ AasMiddleware,
7
+ Reference,
8
+ Identifier,
9
+ DataModel,
10
+ DataModelRebuilder,
11
+ AAS,
12
+ Submodel,
13
+ SubmodelElementCollection,
14
+ Blob,
15
+ File,
16
+ formatting,
17
+ connectors,
18
+ )
19
+ from kapps_semantic_middleware.middleware import SemanticMiddleware
20
+ from kapps_semantic_middleware.modes import Mode
21
+
22
+ __all__ = [
23
+ "SemanticMiddleware",
24
+ "Mode",
25
+ "Middleware",
26
+ "AasMiddleware",
27
+ "Reference",
28
+ "Identifier",
29
+ "DataModel",
30
+ "DataModelRebuilder",
31
+ "AAS",
32
+ "Submodel",
33
+ "SubmodelElementCollection",
34
+ "Blob",
35
+ "File",
36
+ "formatting",
37
+ "connectors",
38
+ ]