Semantic registry validation

- Generated registry validation always re-renders recorded semantics with the current fixer
- Validation canonicalizes the rendering like the installer
- Renderer changes need no version bump
- The renderer identity SPI, both identity strings and the identity-based source preference are gone
- New captures record the fixer class as an informational renderer identity
- The v1 history format is unchanged
- The modded forced datapack, world check and datapack command use the runtime data version
- That is the same data version validation uses
- Tests cover a renderer change under an unchanged identity
- Tests cover 26.3 bare natural_mob_spawns history against the overlay rendering
- Tests cover semantic round trips for every supported fixer
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-29 05:41:18 -04:00
1 parent aa428c0383
commit 3e4a2432bc
16 files changed
+409 -207

No files matched your search

@@ -55,7 +55,7 @@ public final class BukkitBinding implements INMSBinding {
@Override
public PlatformGenerationRegistry generationRegistry() {
return new BukkitGenerationRegistry(registry, "bukkit-generation-registry-v1", generation.generationRendererIdentity());
return new BukkitGenerationRegistry(registry, "bukkit-generation-registry-v1");
}
@Override
@@ -9,13 +9,11 @@ import java.util.Objects;
public record BukkitGenerationRegistry(
NativeGenerationRegistry registry,
String runtimeIdentity,
String generatedDefinitionRendererIdentity
String runtimeIdentity
) implements PlatformGenerationRegistry {
public BukkitGenerationRegistry {
registry = Objects.requireNonNull(registry, "registry");
runtimeIdentity = requireText(runtimeIdentity, "runtimeIdentity");
generatedDefinitionRendererIdentity = requireText(generatedDefinitionRendererIdentity, "rendererIdentity");
}
@Override
@@ -552,15 +552,14 @@ public final class GenerationRegistryContractFactory {
for (Map.Entry<GenerationRegistryContract.PhysicalResourceKey, GenerationRegistryContract.GeneratedSource>
entry
: requiredContract.generatedSources().entrySet()) {
GenerationRegistryContract.GeneratedSource previous = generatedSources.get(entry.getKey());
GenerationRegistryContract.GeneratedSource previous = generatedSources.putIfAbsent(
entry.getKey(),
entry.getValue()
);
if (previous != null && !sameGeneratedSemantic(previous, entry.getValue())) {
throw new IOException("Retained generation epochs require conflicting generated sources for "
+ entry.getKey().registryKey() + " / " + entry.getKey().resourceKey() + ".");
}
generatedSources.put(
entry.getKey(),
preferredGeneratedSource(previous, entry.getValue(), registry)
);
}
}
Map<GenerationRegistryContract.PhysicalResourceKey, String> captured = new TreeMap<>();
@@ -596,18 +595,6 @@ public final class GenerationRegistryContractFactory {
return GenerationRegistryContract.fromDefinitions(captured);
}
public static String requireGeneratedSource(
GenerationRegistryContract contract,
GenerationRegistryContract.PhysicalResourceKey key
) throws IOException {
return requireGeneratedSource(
contract,
key,
DataVersion.getRuntime().get(),
IrisPlatforms.get().registries().generationRegistry()
);
}
public static String renderGeneratedSource(
GenerationRegistryContract contract,
GenerationRegistryContract.PhysicalResourceKey key,
@@ -622,68 +609,9 @@ public final class GenerationRegistryContractFactory {
+ requiredKey.registryKey() + " / " + requiredKey.resourceKey() + ".");
}
requireGeneratedSemanticFingerprint(requiredKey, source, requiredContract.definitions().get(requiredKey));
String sourceFingerprint = fingerprintDefinition(requiredKey,
PlatformGenerationRegistry.Definition.exactJson(source.sourceJson()), "generated");
if (!source.renderedDefinitionSha256().equals(sourceFingerprint)) {
throw new IOException("Historical generated registry source changed for "
+ requiredKey.registryKey() + " / " + requiredKey.resourceKey() + ".");
}
return renderGeneratedSemantic(requiredKey, source, requiredFixer);
}
public static String requireGeneratedSource(
GenerationRegistryContract contract,
GenerationRegistryContract.PhysicalResourceKey key,
IDataFixer fixer
) throws IOException {
return requireGeneratedSource(
contract,
key,
fixer,
IrisPlatforms.get().registries().generationRegistry()
);
}
public static String requireGeneratedSource(
GenerationRegistryContract contract,
GenerationRegistryContract.PhysicalResourceKey key,
PlatformGenerationRegistry generationRegistry
) throws IOException {
return requireGeneratedSource(
contract,
key,
DataVersion.getRuntime().get(),
generationRegistry
);
}
public static String requireGeneratedSource(
GenerationRegistryContract contract,
GenerationRegistryContract.PhysicalResourceKey key,
IDataFixer fixer,
PlatformGenerationRegistry generationRegistry
) throws IOException {
GenerationRegistryContract requiredContract = Objects.requireNonNull(contract, "contract");
GenerationRegistryContract.PhysicalResourceKey requiredKey = Objects.requireNonNull(key, "key");
IDataFixer requiredFixer = Objects.requireNonNull(fixer, "fixer");
PlatformGenerationRegistry registry = Objects.requireNonNull(
generationRegistry,
"generationRegistry"
);
GenerationRegistryContract.GeneratedSource source = requiredContract.generatedSources().get(requiredKey);
if (source == null) {
throw new IOException("Historical generated registry source is missing for "
+ requiredKey.registryKey() + " / " + requiredKey.resourceKey() + ".");
}
String expectedFingerprint = requiredContract.definitions().get(requiredKey);
if (expectedFingerprint == null) {
throw new IOException("Historical generated registry definition is missing for "
+ requiredKey.registryKey() + " / " + requiredKey.resourceKey() + ".");
}
requireGeneratedSemanticFingerprint(requiredKey, source, expectedFingerprint);
return canonicalGeneratedDefinition(requiredKey, source, requiredFixer, registry).value();
}
public static String fingerprintDefinition(
GenerationRegistryContract.PhysicalResourceKey key,
PlatformGenerationRegistry.Definition definition,
@@ -783,7 +711,7 @@ public final class GenerationRegistryContractFactory {
DIMENSION_TYPE_EFFECTIVE_SOURCE_SCHEMA,
GenerationEpochContractFactory.fingerprintDimensionType(dimension.getDimensionType()),
GenerationEpochContractFactory.dimensionTypeSemanticJson(dimension.getDimensionType()),
registry.generatedDefinitionRendererIdentity(),
rendererIdentity(fixer),
canonicalDefinition
);
}
@@ -833,7 +761,7 @@ public final class GenerationRegistryContractFactory {
CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA,
generated.semanticFingerprint(),
generated.semanticJson(),
registry.generatedDefinitionRendererIdentity(),
rendererIdentity(fixer),
generated.definition()
);
Set<String> tags = biomeTags.computeIfAbsent(generated.physicalKey().resourceKey(), ignored -> new TreeSet<>());
@@ -882,7 +810,7 @@ public final class GenerationRegistryContractFactory {
CUSTOM_BIOME_EFFECTIVE_SOURCE_SCHEMA,
generated.semanticFingerprint(),
generated.semanticJson(),
registry.generatedDefinitionRendererIdentity(),
rendererIdentity(fixer),
generated.definition()
);
}
@@ -1087,32 +1015,9 @@ public final class GenerationRegistryContractFactory {
}
}
private static GenerationRegistryContract.GeneratedSource preferredGeneratedSource(
GenerationRegistryContract.GeneratedSource previous,
GenerationRegistryContract.GeneratedSource candidate,
PlatformGenerationRegistry registry
) {
if (previous == null) {
return candidate;
}
String currentRenderer = requireText(
registry.generatedDefinitionRendererIdentity(),
"Generated registry renderer identity"
);
boolean previousMatches = previous.rendererIdentity().equals(currentRenderer);
boolean candidateMatches = candidate.rendererIdentity().equals(currentRenderer);
if (previousMatches != candidateMatches) {
return candidateMatches ? candidate : previous;
}
return generatedSourceOrder(candidate).compareTo(generatedSourceOrder(previous)) < 0
? candidate
: previous;
}
private static String generatedSourceOrder(GenerationRegistryContract.GeneratedSource source) {
return source.rendererIdentity() + '\u0000'
+ source.renderedDefinitionSha256() + '\u0000'
+ source.sourceJson();
// Recorded for diagnostics only. Validation always re-renders the semantics with the current fixer.
private static String rendererIdentity(IDataFixer fixer) {
return fixer.getClass().getName();
}
private static boolean sameGeneratedSemantic(
@@ -1130,36 +1035,19 @@ public final class GenerationRegistryContractFactory {
IDataFixer fixer,
PlatformGenerationRegistry registry
) throws IOException {
String currentRenderer = requireText(
registry.generatedDefinitionRendererIdentity(),
"Generated registry renderer identity"
);
String renderedSource;
if (source.rendererIdentity().equals(currentRenderer)) {
renderedSource = source.sourceJson();
} else {
renderedSource = renderGeneratedSemantic(key, source, fixer);
}
PlatformGenerationRegistry.Definition definition;
String renderedSource = renderGeneratedSemantic(key, source, fixer);
try {
definition = registry.canonicalDefinition(
PlatformGenerationRegistry.Definition definition = registry.canonicalDefinition(
key.registryKey(),
key.resourceKey(),
renderedSource
);
requireGeneratedDefinition(definition, "historical generated registry definition");
return definition;
} catch (RuntimeException failure) {
throw new IOException("Historical generated registry source is incompatible for "
+ key.registryKey() + " / " + key.resourceKey() + ".", failure);
}
if (source.rendererIdentity().equals(currentRenderer)) {
String renderedFingerprint = fingerprintDefinition(key, definition, "generated");
if (!source.renderedDefinitionSha256().equals(renderedFingerprint)) {
throw new IOException("Historical generated registry source changed for "
+ key.registryKey() + " / " + key.resourceKey() + ".");
}
}
return definition;
}
private static String renderGeneratedSemantic(