diff --git a/.claude/rules/new-metaschema-feature-checklist.md b/.claude/rules/new-metaschema-feature-checklist.md
new file mode 100644
index 000000000..2402e4e8c
--- /dev/null
+++ b/.claude/rules/new-metaschema-feature-checklist.md
@@ -0,0 +1,61 @@
+# New Metaschema Feature Checklist
+
+When adding a new Metaschema model feature (e.g., a new instance type), systematically evaluate **all** areas below. Missing any area risks incomplete support.
+
+## Mandatory Areas
+
+### 1. Core Model
+
+- [ ] Create interface extending appropriate base (`IModelInstanceAbsolute`, etc.)
+- [ ] Add `ModelType` enum value if applicable
+- [ ] Update container model interfaces and implementations (`IContainerModelAssemblySupport`, `DefaultContainerModelAssemblySupport`, model builder)
+- [ ] Update visitor pattern: `IModelElementVisitor`, `AbstractModelElementVisitor`, and ALL implementing visitors
+- [ ] Write core model tests
+
+### 2. Databind Binding Layer
+
+- [ ] Create annotation in `databind/model/annotations/`
+- [ ] Create binding interface in `databind/model/` and implementation in `databind/model/impl/`
+- [ ] Create Metaschema binding implementation in `databind/model/metaschema/impl/`
+- [ ] Update `AssemblyModelGenerator` (and `ChoiceModelGenerator` if applicable)
+- [ ] Update class introspection (`DefinitionAssembly` or similar) to scan for new annotation
+
+### 3. Databind I/O (if feature handles data)
+
+- [ ] Update `MetaschemaXmlReader` and `MetaschemaXmlWriter`
+- [ ] Update `MetaschemaJsonReader` and `MetaschemaJsonWriter`
+- [ ] Create format-specific wrapper classes if needed
+- [ ] Write round-trip tests
+
+### 4. Code Generation
+
+- [ ] Create type info class in `databind/codegen/typeinfo/` following existing patterns
+- [ ] Update `AssemblyDefinitionTypeInfoImpl` to process the new instance type
+- [ ] Update `ITypeResolver` if definition resolution is needed
+- [ ] Write codegen tests (compile test module, verify generated annotations/fields)
+
+### 5. Schema Generation
+
+- [ ] Update XML Schema generator for XSD output
+- [ ] Update JSON Schema generator for JSON Schema output
+- [ ] Write schema generation tests for both formats
+
+### 6. Constraint Processing
+
+- [ ] Update `ConstraintComposingVisitor` visitor method
+- [ ] Determine if constraints can target the feature; if not, use `illegalTargetError()`
+
+## Conditional Areas
+
+| Area | When Needed | Key Files |
+|------|-------------|-----------|
+| Metapath/Query | Feature is queryable or affects traversal | `core/.../metapath/` |
+| Maven Plugin | New build configuration needed | `metaschema-maven-plugin/` |
+| CLI | New commands or output options | `metaschema-cli/`, `cli-processor/` |
+| Testing Module | Test infrastructure changes | `metaschema-testing/`, `unit-tests.yaml` |
+
+## Verification
+
+```bash
+mvn clean install -PCI -Prelease
+```
diff --git a/databind/src/main/java/dev/metaschema/databind/codegen/typeinfo/AnyInstanceTypeInfoImpl.java b/databind/src/main/java/dev/metaschema/databind/codegen/typeinfo/AnyInstanceTypeInfoImpl.java
new file mode 100644
index 000000000..e64cf2354
--- /dev/null
+++ b/databind/src/main/java/dev/metaschema/databind/codegen/typeinfo/AnyInstanceTypeInfoImpl.java
@@ -0,0 +1,73 @@
+/*
+ * SPDX-FileCopyrightText: none
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+package dev.metaschema.databind.codegen.typeinfo;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+
+import java.util.Set;
+
+import dev.metaschema.core.model.IAnyContent;
+import dev.metaschema.core.model.IAnyInstance;
+import dev.metaschema.core.model.IModelDefinition;
+import dev.metaschema.core.util.CollectionUtil;
+import dev.metaschema.databind.codegen.typeinfo.def.IAssemblyDefinitionTypeInfo;
+import dev.metaschema.databind.model.annotations.BoundAny;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Type information for generating a {@link BoundAny}-annotated field in a Java
+ * class corresponding to an {@link IAnyInstance} in a Metaschema assembly
+ * model.
+ *
+ * The generated field captures unmodeled content using the {@link IAnyContent}
+ * interface, allowing round-trip preservation of arbitrary properties not
+ * defined by the Metaschema model.
+ */
+public class AnyInstanceTypeInfoImpl
+ extends AbstractInstanceTypeInfo {
+
+ /**
+ * Constructs a new type information object for an any instance.
+ *
+ * @param instance
+ * the any instance
+ * @param parentDefinition
+ * the type information for the parent assembly definition containing
+ * this instance
+ */
+ public AnyInstanceTypeInfoImpl(
+ @NonNull IAnyInstance instance,
+ @NonNull IAssemblyDefinitionTypeInfo parentDefinition) {
+ super(instance, parentDefinition);
+ }
+
+ @Override
+ public String getBaseName() {
+ return "any";
+ }
+
+ @Override
+ public boolean isRequired() {
+ return false;
+ }
+
+ @Override
+ public TypeName getJavaFieldType() {
+ return ClassName.get(IAnyContent.class);
+ }
+
+ @Override
+ public Set buildField(
+ TypeSpec.Builder typeBuilder,
+ FieldSpec.Builder fieldBuilder) {
+ super.buildField(typeBuilder, fieldBuilder);
+ fieldBuilder.addAnnotation(BoundAny.class);
+ return CollectionUtil.emptySet();
+ }
+}
diff --git a/databind/src/main/java/dev/metaschema/databind/codegen/typeinfo/def/AssemblyDefinitionTypeInfoImpl.java b/databind/src/main/java/dev/metaschema/databind/codegen/typeinfo/def/AssemblyDefinitionTypeInfoImpl.java
index 9f210da45..ee766a1ff 100644
--- a/databind/src/main/java/dev/metaschema/databind/codegen/typeinfo/def/AssemblyDefinitionTypeInfoImpl.java
+++ b/databind/src/main/java/dev/metaschema/databind/codegen/typeinfo/def/AssemblyDefinitionTypeInfoImpl.java
@@ -14,6 +14,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import dev.metaschema.core.model.IAnyInstance;
import dev.metaschema.core.model.IAssemblyDefinition;
import dev.metaschema.core.model.IChoiceGroupInstance;
import dev.metaschema.core.model.IChoiceInstance;
@@ -23,6 +24,7 @@
import dev.metaschema.core.model.INamedModelInstanceAbsolute;
import dev.metaschema.core.util.CustomCollectors;
import dev.metaschema.core.util.ObjectUtils;
+import dev.metaschema.databind.codegen.typeinfo.AnyInstanceTypeInfoImpl;
import dev.metaschema.databind.codegen.typeinfo.IInstanceTypeInfo;
import dev.metaschema.databind.codegen.typeinfo.IModelInstanceTypeInfo;
import dev.metaschema.databind.codegen.typeinfo.INamedModelInstanceTypeInfo;
@@ -45,19 +47,29 @@ class AssemblyDefinitionTypeInfoImpl
public AssemblyDefinitionTypeInfoImpl(@NonNull IAssemblyDefinition definition, @NonNull ITypeResolver typeResolver) {
super(definition, typeResolver);
- this.instanceToTypeInfoMap = ObjectUtils.notNull(Lazy.of(() -> Stream.concat(
- getFlagInstanceTypeInfos().stream(),
- processModel(definition))
- .collect(CustomCollectors.toMap(
- IInstanceTypeInfo::getInstance,
- CustomCollectors.identity(),
- (key, v1, v2) -> {
- if (LOGGER.isErrorEnabled()) {
- LOGGER.error(String.format("Unexpected duplicate property name '%s'", key));
- }
- return ObjectUtils.notNull(v2);
- },
- LinkedHashMap::new))));
+ this.instanceToTypeInfoMap = ObjectUtils.notNull(Lazy.of(() -> {
+ Stream extends IInstanceTypeInfo> instances = Stream.concat(
+ getFlagInstanceTypeInfos().stream(),
+ processModel(definition));
+
+ // Add any instance if present on the assembly definition
+ IAnyInstance anyInstance = definition.getAnyInstance();
+ if (anyInstance != null) {
+ instances = Stream.concat(instances,
+ Stream.of(new AnyInstanceTypeInfoImpl(anyInstance, this)));
+ }
+
+ return instances.collect(CustomCollectors.toMap(
+ IInstanceTypeInfo::getInstance,
+ CustomCollectors.identity(),
+ (key, v1, v2) -> {
+ if (LOGGER.isErrorEnabled()) {
+ LOGGER.error(String.format("Unexpected duplicate property name '%s'", key));
+ }
+ return ObjectUtils.notNull(v2);
+ },
+ LinkedHashMap::new));
+ }));
this.propertyNameToTypeInfoMap = ObjectUtils.notNull(Lazy.of(() -> getInstanceTypeInfoMap().values().stream()
.collect(Collectors.toMap(
IInstanceTypeInfo::getPropertyName,
diff --git a/databind/src/test/java/dev/metaschema/databind/codegen/AnyCodegenTest.java b/databind/src/test/java/dev/metaschema/databind/codegen/AnyCodegenTest.java
new file mode 100644
index 000000000..b95bb18ed
--- /dev/null
+++ b/databind/src/test/java/dev/metaschema/databind/codegen/AnyCodegenTest.java
@@ -0,0 +1,77 @@
+/*
+ * SPDX-FileCopyrightText: none
+ * SPDX-License-Identifier: CC0-1.0
+ */
+
+package dev.metaschema.databind.codegen;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.file.Paths;
+
+import dev.metaschema.core.model.IAnyContent;
+import dev.metaschema.core.model.IBoundObject;
+import dev.metaschema.core.model.MetaschemaException;
+import dev.metaschema.core.util.ObjectUtils;
+import dev.metaschema.databind.io.BindingException;
+import dev.metaschema.databind.model.annotations.BoundAny;
+
+/**
+ * Tests that the code generator properly emits {@link BoundAny}-annotated
+ * fields for assemblies containing {@code } in their model.
+ */
+class AnyCodegenTest
+ extends AbstractMetaschemaTest {
+
+ @Test
+ void testAnyFieldGenerated()
+ throws MetaschemaException, IOException, ClassNotFoundException, BindingException,
+ NoSuchMethodException {
+ Class extends IBoundObject> rootClass = compileModule(
+ ObjectUtils.notNull(Paths.get("src/test/resources/metaschema/any/metaschema.xml")),
+ null,
+ "com.example.ns.any_test.Root",
+ ObjectUtils.notNull(generationDir));
+
+ // Verify a field annotated with @BoundAny exists
+ Field anyField = findBoundAnyField(rootClass);
+ assertNotNull(anyField, "Generated class should have a field annotated with @BoundAny");
+
+ // Verify the field type is IAnyContent
+ assertEquals(IAnyContent.class, anyField.getType(),
+ "@BoundAny field should be of type IAnyContent");
+
+ // Verify getter exists and returns IAnyContent
+ Method getter = rootClass.getMethod("getAny");
+ assertNotNull(getter, "Generated class should have getAny() method");
+ assertTrue(IAnyContent.class.isAssignableFrom(getter.getReturnType()),
+ "getAny() should return IAnyContent");
+
+ // Verify setter exists and accepts IAnyContent
+ Method setter = rootClass.getMethod("setAny", IAnyContent.class);
+ assertNotNull(setter, "Generated class should have setAny(IAnyContent) method");
+ }
+
+ /**
+ * Find the field annotated with {@link BoundAny} in the given class.
+ *
+ * @param clazz
+ * the class to search
+ * @return the field, or {@code null} if not found
+ */
+ private static Field findBoundAnyField(Class> clazz) {
+ for (Field field : clazz.getDeclaredFields()) {
+ if (field.isAnnotationPresent(BoundAny.class)) {
+ return field;
+ }
+ }
+ return null;
+ }
+}