feat(dice-storage): Drivine-backed MetamodelVersionStore - #84
Conversation
a51b703 to
2d03ad0
Compare
2d03ad0 to
18575bd
Compare
294455a to
14780fc
Compare
14780fc to
7bcea0d
Compare
|
@jimador - Claude review: PR #84 Review — DrivineMetamodelVersionStore Overall: The design is solid. Sequence counter, MERGE idempotence, integrity check on read, alias backward Design
The class is open with no explanation. If it's open for Spring CGLIB proxy generation (required for
As noted on #83: contentHash alone is a SHA-256 of the full structural content — it's globally unique by
AbstractMetamodelVersionStoreContractTest has only 2 tests — upsert idempotence and ordering. Missing
These are currently tested only in the Neo4j integration test, meaning an alternative implementation can
The KDoc acknowledges that a concurrent counter increment can fail with a constraint violation on MetamodelRowMappers.kt
Igor's comment is valid. The project has EmbabelObjectMapperHolder for exactly this reason — neutral to
private fun deserializeList(serialized: String): List = An empty JSON list serializes as "[]", not "". The isEmpty() branch only fires if the node holds a literal
persistenceManager.query(spec).filterIsInstance<Map<*, *>>().mapNotNull { row -> Rows that aren't Map<*, *> are dropped without logging. If Drivine returns an unexpected type (node
In deserializeMapOfSignatureSets and deserializeMapOfLabelSets, @Suppress is on the function declaration InMemoryMetamodelVersionStore
val at = saved.indexOfFirst { ... } at is an index (-1 = not found). idx or existingIndex is clearer.
override fun versionHistory(schemaName: String): List = This is correct — the lock covers the filter and the copy before returning. However, latestVersion calls Igor's comment assessment |
7bcea0d to
953cc08
Compare
953cc08 to
0618b95
Compare
|
Answered in 3efcc7c and 80fb127 on this branch. The stack above is restacked on it and the full reactor is green at the top.
|
80fb127 to
f0bd7e1
Compare
f0bd7e1 to
a4cfaa1
Compare
Persist MetamodelVersion stamps in Neo4j: idempotent MERGE on the (schemaName, contentHash) natural key, findVersion by hash, and history ordered by a persisted per-schema sequence rather than wall-clock time — a MetamodelVersion(schemaName, sequence) uniqueness constraint makes a duplicate position unstorable, so write-order corruption is loud and retryable. Property signatures serialize as deterministic sorted JSON. Introduce the shared Neo4jTestContainer and adopt it across the module's Spring Boot ITs. Refs #45; stacks on feat/metamodel-versioning.
Comment and doc text only; no code change.
…store Origin is taken by the first save that carries one and never moved; lastStamped moves only on a non-null incoming value; both rules are coalesce expressions inside the existing MERGE, so a routine re-stamp with no provenance rewrites neither. Alias fields serialize only when non-empty and decode absent-as-empty, so an alias-free stamp writes byte-identical node properties to the pre-alias writer and every old row reads back through the strict hash recompute. The in-memory reference store implements the same contract, proven by one shared suite against both backends.
The attribution rework removed the metadata key and recorded that as breaking; the entry describing the key as added survived a rebase two lines below it. One record remains: the key is gone and run lineage answers attribution.
Name the upsert index for what it is. Use require and requireNotNull where the row mapper was throwing IllegalArgumentException by hand; same exception, same messages.
Drop the redundant open modifier, since the module already applies the allopen spring plugin. Log and skip a row that is not a map in place of dropping it silently. Document the retry contract for a lost counter update on both the interface and the Drivine store. Read JSON maps through TypeReference so the unchecked casts go, and let an empty stored string fail the read as the corruption it is. Answer latestVersion in one pass under the lock. Grow the contract suite to cover a missed lookup, newest-first ordering, schema isolation, and an empty schema.
The allopen spring preset only opens a class that carries a Spring annotation itself, and this store annotated its methods alone, which is why it needed a hand-written open. Put @transactional on the class like the other Drivine stores and let the reads keep their readOnly override.
a4cfaa1 to
842203f
Compare
Breaking changes: none. Additive — new classes, a new
dice-metamodeldependency indice-storage, and new Neo4j uniqueness constraints for hosts that adopt the store. No existing class or Cypher schema changed.Stacked on #83 (
feat/metamodel-versioning) — review that first; this goes ready-for-review once #83 lands. PR 2 of the metamodel train.What's in:
DrivineMetamodelVersionStore— Neo4j persistence for theMetamodelVersionStorecontract: MERGE-on-natural-key idempotent saves,latestVersion/versionHistory/findVersion, corrupt-row resilience (a bad newest row hides only itself).MetamodelVersion(schemaName, sequence)uniqueness constraint makes duplicate positions unstorable, so contention failures are loud and retryable. Hosts declare three constraints (documented in the class KDoc, design doc, and CHANGELOG).Map.copyOfiteration order is randomized per run — unsorted encoding would rewrite unchanged stamps).Neo4jTestContaineradopted by all@SpringBootTestITs in the module; 21 version-store ITs including concurrent-save (one node survives at 48 savers) and mixed-timestamp ordering.Tests:
dice-storage160/0/0,dice-metamodel120/0/0,dice1187/0/0. Reviewed by Codex (gpt-5.6-sol); ordering-contract finding fixed with the sequence + constraint design.Next in stack: PR3 observed schema + diff contracts.
Amendment (prior-art adoption, EXPERIMENTAL): the store now persists what the #83 amendment declared.
entityTypeAliasesand per-signaturealiasesserialize only when non-empty and decode absent-as-empty, so an alias-free stamp writes byte-identical node properties to the pre-alias writer and old-shape rows read back cleanly through the strict hash-integrity recompute — pinned by a raw-Cypher old-row test, a both-alias-kinds round-trip, and a dropped-alias-map rejection test.InMemoryMetamodelVersionStore(promoted todice-metamodelmain sources, following the repo'sInMemory*convention) andAbstractMetamodelVersionStoreContractTestrun one suite against both backends.Amendment tests:
dice-storage160/0/0,dice-metamodel120/0/0. Gates: adversarial Claude Fable PASS (coalesce corner semantics, byte-identity, and the store-promotion deviation all verified), Codex (gpt-5.6-sol) clean. Mutation checks confirm each guard bites (naive SET, dropped alias serialization, reverted in-memory rules each fail their exact tests).Review update (2d03ad0): stamp-provenance persistence is removed with the type (#83's review round): the coalesce assignments, the row-mapper provenance fields, the
saveVersioncontract clause, and the provenance tests. Aliases persistence, sequence ordering, the store promotion, and the old-shape readability test are untouched. Current tests:dice-storage134,dice-metamodel113.Why ordering is a persisted sequence. Wall-clock time makes duplicate positions storable and silent. A per-schema monotonic sequence with a uniqueness constraint makes them unstorable, so contention surfaces as a loud retryable failure.
sequenceDiagram participant H as Host participant S as DrivineMetamodelVersionStore participant N as Neo4j H->>S: saveVersion(stamp) S->>N: next per-schema sequence S->>N: MERGE on natural key Note over N: uniqueness on schemaName plus sequence alt position already taken N-->>S: constraint violation S-->>H: loud, retryable else position free N-->>S: stored S-->>H: version at sequence n end