feat(metamodel): schema versioning core with per-type governance - #83
Conversation
fbbecf3 to
db80548
Compare
|
from claude - 1st batch: |
|
feedback from Claude: Overall: Well-engineered. The design decisions are sound and thoroughly documented. A few issues worth
MetamodelVersion.equals includes schemaName. contentHash and hasSameContentAs exclude it by design. This
requireNoTypeAliasReuse and requireNoAliasesOnDuplicateNames are called in both the from() factory and the
The interface KDoc acknowledges this: "reads the whole history to answer a keyed question." But there's no
The same reason applies: java.util.Set.copyOf() in the constructor body means a data class wouldn't
val entityTypeProperties = governedTypes entityTypeLabels does a separate .groupBy { it.name }. Both traverse governedTypes and group by the same
MetamodelVersion.toString() concatenates 7 fields with +. In Kotlin this should be a multiline string
The hash is computed as hashInput.toByteArray(Charsets.UTF_8) — explicitly UTF-8. Good. The KDoc says Summary: The core correctness invariants (length-prefixed hash encoding, immutable collections, |
|
i just realized that hashcode includes already schema name, therefore asked claude to validate: why search by (name, hashcode), is not hashcode sufficient? ● Good catch. If contentHash already excludes schemaName and is a SHA-256 of the full structural content, The only scenario where schemaName adds value as part of the key is if you want to scope history queries Worth raising: either drop schemaName from the store's natural key and keep it only as a query dimension, |
|
Went through all three. The table in the first batch is covered by the thread replies. On the second batch and the key question: 1. 2. Validation in two places. Real. The two refusals now run through one 3. 4. Manual 5. Two traversals in 6. 7. UTF-8. Agreed, and the golden-digest test is what pins it. Is the schema name redundant in the key? No, and the store KDoc now says why. |
f950f24 to
9d32ead
Compare
New dice-metamodel module, first slice of the metamodel train: MetamodelVersion content-hash stamping over governed types only (GovernedTypeSelector — you version what you declare), property signatures (name, kind, type, cardinality) in the fingerprint, JVM-immutable value types, DeclaredSchema/DeclaredSchemaSource as the opt-in seam, and the MetamodelVersionStore contract. Pure JVM — no Spring, no database, no dice-core dependency. Refs #45.
Rewrite the design doc, KDoc, and comments from this slice in the repo's documentation voice. Comment and doc text only; no code change.
Declared renames enter as aliases on property signatures and entity types, hashed only when non-empty so every existing stamp keeps its digest, guarded against reuse collisions and duplicate-name ambiguity, and defensively copied down to the alias set inside each signature. Stamps also carry optional origin and last-stamped provenance, capped and never hashed. Prior art: Iceberg field identity adapted to declaration-time aliases, Snowflake's schema-evolution record adapted to two provenance pairs.
The key existed with nothing specifying who writes it or what the value means. The KDoc now states the contract: the extraction persistence path stamps the declared schema's content hash onto canonical proposition metadata, a missing key marks pre-governance extraction, and the value is opaque. A test proves the stamp round-trips through the in-memory store and propositions are selectable by it. Production stamping lands in a follow-up slice once the extraction-run stack merges.
The metadata key was a second mechanism for one fact: nothing wrote dice.metamodel.version, while the extraction-run stack carries the declared schema's content hash on the run record. The key and its stamping contract go. A proposition's schema version is answered through the run that produced it, the coordinator resolves the hash from the host's DeclaredSchemaSource, and a denormalized per-proposition copy stays a coordinator concern for a slice whose reads demand it. The versioning doc also states the scope rule: versions are application-wide while drift reports and observation are per context.
The new public types in dice-metamodel carry ApiStatus.Experimental, so a consumer sees in the IDE what the changelog says in prose: the shape may change before 1.0.
Manage org.jetbrains:annotations in dice-parent. Neither embabel BOM manages it, so each module pinned its own version and the two had already drifted apart. Simplify the alias comparator. Name the Java compatibility tests in sentences, which is what @DisplayName is for.
Cross-reference equals and hasSameContentAs, since one compares the schema name and the other does not. Run the two declaration refusals through one requireDeclarable so the constructor and from cannot drift. Group governed types by name once. Say why the store key carries the schema name: two schemas with the same shape share a hash, and history is per schema. Say when findVersion's default is enough and when to override it. A design note explains why three classes write their own equals.
9d32ead to
e423a0a
Compare
PR 1 of the metamodel train (refs #45).
dice-metamodelis a new pure-JVM module holding the schema-versioning contracts:MetamodelVersion, a content-addressed stamp of a declared schema whose fingerprint covers entity types, labels, full property signatures and relationships, canonically ordered and length-prefixed, with a derived, unforgeablecontentHash;GovernedTypeSelectorfor per-type opt-in — you version what you declare, and ungoverned exploratory types never perturb a governed stamp;DeclaredSchema/DeclaredSchemaSource; and theMetamodelVersionStorecontract. Declared renames rideSchemaAliases(EXPERIMENTAL): a declaration states the names a type or property used to go by, so a later diff pairs a rename and stops reading it as remove+add. Aliases hash only when non-empty, so every pre-aliascontentHashis byte-stable, pinned by golden-digest tests.Changed in this review round:
DiceMetadataKeys.METAMODEL_VERSIONmetadata key is removed; the extraction run carries the declared schema'scontentHashand per-proposition attribution resolves throughPRODUCED_BY_RUN.docs/design/metamodel-versioning.mdstates the mechanism and that the coordinator may write a denormalised copy once it exists.ContextId.StampProvenancestayed out: nothing constructs a value outside deserialization, so the type returns with the first stamping caller that records it.Breaking changes: none on any released surface. The module is new;
@JvmOverloadsand separate overloads keep every previously shipped JVM descriptor byte-identical. The removed metadata key existed only inside this stack. The changed Kotlin data-class synthetics onPropertySignatureare the stated boundary: Kotlin callers recompile.Opt-in: declaring a
DeclaredSchemaSourcebean is the trigger for everything downstream; with no declared schema, nothing in this train activates.The governance model at a glance. A type reaches the stamp only by being declared and selected:
flowchart LR A[DeclaredSchemaSource bean] --> C[DeclaredSchema] B[GovernedTypeSelector] --> C C --> D[MetamodelVersion] D --> E[entityTypeNames] D --> F[entityTypeLabels, full closure] D --> G[property signatures: name, kind, type, cardinality] D --> H[entityTypeAliases, former names] D --> I[relationships, attributed to declaring type] E --> J[contentHash: derived, unforgeable] F --> J G --> J H --> J I --> J K[ungoverned exploratory types] -. excluded .-> JStack: #84 (Drivine version store) and the later slices are stacked on this branch.