feat(metamodel): observed-schema and diff contracts - #85
Merged
Merged
Conversation
This was referenced Aug 30, 2026
jimador
force-pushed
the
feat/metamodel-diff
branch
from
August 31, 2026 00:49
920cf09 to
95ca07c
Compare
jimador
force-pushed
the
feat/metamodel-diff
branch
2 times, most recently
from
August 31, 2026 05:23
03fee4b to
3783d80
Compare
jimador
commented
Aug 31, 2026
jimador
force-pushed
the
feat/metamodel-diff
branch
2 times, most recently
from
August 31, 2026 19:40
cd3a1d7 to
59b9e89
Compare
jimador
marked this pull request as ready for review
September 1, 2026 04:07
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 2, 2026 10:51
747641a to
add28d0
Compare
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 2, 2026 14:11
add28d0 to
28031df
Compare
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 2, 2026 20:52
28031df to
e71e04d
Compare
jimador
commented
Sep 2, 2026
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 3, 2026 21:04
e71e04d to
2d5d072
Compare
igordayen
reviewed
Sep 4, 2026
|
from Codex:
Example:
Expected result:
What the code does:
For this example, those checks are:
They do not include the declared former name Person. Conclusion:
Example:
Expected result:
What the code does:
For this example, those checks are:
Neither matches the actual observed label Person. Conclusion:
|
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 4, 2026 03:15
2d5d072 to
8d4aaf4
Compare
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 4, 2026 03:47
8d4aaf4 to
a962035
Compare
Collaborator
Author
|
Answering the Codex findings. Finding 1 is right and is fixed in
|
igordayen
approved these changes
Sep 4, 2026
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 7, 2026 03:35
6897662 to
4af1e0e
Compare
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 8, 2026 20:30
4af1e0e to
9164ff1
Compare
ObservedSchema/ObservedSchemaSource SPI for what a live graph actually holds, and a signature-aware diff layer: PropertySignatureChanged reports a reshaped property as one entry, the declared set for drift carries the full label closure so inherited labels never read as drift, and every diff value object is JVM-immutable at the public boundary. Refs #45; stacks on feat/metamodel-version-store.
Comment and doc text only; no code change.
Three new sealed changes: EntityTypeRenamed, PropertyRenamed, and EntityTypeAliasesChanged. Pairing is sorted and one-to-one at both levels, runs on the residual sets after name matching, and folds a rename's own propagation — the name-implied label swap, the implied alias entry, and referrer signatures and child labels compared modulo the paired rename map, REFERENCE kinds only — into the rename itself, so a pure rename reads as one change and nothing else. Alias-only deltas keep the empty-diff-iff-equal-hash contract at both levels, and a renamed type's accumulated former names join the declared side of the observed diff.
Pairing now requires an exclusive claim on both sides. Claims read as a bipartite graph: a connected one-to-one group is a rename, anything larger is contested — reported whole through two new sealed members and fallen back to plain removed and added, never fed to modulo-rename substitution. Report rather than throw, because both sides of a diff are stamped persisted values a caller cannot edit. Typed accessors round out the change list (the ambiguity pair, added and removed relationships), with an eleven-change partition fixture that makes every accessor load-bearing.
DeclaredSchema.from filtered the dictionary down to the governed types before the observed comparison ever ran, so a type the host declared and deliberately left outside governance arrived looking exactly like a type nobody declared. The runner read it as a removal and the quarantine policy could mark its propositions STALE — the opposite of what per-type governance promises. The declaration now carries the known-but-ungoverned type and relationship names beside the governed stamp, as a third set distinct from the governed names and from declared aliases, and diffAgainstObserved excludes them. A selector governing no types now enforces no types: every dictionary type stays ungoverned and only a name the dictionary never declared reads as drift. Refs #45; stacks on feat/metamodel-version-store.
Declared entity type names are JVM FQNs while observed mention types arrive as bare simple names, so diffAgainstObserved reported every declared type unobserved and could misread a same-named observation. DeclaredSchema now derives entityTypeOwnLabels and the differ matches either spelling, reporting drift under declared names. Ungoverned and former names get the same treatment, proven by discriminating tests. TypeIdentity ships as a spec-only SPI for mapping external type systems onto declared identity: interface, contract KDoc and an OpenAPI example, with no production references.
The drift side already treats a declared former name as known, so nodes still labelled with a renamed type's old name are not drift. The unobserved bucket only looked for the current name and its label, so the same graph read as "no drift" and "type unobserved" at once. A type whose data still carries its old label is observed, and both sides now say so.
jimador
force-pushed
the
feat/metamodel-diff
branch
from
September 8, 2026 20:47
9164ff1 to
e280a11
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR 3 of the metamodel train, stacked on #84. This slice defines the two comparisons the governance loop runs on.
ObservedSchema/ObservedSchemaSourceis the SPI a storage backend implements to report what a live graph actually contains — names only, since a graph exposes labels and relationship types without signatures.MetamodelDiff/MetamodelChangeis a sealed change taxonomy: a property that kept its name and changed type, cardinality or kind reports as onePropertySignatureChangedentry.MetamodelDiffercompares two declared versions;DeclaredObservedDiffercompares a declaration against the graph. Declared renames pair in the diff (EXPERIMENTAL):EntityTypeRenamed,PropertyRenamedandEntityTypeAliasesChangedare new sealed members, pairing is one-to-one on the residual sets after name matching, comparison substitutes old-for-new in reference targets and label sets only, and a pure rename yields one change. Accumulated former names join the declared side of the observed diff, so old-labeled data under a declared rename is never drift.Changed in this review round:
JvmType.nameis a fully qualified class name while the graph reports simple labels, so a healthy FQN schema previously came back both unobserved and drifted at once.DeclaredSchema.entityTypeOwnLabels(viaDeclaredSchema.ownLabelOf) fixes both directions; reported spellings stay as the stamp declares them.TypeIdentityadded (EXPERIMENTAL): the contract for mapping external type spellings onto declared names, with no implementation and no wiring yet.Breaking changes: one stated source-level exception —
MetamodelChangeis sealed and gains members, so an external exhaustivewhenover it needs new branches. Binary compatibility is untouched; consumers recompile. Everything else is additive.The two comparisons. Declared-vs-declared answers what a schema edit did; declared-vs-observed answers what the live graph holds that the declaration does not cover:
flowchart TD A[DeclaredSchema, version n] --> B[MetamodelDiffer.diff] C[DeclaredSchema, version n-1] --> B B --> D[declared vs declared: renames paired, signatures compared modulo renames] A --> E[DeclaredObservedDiffer.diffAgainstObserved] F[ObservedSchema, names only from a live graph] --> E E --> G[declared vs observed: undeclared types and relationship names, judged by own label] D --> H[MetamodelDiff: sealed MetamodelChange taxonomy] G --> HNext in stack: #86 drift checking and quarantine.