findAll() {
+ return executeSubgraph(EmploymentSummary.class,
+ g().V().hasLabel("Person").as("person")
+ .outE(WorksAt.LABEL).as("job")
+ .inV().as("employer")
+ .select("person", "job", "employer")
+ );
+ }
+}
+```
+
+---
+
+## Inheritance
+
+### Single-parent
+
+Simply extend the parent Clotho class.
+
+```java
+@VertexType("Manager")
+public class Manager extends Employee { ... }
+```
+
+### Diamond (multi-parent) inheritance
+
+ArcadeDB supports multi-parent inheritance that Java cannot express. Use `additionalParents` to declare extra ArcadeDB parent labels.
+
+```java
+@VertexType(value = "SeniorManager", additionalParents = {"Auditor"})
+public class SeniorManager extends Manager { ... }
+```
+
+### Divergent Java/ArcadeDB hierarchy
+
+When the ArcadeDB parent label differs from the Java parent class label, use `parentLabel`.
+
+```java
+@VertexType(value = "ContractEmployee", parentLabel = "Employee")
+public class ContractEmployee extends Person { ... }
+```
+
+---
+
+## Without Spring
+
+Construct `ClothoSessionFactory` manually using its builder.
+
+```java
+Cluster cluster = Cluster.build()
+ .addContactPoint("localhost")
+ .port(2480)
+ .credentials("root", "playwithdata")
+ .create();
+
+DriverRemoteConnection connection = DriverRemoteConnection.using(cluster, "g");
+GraphTraversalSource g = AnonymousTraversalSource.traversal().withRemote(connection);
+
+TypeRegistry registry = TypeRegistry.scan("com.example.myapp.model");
+
+ClothoSessionFactory sessionFactory = ClothoSessionFactory.builder()
+ .g(g)
+ .registry(registry)
+ .unknownTypeBehavior(UnknownTypeBehavior.STRICT)
+ .build();
+
+// Use sessionFactory ...
+
+// Shutdown
+sessionFactory.close();
+connection.close();
+cluster.close();
+```
+
+---
+
+## Important Limitations
+
+- **No DDL generation.** Clotho is schema-first. Create and migrate your database schema externally.
+- **Not thread-safe.** Sessions bind to the current thread via `ThreadLocal`. Use one session per thread (or per virtual thread).
+- **No transparent proxies.** Lazy loading requires an explicit `session.loadField()` call. There is no bytecode enhancement.
+- **No automatic cascade delete.** Removing an entity from an `@Include` collection does not delete it from the graph. Call `session.delete()` explicitly.
+- **Manual setters required.** `@Property`, `@Include`, `@OutVertex`, `@InVertex`, and `@Version` fields must use hand-written setters that call `notifyFieldChanged()`. Lombok `@Setter` must not be used on those fields.
+- **OrientDB transactions.** In `orient-transactional=false` mode, `openTransactionalSession()` and `@Transactional` work at per-request granularity only. No multi-request rollback is available.
+
+---
+
+## Further Reading
+
+| Document | Contents |
+|---|---|
+| `docs/TYPE_SYSTEM.md` | Entity hierarchy, annotation reference, inheritance, result mapping |
+| `docs/OBJECT_BOUNDARY.md` | `@Include`/`@Via`, multi-hop, edge-only inclusion, cycle detection |
+| `docs/ANNOTATIONS.md` | Complete annotation reference with all attributes |
+| `docs/QUERY_MODEL.md` | Repository pattern, `@SubgraphType` assembly, return type rules |
+| `docs/SPRING_INTEGRATION.md` | Autoconfigure, `@Transactional`, full `application.properties` reference |
+| `docs/TRANSACTIONS.md` | Transactional sessions, Spring `@Transactional`, `@Version` interaction, ArcadeDB/OrientDB limitations |
+| `docs/ARCHITECTURE.md` | Module breakdown, data flow, session identity map, package structure |
diff --git a/clotho-core/src/main/java/com/binarygolem/clotho/model/VersionGuard.java b/clotho-core/src/main/java/com/binarygolem/clotho/model/VersionGuard.java
new file mode 100644
index 0000000..4d2063b
--- /dev/null
+++ b/clotho-core/src/main/java/com/binarygolem/clotho/model/VersionGuard.java
@@ -0,0 +1,50 @@
+package com.binarygolem.clotho.model;
+
+/**
+ * A manual version guard for version-checked writes on graph elements that are not
+ * mapped to a registered Clotho entity class — such as {@link GenericVertex},
+ * {@link GenericEdge}, or any vertex/edge from an unregistered label.
+ *
+ * Use the factory methods {@link #onVertex} and {@link #onEdge} for clarity.
+ * The default version property name ({@value #DEFAULT_VERSION_PROPERTY}) matches
+ * the default value of {@code @Version}.
+ *
+ *
Example — version-checked write on an unregistered vertex:
+ *
{@code
+ * // Read the version from the graph
+ * long v = (long) executeRaw(g().V(id.asString()).values("_version")).get(0);
+ *
+ * session.executeUpdate(
+ * g().V(id.asString()).property("status", "archived"),
+ * VersionGuard.onVertex(id, v)
+ * );
+ * }
+ *
+ * After a successful {@code executeUpdate}, the version in the graph is
+ * {@code expectedVersion + 1}. This guard instance is consumed — create a new one
+ * (or reload the entity) before the next write.
+ */
+public record VersionGuard(ARID id, String versionProperty, long expectedVersion, boolean vertex) {
+
+ public static final String DEFAULT_VERSION_PROPERTY = "_version";
+
+ /** Guard on a vertex using the default {@value #DEFAULT_VERSION_PROPERTY} property. */
+ public static VersionGuard onVertex(ARID id, long expectedVersion) {
+ return new VersionGuard(id, DEFAULT_VERSION_PROPERTY, expectedVersion, true);
+ }
+
+ /** Guard on a vertex using a custom version property name. */
+ public static VersionGuard onVertex(ARID id, String versionProperty, long expectedVersion) {
+ return new VersionGuard(id, versionProperty, expectedVersion, true);
+ }
+
+ /** Guard on an edge using the default {@value #DEFAULT_VERSION_PROPERTY} property. */
+ public static VersionGuard onEdge(ARID id, long expectedVersion) {
+ return new VersionGuard(id, DEFAULT_VERSION_PROPERTY, expectedVersion, false);
+ }
+
+ /** Guard on an edge using a custom version property name. */
+ public static VersionGuard onEdge(ARID id, String versionProperty, long expectedVersion) {
+ return new VersionGuard(id, versionProperty, expectedVersion, false);
+ }
+}